【问题标题】:Support for Stencil Buffer with WebGL使用 WebGL 支持模板缓冲区
【发布时间】:2020-11-27 08:04:47
【问题描述】:

使用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 模板位。

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

【问题讨论】:

    标签: javascript google-chrome firefox webgl stencil-buffer


    【解决方案1】:

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

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

    这是一个测试。你应该看到一个右下角为绿色的红色方块。这将在一个紫色方块内的蓝色方块内。

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

    紫色方块是为了显示画布的大小,并且我们正在绘制比整个画布更小的帧缓冲区纹理。这只是为了表明模板缓冲区在您的机器上工作。对于您自己的解决方案,您希望绘制一个由顶点组成的四边形,而不是使用如下所示的点,并且您希望使附加到帧缓冲区的纹理和渲染缓冲区与您的画布大小相同。

    "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 来检查您是否有模板缓冲区,如果它告诉您在画布上没有模板缓冲区,您可以使用建议的解决方案。

    【讨论】:

    • 更改为 nvidia-driver-440 驱动程序而不是 nvidia-driver-440-server 似乎已解决此问题。我仍将实施您的解决方案作为后备。谢谢!
    猜你喜欢
    • 2020-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-07
    • 1970-01-01
    • 2012-03-13
    • 1970-01-01
    相关资源
    最近更新 更多