sampler2DShadow

Sampling the Shadow Map with sampler2DShadow

The shading pass multiplies each world position by the light's matrix, divides by w and maps -1..1 to 0..1: a texture coordinate plus the depth the light sees. A sampler2DShadow takes all three, and in COMPARE_REF_TO_TEXTURE mode the texture unit compares for you, returning 1 when the reference passes TEXTURE_COMPARE_FUNC (default LEQUAL) against the stored depth, else 0:

A depth texture in comparison mode answers lit (1) or shadowed (0)JavaScript
const map = GLH.framebuffer(gl, 4, 4, { color: false });
gl.bindFramebuffer(gl.FRAMEBUFFER, map.fbo);
gl.clearDepth(0.5); gl.clear(gl.DEPTH_BUFFER_BIT);           // every texel stores depth 0.5
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, map.depth);
gl.activeTexture(gl.TEXTURE1);                               // GLH.evaluate's target: unit 1
for (const reference of [0.4, 0.5, 0.6]) {
  const [lit] = GLH.evaluate(gl, `result = vec4(texture(uMap, vec3(0.5, 0.5, ${reference})));`,
    'uniform highp sampler2DShadow uMap;');
  console.log(`reference ${reference}: ${lit} (${lit ? 'lit' : 'in shadow'})`);
}
Output
reference 0.4: 1 (lit)
reference 0.5: 1 (lit)
reference 0.6: 0 (in shadow)

Declare the sampler highp, since mediump depth can merge nearby surfaces. Fragments outside the map's 0..1 range read the clamped edge texel, so keep receivers inside the light's frustum.

A small depth texture holding a book-shaped caster, read through sampler2DShadow at a fixed reference depthHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  canvas { display: block; width: 100%; max-width: 600px; }
  .names { display: flex; max-width: 600px; text-align: center; font-family: monospace; font-size: 10px; }
  .names div { flex: 1; padding: 4px 2px; }
  p { max-width: 600px; margin: 4px 0; font-family: monospace; }
</style>
<canvas id="c" width="600" height="200"></canvas>
<div class="names"><div>stored depths (grey)</div><div>texture(uMap, vec3(uv, 0.5)), NEAREST</div><div>same, LINEAR: 4 comparisons blended</div></div>
<p id="out"></p>
<script>
const gl = document.getElementById('c').getContext('webgl2');
function program(vs, fs) {
  const p = gl.createProgram();
  for (const [type, src] of [[gl.VERTEX_SHADER, vs], [gl.FRAGMENT_SHADER, fs]]) {
    const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
    gl.attachShader(p, s);
  }
  gl.linkProgram(p);
  return p;
}
const quadVS = `#version 300 es
out vec2 vUV;
void main() { vUV = vec2(gl_VertexID & 1, gl_VertexID >> 1) * 2.0; gl_Position = vec4(vUV * 2.0 - 1.0, 0.0, 1.0); }`;
gl.bindVertexArray(gl.createVertexArray());

// A 16 x 16 depth texture: cleared to 1.0 (far), with a tilted "book" drawn at depth 0.3.
const SIZE = 16;
const map = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, map);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.DEPTH_COMPONENT24, SIZE, SIZE);
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, map, 0);
gl.drawBuffers([gl.NONE]);
gl.viewport(0, 0, SIZE, SIZE);
gl.enable(gl.DEPTH_TEST);
gl.clearDepth(1.0); gl.clear(gl.DEPTH_BUFFER_BIT);
const caster = program(`#version 300 es
void main() {
  vec2 q = vec2[6](vec2(-1), vec2(1, -1), vec2(1), vec2(-1), vec2(1), vec2(-1, 1))[gl_VertexID];
  float c = cos(0.4), s = sin(0.4);
  vec2 p = mat2(c, s, -s, c) * (q * vec2(0.35, 0.6));
  gl_Position = vec4(p, -0.4, 1.0);                   // NDC z -0.4 -> stored depth 0.3
}`, `#version 300 es
precision mediump float; void main() {}`);
gl.useProgram(caster);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

// The book's check: a map cleared to 0.5 answers 1 (lit) for references at or below it.
const half = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, half);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.DEPTH_COMPONENT24, 4, 4);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, half, 0);
gl.clearDepth(0.5); gl.clear(gl.DEPTH_BUFFER_BIT);   // every texel stores 0.5
gl.getExtension('EXT_color_buffer_float');
const result = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, result);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, 1, 1);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, null, 0);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, result, 0);
gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
gl.disable(gl.DEPTH_TEST);
gl.viewport(0, 0, 1, 1);
const probe = program(quadVS, `#version 300 es
precision highp float; uniform highp sampler2DShadow uMap; uniform float uRef; out vec4 c;
void main() { c = vec4(texture(uMap, vec3(0.5, 0.5, uRef))); }`);
gl.useProgram(probe);
gl.bindTexture(gl.TEXTURE_2D, half);
const answers = [0.4, 0.5, 0.6].map(ref => {
  gl.uniform1f(gl.getUniformLocation(probe, 'uRef'), ref);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  const v = new Float32Array(4); gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, v);
  return `reference ${ref}: ${v[0]} (${v[0] ? 'lit' : 'in shadow'})`;
});
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
document.getElementById('out').textContent = 'map cleared to 0.5, LEQUAL: ' + answers.join(' | ');

// Draw the three views of the 16 x 16 map.
const grey = program(quadVS, `#version 300 es
precision highp float; uniform highp sampler2D uMap; in vec2 vUV; out vec4 c;
void main() { c = vec4(vec3(texture(uMap, vUV).r), 1.0); }`);
const compare = program(quadVS, `#version 300 es
precision highp float; uniform highp sampler2DShadow uMap; in vec2 vUV; out vec4 c;
void main() {
  float lit = texture(uMap, vec3(vUV, 0.5));          // 1 where 0.5 <= stored depth, else 0
  c = vec4(mix(vec3(0.25, 0.22, 0.2), vec3(0.95, 0.88, 0.7), lit), 1.0);
}`);
gl.bindTexture(gl.TEXTURE_2D, map);
gl.clearColor(0.97, 0.96, 0.93, 1); gl.clear(gl.COLOR_BUFFER_BIT);
[[grey, gl.NONE, gl.NEAREST], [compare, gl.COMPARE_REF_TO_TEXTURE, gl.NEAREST], [compare, gl.COMPARE_REF_TO_TEXTURE, gl.LINEAR]]
  .forEach(([p, mode, filter], i) => {
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_COMPARE_MODE, mode);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
    gl.useProgram(p);
    gl.viewport(i * 200 + 10, 5, 180, 190);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
  });
</script>