Light, Materials, and BRDFs
Intersection tells you where the ray landed. It does not tell you what number to write into the film. Color begins when a material answers how incident light is scattered toward the camera.
Start with direct lighting only: light to surface to eye. Indirect illumination belongs to path tracing. If you jump there first, you will debug five systems at once and learn none of them.
A diffuse lobe spreads energy broadly. A glossy lobe concentrates energy near the mirror direction.
Data required at a hit
- position
p - unit normal
n - outgoing direction
wotoward the previous path point or camera - material parameters
- texture coordinates when needed
For a point light at light_pos:
to_light = light_pos - p
distance = |to_light|
wi = to_light / distance
cosθ = max(0, n · wi)
If cosθ = 0, that light contributes nothing on this side of the surface.
Lambertian diffuse
For linear albedo a:
f = a / π
A simple point-light estimate without importance-sampling machinery:
L = (a / π) * I * cosθ / distance²
The factor π is required by energy accounting. Omit it and “white” surfaces invent energy when you move to path tracing. Albedo belongs in linear space. Values above 1 are not a style choice; they are a broken material.
Specular reflection
An ideal mirror uses one direction:
r = reflect(incident, n)
Fix one convention for whether vectors point toward the surface or toward the light, and never mix conventions inside one function. Glossy materials place a lobe around the mirror direction. Blinn-Phong is a teaching model. Microfacet BRDFs are what hold up under path tracing.
If you add diffuse and specular lobes, their combined response must remain plausible under bright lights. Random specular coefficients produce glowing plastic.
The reflection equation
A BRDF f(wi, wo) states how radiance from direction wi contributes to radiance leaving toward wo:
Lo(wo) = ∫ f(wi, wo) Li(wi) (n · wi) dwi
Direct lighting approximates the integral with light samples. Path tracing approximates it with recursive path samples. Same equation. Different estimators.
Shadows are part of lighting
A light sample that fails visibility contributes zero. Hard point-light shadows are one visibility query with t_max set to the light distance. Soft shadows are the same idea with many samples over an area light. Do not treat shadows as a separate decorative pass.
Texture color spaces
Albedo maps are usually stored as sRGB and must be decoded to linear before BRDF evaluation. Roughness and metalness maps are commonly linear already. If the scene looks chalky or lifeless under every material, inspect color transforms before rewriting lobe formulas.
Minimum material interface
sample(wo, n, rng) -> wi, pdf, f, specular_flag
evaluate(wo, wi, n) -> f
emit() -> radiance or black
sample proposes the next path direction. evaluate is required when a light sampler chooses wi and asks what the material does. Ideal mirrors evaluate to zero for almost every direction, which is why pure light sampling never finds them alone.
Common errors
- Albedo greater than one “because the screenshot looked dark”
- Feeding sRGB texels straight into Lambert
- Using
n · wiwithout clamping and leaking light through the back face - Mixed incident-direction conventions in
reflectand the light loop - Adding many material knobs before normals and light vectors are correct
When a single hard-shadowed Lambert light looks right on a trusted normal field, materials have an honest place to stand.