WebGL中有没有一种方法可以快速反转模板缓冲区?

时间:2018-08-10 17:09:11

标签: webgl stencil-buffer

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

gl.enable(GL.STENCIL_TEST);

然后,我将颜色绘制到颜色缓冲区中。此后,稍后我想再次绘制 ,但是这次我想剪辑到模板缓冲区中的 inverse 。我知道我可以再次使用模板缓冲区,但是由于我没有使用gl.stencilOp(GL.ZERO, GL.ZERO, GL.ZERO),因此模板缓冲区应该仍然存在,但其中应保留原始值。

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

1 个答案:

答案 0 :(得分:0)

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

示例:

模板下方的代码,带有一个1s的圆圈和一个2s的正方形。然后绘制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://stackoverflow.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,当然还有NEVER和{{1 }}还有其他相反的可能性。