javascript - 支持 WebGL 模板缓冲区

标签 javascript google-chrome firefox webgl stencil-buffer

使用 canvas.getContext("webgl", {stencil : true}) 初始化 webgl 会请求一个模板缓冲区,但并非所有浏览器实际上都会给你一个(对我来说,Ubuntu 20.04 上的 Firefox 79.0) LTS 不起作用,但 Chrome 84.0.4147.89 可以。我的显卡是 NVIDIA RTX 2060,我使用的是 nvidia-driver-440-server 驱动程序。

我想知道模板缓冲区的支持范围有多大,但我找不到有关支持哪些浏览器的信息。像 glStencilOp 这样的函数,这是我唯一能找到支持信息的函数,仍然可以使用,它们只是不使用 0 模板位做任何事情。

是否有支持此功能的浏览器列表?

最佳答案

老实说,这听起来像是 Firefox 中的一个错误,尽管考虑到规范允许实现无法在 Canvas 上提供模板缓冲区,无论出于何种原因,这在技术上都不是一个错误。我会考虑填写一份。使用 Chromium 浏览器进行测试,只是为了检查这是 Firefox 选择不提供模板缓冲区,而不是驱动程序问题或其他问题。

您应该始终能够创建一个DEPTH_STENCIL渲染缓冲区。没有任何 WebGL 版本允许实现不支持该功能。因此,您可以通过渲染到附加到帧缓冲区的纹理+深度模板渲染缓冲区来解决该错误,然后将帧缓冲区颜色纹理渲染到 Canvas 。

这是一个测试。您应该看到一个右下角绿色的红色方 block 。它将位于一个蓝色方 block 内,而该蓝色方 block 又位于一个紫色方 block 内。

蓝色方 block 显示帧缓冲区纹理的范围。如果绿色方 block 没有被模板缓冲区遮盖,它就会渗入蓝色。

紫色方 block 显示 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,那么您将看到绿色方 block 不再被模板遮盖

"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 上的缓冲区。

关于javascript - 支持 WebGL 模板缓冲区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63294094/

相关文章:

css - SVG 和文字阴影,Chrome 和 Firefox 的区别

google-chrome - 什么决定了移动浏览器上 .txt 文档的视口(viewport)(初始比例)?

CSS 表格 td 宽度和右对齐不起作用

javascript - 将邮政编码的开头与indexOf或正则表达式匹配?

javascript - 剑道 UI 网格 : Drag and Drop Hierarchy not working

javascript - Chrome execCommand 返回错误

javascript - 在 Firefox 中的关闭选项卡按钮上叠加图标

javascript - 如何让两个 div 和其中的图像在浏览器调整大小时并排放置?

javascript - 如何创建 .js 文件的快照 -> webkit nwjs v8

javascript - 带有代理脚本的 IMacros