WebGL 中有没有办法快速反转模板缓冲区?

Posted

技术标签:

【中文标题】WebGL 中有没有办法快速反转模板缓冲区?【英文标题】:Is there a way in WebGL to quickly invert the stencil buffer? 【发布时间】:2018-08-10 17:09:10 【问题描述】:

我正在使用 WebGL 1.0。我在模板缓冲区上画了一个圆圈,现在我想多次使用这个模板缓冲区而不清除它。第一次使用它时,我启用了模板测试:

gl.enable(GL.STENCIL_TEST);

然后,我对颜色缓冲区执行绘图。在此之后,在以后的某个日期,我想再次绘制 ,但这次我想剪辑到模板缓冲区中的内容的 inverse。我知道我可以再次绘制到模板缓冲区,但由于我没有使用gl.stencilOp(GL.ZERO, GL.ZERO, GL.ZERO),模板缓冲区应该仍然存在,但其中包含原始值。

我的问题是 - 有没有一种快速的 WebGL 方法来反转这个模板缓冲区,还是我必须使用GL.INVERT 的模板操作再次执行绘图操作?

【问题讨论】:

你不能用gl.stencilFunc改变模板功能吗?假设您将其清除为 0。然后您用 1 写了一个圆圈。因此将 stencilFunc 设置为 gl.EQUALref = 0 仅在有 0 的地方绘制,然后将其设置为 gl.EQUALref = 1 只在有 1 的地方绘制? 【参考方案1】:

假设您将模板清除为一个值并使用不同的值绘制到模板,那么您可以使用gl.stencilFunc(gl.EQUAL, value, 0xFFFF) 来 仅在模板与value 匹配的位置绘制。

例子:

模具下方的代码,圆圈为 1,正方形为 2。然后绘制3个场景。仅当模板为 0 时包含立方体的场景,仅当模板为 1 时包含球体的场景,以及仅当模板为 2 时飞过环环的场景

const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl", 
  stencil: true,
);
const programInfo = makeProgramInfo(gl);


const renderStencil1 = setupSceneStencil1();
const renderStencil2 = setupSceneStencil2();

const renderScene1 = setupScene1();
const renderScene2 = setupScene2();
const renderScene3 = setupScene3();

function render(time) 
  time *= 0.001;
  twgl.resizeCanvasToDisplaySize(gl.canvas);

  gl.disable(gl.STENCIL_TEST);
  gl.clearStencil(0);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  gl.clearColor(1, 1, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

  // draw 1s into stencil
  gl.enable(gl.STENCIL_TEST);
  gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
  if (time / 5 % 2 | 0) 
    // this will end up with a 2s where the square overlaps the circle
    gl.stencilOp(gl.INCR, gl.INCR, gl.INCR);
   else 
    // this will end up with a 2s where the square is drawn
    gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
  
  gl.disable(gl.DEPTH_TEST);
  
  renderStencil1(time);
    
  // draw 2s into stencil
  gl.stencilFunc(gl.ALWAYS, 2, 0xFF);
  gl.disable(gl.DEPTH_TEST);
  
  renderStencil2(time);
  
  // draw where there are 0s
  gl.enable(gl.DEPTH_TEST);
  gl.stencilFunc(gl.EQUAL, 0, 0xFF);
  gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
  
  renderScene1(time);
  
  // draw where there are 1s
  gl.stencilFunc(gl.EQUAL, 1, 0xFF);
  
  renderScene2(time);
  
  // draw where there are 2s
  gl.stencilFunc(gl.EQUAL, 2, 0xFF);
  
  renderScene3(time);

  requestAnimationFrame(render);

requestAnimationFrame(render);

function setupSceneStencil1() 
  const bufferInfo = twgl.primitives.createDiscBufferInfo(gl, 1, 48);
  const color = [1, 0, 0, 1];
  const tex = twgl.createTexture(gl, 
    src: [255, 255, 255, 255],
  );
  
  function render(time, viewProjection) 
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
    const s = 1 + (Math.sin(time) * .5 + .5) * 10;
    let mat = m4.copy(viewProjection);
    mat = m4.translate(mat, [
      Math.sin(time * 1.7) * 3,
      0,
      0,
    ]);
    mat = m4.scale(mat, [s, s, s]);
    mat = m4.rotateX(mat, Math.PI * .5);
    twgl.setUniforms(programInfo, 
      u_diffuse: tex,
      u_diffuseMult: color,
      u_worldViewProjection: mat,
    );
    twgl.drawBufferInfo(gl, bufferInfo);
  
  
  return setupScene(render);


function setupSceneStencil2() 
  const bufferInfo = twgl.primitives.createPlaneBufferInfo(gl, 2, 2);

  const color = [0, 0, 1, 1];
  const tex = twgl.createTexture(gl, 
    src: [255, 255, 255, 255],
  );
  
  function render(time, viewProjection) 
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
    const s = 1 + (Math.cos(time * 2.3) * .5 + .5) * 5;
    let mat = m4.copy(viewProjection);
    mat = m4.translate(mat, [
      Math.cos(time * 1.3) * 3,
      0,
      0,
    ]);
    mat = m4.scale(mat, [s, s, s]);
    mat = m4.rotateZ(mat, -time);
    mat = m4.rotateX(mat, Math.PI * .5);
    twgl.setUniforms(programInfo, 
      u_diffuse: tex,
      u_diffuseMult: color,
      u_worldViewProjection: mat,
    );
    twgl.drawBufferInfo(gl, bufferInfo);
  
  
  return setupScene(render);


function setupScene1() 
  const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 4);
  const color = makeColor();
  
  function render(time, viewProjection, tex) 
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
    const numCubes = 20;
    for (let i = 0; i < numCubes; ++i) 
      const u = i / (numCubes - 1);
      const uu = u * 2 - 1;
      let mat = m4.copy(viewProjection);
      mat = m4.translate(mat, [
        uu * 15 + Math.sin(time), 
        ((u * 8 + time) % 2 - 1) * 15, 
        0,
      ]);
      mat = m4.rotateY(mat, u + time);
      mat = m4.rotateX(mat, u + time);
      twgl.setUniforms(programInfo, 
        u_diffuse: tex,
        u_diffuseMult: color,
        u_worldViewProjection: mat,
      );
      twgl.drawBufferInfo(gl, bufferInfo);
    
  
  
  return setupScene(render);


function setupScene2() 
  const bufferInfo = twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12);
  const color = makeColor();
  
  // adapted from http://***.com/a/26127012/128511
  // used to space the cubes around the sphere
  function fibonacciSphere(samples, i) 
    const rnd = 1.;
    const offset = 2. / samples;
    const increment = Math.PI * (3. - Math.sqrt(5.));

    //  for i in range(samples):
    const y = ((i * offset) - 1.) + (offset / 2.);
    const r = Math.sqrt(1. - Math.pow(y ,2.));

    const phi = ((i + rnd) % samples) * increment;

    const x = Math.cos(phi) * r;
    const z = Math.sin(phi) * r;

    return [x, y, z];
    
  
  function render(time, viewProjection, tex) 
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
    const numSpheres = 100;
    for (let i = 0; i < numSpheres; ++i) 
      const u = i / (numSpheres - 1);
      const uu = u * 2 - 1;
      let mat = m4.copy(viewProjection);
      mat = m4.rotateY(mat, time);
      mat = m4.rotateZ(mat, time);
      mat = m4.translate(mat, v3.mulScalar(fibonacciSphere(numSpheres, i), 8));
      mat = m4.rotateX(mat, u + time);
      twgl.setUniforms(programInfo, 
        u_diffuse: tex,
        u_diffuseMult: color,
        u_worldViewProjection: mat,
      );
      twgl.drawBufferInfo(gl, bufferInfo);
    
  
  
  return setupScene(render);


function setupScene3() 
  const bufferInfo = twgl.primitives.createTorusBufferInfo(gl, 2, 0.4, 24, 12);
  const color = makeColor();
  
  function render(time, viewProjection, tex) 
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
    const numSpheres = 100;
    for (let i = 0; i < numSpheres; ++i) 
      const u = i / (numSpheres - 1);
      const uu = u * 2 - 1;
      let mat = m4.copy(viewProjection);
      mat = m4.rotateZ(mat, time);
      mat = m4.translate(mat, [0, 40, -20]);
      mat = m4.rotateX(mat, time + u * Math.PI * 2);
      mat = m4.translate(mat, [0, 40, 0]);
      mat = m4.rotateX(mat, Math.PI * .5);
      mat = m4.rotateY(mat, u * Math.PI * 20);
      twgl.setUniforms(programInfo, 
        u_diffuse: tex,
        u_diffuseMult: color,
        u_worldViewProjection: mat,
      );
      twgl.drawBufferInfo(gl, bufferInfo);
    
  
  
  return setupScene(render);


function setupScene(renderFn) 
  const camera = m4.identity();
  const view = m4.identity();
  const viewProjection = m4.identity();
  const tex = twgl.createTexture(gl, 
    min: gl.NEAREST,
    mag: gl.NEAREST,
    format: gl.LUMINANCE,
    src: [
      255, 192, 255, 192,
      192, 255, 192, 255,
      255, 192, 255, 192,
      192, 255, 192, 255,
    ],
  );

  return function render(time) 
    const projection = m4.perspective(
       30 * Math.PI / 180, 
       gl.canvas.clientWidth / gl.canvas.clientHeight, 
       0.5, 
       100);
    const eye = [0, 0, -20];
    const target = [0, 0, 0];
    const up = [0, 1, 0];

    m4.lookAt(eye, target, up, camera);
    m4.inverse(camera, view);
    m4.multiply(projection, view, viewProjection);

    renderFn(time, viewProjection, tex);
  


function rand(min, max) 
  if (max === undefined) 
    max = min;
    min = 0;
  
  return min + Math.random() * (max - min);


function makeProgramInfo(gl) 
  const vs = `
  uniform mat4 u_worldViewProjection;

  attribute vec4 position;
  attribute vec2 texcoord;

  varying vec2 v_texcoord;

  void main() 
    v_texcoord = texcoord;
    gl_Position = u_worldViewProjection * position;
  
  `;
  const fs = `
  precision mediump float;

  varying vec2 v_texcoord;

  uniform sampler2D u_diffuse;
  uniform vec4 u_diffuseMult;

  void main() 
    gl_FragColor = texture2D(u_diffuse, v_texcoord) * u_diffuseMult;
  
  `;
  return twgl.createProgramInfo(gl, [vs, fs]);


function makeColor() 
 const color = [rand(1), rand(1), rand(1), 1];
 color[rand(3) | 0] = .8;
 return color;
body  margin: 0; 
canvas  width: 100vw; height: 100vh; display: block; 
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

当然也有LESSLEQUALGREATERGEQUALNOTEQUAL,当然还有NEVERALWAYS作为其他可能的模板。

【讨论】:

所以看起来您设置了stencilFuncstencilOp,然后调用renderXXX 方法之一。我想我正在寻找的是是否可以对模板缓冲区执行绘制(例如 renderXXX 函数之一,然后多次使用模板缓冲区绘制到颜色缓冲区,但第二次使用模板缓冲区的反转副本。 为什么需要反转模板缓冲区?只需使用 gl.stencilFunc 反转测试即可

以上是关于WebGL 中有没有办法快速反转模板缓冲区?的主要内容,如果未能解决你的问题,请参考以下文章

问绘图缓冲区的模板缓冲区会增加性能成本(WebGL)吗?

WebGL - 从渲染缓冲区读取像素数据

webGL - 如何在帧缓冲区旁边设置模板缓冲区并使用它?

webgl 绘图顺序,模板缓冲区

指定默认深度缓冲精度

WebGL:在带有深度模板纹理附件的帧缓冲区上未清除颜色