Shadow Mapping
22.1 Algorithm
Shadow pass: render the scene from the light's point of view into a depth texture (shadow map).
Lighting pass: for each fragment, transform its position into light space; compare its depth with the stored depth — if farther, it's in shadow.
22.2 Shaders
// --- Shadow pass vertex shader (fragment shader can be empty) ---
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 uLightSpace; // lightProjection * lightView
uniform mat4 uModel;
void main() { gl_Position = uLightSpace * uModel * vec4(aPos, 1.0); }
// --- Lighting pass (fragment) ---
uniform sampler2DShadow uShadowMap; // depth texture with GL_TEXTURE_COMPARE_MODE = GL_COMPARE_REF_TO_TEXTURE
in vec4 vLightSpacePos; // computed in the vertex shader: uLightSpace * world position
float shadowFactor(vec3 N, vec3 L) {
vec3 proj = vLightSpacePos.xyz / vLightSpacePos.w; // perspective divide
proj = proj * 0.5 + 0.5; // NDC → [0, 1] texture space
if (proj.z > 1.0) return 1.0; // outside the light frustum
float bias = max(0.005 * (1.0 - dot(N, L)), 0.0005); // slope-scaled bias fights "shadow acne"
// PCF: average multiple hardware-filtered comparisons for soft edges
vec2 texel = 1.0 / vec2(textureSize(uShadowMap, 0));
float lit = 0.0;
for (int y = -1; y <= 1; ++y)
for (int x = -1; x <= 1; ++x)
lit += texture(uShadowMap, vec3(proj.xy + vec2(x,
…22.3 Shadow mapping problems & solutions
Shadow acne (self-shadowing stripes) depth bias (constant + slope-scaled, glPolygonOffset), normal offset Peter-panning (shadows detach) smaller bias, front-face culling in the shadow pass for closed meshes Aliasing/jagged edges
PCF, Poisson disk sampling, higher resolution, rotated sampling
Cascaded shadow maps (CSM) — several maps for different distance ranges omnidirectional shadows with a depth cube map (layered rendering via geometry shader or 6 passes), store linear distance
PCSS (percentage-closer soft shadows), VSM/ESM/MSM (filterable shadow maps)