The most important parts that make this WebGL example possible are: rendering to the texture
that is going to be used as the shadow map, rendering the scene and writing a shader that is
going to use the shadow map to create shadows, this shader will also take the normal information
of the vertices in the model to achieve diffuse lighting and specular lighting effects.

The 3D models.

The first model, the monkey face model, is a default model that you can find in Blender,
a 3D computer graphics software. The name of the model is Suzanne, you can learn more about it
here.

The second model, the bunny model, is a very famous 3D scan of a ceramic figurine of a
rabbit developed at Stanford University. You can learn more about it
here.

The third model, the teapot model, is also a very famous 3D model,
commonly known as the Utah teapot. Learn more about it
here.

These 3 models are included in the .zip folder that you can download at the
beginning of this page. There are three OBJ files inside of the folder, one
for each model, you can check them out in any software that can open OBJ files.

Understanding shadow mapping in WebGL.

If you want a really good explanation of the concept of shadow mapping please check out this article:

The information in that article is a good resource that I used myself to understand shadow mapping better.
That article explains
the theory of shadow mapping and explains how to implement it in OpenGL, which is similar
to how it is done in WebGL.

When drawing a shadow map the scene has to be rendered from the point of view of the light source,
in other words, the camera is placed where the light source is and it is aimed at the scene that
you wish to add shadows to.

What we are looking for is how far each point in the scene is from the light. This is represented
by the depth of each fragment, and that is the information that is stored in the shadow map.
To obtain and use this information we render to a texture that is going to store the depth value in each pixel.

In the final render pass we are going to use this texture to make some comparisons.
We compare the depth of the fragments in the current render pass to the depth of the pixels in the texture.
If the current
fragment is farther away form the light than the corresponding pixel (meaning that the depth value
of the fragment is larger than that of the pixel in the texture), that means that the
fragment is covered, or, is in shadow.

In this specific example we are going to use
the WebGL extension named WEBGL_depth_texture. You can learn more about it
here.
Note: as it is an extension, it might not be available in every device.

When using this extension we can render to a texture that is going to have only
the depth information of the scene. Below are some examples of how the final render
compares to the shadow map. On the left is the final render and on the right is it´s
corresponding shadow map. This is how it looks when the light source is directly above the models:

The shadow map is red because when we use WEBGL_depth_texture
the depth information is stored in the red channel of the texture. Pay attention to this in the code.

As you can see, the darker places in the texture are the ones that are closer to the light
source (closer, in this case, means that the depth value is smaller, the smallest value possible
is 0, which would make the pixel look black). That is why you can see the silhouette of the model
in the shadow map. That silhouette is a darker red because the model is always closer to the light than the floor.

Setting it up:

The variable canvas is defined, this will be used to reference the canvas element in which
we will render. Also, it is set explicitly that the canvas contents will change constantly,
60 FPS when using requestAnimationFrame.

A variable named gl will be the WebGL context. If the device or browser does not allow the use
of WebGL a message is shown. The clear color of the color buffer is set explicitly, this the
default background color of the canvas. We enable DEPTH_TEST and CULL_FACE, this done because
the models are solid models, this ensures a nice effect when rendering.

We need to setup a shader for the model and a shader for the floor. Because we are going
to need a shadow map, there is a shader specific for when the shadow map is rendered and
another for the final render.

This is where we get the pointer for the attributes that are going to be used in
each shader. It's important to notice that, for the model, we need the “normals” attribute,
this attribute will allow us to achieve diffuse and specular lighting on the model.

This section of the code is very important, here is where we setup the things necessary
to be able to render the shadow map. First, we start by getting the extension, we need
to check out the value of depthTextureExt because if it is null we cannot use this extension,
in this case the only thing we can do is alert the user about it. But, if it is successful
there are some things that need to be done. A frame buffer is needed and a texture associated
with it is also needed. Pay special attention to the parameters of texImage2D. As you can see,
we specify that the texture stores the DEPTH_COMPONENT and it is stored in an UNSIGNED_SHORT,
meaning 16 bits, this is important because it gives us more resolution to work with, making
the shadow map possible.

This function is called repeatedly to achieve animation. The function rotMatrix updates
the matrix that is used to rotate the model when the canvas is clicked. The function
updateMatrices is used to calculate the new values of both the perspective matrix and
the orthographic matrix. The function sunPositionUpdate is a very simple function that is
in charge of changing the position of the indicator of the sun's position when the mouse
is moved on the canvas. As you can see, the variable depthTextureExt is checked to see if it's
value is not equal to null, if it's not, then the shadowMapRender function is called to render the
shadow map. After the shadow map is rendered we can finally render the final scene with the function normalRender.

This function starts by binding the shadowMapFramebuffer as the frame buffer, this
tells the program that all the subsequent rendering has to be done on this frame buffer.
We set the viewport to be 1024X1024 pixels to match the texture's resolution. Because we only need the
depth, only the DEPTH_BUFFER_BIT is cleared.

Binding the vertex buffer to use the vertex information of the model, as you can see, we
choose from one of the three models from arrBufVModel, the model that is going to be used
is determined by the value of the variable model.

We start this function by setting the frame buffer to the default frame buffer, this is
done by setting the value to null. Also, the viewport resolution needs to be changed, now
we are not rendering to the texture, we are rendering to the canvas so the resolution is set
to 512X512 pixels. This time, we need the color buffer as well as the depth buffer, so both are
cleared at the start of the rendering.

Binding the vertex buffer to use the vertex information of the model, again, the value
of the variable model is the one that controls which model should be used. This time we
also need an attribute pointer for the normal information of the vertices, this will be
used to create diffuse and specular lighting on the model.

This section of the code is where we use the shadow map the was rendered in the shadowMapRender
function, depthTextureExt is checked to see if it is not null, is it is null it means that the
shadow map was not rendered because the extension is not available, this is done to avoid errors.

The model's fragment shader: because we are using the WEBGL_depth_texture extension we don't need to do
anything inside the fragment shader, the depth value is written to the texture by default.

precision mediump float;
void main()
{
}

The floor's fragment shader:

precision mediump float;
void main()
{
}

The vertex shaders (normal render):

The model's vertex shader: in this shader we apply some transformations to the vertices and
normals, notice that the value that is sent to gl_Position requires the use of the perspective
matrix. There are some varying registers used, through normalsav we send the value of the
transformed normals. The varying registers ww and posOrtho are very important to calculate
shadows as they are used to make comparisons in the fragment shader.

The floor's vertex shader: this shader is very similar to the model´s shader, only it is a
little simpler, once again the value that gl_Position recieves is the value of the vertices
transformed by the perspective matrix. Only the varying register posOrtho is needed here, this
is used in the fragment shader to calculate the shadow.

This is where the specular component of the lighting is calculated, as you see this is a result
of the relationship between the angle of incidence of the light and where the eye is
positioned, since this is done per fragment the resulting effect is very soft.

Here, the shadow is calculated. Something that is important to notice is that these calculations only
happen is shadowMapOK is equal to 1, if is not equal to one it indicates that the depth texture
extension is not available, so the shadow map was not rendered, this is done to avoid errors. The
shadow map is sampled and compared 4 times and then those values are mixed together, this ensures that
the edges of the shadow are softer, giving a nicer effect.

In this section of the code the diffuse color is obtained, as you can see, the diffuse component is
not only a result of the main light source, but we take into account that some light bounces from the
floor into the model. This gives a more realistic effect because in the real world light bounces from one
object to another, so, it is logical that some light would bounce on the floor, illuminating the model.

The floor's fragment shader: the only thing that is needed in this shader is to calculate the
shadow that is going to be projected on the floor. The process is very similar to what was done in
the model's fragment shader.

That is all the important details that make this experiment work. I hope this example
gave you some tools to add lighting to any scene. Also, I hope that this example gives
you a better understanding of how to use WebGL in general.

Make sure to test it for yourself using the download link for the source files provided
at the beginning of this page. I invite you to change some values in the code to see
what happens, you can try to change the resolution of the shadow map to see how this
changes the quality of the resulting shadow, or maybe something a little more technically
challenging like changing the intensity or color of the light source.