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.EQUAL
,ref
= 0 仅在有 0 的地方绘制,然后将其设置为 gl.EQUAL
,ref
= 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>
当然也有LESS
、LEQUAL
、GREATER
、GEQUAL
、NOTEQUAL
,当然还有NEVER
和ALWAYS
作为其他可能的模板。
【讨论】:
所以看起来您设置了stencilFunc
和stencilOp
,然后调用renderXXX
方法之一。我想我正在寻找的是是否可以对模板缓冲区执行绘制(例如 renderXXX
函数之一,然后多次使用模板缓冲区绘制到颜色缓冲区,但第二次使用模板缓冲区的反转副本。
为什么需要反转模板缓冲区?只需使用 gl.stencilFunc
反转测试即可以上是关于WebGL 中有没有办法快速反转模板缓冲区?的主要内容,如果未能解决你的问题,请参考以下文章