# A Small Shadow Map Improvement

Shadows are a great way to relay information in a 3D rendering. They can help demonstrate distances between two objects such as a bouncing ball and the ground. They also relay further information to the structure of an object as they give a second silhouette from the perspective of the light casting the shadow. In this article, I will demonstrate a very small but important improvement for THREEjs’s shadow rendering, a one line change to the shader code.

## Depth Packing

THREEjs, our chosen graphics engine, like many realtime 3D renderers, casts shadows using a technique called shadow maps. A map is a term used to describe image data used on a GPU. A map can be generated by rendering a given scene and then storing the color information for that created image. A shadow map is a generated map that instead of storing color, stores the distance from a light in the scene to the surface of a rendered object.

Correctly generating the shadow map can be non-obvious since there are ways to get values that may be visibly close until you put it through some scrutiny. One part of this work is often referred to as packing where we take a piece of data and store it using a format other than its native representation. Specifically for shadow maps we need to pack the depth, represented as a decimal number between 0, really close, and 1, really far.

Packing is somewhat tricky in GLSL, the programming language for shaders in WebGL. The earlier versions of GLSL, and the ones available on most mobile devices and in WebGL, did not allow bitwise operators that frequent more general programming languages and are the normal choice for performing packing work. Instead, using a floating point modulus function is the only way.

It is not unusual for a piece of software to use published code from others, especially open source software like THREEjs. People figured out one way to pack the depth value in GLSL a while ago and it seems to have circulated a lot either without people questioning or possibly accepting the error it produces to a more accurate and slightly more costly source.

```
// packing a float in glsl with multiplication and fract
vec4 packFloat( 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 );
// fract is the problem
vec4 res = fract( depth * bit_shift );
res -= res.xxyz * bit_mask;
return res;
}
```

Fract, shorthand for the word “fraction” or the part of a decimal value after the dot, is equivalent to a modulus function with a predefined mod of value 1. This may open it to optimizations that the general modulus function cannot achieve. However its use in this specific case will cause errors in the less precise components being stored. Maps can have different precisions depending on how much space a developer wants it to take up in the GPU’s memory (RAM) versus the available precision. An individual cell in our shadow map has 4 components, each storing 256 possible values. On the CPU side these values would be interpreted as binary values of 0 to 255. On the GPU side they are interpreted as decimal values of 0 to 1. Since fract is a mod 1 function, no component in our map will store a value of 1 instead storing a value of 0.

Instead we want to multiply our components by 255 and mod by 256 and then divide back to 255.

```
// packing a float in glsl with multiplication and mod
vec4 packFloat( 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 );
// combination of mod and multiplication and division works better
vec4 res = mod(
depth * bit_shift * vec4( 255 ),
vec4( 256 ) ) / vec4( 255 );
res -= res.xxyz * bit_mask;
return res;
}
```

So first we can say this will cause some values to be incorrectly stored at the highest component value of 255. But if you consider more values in the lower components, some of them will be stored as something lower than it should.

We can see from the above image, that using fract, the image on the left, adds an implicit and likely, in our case definitely, undesired offset. If this offset was a fixed amount maybe it would be an acceptable optimization for the error but it is actually dependent on the higher precision components created by packing the float value.

Page 1 of 2 | Next page