Monograph · 07 · Sampling & MIS
Pipelines · full depth
Sampling & MIS
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
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.
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
samplerInit(uvec2(pixel), sampleIndex)dimIdx = 0- Primary jitter:
getSample2D(dimIdx); dimIdx += 2 - NEE light pick: 1D; sphere UV: 2D; env UV: 2D; lobe samples: 1D/2D as needed
- Never reuse a dimension for two different decisions
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
| Offline | Realtime | |
|---|---|---|
| Default sampler | Sobol (+ Owen scramble) | PCG |
| Init | pixel + sampleIdx → sobol state | hash: px*1973+py*9277+s*26699+1 |
| Cost | tables / scramble | cheap |
| Goal | converge stills | stable 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)
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
- Sample strategy A → direction ω, density \(p_A\)
- Evaluate \(p_B(\omega)\) (other strategy’s PDF at same direction)
- \(w_A =\) balance or power heuristic
- Add \(w_A\cdot f(\omega)/p_A\) (times visibility, lights, …)
- Optionally sample B with symmetric weight (Veach one-sample or multi-sample forms)
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);
}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
- Load HDR equirect; for each texel compute weight ∝ luminance × solid-angle factor
- Per row: prefix sums → conditional CDF; across rows → marginal CDF
- Store integral for normalization bookkeeping
- Upload float arrays → descriptor bindings 17 (marginal), 18 (conditional)
- Binary search marginal with u1 → row y
- Binary search row’s conditional with u2 → column x
- Map (x,y) → direction (θ,φ) Y-up equirect
- pdf_uv from CDF steps × W × H; convert with Jacobian \(2\pi·\pi·\sin\theta\)
pdfEnvMap(dir)for reverse lookup (BSDF-side MIS)
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 implementationsmis.glslHeuristicsenv_sampling.glslSample + pdfenv_cdf.cppCPU CDFpath_tracer_pipeline.cppSpec constant bakesobol_generator.* / owen_scramble.*Offline tablesDesign units in this module
Each card is a focused design page (what / how / why + sources). Full tree: Sitemap.