OHAO · Implementation Monograph

Monograph · 07 · Sampling & MIS

07

Pipelines · full depth

Sampling & MIS

Chapter contract

How randomness is produced, ordered, and combined so NEE, BSDF, and environment strategies stay unbiased and lower variance. After this chapter you can read raygen’s dimIdx advances without mystery.

Monte Carlo baseline

\[ \Big\langle I\Big\rangle=\frac1N\sum_{i=1}^N\frac{f(X_i)}{p(X_i)} \]
Jargon · unbiased

Expected value equals the true integral when \(p>0\) wherever \(f\neq0\). Fireflies are high variance, not necessarily bias. Firefly clamp (realtime profile) trades a little bias for stability.

Why not pure random forever

Variance falls like \(1/\sqrt N\). Better \(p\) (cosine, GGX, env CDF, NEE) and MIS cut the constant — that is this chapter’s job.

Per-pixel dimension stream

Every raygen invocation
  1. samplerInit(uvec2(pixel), sampleIndex)
  2. dimIdx = 0
  3. Primary jitter: getSample2D(dimIdx); dimIdx += 2
  4. NEE light pick: 1D; sphere UV: 2D; env UV: 2D; lobe samples: 1D/2D as needed
  5. Never reuse a dimension for two different decisions
Why dimIdx discipline

Low-discrepancy sequences assume a fixed assignment of dimensions to decisions. Reusing dims correlates light and BRDF samples → structured noise / bias patterns.

Sobol vs PCG

OfflineRealtime
Default samplerSobol (+ Owen scramble)PCG
Initpixel + sampleIdx → sobol statehash: px*1973+py*9277+s*26699+1
Costtables / scramblecheap
Goalconverge stillsstable interactive noise

Selected via RTRenderSettings.samplerType and baked into raygen through a specialization constant at pipeline create (path_tracer_pipeline.cpp + sampler_api.glsl constant_id=0).

Cosine & GGX PDFs (used in MIS)

\[ p_{\mathrm{diff}}(\omega)=\frac{n\!\cdot\!\omega}{\pi},\qquad p_{\mathrm{spec}}\propto\frac{D(h)\,(n\!\cdot\!h)}{4\,(v\!\cdot\!h)} \]

Env MIS in raygen mixes them with a Fresnel/metallic-derived lobe probability before comparing to envPdf. Cosine hemisphere helper builds a TBN and maps unit square → hemisphere (Ch. 06 raygen).

MIS workflow

Two strategies, one integral
  1. Sample strategy A → direction ω, density \(p_A\)
  2. Evaluate \(p_B(\omega)\) (other strategy’s PDF at same direction)
  3. \(w_A =\) balance or power heuristic
  4. Add \(w_A\cdot f(\omega)/p_A\) (times visibility, lights, …)
  5. Optionally sample B with symmetric weight (Veach one-sample or multi-sample forms)
\[ w_A^{\mathrm{bal}}=\frac{p_A}{p_A+p_B},\quad w_A^{\mathrm{pow}}=\frac{p_A^{2}}{p_A^{2}+p_B^{2}} \]
mis.glsl
float misBalanceHeuristic(float pdfA, float pdfB) {
    return pdfA / max(pdfA + pdfB, 1e-6);
}
float misPowerHeuristic(float pdfA, float pdfB) {
    float a = pdfA*pdfA, b = pdfB*pdfB;
    return a / max(a + b, 1e-6);
}
Why MIS at all

Env map sampling alone misses sharp BRDF peaks; BRDF sampling alone misses sun texels. MIS lets each strategy win where it is strong.

Env CDF: build → upload → sample

CPU (env_cdf.cpp)
  1. Load HDR equirect; for each texel compute weight ∝ luminance × solid-angle factor
  2. Per row: prefix sums → conditional CDF; across rows → marginal CDF
  3. Store integral for normalization bookkeeping
  4. Upload float arrays → descriptor bindings 17 (marginal), 18 (conditional)
GPU (env_sampling.glsl)
  1. Binary search marginal with u1 → row y
  2. Binary search row’s conditional with u2 → column x
  3. Map (x,y) → direction (θ,φ) Y-up equirect
  4. pdf_uv from CDF steps × W × H; convert with Jacobian \(2\pi·\pi·\sin\theta\)
  5. pdfEnvMap(dir) for reverse lookup (BSDF-side MIS)
\[ p(\omega)=p_{uv}(u,v)\Big/\big(2\pi^{2}\sin\theta\big) \]
No-env contract

envWidth == 0 ⇒ skip importance sampling. Renderer still binds tiny dummy buffers so descriptor writes remain valid (setEnvCDFBuffers).

Specialization constant

Sampler type is not a runtime branch in every sample call for free: pipeline creation bakes m_renderSettings.samplerType into raygen via spec constant so SPIR-V can dead-strip the unused sampler implementation.

sampler_api.glsl / sobol / pcgRNG implementations
mis.glslHeuristics
env_sampling.glslSample + pdf
env_cdf.cppCPU CDF
path_tracer_pipeline.cppSpec constant bake
sobol_generator.* / owen_scramble.*Offline tables

Design units in this module

Each card is a focused design page (what / how / why + sources). Full tree: Sitemap.