5

is it possible to retrieve pixels value as a float on framebuffer with multiple attachments ? (WebGL 2)

I tried this :

var framebuffer = _gl.createFramebuffer();

_gl.bindFramebuffer(_gl.FRAMEBUFFER, framebuffer);

_gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, texture1, 0);
_gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT1, _gl.TEXTURE_2D, texture2, 0);

_gl.drawBuffers([_gl.COLOR_ATTACHMENT0, _gl.COLOR_ATTACHMENT1]);

With float textures setup as follow :

_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA32F, 256, 256, 0, _gl.RGBA, _gl.FLOAT, null);

Then i bind the framebuffer and call readPixels to get values for the first attachment :

_gl.readPixels(0, 0, 1, 256, _gl.RGBA, _gl.FLOAT, 0);

Without float textures, this work but with float textures, the framebuffer stay incomplete.

The WebGL 2 spec seem to say that this should work, I now have some doubt however, it seem that _gl.RGBA32F seem to be the problem, with an internal format of _gl.RGBA, it generate incompatible type error.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Onirom
  • 532
  • 1
  • 7
  • 22
  • "*the framebuffer stay incomplete*" What is the specific reason for the incomplete framebuffer? Framebuffer completeness has a plethora of reasons for failure. – Nicol Bolas Aug 08 '17 at 14:52
  • I am using https://github.com/vorg/webgl-debug and no errors are thrown but still the checkFramebufferStatus function report that it is not complete, without float, the framebuffer is created without problems with the same calls but with different parameters (_gl.RGBA for internal format and _gl.UNSIGNED_BYTE for format). – Onirom Aug 08 '17 at 15:02
  • "*the checkFramebufferStatus function report that it is not complete*" No, it doesn't. It reports a *specific reason* why the framebuffer is not complete. I'm asking you want that reason is. – Nicol Bolas Aug 08 '17 at 15:06
  • Indeed, it report an incomplete attachment. – Onirom Aug 08 '17 at 15:13

1 Answers1

16

First off, rendering to floating point requires an extension in WebGL2, EXT_color_buffer_float.

You can see in the table here copied from the spec section 3.8.3.2 that floating point textures are not renderable in WebGL2 by default.

Otherwise you call gl.readBuffer to set the buffer to read from before calling gl.readPixels

Example:

function main() {
  const gl = document.createElement("canvas").getContext("webgl2");
  if (!gl) {
    alert("need WebGL2");
    return;
  }
  const ext = gl.getExtension("EXT_color_buffer_float");
  if (!ext) {
    alert("need EXT_color_buffer_float");
    return;
  }
  
  const tex1 = createTexture(gl, [12, 0, 0, 34]);
  const tex2 = createTexture(gl, [0, 56, 78, 0]);
  
  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex1, 0);

  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, tex2, 0);

  gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
  
  readPixelsFromBuffer(gl, gl.COLOR_ATTACHMENT0);
  readPixelsFromBuffer(gl, gl.COLOR_ATTACHMENT1);
}

function readPixelsFromBuffer(gl, attachment) {
  gl.readBuffer(attachment);
  const data = new Float32Array(4);
  const x = 0;
  const y = 0;
  const width = 1;
  const height = 1;
  const format = gl.RGBA;
  const type = gl.FLOAT;
  gl.readPixels(x, y, width, height, format, type, data);
  log(glEnumToString(gl, attachment), data);
}

function createTexture(gl, color) {
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  const level = 0;
  const internalFormat = gl.RGBA32F;
  const width = 1;
  const height = 1;
  const border = 0;
  const format = gl.RGBA;
  const type = gl.FLOAT;
  const data = new Float32Array(color);
  gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
                format, type, data);
  // unless we get `OES_texture_float_linear` we can not filter floating point
  // textures
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  
  return tex;
}

function glEnumToString(gl, value) {
  for (let key in gl) {
    if (gl[key] === value) {
      return key;
    }
  }
  return `0x${value.toString(16)}`;
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}

main();
gman
  • 100,619
  • 31
  • 269
  • 393
  • Once to read from the first buffer, again to read from the second. – gman Aug 08 '17 at 16:16
  • Sorry, I see how that could be confusing. I edited the function name. Hopefully it's less confusing now – gman Aug 08 '17 at 16:17
  • Thank you, this work and learned few things from that code as well, i did not know about the need of EXT_color_buffer_float. – Onirom Aug 08 '17 at 16:22