Shadows in WebGL

Carlos Eduardo González Alvarez
cegonzalez13
Published in
3 min readApr 25, 2018

The full example and code can be seen here:

In this example, we are going to show how to add shadows to our previous example, which we modified to add some extra lighting effects and let it work with the shadows we needed to add to this week’s example. We followed this example to get it working correctly.

Now then, the main thing we need to focus when we talk about shadows is the shadow mapping. When we render a scene from a view camera, we need to ask this question of each fragment, “Is this the closest surface to the light source?” If it is, the fragment gets full lighting. If it is not, the fragment is in a shadow. We can answer this question using a shadow map, which is a texture map, where each component value is the distance from a light source to the closest fragment. The “trick” is to get the correct distance out of the shadow map.

When we render a scene, we will interpolate two different (x,y,z) locations on the surface: the one by the light source and the one calculated by the view camera transformation matrix. In order to do this we need a shadow buffer that handles the cases when we need to show a shadow or not.

According to the tutorial that implements this creation of the shadow buffer, it has 5 different key parts for it to work:

  1. Create a new frame buffer object. gl.createFramebuffer()
  2. Create a texture object to store the color buffer values. The size of the texture object determines the resolution of the rendering. It’s internal format is RGBA (red, green, blue, alpha), where each value is an unsigned byte, gl.UNSIGNED_BYTE. (This is the only format WebGL 1.0 supports.) gl.createTexture(), gl.bindTexture(), gl.texImage2D(),gl.texParameteri()
  3. Create a second texture object to store the depth buffer values. The size of this texture object must match the size of the first texture object. It’s internal format is gl.DEPTH_COMPONENT and each value will be a 32-bit integer, gl.UNSIGNED_INT, which will represent a depth value in the range [0.0, +1.0]. (The integer values are scaled such that 0.0 represents the z-near clipping plane, and 1.0 represents the z-far clipping plane.)
  4. Attach the first texture object to the “Color attachment” of the frame buffer, and attach the second texture object to the “Depth attachment” of the frame buffer.gl.bindFramebuffer(), gl.framebufferTexture2D()
  5. Verify that the frame buffer object is valid. gl.checkFramebufferStatus()

Now we will have this inside our drawScene function, so it executes both buffers:

Now we have to define the vertex and fragment shaders of the shadow buffer. These are really simple because they are set according to the sun, not the projection view. It just defines the position of the shadows according to the current position of the sun.

In the main vertex and fragment shaders we did some changes in order to calculate the positions where there should be shadows. We do this with this function in the vertex shader:

This function just tests the distance between the fragment and the light source as calculated using the shadowmap transformation and the smallest distance between the closest fragment to the light source for this location, as stored in the shadowmap.

When the closest distance to the light source was saved in the shadowmap, some precision was lost. Therefore we need a small tolerance of 0.00004 factor to compensate for the lost precision.

Now we just need to apply the shadow if the sun is up and the isShadow function returns us true:

Notice that we change completely the lightWeighting and it is no longer in terms of the directional or the ambient light. We set it to be a little bit darker than our ambient light (0.3,0.3,0.3).

Time Log

I estimated a short amount of time doing this exercise… around 4 hours. But the handling of the new scene and shaders got most of my time, also the handling of the perspectives of the source of the light (the sun) were a little bit troublesome. That’s why I ended up taking up 7 hours to do the exercise.

--

--