This part of the first commit begins with the hit record produced by the BVH. It resolves a material closure, evaluates emitted and direct light, samples a new BSDF event, and multiplies the four-component spectral throughput carried to the next bounce.

Commit contents Files discussed in this chapter ggx.cpp · material.h · preview_surface.cpp · path_trace_pass.cpp · direct_lighting.cpp
Complete commit diff ↗

Material closure and BSDF object

The renderer first resolves the material ID stored in the hit. Missing materials use a defined fallback rather than leaving a null pointer in the bounce loop:

HitRecord hit{*nextHit};
const IMaterial* material{scene.GetMaterial(hit.MatId)};
if (material == nullptr) {
    material = &DefaultMaterial::Instance();
}

BSDFClosure c{material->GetClosure(hit)};
if (!settings.EnableSubsurface) {
    c.Subsurface = 0.0f;
}

This is a literal excerpt from path_trace_pass.cpp. The closure contains resolved parameters; the IBSDF created from it provides evaluation, density, and sampling behavior. Keeping the hit and closure separate allows texture evaluation and Hydra material translation to finish before the transport loop requests a scattering operation.

The earlier volumetric path-tracer description uses a phase function for a medium interaction and GGX for a surface interaction. This chapter examines the surface branch in the HdRestir source instead of repeating the complete path algorithm.

GGX evaluation

The specular component uses a GGX microfacet BRDF:

\[f_{\mathrm{spec}}(\omega_i,\omega_o)= \frac{D(h)\,F(\omega_i,h)\,G(\omega_i,\omega_o)} {4\lvert n\cdot\omega_i\rvert\lvert n\cdot\omega_o\rvert}, \qquad h=\frac{\omega_i+\omega_o}{\lVert\omega_i+\omega_o\rVert}.\]

The implementation evaluates the distribution and a separable masking term:

const float alpha {std::max(0.001f, _c.Roughness * _c.Roughness)};
const float alpha2{alpha * alpha};
const float denom {nDotH * nDotH * (alpha2 - 1.0f) + 1.0f};
const float Dval  {alpha2 / (kPi * denom * denom)};
const float k_g   {alpha / 2.0f};
const float G_l   {nDotL / (nDotL * (1.0f - k_g) + k_g)};
const float G_v   {nDotV / (nDotV * (1.0f - k_g) + k_g)};
const float Gval  {G_l * G_v};

For \(c=n\cdot h\), the coded distribution is

\[D_{\mathrm{GGX}}(h)= \frac{\alpha^2} {\pi\left(c^2(\alpha^2-1)+1\right)^2}.\]

The max(0.001f, ...) operations are denominator guards and roughness floors. They modify the exact BRDF near grazing angles and at zero roughness. The code therefore implements a regularized approximation rather than an ideal delta mirror at roughness zero.

The Fresnel term uses Schlick’s fifth-power approximation:

const GfVec3f F0{
    _c.SpecularColor * (1.0f - _c.Metallic) * 0.04f
    + _c.BaseColor * _c.Metallic};
const GfVec3f Fval{
    F0 + (GfVec3f{1.0f, 1.0f, 1.0f} - F0)
        * std::pow(1.0f - lDotH, 5.0f)};
const GfVec3f specBsdf{
    (Fval * Dval * Gval) / (4.0f * nDotL * nDotV)};

F0 interpolates between a dielectric baseline and base-color-controlled metal reflectance. This is the renderer’s material model; it is not a recovery of measured complex index-of-refraction data. The derivation of the microfacet denominator and the reflection/refraction Jacobians is available in Walter et al..

Diffuse and specular mixture

The initial model adds a Lambertian diffuse term:

const GfVec3f finalDiffuse{
    _c.BaseColor * (1.0f - _c.Subsurface)
    + _c.SubsurfaceColor * _c.Subsurface};
const GfVec3f diffBsdf{
    finalDiffuse
    * (1.0f - _c.Metallic)
    * (1.0f - _c.Transmission)
    / kPi};

return diffBsdf + specBsdf;

Division by \(\pi\) normalizes a Lambertian lobe because \(\int_{\Omega^+}\cos\theta\,d\omega=\pi\). The metallic and transmission factors remove diffuse response for those limiting material modes. The sum is an implementation model; this block alone does not prove energy conservation for every combination of parameters because the diffuse term is not explicitly reduced by the angle-dependent Fresnel value.

One bounce of spectral transport

The first commit samples one wavelength packet per camera path. Emission, direct lighting, absorption, and bounce throughput are evaluated at those same four wavelengths:

totalRadiance += throughput * RGBToSpectrum(c.Emission, lambda);
const std::unique_ptr<IBSDF> bsdfOwner{
    material->CreateBSDF(BSDFClosure{c})};
const ShadingPoint surface{
    hit,
    *bsdfOwner,
    c,
    shadingNormal,
    currentRay.Dir,
    lambda,
    isInside};
totalRadiance += throughput
    * SampleDirectLighting(surface, scene.GetLights(), scene, rng);

const BounceConfig config{
    settings.MaxReflectionBounces,
    settings.MaxRefractionBounces};
const BounceSample bs{
    material->SampleBounce(surface, config, bounceState, rng)};

The line adding direct lighting estimates paths that connect the current surface to a light. SampleBounce generates the continuation event. When that event is valid, the loop applies:

throughput *= bs.ThroughputMul;
currentRay = bs.NextRay;
nextHit = scene.IntersectScene(currentRay.Origin, currentRay.Dir);

If ThroughputMul represents \(f_s\lvert n\cdot\omega_i\rvert/p(\omega_i)\), multiplying it into the prior throughput implements the recurrence derived in step 0.b. Every component refers to the corresponding wavelength in the unchanged packet.

Limits visible in this commit

Direct-light estimation is a free function called by the path pass. Its light sampler and estimator cannot be replaced independently. The GGX evaluation also regularizes several dot products and adds diffuse and specular terms without a general energy-compensation construction. These are properties of the historical implementation, not conclusions implied by the general rendering equation.

The next commit separates path continuation, direct-light integration, and light selection. The current first-commit discussion stops before introducing those later interfaces.

Record this: At commit e6fe6bb, render a roughness sweep and a metallic sweep under one rectangle light. Keep exposure and camera fixed. Include the albedo and normal AOVs next to the color result.
Caption to keep: The first material model combines a Lambertian diffuse term with a regularized GGX specular term, then uplifts RGB parameters to the active wavelength packet.