javascript - 如何渲染对象而不混合 webgl 中启用的透明度

标签 javascript html css webgl

我正在尝试渲染两个对象,使用两个单独的 gl.drawArrays 调用。我想让对象的透明部分不可见。此外,我想在另一个对象之上渲染一个对象,这样第一个绘制的对象在与第二个对象重叠的地方不可见。

我在我的渲染循环中使用这个设置:

 gl.clearColor(0, 0, 0, 1);
 // https://stackoverflow.com/questions/18439897/webgl-fragment-shader-opacity
 gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
 gl.enable(gl.BLEND);
 gl.disable(gl.DEPTH_TEST);

我不确定混合函数的作用,但我使用它们来启用透明度。但这会导致两个物体混合并产生黄色。 (一个物体是红色的,另一个是绿色的)。如果我最后画红色,我想要红色,反之亦然,同时启用透明度。

const fShaderSource2 = `#version 300 es

precision mediump float;

out vec4 outColor;

void main() {
  outColor = vec4(0.0, 1.0, 0.0, 1.0);
}

`;


const fShaderSource = `#version 300 es

precision mediump float;

out vec4 outColor;

uniform sampler2D u_texture;

void main() {
  outColor = texture(u_texture, vec2(0.0));
}

`;

const vShaderSource = `#version 300 es

precision mediump float;

in vec2 a_position;

void main() {
  gl_Position = vec4(a_position, 0, 1);
}
`;

main(document.getElementById('app'));

function main(element) {
  
  const canvas = document.createElement('canvas'),
        gl = canvas.getContext('webgl2');
  element.append(canvas);
  const displayWidth = canvas.clientWidth,
        displayHeight = canvas.clientHeight;
  canvas.width = displayWidth;
  canvas.height = displayHeight;


  let graphics = new Graphics({width: displayWidth, height: displayHeight}, gl);
  
  new Loop(() => {
     graphics.render();
  }).start();
}

function Graphics(state, gl) {

  const { width, height } = state;

  gl.clearColor(0, 0, 0, 0);
  
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
  gl.enable(gl.BLEND);
  gl.disable(gl.DEPTH_TEST);

  
  
  let minibatch = [];
  
  const redText = makeGlQuad(gl, fShaderSource, canvasTexture());
  const greenText = makeGlQuad(gl, fShaderSource2);

  this.render = () => {  
  
    minibatch.push(redText);
    minibatch.push(greenText);
    
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT);

    minibatch.forEach(({
      program,
      resUniformLocation,
      vao,
      glTexture
    }) => {

    gl.useProgram(program);

    gl.uniform2f(resUniformLocation, gl.canvas.width, gl.canvas.height);
    
    if (glTexture) {
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, glTexture);
    }
    
    gl.bindVertexArray(vao);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  });
  minibatch = [];
  };

}

function makeGlQuad(gl, fShaderSource, texture) {

  let vShader = createShader(gl, gl.VERTEX_SHADER, vShaderSource);
  let fShader = createShader(gl, gl.FRAGMENT_SHADER, fShaderSource);

  let program = createProgram(gl, vShader, fShader);

  let posAttrLocation = gl.getAttribLocation(program, "a_position");
  let posBuffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);


  /*
    (-1, 1).( 1, 1)
        .
    (-1,-1).( 1,-1)
   */
  let positions = [
    -1, 1,
    -1, -1,
    1, -1,
    -1, 1,
    1,-1,
    1, 1
  ];

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);


  let vao = gl.createVertexArray();
  gl.bindVertexArray(vao);

  gl.enableVertexAttribArray(posAttrLocation);

  let size = 2,
      type = gl.FLOAT,
      normalize = false,
      stride = 0,
      offset = 0;

  gl.vertexAttribPointer(posAttrLocation,
                         size,
                         type,
                         normalize,
                         stride,
                         offset);

  let glTexture;
  if (texture) {
    glTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, glTexture);
  
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture);
    //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));


    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  }



  let resUniformLocation = gl.getUniformLocation(program, "u_resolution");
  let texUniformLocation = gl.getUniformLocation(program, "u_texture");
 
 
  return {
   program,
   resUniformLocation,
   vao,
   glTexture
  }
}

function canvasTexture() {
 
 return withCanvasTexture(256, 256, (w, h, canvas, ctx) => {
    ctx.fillStyle = 'red';
    ctx.fillRect(0, 0, w, h);
    ctx.font = '50pt Comic Sans';
    ctx.fillStyle = 'white';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('label', w / 2, 50);

    return canvas;
 });
 
function withCanvasTexture(width, height, f) {
  var canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  f(width, height, canvas, canvas.getContext('2d'));
  const texture = canvas;
  //document.body.append(canvas);
  return texture;
}
}

function createShader(gl, type, source) {
  let shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);

  if (success) {
    return shader;
  }

  console.error(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
  return null;
};

function createProgram(gl, vShader, fShader) {
  let program = gl.createProgram();
  gl.attachShader(program, vShader);
  gl.attachShader(program, fShader);
  gl.linkProgram(program);
  let success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.error(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
  return null;
}


// Loop Library
function Loop(fn) {

const perf = window.performance !== undefined ? window.performance : Date;

const now = () => perf.now();

const raf = window.requestAnimationFrame;

  let running = false,
      lastUpdate = now(),
      frame = 0;

  this.start = () => {
    if (running) {
      return this;
    }

    running = true;
    lastUpdate = now();
    frame = raf(tick);
    return this;
  };

  this.stop = () => {
    running = false;

    if (frame != 0) {
      raf.cancel(frame);
    }

    frame = 0;
    return this;
  };

  const tick = () => {
    frame = raf(tick);
    const time = now();
    const dt = time - lastUpdate;
    fn(dt);
    lastUpdate = time;
  };
}
#app canvas {
  position: fixed;
  top: 50%;
  bottom: 0;
  left: 50%;
  right: 0;

  width: 100vmin;
  height: 70vmin;

  transform: translate(-50%, -25%);

  image-rendering: optimizeSpeed;
  cursor: none;
  margin: auto;
}
<div id="app">
</div>

你可以在这里看到:

minibatch.push(redText);
minibatch.push(greenText);

我先渲染红色,然后渲染绿色,但我变成了黄色。

最佳答案

问题是您使用了错误的混合函数 (blendFunc)。混合定义了一个函数,它将片段颜色输出与颜色缓冲区中的当前颜色相结合。第一个参数是片段颜色输出的因子,第二个参数是颜色缓冲区中颜色的因子。颜色相加,因为默认混合方程 (blendEquation()) 是 FUNC_ADD

混合函数

gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

可以用公式表示

destColor = srcColor * srcAlpha + destColor * 1 

其中 destColor 是帧缓冲区中的当前颜色,srcColor 是设置为片段的颜色 (outColor)。
这会导致当前帧缓冲区中的颜色被保留(乘以 1)。新颜色乘以 alpha channel 并添加到帧缓冲区中的颜色。如果帧缓冲区中的颜色为红色 (1, 0, 0),而新颜色为绿色 (0, 1, 0),则结果为黄色(如果 alpha channel 为 1):

(0, 1, 0) * 1 + (1, 0, 0) * 1 == (1, 1, 0)    

使用混合函数:

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

这会导致 frambuffer 中的颜色和新颜色“混合”,这取决于新颜色的 alpha channel :

destColor = srcColor * srcAlpha + destColor * (1-srcAlpha)

Alpha blending的总则与 OpenGL 中的类似(因为 WebGL (2.0) 与 OpenGL ES (3.0) 非常一致),因此可以在 OpenGL 维基页面 Blending 上找到更多信息。 .

关于javascript - 如何渲染对象而不混合 webgl 中启用的透明度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57612782/

相关文章:

javascript - 如何使下拉(鼠标悬停)列表(用D3制作)保持更长的时间?

javascript - 使用修改后的 .click 函数扩展 jQuery

javascript - 按钮控制javascripts中的功能

jquery - 如何在 Jquery Ajax 中正确引用发布的表单数据

css - 如何修复 IE 中的平滑文本?

html - 如何在 Shiny 的应用程序的 navbarPage 中设置 tabPanel 标题的颜色?

javascript - 在相同的 <td> 内获取输入

javascript - OS.File 在 OS.read 之前检查最后修改日期

html - 悬停时更改背景 svg 的描边

html - 使用 .hover 选择器与上下键混合