Shadow Mapping

We can implement shadows by drawing the scene twice, using two pairs of shaders. Compared to stencil shadow volume, shadow mapping is often faster but may use more memory.

In the first pass, the drawing occurs in a framebuffer object. The camera is assumed to be at the location of the light source. Its distances to each fragment are recorded in a texture image known as the image map. We shall use gl_FragCoord, a built-in vec4, in the fragment shader. gl_FragCoord.x and gl_FragCoord.y represent the position of the fragment on the screen, and gl_FragCoord.z represents the depth distance in the range [0,1] which corresponds to the near clipping plane and far clipping plane.

In the second pass, we draw on the default framebuffer. If the distance from the light source to the fragment in this step is greater than the distance recorded in the shadow map, the fragment is in the shadow.

Because gl_FragCoord.z has only 8-bit precision, it is not stored in gl_FragColor directly. Rather, all the R, G, B, and A components are used to store it.
RESETRUNFULL
// /shared/webgl-library.js
Matrix4.prototype.set = function(src) {
  var i, s, d;
  s = src.entries;
  d = this.entries;
  if (s === d) return;
  for (i = 0; i < 16; ++i) d[i] = s[i];
  return this;};

<!DOCTYPE html><html><head>
<script src="/shared/webgl-library.js"></script>
<script id="shadow-vs" type="x-shader/x-vertex">
   attribute vec4 a_Position;
   uniform mat4 u_MvpMatrix;
   void main() {
      gl_Position = u_MvpMatrix * a_Position;
   }
</script>
<script id="shadow-fs" type="x-shader/x-fragment">
   #ifdef GL_ES
   precision mediump float;
   #endif
   void main() {
      const vec4 bitShift = vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0);
      const vec4 bitMask = vec4(1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0);
      vec4 rgbaDepth = fract(gl_FragCoord.z * bitShift);
      rgbaDepth -= rgbaDepth.gbaa * bitMask;
      gl_FragColor = rgbaDepth;
   }
</script>
<script id="vs" type="x-shader/x-vertex">
   attribute vec4 a_Position;
   attribute vec4 a_Color;
   uniform mat4 u_MvpMatrix;
   uniform mat4 u_MvpMatrixFromLight;
   varying vec4 v_PositionFromLight;
   varying vec4 v_Color;
   void main() {
      gl_Position = u_MvpMatrix * a_Position;
      v_PositionFromLight = u_MvpMatrixFromLight * a_Position;
      v_Color = a_Color;
   }
</script>
<script id="fs" type="x-shader/x-fragment">
   #ifdef GL_ES
   precision mediump float;
   #endif
   uniform sampler2D u_ShadowMap;
   varying vec4 v_PositionFromLight;
   varying vec4 v_Color;
   float unpackDepth(const in vec4 rgbaDepth) {
      const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0*256.0), 1.0/(256.0*256.0*256.0));
      float depth = dot(rgbaDepth, bitShift);
      return depth;
   }
   void main() {
      vec3 shadowCoord = (v_PositionFromLight.xyz / v_PositionFromLight.w)/2.0 + 0.5;
      vec4 rgbaDepth=texture2D(u_ShadowMap,shadowCoord.xy);
      float depth = unpackDepth(rgbaDepth);
      float visibility = (shadowCoord.z>depth+0.0015) ? 0.7 : 1.0;
      gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);
   }
</script>
<script>
var LIGHT_X = 0, LIGHT_Y = 40, LIGHT_Z = 2; 
function WebGLStart() {
   var canvas = document.getElementById('cv');
   var gl = canvas.getContext('webgl');

   var shadowProgram=initShaders(gl,"shadow-vs","shadow-fs");
   shadowProgram.a_Position = gl.getAttribLocation(shadowProgram, 'a_Position');
   shadowProgram.u_MvpMatrix = gl.getUniformLocation(shadowProgram, 'u_MvpMatrix');
   
   var normalProgram = initShaders(gl, "vs", "fs");
   normalProgram.a_Position = gl.getAttribLocation(normalProgram, 'a_Position');
   normalProgram.a_Color = gl.getAttribLocation(normalProgram, 'a_Color');
   normalProgram.u_MvpMatrix = gl.getUniformLocation(normalProgram, 'u_MvpMatrix');
   normalProgram.u_MvpMatrixFromLight=gl.getUniformLocation(normalProgram,'u_MvpMatrixFromLight');
   normalProgram.u_ShadowMap = gl.getUniformLocation(normalProgram, 'u_ShadowMap');
   
   var triangle = initVertexBuffersForTriangle(gl);
   var plane = initVertexBuffersForPlane(gl);
   
   var fbo = initFramebufferObject(gl,2048,2048);
   gl.activeTexture(gl.TEXTURE0);
   gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
   
   gl.clearColor(0, 0, 0, 1);
   gl.enable(gl.DEPTH_TEST);
   
   var tmp = new Matrix4();
   var viewProjMatrixFromLight = new Matrix4();
   viewProjMatrixFromLight.perspective(70.0,1.0,1.0,200.0);
   tmp.lookAt(LIGHT_X, LIGHT_Y, LIGHT_Z,0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
   viewProjMatrixFromLight.multiply_matrix(tmp.entries);
   
   tmp = new Matrix4();
   var viewProjMatrix = new Matrix4();
   viewProjMatrix.perspective(45, canvas.width/canvas.height, 1.0, 100.0);
   tmp.lookAt(0.0, 7.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
   viewProjMatrix.multiply_matrix(tmp.entries);
   
   var currentAngle = 0.0;
   var mvpMatrixFromLight_t = new Matrix4();
   var mvpMatrixFromLight_p = new Matrix4();
   
   gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
   gl.viewport(0, 0, 2048, 2048);
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
   gl.useProgram(shadowProgram);
   draw(gl, shadowProgram, triangle, viewProjMatrixFromLight);
   mvpMatrixFromLight_t.set(g_mvpMatrix);
   draw(gl, shadowProgram, plane, viewProjMatrixFromLight);
   mvpMatrixFromLight_p.set(g_mvpMatrix);
   
   gl.bindFramebuffer(gl.FRAMEBUFFER, null);
   gl.viewport(0, 0, canvas.width, canvas.height);
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
   gl.useProgram(normalProgram);
   gl.uniform1i(normalProgram.u_ShadowMap, 0);
   gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.entries);
   draw(gl, normalProgram, triangle, viewProjMatrix);
   
   gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.entries);
   draw(gl, normalProgram, plane, viewProjMatrix);
}
var g_modelMatrix = new Matrix4();
var g_mvpMatrix = new Matrix4();
function draw(gl, program, o, viewProjMatrix) {
  initAttributeVariable(gl, program.a_Position,o.vertexBuffer);
  if (program.a_Color != undefined) initAttributeVariable(gl, program.a_Color, o.colorBuffer);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);
  g_mvpMatrix.set(viewProjMatrix);
  g_mvpMatrix.multiply_matrix(g_modelMatrix.entries);
  gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.entries);
  gl.drawElements(gl.TRIANGLES, o.numIndices, gl.UNSIGNED_BYTE, 0);
}
function initAttributeVariable(gl, a_attribute, buffer) {
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type,false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
}
function initVertexBuffersForPlane(gl) {
  // Create a plane
  //  v1------v0
  //  |        |
  //  |        |
  //  |        |
  //  v2------v3
   var vertices = new Float32Array([
        3.0, -1.7, 2.5,    -3.0, -1.7, 2.5,
       -3.0, -1.7,-2.5,     3.0, -1.7, -2.5    // v0-v1-v2-v3
   ]);
   var colors = new Float32Array([
      1.0, 1.0, 1.0,    1.0, 1.0, 1.0,   1.0, 1.0, 1.0,    1.0, 1.0, 1.0
   ]);
   var indices = new Uint8Array([0, 1, 2,   0, 2, 3]);
   var o = new Object();
   o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
   o.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
   o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
   if (!o.vertexBuffer || !o.colorBuffer || !o.indexBuffer) return null;
   o.numIndices = indices.length;
   gl.bindBuffer(gl.ARRAY_BUFFER, null);
   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
   return o;
}
function initVertexBuffersForTriangle(gl) {
  // Create a triangle
  //      v2
  //     /  |
  //    /   |
  //   /    |
  //  v0----v1
   var vertices = new Float32Array([-0.8, 3.5, 0.0,  0.8, 3.5, 0.0,  0.0, 3.5, 1.8]);
   var colors = new Float32Array([1.0, 0.5, 0.0,  1.0, 0.5, 0.0,  1.0, 0.0, 0.0]);
   var indices = new Uint8Array([0, 1, 2]);
   
   var o = new Object();
   o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
   o.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
   o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
   if (!o.vertexBuffer || !o.colorBuffer || !o.indexBuffer) return null;
   o.numIndices = indices.length;
   gl.bindBuffer(gl.ARRAY_BUFFER, null);
   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
   return o;
}
function initArrayBufferForLaterUse(gl, data, num, type) {
   var buffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
   gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
   buffer.num = num;
   buffer.type = type;
   return buffer;
}
function initElementArrayBufferForLaterUse(gl, data, type) {
   var buffer = gl.createBuffer();
   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
   gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
   buffer.type = type;
   return buffer;
}
</script>
</head>
<body onload="WebGLStart();">
   <canvas id="cv" style="border: none;" width="500" height="500"></canvas>
   <p id="msg"></p>
</body></html>