不要在 webgl 中混合与自身交叉的多段线

Don't blend a polyline that has crossed lines with itself in webgl

我正在启用混合的 webgl 中绘制两条多段线(它们是示例中的线)。

gl.uniform4f(colorUniformLocation, 0, 0, 0, 0.3);

gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.2, -1, 0.2, 1,]), gl.STATIC_DRAW);
gl.drawArrays(gl.LINE_STRIP, 0, 2);

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, -1, 0, 1,0, -1, 0, 1]), gl.STATIC_DRAW);
gl.drawArrays(gl.LINE_STRIP, 0, 4);

Here 是codepen样本。

左边那条线是自己交叉的,好像和自己融为一体,所以颜色变深了。

我希望在这些多段线之间进行混合,但不希望多段线与自身混合。有办法吗?

一种方法是使用模板测试。您将设置 webgl,以便在绘制像素时模板存储特定值,并且您将设置模板测试,以便它在看到该值时失败。

首先是绘制 2 组 2 个重叠三角形并启用混合的示例。这些对在重叠的地方会变暗

function main() {
  const m4 = twgl.m4;
  const gl = document
      .querySelector('canvas')
      .getContext('webgl');

  const vs = `
  attribute vec4 position;
  uniform mat4 matrix;
  void main() {
    gl_Position = matrix * position;
  }
  `;
  
  const fs = `
  precision mediump float;
  uniform vec4 color;
  void main() {
    gl_FragColor = color;
  }
  `;
  
  // compile shader, link program, look up locations
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  gl.useProgram(programInfo.program);
  
  // create a buffer and put data in it
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
      numComponents: 2,
      data: [
        -0.5, -0.2,
         0.5, -0.2,
         0.5,  0.2,
         
        -0.2, -0.5,
        -0.2,  0.5,
         0.2,  0.5,
      ],
    },
  });
  
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  // calls gl.uniform??
  twgl.setUniforms(programInfo, {
    color: [0.5, 0, 0, 0.5],
    matrix: m4.identity(),
  });
  
  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);
  
  twgl.setUniforms(programInfo, {
    color: [0, 0, 0.5, 0.5],
    matrix: m4.rotateZ(
        m4.translation([-0.1, 0.2, 0]),
        Math.PI * 1.2),
  });
  twgl.drawBufferInfo(gl, bufferInfo);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

然后是模板测试的相同示例

首先我们需要一个模板缓冲区

  const gl = someCanvas.getContext('webgl2', {stencil: true});

然后我们开启模板测试

  gl.enable(gl.STENCIL_TEST);

设置测试,使其仅在模板缓冲区为零时绘制

  gl.stencilFunc(
     gl.EQUAL,   // the test
     0,          // reference value
     0xFF,       // mask
  );

并设置操作,以便我们在绘制时增加模板,这样它们将不再为零,因此无法通过测试

  gl.stencilOp(
     gl.KEEP,  // what to do if the stencil test fails
     gl.KEEP,  // what to do if the depth test fails
     gl.INCR,  // what to do if both tests pass
  );

在第一次绘制和第二次绘制之间我们清除模板缓冲区

gl.clear(gl.STENCIL_BUFFER_BIT);

例子

function main() {
  const m4 = twgl.m4;
  const gl = document
      .querySelector('canvas')
      .getContext('webgl', {stencil: true});

  const vs = `
  attribute vec4 position;
  uniform mat4 matrix;
  void main() {
    gl_Position = matrix * position;
  }
  `;
  
  const fs = `
  precision mediump float;
  uniform vec4 color;
  void main() {
    gl_FragColor = color;
  }
  `;
  
  // compile shader, link program, look up locations
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  gl.useProgram(programInfo.program);
  
  // create a buffer and put data in it
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
      numComponents: 2,
      data: [
        -0.5, -0.2,
         0.5, -0.2,
         0.5,  0.2,
         
        -0.2, -0.5,
        -0.2,  0.5,
         0.2,  0.5,
      ],
    },
  });
  
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  
  gl.enable(gl.STENCIL_TEST);
  gl.stencilFunc(
     gl.EQUAL,  // the test
     0,         // reference value
     0xFF,      // mask
  );
  gl.stencilOp(
     gl.KEEP,    // what to do if the stencil test fails
     gl.KEEP,    // what to do if the depth test fails
     gl.INCR,    // what to do if both tests pass
  );
  
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  // calls gl.uniform??
  twgl.setUniforms(programInfo, {
    color: [0.5, 0, 0, 0.5],
    matrix: m4.identity(),
  });
  
  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);
  
  gl.clear(gl.STENCIL_BUFFER_BIT);  
  
  twgl.setUniforms(programInfo, {
    color: [0, 0, 0.5, 0.5],
    matrix: m4.rotateZ(
        m4.translation([-0.1, 0.2, 0]),
        Math.PI * 1.2),
  });
  twgl.drawBufferInfo(gl, bufferInfo);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

另一种解决方案,如果您正在绘制 2D 内容,您还可以使用深度测试。默认深度测试仅在深度比当前深度 gl.LESS 时绘制,因此如果三角形的深度相同,只需打开深度测试并在绘制之间设置不同的深度也可以。您可以为绘制的每个东西计算不同的深度值,您需要查找深度缓冲区的位分辨率。或者,您可以使用 gl.polygonOffset

gl.enable(gl.DEPTH_TEST);
gl.enable(gl.POLYGON_OFFSET_FILL); 

... then ...

for (let i = 0; i < numThingsToDraw; ++i) {
  gl.polygonOffset(0, -i);  // each thing 1 depth unit less
  draw2DThing(things[i]);
}

例子

function main() {
  const m4 = twgl.m4;
  const gl = document
      .querySelector('canvas')
      .getContext('webgl');
  
  const vs = `
  attribute vec4 position;
  uniform mat4 matrix;
  void main() {
    gl_Position = matrix * position;
  }
  `;
  
  const fs = `
  precision mediump float;
  uniform vec4 color;
  void main() {
    gl_FragColor = color;
  }
  `;
  
  // compile shader, link program, look up locations
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  gl.useProgram(programInfo.program);
  
  // create a buffer and put data in it
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
      numComponents: 2,
      data: [
        -0.5, -0.2,
         0.5, -0.2,
         0.5,  0.2,
         
        -0.2, -0.5,
        -0.2,  0.5,
         0.2,  0.5,
      ],
    },
  });
  
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  
  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.POLYGON_OFFSET_FILL);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  // calls gl.uniform??
  twgl.setUniforms(programInfo, {
    color: [0.5, 0, 0, 0.5],
    matrix: m4.identity(),
  });
  
  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);

  gl.polygonOffset(0, -1);
  
  twgl.setUniforms(programInfo, {
    color: [0, 0, 0.5, 0.5],
    matrix: m4.rotateZ(
        m4.translation([-0.1, 0.2, 0.0]),
        Math.PI * 1.2),
  });
  twgl.drawBufferInfo(gl, bufferInfo);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>