Multiple output textures from the same program
Asked Answered
J

1

5

I'm trying to learn how to do multiple outputs from the same program in WebGL2 leveraging gl.drawBuffer() capabilities.

I looked at the book "OpenGL ES 3.0 Programming Guide", chapter 11 where it lists what is needed for multi-output to take place. However the shader source example is very trivial outputting only constant values.

I'd like to know if someone has a better example? or if one could explain what happened to the TextureCoordinates varying? In normal shader code I would use that to find data values from my inputs and write them out. Now in the face of multiple layouts, how would the TextureCoordinates varying correspond to each layout? What happens to the dimensions of my viewPort? which output Texture does that correspond with?

Here are some steps the way I understood them:

  1. Create a Color attachment array GL_COLOR_ATTACHMENT0, ...
  2. Create a framebuffer object for each output
  3. Create output textures
  4. For each FB:

    • BindFramebuffer
    • BindTexture
    • Associate texture with FBO: frameBufferTexture2D (..., color_attchment_from_step1)
  5. call drawBuffers passing the color attachment array

Inside the shader access output values like this:

layout(location = 0) out vec4 fragData0;

layout(location = 1) out vec4 fragData1;
Jelks answered 10/8, 2018 at 19:57 Comment(0)
M
10

You only need one framebuffer object. You attach all the textures to it. So your steps would be

  1. Create a framebuffer object and BindFramebuffer
  2. Create output textures
  3. For each texture:
  4. Associate texture with FBO: frameBufferTexture2D(...)
  5. Create a Color attachment array GL_COLOR_ATTACHMENT0, ...
  6. call drawBuffers passing the color attachment array

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>
Mattland answered 11/8, 2018 at 8:55 Comment(7)
Thanks a lot @gman. The questions I still have are: Do all textures need to be of the same size? It looks like this is a good solution to output different colors but may not be so useful to output to arbitrary sizes of textures. If that were possible, I would need different texture coordinate input variables to passed to my fragment source.Jelks
And on top of that how would the scaling work? I think currently I am setting the scale by setting the gl.viewport(0,0,width, height) and that's how I know when getting vTexCoords varying in my fragment shader I would have to multiply s and t by the scales along X and Y to get the logical coordinates. So if the output textures are allowed to be of different sizes I would also need different scale factors as inputs?Jelks
The textures attached to the same framebuffer can not be difference sizes. They must all be the same dimensions. I'm also not sure what texcoords has to do with viewport. In most shaders the 2 aren't related. Also you make up your own attributes so your not limited to one set of texcoords nor do you even have to have texture coords.Mattland
Thanks again for the clarification.Jelks
I am trying to do this however I need to attach yet another texture after the first draw. I notice that you bind the framebuffer to NULL. I can't do that. However if I just call gl.framebufferTexture2D() I get an FRAMEBUFFER_INCOMPLETE_DIMENSIONS status. How do I reset my FB after doing a multi output? thanksJelks
This is old but I have no idea what "reset my fb" means. You should make one fb for each combination of attachments you want to use. You should not modified fbsMattland
that does not work. I removed the "for" loop in the shader, and then it works. jsfiddle.net/yc2eq5ap/1Diwan

© 2022 - 2024 — McMap. All rights reserved.