-
What Is Normal Mapping?
- Technique to add surface detail without adding geometry
- A texture stores per-pixel surface normals (in tangent space)
- The shading normal is perturbed based on the texture
- Result: the surface appears to have bumps and grooves at no geometry cost
-
Tangent Space
- A local coordinate system defined per surface point
- Axes:
T (tangent), B (bitangent), N (normal)
N — geometric surface normal
T — tangent direction, aligned with UV u axis
B — bitangent, aligned with UV v axis, B = cross(N, T)
- Normal map stores normals in this local space
- Flat surface:
(0, 0, 1) in tangent space → encoded as (0.5, 0.5, 1.0) in texture
- Bump pointing right:
(1, 0, 0) → encoded as (1.0, 0.5, 0.5)
-
TBN Matrix
- Transforms from tangent space to world space
TBN = mat3(T, B, N) — columns are the tangent space axes in world space
world_normal = normalize(TBN * tangent_space_normal)
- Computing T and B from mesh data
- Stored per-vertex in the mesh (precomputed from UVs)
- Or computed in shader from UV derivatives:
dFdx(uv), dFdy(uv)
- Mikktspace (Mikkelsen 2008)
- Standard algorithm for computing tangents
- Used by Blender, Godot, Unreal, Unity
- Ensures consistent tangents across different tools
-
Normal Map Encoding
- Tangent-space normal maps: blue-ish (most normals point up = (0,0,1))
- Decode:
N = normalize(texture(normalMap, uv).rgb * 2.0 - 1.0)
- BC5 compression: stores only RG channels (Z reconstructed as
sqrt(1 - R² - G²))
- OpenGL vs DirectX convention: Y axis is flipped
- OpenGL:
+Y = up in tangent space
- DirectX:
-Y = up in tangent space
- Fix:
N.y = -N.y when loading DirectX normal maps in OpenGL
-
In a Path Tracer
- Use the perturbed normal for BRDF evaluation and sampling
- Important: use geometric normal for ray offset (avoid self-intersection)
- Use shading normal for BRDF evaluation
-
Bump Mapping vs Normal Mapping vs Displacement
- Bump mapping: height texture → compute normal from height gradient
N = normalize(N + dh/du * T + dh/dv * B)
- Normal mapping: directly store normals in texture (more control)
- Displacement mapping: actually move vertices based on height texture
- Requires tessellation or ray marching
- True geometric detail — correct silhouettes and self-shadowing
-
Geometric vs Shading Normal Consistency
- Problem: shading normal can point away from the ray direction
- Happens at silhouette edges where normal interpolation creates inconsistencies
dot(ray_dir, shading_normal) > 0 — ray hits “back” of shading normal
- Fix: flip shading normal if inconsistent with geometric normal
- Or: use geometric normal for ray offset, shading normal for BRDF only