Using 16-bit Buffers
The Smudge API is still changing pretty often. Code on this post is out of date.
Most digital color images represent colors using three channels—red, green, and blue—each having 8-bit precision. 8-bits allows for 256 values per channel: 0 for “black”, and 255 for “white”. This is pretty good precision and, depending on who you ask, just about as good as our eyes can discern.
But more precision has some important benefits. First, if you have used 255 to represent the white color of a sheet of paper, you don’t have any room to represent the much brighter white color of a lit lightbulb. Second, working with higher precision color channels greatly reduces artifacts due to rounding.
Imagine you have a canvas that is white—has a value of 255—and you apply a very transparent black paint—a value of 0, but an alpha of just .1. The paint should—very slightly—darken the canvas. In an 8-bit buffer, rounding will negate the slight darkening. In a 16-bit buffer the layers of paint will build up.
WebGL2 supports using 16-bit and even 32-bit textures, opening the way for high precision, HDR rendering and compositing.
Enabling 16-bit (HDR) Rendering
As far as I can tell, a canvas element always has an 8-bit color depth. That is fine though, PBR5 already renders to textures first, and then copies the image from the texture to the canvas. WebGL2 introduces new internal formats for textures, including gl.RGBA16F
so using a high-bit-depth render texture should be pretty easy.
Just change these two lines:
gl = canvas.getContext('webgl');
...
gl.texImage2D(gl.TEXTURE_2D, 0, gl2.RGBA, width, height,
0, gl2.RGBA, gl.UNSIGNED_BYTE, null);
to these:
gl = canvas.getContext('webgl2');
...
gl.texImage2D(gl.TEXTURE_2D, 0, gl2.RGBA16F, width, height,
0, gl2.RGBA, gl2.HALF_FLOAT, null);
A Snag
Unfortunately, that didn’t quite work. Chrome reported the following error:
GL ERROR :GL_INVALID_OPERATION : glGenerateMipmap: Can not generate mips
That error led me to search for issues regarding mipmaps for a bit, but that wasn’t actually the issue. While WebGL can read from 16-bit textures just fine, they are not color-renderable as an attachment to a FrameBuffer. So the framebuffer wasn’t properly built, and anything that used the buffer caused an error like this in Chrome:
GL ERROR :GL_INVALID_FRAMEBUFFER_OPERATION : glClear: framebuffer incomplete
Or this very clear, helpful error message from Firefox:
Framebuffer not complete. (status: 0x8cd6) COLOR_ATTACHMENT0 has an effective format of RGBA16F, which is not renderable
Of course, proper error handling—or at least error checking—would have saved me quite a bit of debugging time:
let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
console_error("Failed to build Framebuffer: Incomplete or Unsupported");
}
A Solution
Fortunately, while the texture format I wanted isn’t color-renderable in plain WebGL, it is if the EXT_color_buffer_float is enabled.
var ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) {
// report + handle
}
WebGL2 and EXT_color_buffer_float are both available in current versions of Chrome and Firefox.