A from-scratch, CPU-only software implementation of the five GAMES202 programming assignments — an independent build that is part of a csdiy.wiki full-catalog effort.
GAMES202 — High-Quality Real-Time Rendering (Lingqi Yan, UC Santa Barbara) teaches the algorithms behind a modern real-time renderer: soft shadows, precomputed global illumination, screen-space effects, physically based shading, and real-time ray tracing with denoising. The official homeworks ship as an interactive WebGL / GLSL framework that needs a GPU and a browser.
This repository re-implements the core algorithm of every assignment as an offline software renderer in pure Python + NumPy, so each one actually runs on a CPU-only machine and produces a real, committed output image / measured result. The interactive GPU/WebGL harness is documented as a partial (see Scope & partials); every algorithm here is genuinely executed and verified, no fabricated renders.
| # | Assignment | Core implemented | Measured result |
|---|---|---|---|
| HW1 | Real-time shadows | shadow map + PCF + PCSS (blocker search → penumbra → variable PCF) | soft, contact-hardening penumbrae over a 9.3k-tri scene; hard vs PCF vs PCSS |
| HW2 | PRT (precomputed radiance transfer) | order-2 SH env projection + per-vertex diffuse transport (unshadowed + ray-traced shadowed) | 745,744 BVH visibility rays; white-furnace energy check radiance/albedo = 1.0007 |
| HW3 | Screen-space | SSAO + SSR on a deferred G-buffer | SSAO mean AO 0.907; SSR reflection hit on 50.8 % of the reflective floor |
| HW4 | Kulla-Conty BRDF | GGX directional-albedo LUT + multiple-scattering energy compensation | single-scatter GGX loses 20.6 % energy (down to 0.40 at roughness 1.0); KC restores reflectance to 1.0000 |
| HW5 | Real-time ray tracing | Monte-Carlo path tracer (NEE) + edge-avoiding à-trous denoiser | PSNR 1 spp → denoised: 23.55 dB → 34.08 dB (+10.53 dB) vs 256-spp reference |
Hard shadow map (crisp) → PCF (uniform soft edge) → PCSS (penumbra widens with blocker distance, sharp at contact):
Diffuse PRT under a low-frequency environment. Left→right the shadowed transport adds self-shadowing in the torus hole and a soft contact shadow on the ground that the unshadowed transport cannot represent:
| shadowed (ray-traced visibility) | unshadowed |
|---|---|
![]() |
![]() |
Deferred G-buffer, hemisphere-kernel SSAO, and screen-space reflections marched in view space with binary-search refinement:
The white-furnace test: with only single scattering (top) a GGX metal darkens as roughness grows because masking-shadowing discards energy; adding the Kulla-Conty multiple-scattering lobe (bottom) conserves energy at every roughness.
| single scatter only (energy lost) | + Kulla-Conty multi-scatter (conserved) |
|---|---|
![]() |
![]() |
Precomputed directional-albedo LUT E(mu, roughness) and E_avg(roughness):
1 spp path trace (noisy) → à-trous reconstruction → high-spp reference:
- HW1 — Real-time shadows: light-space depth map, hard shadows with slope-scaled bias, PCF (Poisson disk), and PCSS (blocker search → similar- triangles penumbra estimate → variable-radius PCF).
- HW2 — PRT: project HDR environment maps onto 9 real SH coefficients; precompute per-vertex diffuse transport vectors (unshadowed analytic cosine lobe, and shadowed via BVH ray-cast visibility); relight as a 9-D dot product incl. a rotating-light sequence.
- HW3 — Screen space: G-buffer deferred shading; SSAO (view-space hemisphere kernel + range check + blur); SSR (view-space reflection ray marched in screen space with binary-search hit refinement).
- HW4 — Kulla-Conty: GGX microfacet BRDF (D/G/F), importance-sampled
directional-albedo LUT,
E_avg, and the multiple-scattering compensation lobe; verified with a white-furnace energy test; environment-lit metal spheres. - HW5 — RTRT + denoise: vectorized Monte-Carlo path tracer (Cornell box) and the edge-avoiding à-trous wavelet filter (albedo demodulation + joint luminance/normal/position weights), scored by PSNR vs a high-spp reference.
games202/
├── common/ # shared CPU rendering core
│ ├── geometry.py # vectors, procedural meshes, camera matrices
│ ├── rasterizer.py # software triangle rasterizer + G-buffer
│ ├── raytracer.py # BVH + batched vectorized occlusion queries
│ ├── sh.py # order-2 real spherical harmonics
│ ├── brdf.py # GGX microfacet BRDF + Kulla-Conty
│ └── img.py # PNG I/O, tone mapping, PSNR
├── hw1_shadow/ # shadow map / PCF / PCSS
├── hw2_prt/ # precomputed radiance transfer
├── hw3_screenspace/ # SSAO + SSR
├── hw4_kulla_conty/ # microfacet energy compensation
├── hw5_rtrt_denoise/ # path tracer + à-trous denoiser
├── results/ # all rendered PNGs + LUT figure (committed)
└── run_all.py # run every assignment
# uses the shared csdiy Python 3.11 env (numpy / pillow / matplotlib):
# D:\Project\_csdiy\.venv-ml\Scripts\python.exe
pip install -r requirements.txt
python run_all.py # run everything, regenerate results/
python run_all.py hw4 hw5 # or a subset
python hw1_shadow/run.py # or one assignment directlyEverything is single-threaded NumPy; approximate CPU wall-times: HW1 ~10 s, HW2 ~65 s (745k visibility rays), HW3 ~35 s, HW4 ~55 s, HW5 a few minutes (high-spp reference path trace).
Each assignment prints measured quantities and writes images to results/:
- HW1 prints mean surface visibility per technique; PCSS penumbra visibly
widens with blocker distance (
results/hw1_{hard,pcf,pcss}.png). - HW2 white-furnace energy check
radiance/albedo = 1.0007(expect 1.0), confirming the SH projection + cosine-lobe convolution are energy-correct. - HW3 SSAO mean AO
0.907; SSR hits50.8 %of the reflective floor. - HW4 furnace test table — single-scatter reflectance
0.9994 → 0.4036as roughness0.1 → 1.0; Kulla-Conty compensated reflectance1.0000at every roughness (energy conservation). - HW5 against a 256-spp reference, PSNR improves from
23.55 dB(raw 1 spp) to34.08 dBafter the à-trous denoiser (+10.53 dB); the denoised image is visually as clean as the reference (results/hw5_*.png).
Python 3.11, NumPy (all rendering math), Pillow (PNG), Matplotlib (LUT figure).
No GPU, no external rendering libraries — the rasterizer, BVH ray tracer, path
tracer, SH, and BRDF are all implemented from scratch in common/.
- PCSS turns a shadow map into soft shadows by estimating a penumbra width from the average blocker depth (similar triangles) and scaling the PCF kernel.
- PRT moves the lighting integral offline: a per-vertex SH transport vector bakes cosine + visibility, so relighting is a cheap dot product — and the shadowed transport is what makes self-shadowing "free" at runtime.
- Screen-space methods trade correctness for speed by working in the G-buffer: SSAO and SSR both march/sample in view space and must handle depth discontinuities and off-screen misses.
- Kulla-Conty shows why naive microfacet metals look too dark and fixes it with an energy-conserving multiple-scattering term derived from a precomputed directional-albedo table — verifiable exactly with a furnace test.
- à-trous / SVGF-style denoising reconstructs a 1-spp path trace by filtering albedo-demodulated irradiance with edge-stopping weights from the G-buffer.
The original assignments are interactive WebGL/GLSL programs. This repo implements and verifies the core algorithm of each as an offline CPU renderer producing real images and measured numbers. What is intentionally not reproduced (and why): the real-time GPU shader harness itself — live WebGL frame loop, GLSL fragment shaders, mouse-orbit camera, and interactive frame-rate — because this machine is CPU-only with no GPU/browser rendering target. The algorithmic content (PCSS math, SH transport, SSAO/SSR sampling, Kulla-Conty LUT + compensation, path tracing + à-trous filter) is fully implemented and executed here.
Based on the assignments of GAMES202 — High-Quality Real-Time Rendering by Prof. Lingqi Yan (UC Santa Barbara / GAMES). This repository is an independent educational reimplementation; all course materials and assignment specifications belong to their original authors. Original code in this repo is released under the MIT License.







