Project 3

PBS and IBL

Replacing direct lights with image-based lighting: a Cook-Torrance GGX BRDF, spherical-harmonic irradiance for diffuse, and GGX importance sampling with PDF-driven mip selection for specular.

Course
CS 562, Advanced Real Time Rendering
Submitted
28 March 2026
Status
Complete

Project overview

This project extends the deferred rendering pipeline from Projects 1 and 2 by replacing direct light sources with image-based lighting (IBL) and a full Cook-Torrance PBR BRDF. The BRDF combines a Lambertian diffuse term with a microfacet specular term built from the Trowbridge-Reitz GGX NDF, Smith G1 geometry masking, and Schlick Fresnel, with energy conservation enforced through a Fresnel-derived kD/kS split.

Environment illumination comes entirely from real HDR panoramas, through two techniques. Spherical harmonic (SH) irradiance covers the low-frequency diffuse contribution, precomputed into 9 coefficients at load time and evaluated at O(1) cost per fragment. GGX importance sampling covers the high-frequency specular contribution, evaluated at runtime by drawing N quasi-random samples from the Hammersley sequence and warping them into the GGX lobe. Mip level selection based on the GGX PDF makes smooth surfaces fetch sharp environment samples while rough surfaces fetch blurred ones. The lighting responds to material properties across the full metallic-roughness PBR parameter space.


Architecture overview

Updated rendering pipeline

The lighting pass is fully replaced by environment-driven IBL; no direct lights remain in the main lighting equation:

 ────────────────────────────────────────────────────────────
                     RENDERING PIPELINE                       
 ─────────────────────────────────────────────────────────────
                                                             
   1. GEOMETRY PASS                                           
      ↓                                                       
      Render scene geometry to G-Buffer (Same as Prj 1 & 2)  
                                                             
   2. IBL LIGHTING PASS                                       
      ↓                                                       
      Evaluate SH irradiance for diffuse                      
      Importance-sample environment map for specular          
      Apply Fresnel, energy conservation, tone mapping        
                                                              
   3. POST-PROCESSING                                         
      ↓                                                       
      Draw sky dome from active HDR map                       
      Render UI (ImGui)                                       
                                                             
 ─────────────────────────────────────────────────────────────

Components added

ComponentFilePurpose
SHIrradianceinclude/SHIrradiance.hPre-compute 9 SH coefficients from HDR panorama
HammersleyRandomGeneratorinclude/HammersleyRandomGenerator.hGenerate quasi-random (e1, e2) sample pairs
IBL lighting shadershaders/deferred_lighting.fragEvaluate diffuse + specular IBL per pixel
Sky domeinclude/SkyDome.hRender the active equirectangular HDR as background

Scene setup and materials

Scene composition

Instead of the single scene used in previous projects, we show one model at a time, so we can inspect models made of different materials against different environment maps.

Models in scene

ModelMaterial
Lion HeadAged Bronze
Bull HeadPolished Bronze
Horse HeadPolished Bronze
Marble BustRough Marble
Horse StatueSmooth Marble
Vintage Video CameraMetal / Resin
Cassette PlayerMetal / Plastic
KatanaMetal / Leather
War HammerMetal / Wood
MegaphonePlastic

Environment maps

Eight HDR panoramas are available at runtime, selectable via the ImGui panel:

IndexFileCharacter
0cedar_bridge_sunset_1_4k.exrWarm sunset
1moonless_golf_4k.exrDark night sky
2cobblestone_street_night_4k.exrUrban night with street lights
3suburban_garden_4k.exrSuburban afternoon sun
4illovo_beach_balcony_4k.exrBright coastal
5aft_lounge_4k.exrInterior warm
6signal_hill_sunrise_4k.exrHigh-contrast sunrise
7ferndale_studio_04_4k.exrStudio with colored lights

BRDF implementation

The lighting shader uses the Cook-Torrance BRDF throughout, combining a diffuse Lambertian term with a microfacet specular term.

Normal distribution function: Trowbridge-Reitz GGX

The NDF describes the statistical distribution of microfacet orientations. The GGX variant is used because it produces a longer specular tail compared to Blinn-Phong, which is more physically accurate for most real surfaces:

float DistributionGGX(vec3 N, vec3 H, float alpha) {
    float a2     = alpha * alpha;
    float NdotH2 = pow(max(dot(N, H), 0.0), 2.0);
    float denom  = PI * pow(NdotH2 * (a2 - 1.0) + 1.0, 2.0);
    return a2 / denom;
}

The NDF takes alpha (roughness) as input, with a2 = alpha² used in the denominator following the standard GGX form.

Geometry function: Smith G1 for GGX

The geometry term accounts for microfacet self-shadowing and masking. Smith’s method applies the G1 function independently to the view and light directions and multiplies them:

float GeometryG1(float dotVN, float alpha) {
    float tanTheta2 = (1.0 - dotVN * dotVN) / (dotVN * dotVN);
    return 2.0 / (1.0 + sqrt(1.0 + alpha * alpha * tanTheta2));
}

float GeometrySmith(vec3 N, vec3 V, vec3 L, float alpha) {
    return GeometryG1(dot(N,V), alpha) * GeometryG1(dot(N,L), alpha);
}

G1 is expressed in terms of tanTheta², computed as (1 - NdotV²) / NdotV², which is the exact GGX masking function derived from the microfacet height-correlated distribution.

Fresnel: Schlick approximation

The Fresnel term controls view-angle-dependent reflectance. Ks is the specular base reflectance: 0.04 for dielectrics, blended toward the albedo for metals:

vec3 FresnelSchlick(float LdotH, vec3 Ks) {
    return Ks + (1.0 - Ks) * pow(1.0 - LdotH, 5.0);
}

vec3 F0 = mix(vec3(0.04), albedo, metallic);

Energy conservation: kD / kS split

The diffuse and specular terms are balanced using Fresnel-derived weights to ensure the total outgoing energy never exceeds the incoming energy:

vec3 kS = F;                               // specular fraction
vec3 kD = (1.0 - kS) * (1.0 - metallic);  // diffuse fraction, zero for metals
return kD * diffuse + specColor;

Metals have zero diffuse by definition, and the (1.0 - metallic) factor drives kD to zero as metallic approaches 1.


IBL implementation

Diffuse: spherical harmonic irradiance

The diffuse component is precomputed into 9 second-order SH coefficients per environment map at application bootstrap. At render time, evaluating the irradiance at a surface normal is an O(1) dot product sum.

Coefficient computation (SHIrradiance.h)

The HDR panorama is read via OpenImageIO. Each pixel is mapped to a world-space direction using the inverse of uvOfW. The mapping must be the exact inverse of the UV convention used in the shader: otherwise the SH coefficients and the shader evaluation disagree on which direction a pixel represents:

// Exact inverse of uvOfW:
//   uvOfW: u = 0.5 - atan(x,z)/(2PI),  v = acos(y)/PI
// Inverting:
//   phi   = (0.5 - u) * 2PI
//   theta = v * PI
double u   = (i + 0.5) / double(W);
double phi = (0.5 - u) * 2.0 * M_PI;

float x = float(sinTheta * std::sin(phi));   // first arg of atan2
float y = float(cosTheta);                   // Y-up
float z = float(sinTheta * std::cos(phi));   // second arg of atan2

Each pixel is weighted by the solid-angle differential sin(θ) dθ dφ and accumulated into the raw SH projection:

// SH projection integral:
//   L_lm = Σ  L(x,y,z) · Y_lm(x,y,z) · sinθ · dθ · dφ
float Y[9];
evalBasis(x, y, z, Y);

float weight = float(sinTheta * dTheta * dPhi);
for (int k = 0; k < 9; ++k)
    Llm[k] += L * Y[k] * weight;

The clamped-cosine Ahat convolution factors are baked in at the end, so the shader never needs to apply them at runtime:

// E_lm = Ahat_l * L_lm
// l=0: PI,   l=1 (×3): (2/3)PI,   l=2 (×5): (1/4)PI
const float Ahat[9] = {
    float(M_PI),
    float((2.0/3.0)*M_PI), float((2.0/3.0)*M_PI), float((2.0/3.0)*M_PI),
    float((1.0/4.0)*M_PI), float((1.0/4.0)*M_PI), float((1.0/4.0)*M_PI),
    float((1.0/4.0)*M_PI), float((1.0/4.0)*M_PI),
};

for (int k = 0; k < 9; ++k)
    coeffs[k] = Llm[k] * Ahat[k];

Because Ahat is already folded into shCoeffs, the diffuse term in the shader reduces to a simple weighted sum with no extra multiplication:

vec3 irradN = evalSHIrradiance(normalize(N));
vec3 diffuse = (albedo / PI) * irradN;

SH basis functions (GLSL)

vec3 evalSHIrradiance(vec3 n) {
    float x = n.x, y = n.y, z = n.z;

    float Y[9];
    Y[0] = 0.5  * sqrt(1.0  / PI);
    Y[1] = 0.5  * sqrt(3.0  / PI) * y;
    Y[2] = 0.5  * sqrt(3.0  / PI) * z;
    Y[3] = 0.5  * sqrt(3.0  / PI) * x;
    Y[4] = 0.5  * sqrt(15.0 / PI) * x * y;
    Y[5] = 0.5  * sqrt(15.0 / PI) * y * z;
    Y[6] = 0.25 * sqrt(5.0  / PI) * (3.0 * z*z - 1.0);
    Y[7] = 0.5  * sqrt(15.0 / PI) * x * z;
    Y[8] = 0.25 * sqrt(15.0 / PI) * (x*x - y*y);

    vec3 irradiance = vec3(0.0);
    for (int k = 0; k < 9; k++)
        irradiance += shCoeffs[k] * Y[k];

    return max(irradiance, vec3(0.0));
}
Click to zoom
| Rotating around scene showing Diffuse only IBL |

Specular: GGX importance sampling

The specular integral is evaluated at runtime by importance-sampling the GGX NDF. For each fragment, N samples are drawn from the Hammersley quasi-random sequence and warped into the GGX lobe around the reflection vector R.

Hammersley sequence (HammersleyRandomGenerator.h)

The generator produces N pairs (e1, e2) using the Van der Corput radical-inverse sequence for low-discrepancy sampling:

float x = float(i) / float(numSamples);   // Stratified i/N
float y = radicalInverse_VdC(i);           // Bit-reversed radical inverse

Pairs are packed into vec4 to satisfy std140 alignment and uploaded once to a UBO (HammersleyBlock). The sample count can be changed at runtime via updateUBO(newNumSamples), which reallocates the buffer.

Sampling direction construction

For each sample (e1, e2) and roughness a:

float theta = atan(a * sqrt(e2) / sqrt(1.0 - e2));   // GGX half-angle
vec3 D = vectorOf(e1, theta / PI);                     // Microfacet half-vector in local frame

// Rotate into reflection-aligned frame
vec3 L = tangent * D.x + R * D.y + bitangent * D.z;   // World-space light direction

Environment map mip-level selection

To avoid noise and aliasing, each sample fetches a mip-filtered environment sample. The mip level is derived from the total environment resolution, the sample count N, and the GGX PDF value D. A larger D means the sample falls in a high-probability, narrow lobe direction (smooth surface) and fetches a sharper mip, directly reflecting the environment. A smaller D means the sample is spread across a wide lobe (rough surface) and fetches a blurrier mip, smearing the environment contribution:

ivec2 size = textureSize(envMap, 0); 
int width = size.x;
int height = size.y;        
level = 0.5 * (log2( float(width * height) / float(ibl.N))) - 0.5 * log2( max(D, 0.0001) / 4.0);        
level = clamp(level, 0.0, maxLod);
return textureLod(envMap, uv, level).rgb;

Specular accumulation

Only samples where dot(N, L) > 0 contribute. The Cook-Torrance BRDF is evaluated per accepted sample:

float D = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3  F = FresnelSchlick(HdotV, F0);

specColor += Li * NdotL * (G * F) / (4.0 * NdotV * NdotL);

The accumulator is normalized by acceptedSamples (samples with dot(N,L) > 0) rather than N.

Click to zoom
| Rotating around scene showing Diffuse + Specular IBL |

Tone mapping

Four tone mapping operators are implemented and switchable at runtime:

MethodDescription
NoneLinear (clamp only, for reference)
Reinhardx / (1 + x), simple, desaturates highlights
ACES(x(ax+b)) / (x(cx+d)+e), industry filmic standard
Uncharted 2Hable curve with configurable shoulder, toe, and linear
PBR NeutralKhronos specification, desaturation-preserving

All operators are followed by gamma correction: color = pow(color, vec3(1.0/2.2)). All the videos are recorded with ACES for consistency (and a personal bias towards it).

Click to zoom
| Showing different tonemapping, with ACES being personal favoirte |

UI and debug controls

Interactive IBL controls

ControlRange / OptionsEffect
Environment map0-7 (8 HDR maps)Switches active panorama + reloads SH coefficients
Sample count (N)1-1024 (default 40)Specular quality vs. performance
Diffuse onlyToggleDisables specular loop, shows SH diffuse only
ExposureScalarPre-tonemapping linear exposure multiplier
Tone map methodNone / Reinhard / ACES / Uncharted 2 / PBR NeutralPost-exposure curve
Material overrideToggle + metallic / roughness / colorUniform material for comparison

Debug visualization modes

Inherited from Projects 1 and 2, the G-Buffer components remain accessible:

ModeChannelVisualization
PositionRGB (log-mapped)World-space positions
NormalRGBSurface normals
AlbedoRGBBase color
MetallicGrayscaleMetal/dielectric factor
RoughnessGrayscaleSurface roughness

Visual results

Diffuse-only IBL

We visualize the diffuse-only part of IBL against different environment maps.

Click to zoom

For the first pair, a sunset scene with the sun low on the horizon casts the lower hemisphere in warm yellow while the upper hemisphere receives a cooler sky white, followed by a nighttime street scene where most of the sphere takes on a yellow cast from surrounding lights, with the section facing away from any source remaining dark and untinted.

Click to zoom

For the next pair, a suburban garden under a sharp noon sun lights the top of the sphere bright white while the lower hemisphere picks up a green tint from the grass, followed by a coastal balcony with the sun at an angle where surfaces facing the bright walls and sun appear whiter and those oriented toward shade are visibly darker.

Click to zoom

For the next pair, an indoor lounge with large glass windows on one side and warm ceiling lights on the other lights the window-facing hemisphere with the brighter afternoon sun while the interior-facing side picks up a warm yellow tint from the ceiling lights, followed by an early morning sunrise where the lower hemisphere picks up a yellow tint from ground bounce, the backside catches additional bounce off nearby rocks, and areas facing away from any surface fall into darker, cooler shadow.

Click to zoom

The final map is a studio setup with multiple colored lights placed around the scene. As the sphere rotates, the irradiance transitions smoothly across green, yellow, and blue, each tint corresponding to the dominant light source in that direction.

Sample count comparison

To validate the importance sampling direction computation, we isolate the effect of N by fixing mip level to 0 and using a fully metallic surface (metallic = 1.0) at medium roughness (0.5).

Click to zoom

Starting at N = 1, the sphere shows a clean single reflection of the environment map. Stepping to N = 2 produces two superimposed reflections, and N = 3 produces three. Each additional sample contributes a distinct reflected image, which confirms that the importance sampling directions are being computed correctly.

Click to zoom

With N held constant, we vary roughness to observe its effect on the sampled directions. As roughness increases toward 1.0, the superimposed reflections diverge and spread apart, confirming that the GGX lobe is widening. As roughness decreases back to 0, all samples collapse to the same reflection direction and the images merge into one.

Click to zoom

Finally with N pushed into the 40s, the sphere is covered with so many superimposed reflections that the individual images become indistinguishable. The same roughness behaviour holds: increasing roughness spreads the samples across a wider lobe, and reducing it back to 0 collapses them all to a single sharp reflection.

Material response

We traverse the material parameter space in a loop, varying metallic and roughness independently to isolate their effects. Since the GGX model treats these as separate parameters, we have dedicated sliders for each rather than a single combined alpha.

Click to zoom

Starting with metallic = 0, roughness = 0 and increasing metallic to 1.0. At metallic = 0, the surface is a smooth dielectric: it shows a sharp specular highlight from Fresnel reflectance alongside a diffuse base. As metallic increases, the diffuse term is suppressed and F0 rises, transitioning the surface into a mirror metal that reflects the full environment.

Click to zoom

With metallic fixed at 1.0, roughness increases from 0 to 1.0. At roughness = 0 the surface is a perfect mirror with a sharp sun reflection. As roughness increases, the GGX NDF broadens, spreading the specular lobe and smearing the sun spot into a wider, softer highlight until it becomes nearly indistinguishable at roughness = 1.0.

Click to zoom

With roughness fixed at 1.0, metallic decreases from 1.0 to 0. As metallic drops, the specular contribution weakens (F0 falls to the dielectric baseline) and the diffuse term grows, gradually shifting the surface from a dark rough metal to a diffuse-dominant rough dielectric.

Click to zoom

Finally, with metallic fixed at 0, roughness decreases from 1.0 back to 0. The surface transitions from a fully diffuse rough dielectric to a smooth marble. As roughness decreases, the specular lobe narrows and the broad soft highlight converges into a sharp, concentrated sun reflection.

Full IBL with different environment maps

We fix the surface as a brushed metal (metallic = 1.0, roughness = 0.5) and swap between environment maps to observe how the specular response changes with the lighting conditions.

Click to zoom

For the first pair, a sunset HDRI with a single low-angle sun concentrates the specular lobe in the hemisphere tracking its reflection direction while the rest of the surface shows only the metallic sheen, followed by a nighttime HDRI with multiple scattered light sources producing several distinct highlights across the sphere, each carrying a faint color tint from its corresponding light.

Click to zoom

For the next pair, a dense street scene with bright lights spread across a wide solid angle fills much of the sphere with overlapping highlights, with window lights on the darker building face also visible as the scene rotates, followed by a suburban noon HDRI where the sun directly overhead concentrates the specular lobe at the top while the sides show only a broad metallic sheen.

Click to zoom

For the next pair, a coastal balcony HDRI with the sun at an angle produces a strong primary lobe tracking the sun direction and a fainter secondary from sunlight bouncing off nearby walls, so the specular integral picks up indirect bounce light captured in the map. Then an indoor lounge HDRI, where two distinct light sources produce two separate highlights: the window-side lobe sharp and intense from the high-radiance sun, the ceiling light lobe noticeably softer and dimmer, directly reflecting the radiance difference between the two sources.

Click to zoom

For the next pair, a sunrise HDRI produces a concentrated warm-tinted lobe in the hemisphere facing the low-horizon sun (similar to the sunset case), followed by a studio HDRI with multiple colored lights where distinct green, blue, and yellow highlights appear and fade across the sphere as the scene rotates.

Full IBL with more complex geometry

Rather than uniform material sliders, these models load color, normal, metallic, and roughness from per-UV texture maps, producing spatially varying material responses across the surface.

Lion head statue

The lion head statue is an aged bronze (high metallic, high roughness) with the worn texture of aged metal spread non-uniformly across its surface.

Click to zoom

In the sunset scene, the specular sheen appears as broad strokes across the crevices of the mane as we orbit the scene. Fixing the camera and rotating the statue, the sheen travels across the surface tracing the high-metallic regions of the texture map.

Click to zoom

In the urban street scene with multiple light sources, the statue is more evenly and brightly lit. The specular reflections are broader and more distributed, with each light contributing its own streak across the mane’s crevices as the model rotates.

Click to zoom

Switching to a brighter neutral outdoor scene, the aged bronze reads more clearly: the sharper sun brings out the contrast between the rough oxidized patches and the slightly smoother ridges. The indoor lounge scene produces a similar result but with a warmer, more subdued tint from the ceiling lights.

Click to zoom

In the studio map, the result is less dramatic than the outdoor scenes. Since metals have near-zero diffuse, the color transitions between different light sources are largely suppressed. The most notable effect is on the flat back surface, which presents a large area facing the blue light and picks up a visible blue tint.

Horse head statue

The horse head is a similar bronze-brass metal but with lower roughness than the lion head, giving it a less aged and smoother appearance.

Click to zoom

The lower roughness produces a sharper, more concentrated specular response. As the model rotates against the sunset, the sun’s reflection travels cleanly across the surface as a crisp highlight that follows the geometry.

Click to zoom

Switching to a neutral midday scene cools the overall tint while keeping the sharp specular reflection. The indoor scene introduces a blend of cooler window light and warmer ceiling tones, with the specular sheen still visible as the model rotates.

Marble bust statue

Moving away from metals, the marble bust is a predominantly rough diffuse surface with only faint smoothness in certain areas, producing specular glints only at grazing angles.

Click to zoom

In the urban street scene, the bust picks up the warm yellow diffuse tint from the surrounding lights. Switching to a nighttime map removes the warm tint and the surface reverts to its natural diffuse color, with faint specular glints at grazing angles and a subtle green tint from distant lights visible as the scene rotates.

Click to zoom

In a neutral outdoor scene the statue appears whiter, losing its warm tones: surfaces facing the sun are brighter, those in shade are darker. Switching to the indoor lounge blends the cool window light and warm ceiling glow across the surface, producing a mixed tint that is neither fully warm nor fully cool.

Click to zoom

The studio map is where the marble surface is most visually interesting. Without a strong metallic term suppressing the diffuse, it picks up all the colored lights cleanly and transitions through yellow, green, blue, and back to yellow with hints of purple as the scene rotates.

Marble horse statue

Similar to the marble bust but with slightly higher smoothness, the marble horse shows a more noticeable specular sheen at grazing angles.

Click to zoom

The additional smoothness is most apparent in the nighttime scene, where the faint specular sheen at grazing angles is easier to distinguish against the darker background. The sheen is more prevalent here than on the marble bust under the same conditions.

Click to zoom

In the indoor lounge scene, the statue picks up both the cool window light and the warm ceiling tint, with the ceiling light specular visible on the smoother surfaces as the model rotates. In the studio map, the horse shows a similar multi-colored transition as the marble bust, though more dispersed due to the different geometry and surface orientation.

Vintage video camera

The vintage camera is a mixed-material model: a rough resin body with embedded metallic components such as lens rings and side plates. Transparent lens rendering requires a more involved pipeline, so the lenses are rendered as smooth black surfaces instead.

Click to zoom

In the sunset scene, the three smooth lens surfaces each show a soft spread specular highlight from the sun consistent with their low roughness. As the camera rotates, the embedded metallic components (lens rings, side plates) catch the sunlight and produce sharper, more intense reflections.

Click to zoom

In the urban scene with multiple street lights, the lens surfaces pick up several overlapping highlights as the model rotates, one for each light source they face. The metal components similarly glisten multiple times per rotation, confirming the specular response from each individual source.

Click to zoom

In a neutral midday scene with a single dominant sun, the interaction is closer to the sunset case: the lenses show a clean specular reflection tracking the sun, the metal components glisten as they rotate into alignment, and the overall appearance is cooler and less tinted.

Cassette player

The cassette player is predominantly plastic (a mix of smooth and rough surfaces) with minor metallic components such as the antenna and jack pins.

Click to zoom

As the scene rotates, the smooth plastic panels show a soft specular sheen tracking the sun. Rotating the model itself moves the sheen across the surface, and the contrast between the smooth panels and the rough grill sections is clearly visible: the grill areas show no specular contribution while the smooth panels glint.

Click to zoom

In a neutral scene the interaction is similar with a cooler tint. The difference between the plastic sheen and the metallic antenna is clearly visible: the antenna produces a sharper, more intense highlight consistent with its higher metallic and lower roughness values compared to the plastic body.

Antique katana

The katana is a predominantly metallic model with a leather-wrapped handle and a gold tip, offering a clear contrast between the smooth metal blade and the rough leather.

Click to zoom

Placed horizontally against the sunset, the sun’s reflection travels cleanly along the length of the blade as the scene rotates: the specular highlight sweeps from one end to the other. The back face of the blade, away from the sun, remains dark with only the faint diffuse metallic sheen.

Click to zoom

In a neutral midday scene, the reflection is less dramatic but still clearly present. The brighter overall lighting brings out the metallic character of the blade with the specular sheen visible as the model rotates, while the leather handle remains flat and diffuse throughout.

War hammer

The war hammer is predominantly metallic with a substantial wooden handle, offering a clear material contrast between the metal head and the rough wood.

Click to zoom

In the sunrise scene, the metal head picks up the warm sun reflection and carries a specular sheen across its surface as the model rotates. The geometry of the hammer head produces a less sweeping highlight than the katana blade, but the sheen is still clearly present and tracks the reflection direction.

Click to zoom

In a brighter neutral scene, the metallic character of the head is more prominent and the glistening is more visible as the model rotates. The wooden handle remains unchanged throughout, showing no specular response regardless of orientation.

Megaphone

The megaphone is an entirely smooth plastic object with a curved conical geometry that produces distinctive specular behavior.

Click to zoom

Against the sunset, a specular streak runs across the face of the cone pointing toward the sun as the scene rotates. The opposite face shows no highlight. Rotating the model transfers the streak to the other face as it comes into sun alignment, and a faint secondary streak is also visible along the connecting wire to the amplifier.

Click to zoom

In the studio map with multiple colored lights, the single streak gives way to several simultaneous streaks from different light sources, each with its own color. The interior of the cone picks up whichever light source it is oriented toward, producing a smooth color transition as the scene rotates.


File structure

src/
├── shaders/
│   ├── deferred_lighting.frag       # IBL lighting: SH diffuse + importance-sampled specular
│   ├── deferred_lighting.vert       # Full-screen quad vertex shader
│   ├── deferred_lighting_debug.frag # G-Buffer visualization (unchanged from Prj 1 & 2)
│   ├── gbuffer.vert / gbuffer.frag  # Geometry pass (unchanged)
│   ├── skydome.vert / skydome.frag  # HDR sky dome rendering
│   └── ...
│
├── include/
│   ├── SHIrradiance.h               # SH coefficient precomputation from HDR
│   ├── HammersleyRandomGenerator.h  # Quasi-random Hammersley UBO
│   ├── SkyDome.h                    # Sky dome mesh and binding
│   ├── Scene.h                      # Scene orchestration
│   └── ...
│
├── Scene.cpp                        # InitIBL(), LightingPass() with IBL uniforms
└── Main.cpp                         # Application entry point and render loop

Conclusion

This project replaced the direct lighting pipeline with an environment-driven IBL system. The SH irradiance precompute produces smooth, low-frequency diffuse response at O(1) shader cost, while GGX importance sampling handles the high-frequency specular contribution with quality controllable via sample count N. The acceptance-based normalization, dividing by acceptedSamples rather than N, prevents darkening on surfaces where GGX samples fall below the horizon. The mip level selection based on the GGX PDF value D ensures smooth surfaces fetch sharp environment mips for crisp reflections, while rough surfaces fetch blurred mips to spread the contribution across the wider lobe.

The material results confirmed expected PBR behavior across the full parameter space. Rough dielectrics like marble picked up environment colors cleanly, most evident in the studio map where the colored-light spread was captured without metallic suppression. High-roughness metals showed broad specular strokes tracking surface geometry, while low-roughness metals produced sharper mirror-like reflections. Mixed-material models clearly separated their regions under rotation, with metallic and dielectric surfaces responding distinctly to the same environment. The most visually rich interactions came from multi-source environments (the studio and urban street maps), where the specular integral accumulated independent contributions from each light, producing multi-lobe highlights and colored transitions not achievable with direct lighting.


References