Normal Mapping

Description

Normal mapping is a technique used to simulate high-resolution surface details on low-polygon models by altering the surface normals during shading calculations. By perturbing the normal vectors based on a texture map, we can create the appearance of complex depth, bumps, and scratches without the computational cost of processing actual geometry.

Implementation Details

Local Frame Perturbation & Standards:
The implementation is contained entirely within the Instance class. When a normal map is assigned, the intersection routine samples the texture, remaps the values from the \([0, 1]\) color range to the \([-1, 1]\) vector range, and applies the result to the shading frame.
To support assets from different workflows (DirectX vs. OpenGL), I implemented a configurable toggle. This boolean parameter allows the green (Y) channel of the normal map to be inverted on demand, ensuring correct surface orientation regardless of the source format.
This feature relies heavily on the underlying shape intersections providing a stable and valid tangent space (coordinate frame), which was already established during the core assignment implementation.

Renders
Normal Mapping Applied No Normal Mapping (Flat Geometry)
Difficulties

Implementation was relatively straightforward because I had already implemented correct shading frame calculations during the base assignments and had recently refactored the Instance intersection logic for alpha masking.

The significant issue arose when the initial test asset appeared incorrect, producing inverted lighting details. I discovered the normal map had a flipped green channel—a common discrepancy between DirectX and OpenGL standards. Instead of manually editing assets, I implemented the green-channel toggle mentioned above, which provided a robust solution for handling diverse third-party assets.

Alpha Masking

Description

Alpha masking is a technique used to simulate complex, fine-detailed geometry—such as foliage, fences, or cloth—without the computational cost of modeling every individual strand or leaf. By utilizing an alpha texture to control transparency, we can create the illusion of intricate silhouettes on simple geometric proxies, significantly reducing the polygon count while avoiding the artifacts.

Implementation Details

Stochastic Intersection & State Safety:
The core logic is implemented within the Instance class using a stochastic approach: when a ray hits the geometry, the alpha value is sampled to probabilistically accept or reject the hit.
Crucially, to prevent overriding valid data (like UV coordinates or geometric normals) during these recursive checks, I utilize a dummy intersection object for tentative hits. Only when a hit is probabilistically accepted is the data copied to the main intersection structure. If all potential intersections along the ray are rejected (meaning the ray passed through a transparent section), the previous intersection state is strictly restored to ensure the renderer treats the object as if it were never hit.

Deterministic Transmittance:
For shadow rays (transmittance), I opted for a deterministic approach to reduce variance. Instead of randomly terminating shadow rays, the integrator accumulates the alpha values of all intersections along the ray path. The final visibility is the product of these values, providing a noise-free shadow for partially transparent objects without requiring extra samples.

Renders

Plant Model Alpha Masking

With Alpha Masking (Correct silhouette and shadows) Alpha Masking without Transmittance support (Solid shadows) No Alpha Masking

Attributions

  • Plant Model: "Alexandria palm plant elder" by Blenderkit. Link
Difficulties

Refactoring the intersection loop to support recursive "skipping" gave me incorrect results in the BVH tests. I encountered a bug where the Intersection object failed to correctly restore the internal state (specifically the tracked BVH instance) after skipping a transparent hit.

Initially, I implemented the transmittance check probabilistically (mirroring the intersection logic). However, this introduced unnecessary noise in the shadows. Switching to the deterministic method (multiplying alpha values) significantly improved shadow quality and convergence speed.