Lambertian Reflectance Model
Compute the observed intensity under Lambertian (diffuse) reflectance.
The Lambertian reflectance model describes how light reflects from matte surfaces:
I=ρ⋅max(0,n⋅l)
where:
- I is the observed intensity
- ρ is the surface albedo (reflectance) in range [0, 1]
- n is the unit surface normal vector
- l is the unit light direction vector
- The dot product is clamped to non-negative (surfaces facing away get no light)
Key properties:
- Intensity is maximum when n=l (light directly overhead)
- Intensity decreases as angle between normal and light increases
- Intensity is zero when angle exceeds 90° (backlit surface)
Example:
lambertian(0.8, [0, 0, 1], [0, 0, 1])
0.8
Surface facing directly into light: dot(n, l) = 0×0 + 0×0 + 1×1 = 1.0
- I = 0.8 × max(0, 1.0) = 0.8 Maximum intensity when normal equals light direction.
Constraints:
- albedo: surface reflectance in range [0, 1]
- normal: unit surface normal [nx, ny, nz]
- light_dir: unit light direction [lx, ly, lz]
- Return intensity clamped to non-negative
Here’s how to think about and implement this problem without jumping straight to code.
1. Background Knowledge
In computer vision and graphics, the Lambertian reflectance model is a simple model for how light reflects off an ideal matte (diffuse) surface. It assumes that the surface reflects light equally in all viewing directions, so the observed intensity depends only on the angle between the incoming light direction and the surface normal, not on where the camera is.
Given:
- unit surface normal n,
- unit light direction l (typically pointing toward the light),
- surface albedo ρ∈[0,1],
the Lambertian model says:
I=ρ⋅max(0,n⋅l)The dot product n⋅\mathbf{l}=cosθ, where θ is the angle between normal and light. So:
- When light hits head-on (θ=0), cos\theta=1 → maximum intensity.
- As θ increases, cosθ decreases → dimmer.
- When θ>90∘, cos\theta<0, but physically the surface is back-facing, so intensity is clamped to 0.
This model is a building block for photometric stereo, where multiple images under different known light directions are used to infer surface normals and albedo.
2. Algorithm / General Approach
For this specific task, the algorithm pattern is very simple:
- Compute the dot product between the given normal vector and light direction vector.
- Clamp this dot product at 0 from below (no negative values).
- Multiply the clamped value by the given albedo ρ to get the intensity.
Conceptually, it’s just “projection + clamp + scale.”
3. Step-by-Step Strategy
Assume you are given:
- rho (float),
- n as a 3D vector (e.g., [nx, ny, nz]),
- l as a 3D vector (e.g., [lx, ly, lz]), both already unit length in most problems of this type.
Steps:
- (Optionally) Normalize n and l if the problem doesn’t guarantee they’re unit vectors:
def normalize(v):
length = math.sqrt(v**2 + v**2 + v**2)
return [v/length, v/length, v/length]
- Compute dot product:
dot = n*l + n*l + n*l
- Clamp to non-negative:
dot_clamped = max(0.0, dot)
- Multiply by albedo:
I = rho * dot_clamped
- Return/output I as the observed intensity.
If the problem uses arrays of normals or lights, you just apply this element-wise (possibly vectorized).
4. Common Pitfalls
- Forgetting to clamp: Using I=ρ(\mathbf{n}⋅\mathbf{l}) without max(0,...) can produce negative intensities, which are physically meaningless.
- Non-unit vectors: If n or l are not normalized, the dot product is not equal to cosθ, and intensities will be scaled incorrectly.
- Albedo range: Ensure ρ is in [0,1] or as specified. If input can be outside, consider clamping or at least be aware.
- Floating point errors: Dot products of near-unit vectors can give values slightly above 1 or below -1 due to rounding (rare but can matter if you later use acos). For this simple intensity computation, it’s usually fine, but clamping with max(0, dot) already protects from small negative noise.
5. Time & Space Complexity
For a single intensity computation:
-
Time complexity:
-
Dot product: constant work (a few multiplications and additions).
-
Clamp and multiply: constant work. So overall: O(1).
-
Space complexity:
-
Uses a constant number of scalar variables; no extra significant memory. So overall: O(1).
For N pixels (e.g., per-pixel shading), it scales linearly:
- Time: O(N)
- Extra space: O(N) if you store all intensities, or O(1) if you compute and immediately output/accumulate.