21
votes

I am trying to study shadow mapping in WebGL. I see same piece of shader code copied in various libraries and examples that achieve this. However nowhere did I find the explanation of how it works.

The idea is to save a depth value (a single float) into the color buffer (vec4). There is a pack function that saves float to vec4 and unpack function that retrieves the float from vec4.

vec4 pack_depth(const in float depth)
{
    const vec4 bit_shift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);
    const vec4 bit_mask  = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);
    vec4 res = fract(depth * bit_shift);
    res -= res.xxyz * bit_mask;
    return res;
}

float unpack_depth(const in vec4 rgba_depth)
{
    const vec4 bit_shift = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0);
    float depth = dot(rgba_depth, bit_shift);
    return depth;
}

I would have imagined that packing a float into vec4 should be a trivial problem, just copy it into one of the 4 slots of vec4 and leave others unused. That's why the bit shifting logic in above code is puzzling to me.

Can anyone shed some light?

3

3 Answers

13
votes

It's not storing a GLSL float in a GLSL vec4. What it's doing is storing a value in a vec4 which, when written to an RGBA8 framebuffer (32-bit value) can be read as a vec4 and then reconstituted into the same float that was given previously.

If you did what you suggest, just writing the floating-point value to the red channel of the framebuffer, you'd only get 8 bits of accuracy. With this method, you get all 32-bits working for you.

3
votes

In addition to the answer above, you might be interested in the floating point texture extension described here:

http://www.khronos.org/registry/webgl/extensions/OES_texture_float/

Note that there are hardware/software setups out there where this extension doesn't exist/run, but if it do it sure is a good extension. My experience is that it's fast as well. If you use this, you can use the remaining three channels to store other information, such as color from a projected texture.

3
votes

If you're interested into the nitty-gritty details of how these routines work I suggest you to read my blog post. I'm adding some details here on how that code works and to address some possible use cases.

As you probably figured out, that code is encoding a normalized float value to a vec4. OpenGL ES 2.0 or WebGL (at the time of writing), might make use of those pack/unpack routines to provide 32 bit precision floating points via RGBA8 textures (more on this in the spec).

Even with the extension posted by Mikael (OES_texture_float) it might be necessary (for debugging purposes for instance) to dump full 32 bit precision normalized floating points and as described in the spec readPixels is currently limited by the following

Only two combinations of format and type are accepted. The first is format RGBA and type UNSIGNED_BYTE. The second is an implementation-chosen format.