将数据放在 WebGL 中的不同纹理上并将其取出

Putting data onto different textures in WebGL and getting it out

我正在尝试从着色器输出多个缓冲区 - 总体目标是将其用于 GPGPU 目的。我看过这个 并通过这个更接近目标:

document.addEventListener("DOMContentLoaded", function() {
    function main() {
        const gl = document.querySelector('canvas').getContext('webgl2');
        if (!gl) {
            return alert("need WebGL2");
        }
        gl.canvas.width  = 2;
        gl.canvas.height = 2;


        const vs = `
#version 300 es
in vec2 position;

void main(void) {
     gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;

        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;
  layout(location = 4) out vec4 outColor4;
  layout(location = 5) out vec4 outColor5;

  void main() {
    // simplified for question purposes
    outColor0 = vec4(1, 0, 0, 1);
    outColor1 = vec4(0, 1, 0, 1);
    outColor2 = vec4(0, 0, 1, 1);
    outColor3 = vec4(1, 1, 0, 1);
    outColor4 = vec4(1, 0, 1, 1);
    outColor5 = vec4(0, 1, 1, 1);
  } 
  `

        const program = twgl.createProgram(gl, [vs, fs]);

        const textures = [];

        const fb = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, fb);

        for (let i = 0; i < 6; ++i) {
            const tex = gl.createTexture();
            textures.push(tex);
            gl.bindTexture(gl.TEXTURE_2D, tex);

            const width = 2;
            const height = 2;
            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);
        }

        gl.viewport(0, 0, 2, 2);

        // tell it we want to draw to all 4 attachments

        gl.drawBuffers([
            gl.COLOR_ATTACHMENT0,
            gl.COLOR_ATTACHMENT1, 
            gl.COLOR_ATTACHMENT2,
            gl.COLOR_ATTACHMENT3,
            gl.COLOR_ATTACHMENT4,
            gl.COLOR_ATTACHMENT5,                   
        ]);

        // draw a single point
        gl.useProgram(program);
        {
            const offset = 0;
            const count = 1
            gl.drawArrays(gl.TRIANGLE, 0, 4);
        }

        for (var l = 0; l < 6; l++) { 
            var pixels  = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
            
            gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
            gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

            console.log(pixels.join(' '));
        }
    }
    main();
})

然而,结果是每个缓冲区中只有一个像素被设置,所以输出是:

0 0 0 0 255 0 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 255 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 255 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 255 255 0 0 0 0 0 0 0 0

而不是我 hoping/expecting:

255 0 0 255 255 0 0 255 255 0 0 255 255 0 0 255
etc.

我期待

outColor0 = vec4(1, 0, 0, 1);

相当于

gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

但显然我错了。

那么我如何才能达到预期的结果 - 能够在每个缓冲区上设置每个像素?

该代码不提供任何顶点数据,即使它要求它绘制 4 个顶点。此外,它传递了不存在的 gl.TRIANGLE 。它是 gl.TRIANGLES,末尾有一个 Sgl.TRIANGLE 将是未定义的,它被强制转换为匹配 gl.POINTS

的 0

在 JavaScript 控制台中

> const gl = document.createElement('canvas').getContext('webgl2');
< undefined
> gl.TRIANGLE 
< undefined
> gl.TRIANGLES
< 4
> gl.POINTS
< 0

换句话说,所有 gl.CONSTANTS 都只是整数值。而不是

gl.drawArrays(gl.TRIANGLES, offset, count)

你可以这样做

gl.drawArrays(4, offset, count) 

因为 gl.TRIANGLES = 4.

但你没有使用 gl.TRIANGLES 你使用了 gl.TRIANGLE(没有 S)所以你有效地做到了这一点

gl.drawArrays(undefined, offset, count) 

被解释为

gl.drawArrays(0, offset, count) 

0 = gl.POINTS 所以这与

相同
gl.drawArrays(gl.POINTS, offset, count) 

代码随后在同一位置绘制一个 1 像素点 4 次,因为您调用它时计数为 4

gl.drawArrays(gl.POINTS, 0, 4) 

顶点着色器中的任何内容都不会改变每次迭代,因此每次迭代都将做完全相同的事情。在这种情况下,它将在剪辑 space 位置 0,0,0,1 处绘制一个 1x1 像素点,最终将成为 2x2 像素的左下角像素。

在任何情况下你可能想提供顶点但作为一个简单的测试如果我添加

  gl_PointSize = 2.0;

到顶点着色器并将绘制调用更改为

gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point

然后它会产生您期望的结果。它在剪辑 space 位置 0,0,0,1

处绘制一个 2x2 像素点

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert("need WebGL2");
  }
  gl.canvas.width = 2;
  gl.canvas.height = 2;


  const vs = `
    #version 300 es
    in vec2 position;
    
    void main(void) {
         gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
         gl_PointSize = 2.0;
    }
    `;

  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;
      layout(location = 4) out vec4 outColor4;
      layout(location = 5) out vec4 outColor5;
    
      void main() {
        // simplified for question purposes
        outColor0 = vec4(1, 0, 0, 1);
        outColor1 = vec4(0, 1, 0, 1);
        outColor2 = vec4(0, 0, 1, 1);
        outColor3 = vec4(1, 1, 0, 1);
        outColor4 = vec4(1, 0, 1, 1);
        outColor5 = vec4(0, 1, 1, 1);
      } 
      `

  const program = twgl.createProgram(gl, [vs, fs]);

  const textures = [];

  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);

  for (let i = 0; i < 6; ++i) {
    const tex = gl.createTexture();
    textures.push(tex);
    gl.bindTexture(gl.TEXTURE_2D, tex);

    const width = 2;
    const height = 2;
    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);
  }

  gl.viewport(0, 0, 2, 2);

  // tell it we want to draw to all 4 attachments

  gl.drawBuffers([
    gl.COLOR_ATTACHMENT0,
    gl.COLOR_ATTACHMENT1,
    gl.COLOR_ATTACHMENT2,
    gl.COLOR_ATTACHMENT3,
    gl.COLOR_ATTACHMENT4,
    gl.COLOR_ATTACHMENT5,
  ]);

  // draw a single point
  gl.useProgram(program); {
    const offset = 0;
    const count = 1
    gl.drawArrays(gl.POINTS, 0, 1);
  }

  for (var l = 0; l < 6; l++) {
    var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);

    gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
    gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

    console.log(pixels.join(' '));
  }
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

您可以尝试使用 webgl-lint 如果我 运行 使用您的原始代码至少会抱怨

Uncaught Error: https://greggman.github.io/webgl-lint/webgl-lint.js:2942: error in drawArrays(/UNKNOWN WebGL ENUM/ undefined, 0, 4): argument 0 is undefined with WebGLProgram("unnamed") as current program with the default vertex array bound

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert("need WebGL2");
  }
  gl.canvas.width = 2;
  gl.canvas.height = 2;


  const vs = `
    #version 300 es
    in vec2 position;
    
    void main(void) {
         gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    }
    `;

  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;
      layout(location = 4) out vec4 outColor4;
      layout(location = 5) out vec4 outColor5;
    
      void main() {
        // simplified for question purposes
        outColor0 = vec4(1, 0, 0, 1);
        outColor1 = vec4(0, 1, 0, 1);
        outColor2 = vec4(0, 0, 1, 1);
        outColor3 = vec4(1, 1, 0, 1);
        outColor4 = vec4(1, 0, 1, 1);
        outColor5 = vec4(0, 1, 1, 1);
      } 
      `

  const program = twgl.createProgram(gl, [vs, fs]);

  const textures = [];

  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);

  for (let i = 0; i < 6; ++i) {
    const tex = gl.createTexture();
    textures.push(tex);
    gl.bindTexture(gl.TEXTURE_2D, tex);

    const width = 2;
    const height = 2;
    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);
  }

  gl.viewport(0, 0, 2, 2);

  // tell it we want to draw to all 4 attachments

  gl.drawBuffers([
    gl.COLOR_ATTACHMENT0,
    gl.COLOR_ATTACHMENT1,
    gl.COLOR_ATTACHMENT2,
    gl.COLOR_ATTACHMENT3,
    gl.COLOR_ATTACHMENT4,
    gl.COLOR_ATTACHMENT5,
  ]);

  // draw a single point
  gl.useProgram(program); {
    const offset = 0;
    const count = 1
    gl.drawArrays(gl.TRIANGLE, 0, 4);
  }

  for (var l = 0; l < 6; l++) {
    var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);

    gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
    gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

    console.log(pixels.join(' '));
  }
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js" crossorigin="anonymous"></script>