【问题标题】:Multiple output textures from the same program来自同一程序的多个输出纹理
【发布时间】:2019-01-18 11:20:21
【问题描述】:

我正在尝试学习如何利用 gl.drawBuffer() 功能在 WebGL2 中从同一程序执行多个输出。

我查看了“OpenGL ES 3.0 编程指南”一书的第 11 章,其中列出了发生多输出所需的内容。然而,着色器源示例非常简单,只输出常量值。

我想知道是否有人有更好的例子?或者是否可以解释 TextureCoordinates 变化发生了什么?在普通的着色器代码中,我会使用它从我的输入中查找数据值并将它们写出来。现在面对多种布局,TextureCoordinates怎么变化对应每个布局呢?我的视口尺寸会发生什么变化?这与哪个输出纹理对应?

以下是我理解的一些步骤:

  1. 创建一个颜色附件数组 GL_COLOR_ATTACHMENT0, ...
  2. 为每个输出创建一个帧缓冲区对象
  3. 创建输出纹理
  4. 对于每个FB:

    • 绑定帧缓冲区
    • 绑定纹理
    • 将纹理与 FBO 关联:frameBufferTexture2D (..., color_attchment_from_step1)
  5. 调用 drawBuffers 传递颜色附件数组

在着色器内部访问输出值如下:

layout(location = 0) out vec4 fragData0;

layout(location = 1) out vec4 fragData1;

【问题讨论】:

    标签: glsl webgl2


    【解决方案1】:

    您只需要一个帧缓冲区对象。您将所有纹理附加到它。所以你的步骤是

    1. 创建帧缓冲区对象和BindFramebuffer
    2. 创建输出纹理
    3. 对于每个纹理:
    4. 将纹理与 FBO 关联:frameBufferTexture2D(...)
    5. 创建一个颜色附件数组 GL_COLOR_ATTACHMENT0, ...
    6. 调用 drawBuffers 传递颜色附件数组

    function main() {
      const gl = document.querySelector('canvas').getContext('webgl2');
      if (!gl) {
        return alert("need WebGL2");
      }
      const vs = `
      #version 300 es
      void main() {
        gl_PointSize = 300.0;
        gl_Position = vec4(0, 0, 0, 1);
      }
      `;
    
      const fs = `
      #version 300 es
      precision mediump float;
    
      layout(location = 0) out vec4 outColor0;
      layout(location = 1) out vec4 outColor1;
      layout(location = 2) out vec4 outColor2;
      layout(location = 3) out vec4 outColor3;
    
      void main() {
        outColor0 = vec4(1, .5, .3, .7);   // orange
        outColor1 = vec4(.6, .5, .4, .3);  // brown
        outColor2 = vec4(.2, .8, .0,  1);  // green
        outColor3 = vec4(.3, .4, .9, .6);  // blue
      } 
      `
    
      const program = twgl.createProgram(gl, [vs, fs]);
    
      const textures = [];
      const fb = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
      for (let i = 0; i < 4; ++i) {
        const tex = gl.createTexture();
        textures.push(tex);
        gl.bindTexture(gl.TEXTURE_2D, tex);
        const width = 1;
        const height = 1;
        const level = 0;
        gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, 
                      gl.RGBA, gl.UNSIGNED_BYTE, null);
        // attach texture to framebuffer
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i,
                                gl.TEXTURE_2D, tex, level);
      }
    
      // our framebuffer textures are only 1x1 pixels
      gl.viewport(0, 0, 1, 1);
    
      // tell it we want to draw to all 4 attachments
      gl.drawBuffers([
        gl.COLOR_ATTACHMENT0,
        gl.COLOR_ATTACHMENT1, 
        gl.COLOR_ATTACHMENT2,
        gl.COLOR_ATTACHMENT3,
      ]);
    
      // draw a single point
      gl.useProgram(program);
      {
        const offset = 0;
        const count = 1
        gl.drawArrays(gl.POINT, 0, 1);
      }
    
      // --- below this is not relevant to the question but just so we
      // --- we can see it's working
    
      // render the 4 textures
      const fs2 = `
      #version 300 es
      precision mediump float;
      uniform sampler2D tex[4];
      out vec4 outColor;
      void main() {
        vec4 colors[4];
    
        // you can't index textures with non-constant integer expressions
        // in WebGL2 (you can in WebGL1 lol)
        colors[0] = texture(tex[0], vec2(0));
        colors[1] = texture(tex[1], vec2(0));
        colors[2] = texture(tex[2], vec2(0));
        colors[3] = texture(tex[3], vec2(0));
        
        vec4 color = vec4(0);
        for (int i = 0; i < 4; ++i) { 
          float x = gl_PointCoord.x * 4.0;
          float amount = step(float(i), x) * step(x, float(i + 1));
          color = mix(color, colors[i], amount);
        }
        outColor = vec4(color.rgb, 1);
      }
      `;
      const prgInfo2 = twgl.createProgramInfo(gl, [vs, fs2]);
      gl.bindFramebuffer(gl.FRAMEBUFFER, null);
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
      gl.useProgram(prgInfo2.program);
      // binds all the textures and set the uniforms
      twgl.setUniforms(prgInfo2, {
        tex: textures,
      });
      gl.drawArrays(gl.POINTS, 0, 1);
    }
    main();
    <script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
    <canvas></canvas>

    【讨论】:

    • 非常感谢@gman。我仍然有的问题是:所有纹理都需要具有相同的大小吗?看起来这是输出不同颜色的一个很好的解决方案,但对于输出到任意大小的纹理可能不是那么有用。如果可能的话,我需要将不同的纹理坐标输入变量传递给我的片段源。
    • 除此之外,缩放将如何工作?我认为目前我正在通过设置gl.viewport(0,0,width, height) 来设置比例,这就是我知道当我的片段着色器中的 vTexCoords 发生变化时,我必须将 s 和 t 乘以 X 和 Y 的比例以获得逻辑坐标。因此,如果允许输出纹理具有不同大小,我还需要不同的比例因子作为输入?
    • 同一个framebuffer的贴图大小不能不同。它们都必须是相同的尺寸。我也不确定 texcoords 与视口有什么关系。在大多数着色器中,这 2 是不相关的。此外,您还可以构建自己的属性,因此您不仅限于一组 texcoord,甚至不必拥有纹理坐标。
    • 再次感谢您的澄清。
    • 我正在尝试这样做,但是我需要在第一次绘制后附加另一个纹理。我注意到您将帧缓冲区绑定到 NULL。我不能那样做。但是,如果我只是打电话给gl.framebufferTexture2D(),我会得到FRAMEBUFFER_INCOMPLETE_DIMENSIONS 状态。进行多路输出后如何重置我的FB?谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多