使用 WebGL 支持模板缓冲区
Support for Stencil Buffer with WebGL
使用 canvas.getContext("webgl", {stencil : true})
初始化 webgl 请求模板缓冲区,但并非所有浏览器实际上都会为您提供模板缓冲区(对我来说,Ubuntu 20.04 LTS 上的 Firefox 79.0 不起作用,但 Chrome 84.0.4147.89 可以。我的显卡是 NVIDIA RTX 2060,我使用的是 nvidia-driver-440-server 驱动。
我想知道受支持的模板缓冲区有多广泛,但找不到有关支持哪些浏览器的信息。像 glStencilOp
这样的函数是我唯一能找到支持信息的东西,它们仍然可以使用,它们只是不使用 0 模板位做任何事情。
是否有支持此功能的浏览器列表?
老实说,这听起来像是 firefox 中的错误,尽管鉴于规范允许实现无法在 canvas 上提供模板缓冲区,无论出于何种原因,这在技术上都不是错误。我会考虑填一个。使用 Chromium 浏览器进行测试,以检查这是 Firefox 选择不提供模板缓冲区,而不是驱动程序问题或其他问题。
您应该能够始终制作 DEPTH_STENCIL
渲染缓冲区。没有允许实现不支持的 WebGL 版本。因此,您可以通过渲染到附加到帧缓冲区的纹理 + 深度模板渲染缓冲区来解决该错误,然后将帧缓冲区颜色纹理渲染到 canvas.
这是一个测试。您应该会看到一个右下角为绿色的红色正方形。它将在紫色方块内的蓝色方块内。
蓝色方块用于显示帧缓冲区纹理的范围。如果绿色方块没有被模板缓冲区遮盖,它会渗入蓝色。
紫色方块用于显示 canvas 的大小,我们正在绘制比完整 canvas 小的帧缓冲区纹理。这只是为了表明模板缓冲区可以在您的机器上工作。对于您自己的解决方案,您希望绘制一个由顶点组成的四边形而不是使用如下所示的点,并且您希望使附加到帧缓冲区的纹理和渲染缓冲区与您的 canvas 大小相同。
"use strict";
function main() {
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 64.0;
}
`;
const fs = `
precision mediump float;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, gl_PointCoord.xy);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(program, "position");
// Create a texture to render to
const targetTextureWidth = 128;
const targetTextureHeight = 128;
const targetTexture = createTexture(gl);
{
// define size and format of level 0
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
}
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
const level = 0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
// create a depth-stencil renderbuffer
const depthStencilBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
// make a depth-stencil buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
function createTexture(gl, color) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// set the filtering so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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);
if (color) {
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
}
return tex;
}
// create a red texture and a green texture
const redTex = createTexture(gl, [255, 0, 0, 255]);
const greenTex = createTexture(gl, [0, 255, 0, 255]);
gl.enable(gl.STENCIL_TEST);
gl.useProgram(program);
gl.clearColor(0, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, redTex);
gl.stencilFunc(
gl.ALWAYS, // the test
1, // 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.REPLACE, // what to do if both tests pass
);
// draw a 64x64 pixel red rect in middle
gl.drawArrays(gl.POINTS, 0, 1);
gl.stencilFunc(
gl.EQUAL, // the test
1, // 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.KEEP, // what to do if both tests pass
);
// draw a green 64x64 pixel square in the
// upper right corner. The stencil will make
// it not go outside the red square
gl.vertexAttrib2f(posLoc, 0.5, 0.5);
gl.bindTexture(gl.TEXTURE_2D, greenTex);
gl.drawArrays(gl.POINTS, 0, 1);
// draw the framebuffer's texture to
// the canvas. we should see a 32x32
// red square with the bottom right corner
// green showing the stencil worked. That will
// be surrounded by blue to show the texture
// we were rendering to is larger than the
// red square. And that will be surrounded
// by purple since we're drawing a 64x64
// point on a 128x128 canvas which we clear
// purple.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.vertexAttrib2f(posLoc, 0.0, 0.0);
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>
如果您将渲染缓冲区格式更改为 DEPTH_COMPONENT16 并将附着点更改为 DEPTH_ATTACHMENT 那么您将看到绿色方块不再被模板遮盖
"use strict";
function main() {
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 64.0;
}
`;
const fs = `
precision mediump float;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, gl_PointCoord.xy);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(program, "position");
// Create a texture to render to
const targetTextureWidth = 128;
const targetTextureHeight = 128;
const targetTexture = createTexture(gl);
{
// define size and format of level 0
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
}
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
const level = 0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
// create a depth-stencil renderbuffer
const depthStencilBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
// make a depth-stencil buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
function createTexture(gl, color) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// set the filtering so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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);
if (color) {
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
}
return tex;
}
// create a red texture and a green texture
const redTex = createTexture(gl, [255, 0, 0, 255]);
const greenTex = createTexture(gl, [0, 255, 0, 255]);
gl.enable(gl.STENCIL_TEST);
gl.useProgram(program);
gl.clearColor(0, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, redTex);
gl.stencilFunc(
gl.ALWAYS, // the test
1, // 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.REPLACE, // what to do if both tests pass
);
// draw a 64x64 pixel red rect in middle
gl.drawArrays(gl.POINTS, 0, 1);
gl.stencilFunc(
gl.EQUAL, // the test
1, // 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.KEEP, // what to do if both tests pass
);
// draw a green 64x64 pixel square in the
// upper right corner. The stencil will make
// it not go outside the red square
gl.vertexAttrib2f(posLoc, 0.5, 0.5);
gl.bindTexture(gl.TEXTURE_2D, greenTex);
gl.drawArrays(gl.POINTS, 0, 1);
// draw the framebuffer's texture to
// the canvas. we should see a 32x32
// red square with the bottom right corner
// green showing the stencil worked. That will
// be surrounded by blue to show the texture
// we were rendering to is larger than the
// red square. And that will be surrounded
// by purple since we're drawing a 64x64
// point on a 128x128 canvas which we clear
// purple.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.vertexAttrib2f(posLoc, 0.0, 0.0);
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>
你应该能够调用 gl.getContextAttributes
来检查你是否有模板缓冲区,这样你就可以使用建议的解决方案,如果它告诉你你没有在canvas.
使用 canvas.getContext("webgl", {stencil : true})
初始化 webgl 请求模板缓冲区,但并非所有浏览器实际上都会为您提供模板缓冲区(对我来说,Ubuntu 20.04 LTS 上的 Firefox 79.0 不起作用,但 Chrome 84.0.4147.89 可以。我的显卡是 NVIDIA RTX 2060,我使用的是 nvidia-driver-440-server 驱动。
我想知道受支持的模板缓冲区有多广泛,但找不到有关支持哪些浏览器的信息。像 glStencilOp
这样的函数是我唯一能找到支持信息的东西,它们仍然可以使用,它们只是不使用 0 模板位做任何事情。
是否有支持此功能的浏览器列表?
老实说,这听起来像是 firefox 中的错误,尽管鉴于规范允许实现无法在 canvas 上提供模板缓冲区,无论出于何种原因,这在技术上都不是错误。我会考虑填一个。使用 Chromium 浏览器进行测试,以检查这是 Firefox 选择不提供模板缓冲区,而不是驱动程序问题或其他问题。
您应该能够始终制作 DEPTH_STENCIL
渲染缓冲区。没有允许实现不支持的 WebGL 版本。因此,您可以通过渲染到附加到帧缓冲区的纹理 + 深度模板渲染缓冲区来解决该错误,然后将帧缓冲区颜色纹理渲染到 canvas.
这是一个测试。您应该会看到一个右下角为绿色的红色正方形。它将在紫色方块内的蓝色方块内。
蓝色方块用于显示帧缓冲区纹理的范围。如果绿色方块没有被模板缓冲区遮盖,它会渗入蓝色。
紫色方块用于显示 canvas 的大小,我们正在绘制比完整 canvas 小的帧缓冲区纹理。这只是为了表明模板缓冲区可以在您的机器上工作。对于您自己的解决方案,您希望绘制一个由顶点组成的四边形而不是使用如下所示的点,并且您希望使附加到帧缓冲区的纹理和渲染缓冲区与您的 canvas 大小相同。
"use strict";
function main() {
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 64.0;
}
`;
const fs = `
precision mediump float;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, gl_PointCoord.xy);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(program, "position");
// Create a texture to render to
const targetTextureWidth = 128;
const targetTextureHeight = 128;
const targetTexture = createTexture(gl);
{
// define size and format of level 0
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
}
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
const level = 0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
// create a depth-stencil renderbuffer
const depthStencilBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
// make a depth-stencil buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
function createTexture(gl, color) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// set the filtering so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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);
if (color) {
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
}
return tex;
}
// create a red texture and a green texture
const redTex = createTexture(gl, [255, 0, 0, 255]);
const greenTex = createTexture(gl, [0, 255, 0, 255]);
gl.enable(gl.STENCIL_TEST);
gl.useProgram(program);
gl.clearColor(0, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, redTex);
gl.stencilFunc(
gl.ALWAYS, // the test
1, // 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.REPLACE, // what to do if both tests pass
);
// draw a 64x64 pixel red rect in middle
gl.drawArrays(gl.POINTS, 0, 1);
gl.stencilFunc(
gl.EQUAL, // the test
1, // 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.KEEP, // what to do if both tests pass
);
// draw a green 64x64 pixel square in the
// upper right corner. The stencil will make
// it not go outside the red square
gl.vertexAttrib2f(posLoc, 0.5, 0.5);
gl.bindTexture(gl.TEXTURE_2D, greenTex);
gl.drawArrays(gl.POINTS, 0, 1);
// draw the framebuffer's texture to
// the canvas. we should see a 32x32
// red square with the bottom right corner
// green showing the stencil worked. That will
// be surrounded by blue to show the texture
// we were rendering to is larger than the
// red square. And that will be surrounded
// by purple since we're drawing a 64x64
// point on a 128x128 canvas which we clear
// purple.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.vertexAttrib2f(posLoc, 0.0, 0.0);
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>
如果您将渲染缓冲区格式更改为 DEPTH_COMPONENT16 并将附着点更改为 DEPTH_ATTACHMENT 那么您将看到绿色方块不再被模板遮盖
"use strict";
function main() {
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 64.0;
}
`;
const fs = `
precision mediump float;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, gl_PointCoord.xy);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(program, "position");
// Create a texture to render to
const targetTextureWidth = 128;
const targetTextureHeight = 128;
const targetTexture = createTexture(gl);
{
// define size and format of level 0
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
}
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
const level = 0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
// create a depth-stencil renderbuffer
const depthStencilBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
// make a depth-stencil buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
function createTexture(gl, color) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// set the filtering so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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);
if (color) {
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
}
return tex;
}
// create a red texture and a green texture
const redTex = createTexture(gl, [255, 0, 0, 255]);
const greenTex = createTexture(gl, [0, 255, 0, 255]);
gl.enable(gl.STENCIL_TEST);
gl.useProgram(program);
gl.clearColor(0, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, redTex);
gl.stencilFunc(
gl.ALWAYS, // the test
1, // 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.REPLACE, // what to do if both tests pass
);
// draw a 64x64 pixel red rect in middle
gl.drawArrays(gl.POINTS, 0, 1);
gl.stencilFunc(
gl.EQUAL, // the test
1, // 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.KEEP, // what to do if both tests pass
);
// draw a green 64x64 pixel square in the
// upper right corner. The stencil will make
// it not go outside the red square
gl.vertexAttrib2f(posLoc, 0.5, 0.5);
gl.bindTexture(gl.TEXTURE_2D, greenTex);
gl.drawArrays(gl.POINTS, 0, 1);
// draw the framebuffer's texture to
// the canvas. we should see a 32x32
// red square with the bottom right corner
// green showing the stencil worked. That will
// be surrounded by blue to show the texture
// we were rendering to is larger than the
// red square. And that will be surrounded
// by purple since we're drawing a 64x64
// point on a 128x128 canvas which we clear
// purple.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.vertexAttrib2f(posLoc, 0.0, 0.0);
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>
你应该能够调用 gl.getContextAttributes
来检查你是否有模板缓冲区,这样你就可以使用建议的解决方案,如果它告诉你你没有在canvas.