Real-time global illumination — faking the integral in 16 milliseconds实时全局光照——在 16 毫秒内伪造那个积分
Lesson 11 solved the rendering equation correctly with Monte Carlo path tracing — and paid for it with hundreds to thousands of rays per pixel. A game has about 16 ms per frame; it cannot converge an integral in that time. So real-time rendering does not solve the equation, it fakes each term cheaply. This lesson is the catalogue of those fakes — shadow maps, ambient occlusion, baked light, probes, screen-space reflections — and the three verbs that generate them all: precompute, approximate, amortize. 第 11 课用蒙特卡洛路径追踪正确地解出了渲染方程——代价是每像素几百到几千条光线。而游戏每帧只有约 16 毫秒;它没法在这点时间里让一个积分收敛。所以实时渲染不去解那个方程,而是把每一项便宜地伪造出来。这一课就是这些“伪造术”的目录——阴影贴图、环境光遮蔽、烘焙光照、探针、屏幕空间反射——以及生成它们的三个动词:预计算、近似、摊销。
Five moves. (1) The budget wall: restate why a per-frame Monte Carlo integral is impossible, and name the three escape routes — precompute (bake), approximate (screen-space), amortize (temporal) — each buying one term of the rendering equation cheaply. (2) Shadows via shadow mapping: render depth from the light's view, then compare; meet acne and peter-panning and the bias that trades between them, then PCF and cascades. (3) Ambient occlusion: the "how open is the sky here" term, faked in screen space by SSAO. (4) Precomputed indirect light: radiosity, lightmaps (static only), and irradiance probes with spherical harmonics (for dynamic objects). (5) Screen-space and the new era: SSR and its on-screen-only limit, then hardware ray tracing + ML denoising + DDGI closing the gap to true dynamic GI (bridge to lesson 15). The widget dissects the single most instructive bug in the whole set: shadow-map bias.
五步走。(1) 预算之墙:重申为何逐帧的蒙特卡洛积分不可行,并点出三条逃生路线——预计算(烘焙)、近似(屏幕空间)、摊销(时序)——每条都便宜地买下渲染方程的某一项。(2) 用阴影贴图做阴影:从光源视角渲染深度,再比较;见识阴影痤疮与彼得潘,以及在二者之间权衡的偏置,然后是 PCF 与级联。(3) 环境光遮蔽:那个“这里天空有多开阔”的项,由 SSAO 在屏幕空间伪造。(4) 预计算间接光:辐射度、光照贴图(仅限静态)、以及带球谐的辐照度探针(用于动态物体)。(5) 屏幕空间与新纪元:SSR 及其“只有屏幕上才有”的局限,然后是硬件光线追踪 + ML 去噪 + DDGI 逼近真正的动态 GI(通往第 15 课的桥)。小控件将解剖整套方法里最有教益的那个 bug:阴影贴图偏置。
1 · The budget wall — you cannot converge an integral per frame预算之墙——你没法逐帧让积分收敛
Recall the constraint from lesson 01: a real-time frame must finish in roughly 16.7 ms at 60 FPS (and half that, ~8 ms, at 120 FPS). Now recall lesson 11: path tracing estimates the recursive integral ∫ fr Li (ωi·n) dωi by averaging random light paths, and its noise falls only as 1/√N — to halve the noise you must quadruple the rays. A clean offline frame might spend hundreds to thousands of paths per pixel. Multiply that out: a full-HD frame is about 2 × 10⁶ pixels, so even 256 paths each is ~5 × 10⁸ ray-scene queries. There is no budget for that in 16 ms on commodity hardware; the integral simply cannot converge in time.
回忆第 01 课的约束:实时一帧在 60 FPS 下必须在大约 16.7 毫秒 内画完(120 FPS 下则减半,~8 毫秒)。再回忆第 11 课:路径追踪通过对随机光路取平均来估计递归积分 ∫ fr Li (ωi·n) dωi,其噪声只以 1/√N 下降——要把噪声减半,就得把光线数翻四倍。一帧干净的离线画面每像素可能要花几百到几千条路径。乘出来看:一帧全高清约有 2 × 10⁶ 个像素,即便每像素只 256 条路径也是 ~5 × 10⁸ 次光线—场景查询。在普通硬件上、16 毫秒内根本没有这个预算;那个积分就是来不及收敛。
So real-time rendering gives up on solving the equation and instead spends its milliseconds buying the look of each term as cheaply as possible. There are exactly three ways to make an expensive term affordable, and every technique in this lesson is one of them:
于是实时渲染放弃了去解那个方程,转而把它有限的毫秒花在尽可能便宜地买下每一项的观感上。让一个昂贵的项变得负担得起,恰好只有三种办法,而本课的每一种技术都是其中之一:
Keep this triple in view: it is the whole strategy. When you meet a new real-time trick, ask which verb it is. A shadow map is precompute the light's visibility (§2). SSAO is approximate the ambient occlusion term from the depth buffer (§3). Lightmaps are precompute the indirect integral (§4). Temporal accumulation in modern GI is amortize across frames (§5). None of them solve the rendering equation; each one buys one factor of it at a price the frame can pay.
把这三元组一直摆在眼前:它就是全部策略。当你遇到一个新的实时技巧,就问它是哪个动词。阴影贴图是把光源的可见性预计算下来(§2)。SSAO 是从深度缓冲近似出环境光遮蔽项(§3)。光照贴图是把间接光积分预计算好(§4)。现代 GI 里的时序累积是跨帧摊销(§5)。它们都不解渲染方程;每一个都以帧付得起的价格买下方程的某一个因子。
2 · Shadows via shadow mapping — a depth test from the light's eye用阴影贴图做阴影——从光源之眼做一次深度测试
A shadow is pure visibility toward a light: point x is lit if the light can see it, and shadowed if some closer surface blocks the view. Path tracing answers this with a shadow ray. Rasterization cannot shoot arbitrary rays cheaply — but it is superb at one thing: rendering depth from a viewpoint. So flip the viewpoint. Shadow mapping is a two-pass idea:
阴影本质上就是朝向光源的可见性:如果光源能看到点 x,它就被照亮;如果有更近的表面挡住了视线,它就在阴影里。路径追踪用一条阴影光线来回答。光栅化没法便宜地发射任意光线——但它极擅长一件事:从某个视点渲染深度。那就把视点翻过去。阴影贴图是一个两遍(two-pass)的想法:
- Pass 1 — build the map. Put a virtual camera at the light, looking into the scene, and rasterize only depth. The result is the shadow map: a texture where each texel stores the distance from the light to the nearest surface along that direction. This is exactly the light's z-buffer (lesson 04).第一遍——建图。把一台虚拟相机放在光源处、朝场景看,只光栅化深度。结果就是阴影贴图:一张纹理,每个纹素存着沿该方向从光源到最近表面的距离。这恰好就是光源的 z 缓冲(第 04 课)。
- Pass 2 — shade and test. Render the scene normally from the eye. For each visible point x, transform it into the light's space to get its true light-space depth d(x) and the texel it falls under. Look up the stored depth zmap. If d(x) > zmap + bias, something nearer to the light was recorded there — so x is in shadow. Otherwise it is lit.第二遍——着色并测试。照常从眼睛渲染场景。对每个可见点 x,把它变换到光源空间,得到它真正的光源空间深度 d(x) 以及它落在哪个纹素下。查出存储的深度 zmap。若 d(x) > zmap + 偏置,说明那里记录过更靠近光源的东西——于是 x 在阴影里。否则它被照亮。
The whole subtlety hides in that + bias. The shadow map is a finite grid: each texel stores one depth for a little patch of surface, but the real surface under that texel slopes and spans a range of depths. So the stored value is a quantized, slightly-wrong estimate of the true depth. Compare a point against it naively and half the lit surface reports d(x) > zmap purely from that quantization error — the surface shadows itself in stripes. This is shadow acne.
全部的微妙都藏在那个 + 偏置 里。阴影贴图是一张有限的网格:每个纹素为一小片表面只存一个深度,可那纹素底下真实的表面是倾斜的、跨着一段深度范围。所以存下来的值是对真实深度的、量化过的、略有偏差的估计。若拿一个点朴素地去和它比,光照表面里会有一半仅仅因为这点量化误差就报出 d(x) > zmap——表面把自己投成了一条条纹的阴影。这就是阴影痤疮(shadow acne)。
The fix is to nudge the comparison: add a small bias so a point must be meaningfully farther than the stored depth before it counts as shadowed. Too little bias and the acne stripes remain. But push the bias too high and you overshoot the other way: real contact shadows lift off, and an object's shadow visibly detaches from its base as if the object were floating — peter-panning (the shadow, like Peter Pan's, comes unstuck from the feet). Bias is a single knob trading one artifact against the other, and the widget below lets you feel exactly where the safe middle is.
解法是把比较推一下:加一个小小的偏置,让一个点必须明显地比存储深度更远,才算作被遮蔽。偏置太小,痤疮条纹还在。可偏置推得太高就矫枉过正到另一头:真实的接触阴影被抬起,物体的阴影会明显地从它的底部脱开,仿佛物体悬浮着——彼得潘(peter-panning)(阴影像彼得潘那样,从脚上松脱了)。偏置这一个旋钮在两种瑕疵之间权衡,下面的控件让你精确地感受到那个安全的中间地带在哪。
A constant bias is a compromise: surfaces nearly edge-on to the light need a large bias (their depth changes fast across one texel), while surfaces facing the light need almost none. The standard refinement is slope-scaled bias — scale the bias by how grazing the light angle is (roughly by tan of the angle between the surface normal and the light). This shrinks the peter-panning you pay on flat faces while still killing acne on steep ones. The widget uses a single flat receiver, so a constant bias suffices to show the trade cleanly.
常数偏移是一种折中:几乎与光线平行(掠射)的表面需要大偏移(它们的深度在一个纹素内变化很快),而正对光的表面几乎不需要。标准的改进是斜率缩放偏移(slope-scaled bias)——按光照角度有多掠射来缩放偏移(大致按表面法线与光线夹角的 tan)。这在平面上减少了你要付出的彼得潘现象,同时仍能消灭陡面上的痤疮。本控件只用了一个平整的接收面,所以一个常数偏移就足以干净地展示这个取舍。
Two more pieces make shadow maps usable in practice. PCF (percentage-closer filtering) softens the hard, aliased shadow edge: instead of one depth comparison, sample the map at several neighboring texels, run the test on each, and average the pass/fail results. A point half-inside the penumbra comes back "60% lit," giving a soft edge instead of a jagged binary one. And cascaded shadow maps (CSM) handle large scenes: a single map spread over a whole outdoor level would give each texel a huge footprint (chunky shadows). CSM slices the view frustum by distance into a few cascades and gives each its own map — high resolution near the camera where you look, coarse far away where you don't.
还有两件事让阴影贴图在实践中可用。PCF(百分比渐近滤波)柔化那道生硬、走样的阴影边缘:不做单次深度比较,而是在邻近的几个纹素上采样该图、对每个都跑一遍测试、再把通过/未通过的结果平均。一个半陷在半影里的点会返回“照亮 60%”,从而得到柔和的边缘,而不是锯齿状的非黑即白。而级联阴影贴图(CSM)应对大场景:单张图铺满整个户外关卡,会让每个纹素覆盖一大片(块状阴影)。CSM 按距离把视锥切成几段级联、各给一张自己的图——相机近处(你看的地方)高分辨率,远处(你不看的地方)粗糙。
3 · Ambient occlusion — how open is the sky here?环境光遮蔽——这里的天空有多开阔?
Split the incoming light Li into direct (from named light sources — handled by shadow maps) and ambient (a soft, everywhere glow standing in for all the indirect bounces we cannot afford). A crude but common ambient model just adds a constant to every surface. The problem: that constant floods creases, contact points, and cavities that should be darker because surrounding geometry blocks most of the sky. Ambient occlusion is the missing factor — for each point, what fraction of the hemisphere above it is open versus occluded by nearby surfaces. A corner where two walls meet is occluded, so it should be darker; an exposed flat is fully open.
把入射光 Li 拆成直接光(来自具名光源——由阴影贴图处理)与环境光(一层柔和、无处不在的辉光,替代所有我们负担不起的间接反弹)。一个粗糙但常见的环境光模型只是给每个表面加一个常数。问题在于:这个常数会漫进那些本该更暗的折缝、接触点与凹陷——因为周围的几何挡住了大部分天空。环境光遮蔽就是那个缺失的因子——对每个点,问它上方半球有多大比例是开阔的,又有多少被邻近表面遮蔽。两面墙相交的角落是被遮蔽的,故应更暗;一块暴露的平面则完全开阔。
Computing true occlusion means casting many rays over the hemisphere per point — again, too expensive per frame. SSAO (screen-space ambient occlusion) is the classic approximate trick: it never touches the real geometry. It reads the depth buffer — which the rasterizer already produced — and, around each pixel, samples a few nearby depth values. If many neighbors are closer to the camera than the surface here, this pixel sits in a pocket surrounded by geometry, so darken it; if neighbors fall away, it is open, so leave it bright. It is a purely 2.5-D estimate from data already on screen — which is exactly why it is cheap, and exactly why it has the same blind spot as everything screen-space (§5): it can only account for occluders the camera can see.
计算真实遮蔽意味着对每个点在半球上投出许多光线——同样,逐帧太贵。SSAO(屏幕空间环境光遮蔽)是经典的近似技巧:它根本不碰真实几何。它读取深度缓冲——那是光栅器已经产出的——在每个像素周围采样几个邻近的深度值。若许多邻居比此处表面更靠近相机,说明这个像素坐在被几何环绕的凹坑里,就把它压暗;若邻居纷纷退远,它就是开阔的,保持明亮。这是一个纯粹 2.5 维的、从屏幕已有数据得来的估计——这正是它便宜的原因,也正是它和一切屏幕空间方法(§5)有同样盲点的原因:它只能顾及相机看得见的遮挡物。
4 · Precomputed indirect light — radiosity, lightmaps, and probes预计算间接光——辐射度、光照贴图与探针
The hardest term is the recursive one: light that bounces off other surfaces before reaching x — indirect illumination, the soft color-bleeding fill that makes a scene look real. If the geometry and lights do not move, we can pay for it once, offline, and read the result back for free every frame. That is the precompute route, and it has a lineage.
最难的项是那个递归项:在到达 x 之前先从别的表面反弹过的光——间接光照,那种柔和的、渗色的补光,正是它让场景显得真实。如果几何与光源都不动,我们就可以一次性、离线地为它付账,之后每一帧免费回读结果。这就是预计算路线,它有一条谱系。
Radiosity is the classical method for the diffuse case. Break every surface into small patches; the light leaving patch i is its emission plus the light it receives from every other patch j, weighted by a form factor Fij that encodes how much of j's hemisphere i occupies (their mutual geometry — size, distance, orientation). That is a big linear system of patch radiosities; solve it once and you have full diffuse interreflection — soft, physically-based fill light. It is view-independent (diffuse light looks the same from every angle), which is exactly what makes the solution reusable.
辐射度(radiosity)是处理漫反射情形的经典方法。把每个表面拆成小面片;离开面片 i 的光,等于它自身的发射加上它从其他每个面片 j 收到的光,后者按一个形状因子 Fij 加权——该因子编码了 i 占据 j 半球的比例(它们相互的几何:大小、距离、朝向)。这是一个关于各面片辐射度的大型线性系统;解一次,你就得到完整的漫反射互反射——柔和的、基于物理的补光。它与视角无关(漫射光从任何角度看都一样),而这正是解可以复用的原因。
Lightmaps are how that precomputed result ships in a game. Bake the indirect (and often the static direct) illumination into a texture unwrapped over the static geometry; at runtime, shading a wall is just a texture fetch — nearly free, and gorgeous. The iron catch is in the word static: a lightmap is correct only for the geometry and lights it was baked with. Move a wall, open a door, or swing a light and the baked shadows and bounce lie. And crucially, lightmaps live on static surfaces — they say nothing about how to light a moving character walking through that baked room.
光照贴图(lightmaps)是那份预计算结果在游戏里的交付形式。把间接光(常常还有静态直接光)烘焙进一张在静态几何上展开的纹理;运行时,给一面墙着色只是一次纹理取样——几乎免费,而且极美。铁一般的坑就在静态二字:光照贴图只对烘焙时所用的几何与光源才正确。挪一面墙、开一扇门、或转动一盏灯,烘焙好的阴影与反弹就都在撒谎。而且关键在于,光照贴图长在静态表面上——它对如何照亮一个穿过那间烘焙房间的移动角色只字未提。
That gap is filled by irradiance probes (light probes). Scatter sample points through the volume of the scene; at each probe, precompute the light arriving from every direction and store it compactly. A dynamic object then reads the nearest probes and interpolates — so a character darkens as it walks into shade and picks up a red glow beside a red wall, all without per-frame bounce computation. The compact storage is the elegant part: incoming light over a sphere is a smooth low-frequency function, so we expand it in spherical harmonics (SH) and keep only the first few coefficients (often just 9, for the diffuse "L2" band). Those few numbers reconstruct the soft directional irradiance well enough for diffuse shading. Probes are precompute for the light field in space; lightmaps are precompute for it on surfaces — the pair covers static rooms and the dynamic actors moving through them.
这个空缺由辐照度探针(光照探针)填补。在场景体积里撒下采样点;在每个探针处,预计算从各个方向到来的光并紧凑地存下。动态物体随后读取最近的几个探针并插值——于是角色走进阴影会变暗、走到红墙边会染上一层红晕,全程无需逐帧计算反弹。紧凑存储是其精巧之处:球面上的入射光是一个平滑的低频函数,所以我们用球谐(SH)把它展开,只保留前几个系数(漫反射常常只用 9 个,即“L2”band)。这寥寥几个数就能重建出足够好的、柔和的方向性辐照度,用于漫反射着色。探针是对空间中光场的预计算;光照贴图是对表面上光场的预计算——二者搭配,覆盖了静态房间和穿行其中的动态角色。
| Technique | Buys which term | Verb | Handles dynamic? |
|---|---|---|---|
| Shadow map | direct-light visibility | precompute (per frame) | yes (rebuilt each frame) |
| SSAO | ambient occlusion | approximate (screen-space) | yes, but on-screen only |
| Lightmap | indirect on surfaces | precompute (offline bake) | no — static geometry & lights |
| Light probe + SH | indirect on moving objects | precompute (offline bake) | dynamic objects, static lights |
| SSR | glossy/mirror reflection | approximate (screen-space) | yes, but on-screen only |
| 技术 | 买下哪一项 | 动词 | 能处理动态吗 |
|---|---|---|---|
| 阴影贴图 | 直接光可见性 | 预计算(每帧) | 能(每帧重建) |
| SSAO | 环境光遮蔽 | 近似(屏幕空间) | 能,但仅限屏幕上 |
| 光照贴图 | 表面上的间接光 | 预计算(离线烘焙) | 否——静态几何与光源 |
| 光照探针 + SH | 移动物体上的间接光 | 预计算(离线烘焙) | 动态物体,静态光源 |
| SSR | 光泽/镜面反射 | 近似(屏幕空间) | 能,但仅限屏幕上 |
5 · Screen-space, and the new era屏幕空间,以及新纪元
SSR (screen-space reflections) fakes the mirror/glossy term the same way SSAO fakes occlusion: reuse the frame you already rendered. From a reflective pixel, reflect the view ray about the surface normal and march that ray through the depth buffer, stepping until it crosses a stored depth — a hit. Sample the already-shaded color there and blend it in as the reflection. When it works, you get gorgeous wet floors and glossy tabletops for the price of a screen-space raymarch, no extra scene traversal.
SSR(屏幕空间反射)伪造镜面/光泽项的方式,和 SSAO 伪造遮蔽如出一辙:复用你已经渲染好的这一帧。从一个反射像素出发,把视线光线关于表面法线反射,然后让那条光线在深度缓冲里步进,一步步走到它越过某个存储深度处——即一次命中。取那里已经着色好的颜色,作为反射混合进来。当它奏效时,你能以一次屏幕空间光线步进的代价换来极美的湿地板和光泽桌面,无需额外的场景遍历。
SSR, SSAO, and every screen-space method share one fundamental limitation: they can only use what is currently on screen. The depth buffer holds the visible surfaces and nothing else — so a reflection of the sky, of an object just off the edge of the frame, or of a surface hidden behind the reflector simply has no data to draw from. SSR characteristically fails at screen edges (reflections fade out) and cannot reflect anything the camera itself cannot see. It approximates the reflection integral using a 2-D shadow of the 3-D world; whatever that shadow omits, the reflection omits too.
SSR、SSAO 以及每一种屏幕空间方法都共享一个根本局限:它们只能用当前屏幕上有的东西。深度缓冲只保存可见表面、别无其他——所以对天空、对刚好落在画面边缘之外的物体、或对藏在反射体背后的表面的反射,根本没有数据可取。SSR 的典型失败发生在屏幕边缘(反射逐渐淡出),并且无法反射任何相机自己看不到的东西。它用三维世界的一张二维"影子"来近似反射积分;那张影子漏掉什么,反射就跟着漏掉什么。
This blind spot is precisely what the new era removes. Hardware ray tracing (lesson 10's BVH, now in silicon) lets a real-time renderer shoot a modest number of true rays per pixel into the actual scene — off-screen geometry included — for shadows, reflections, and one or two bounces of GI. The catch returns us to §1: a modest ray count is noisy. The unlock is pairing it with two of our three verbs — amortize across frames (reuse and accumulate samples over time) and ML denoising (a neural network trained to reconstruct a clean image from a sparse, noisy one). Schemes like DDGI (dynamic diffuse GI) go further: keep the light-probe idea from §4 but update the probes every frame with a few traced rays each, so the baked indirect light of lightmaps becomes fully dynamic — lights and geometry can move. This is the direct bridge to lesson 15, where real-time and offline rendering, split back in lesson 01, finally begin to converge.
这个盲点,正是新纪元所消除的。硬件光线追踪(第 10 课的 BVH,如今做进了芯片)让实时渲染器能向真实场景——包括屏幕外的几何——每像素射出为数不多的真光线,用于阴影、反射、以及一两次反弹的 GI。而问题又把我们带回 §1:光线数不多就有噪声。破局之道是把它与我们三个动词中的两个配对——跨帧摊销(随时间复用并累积样本)与 ML 去噪(一个被训练来从稀疏、含噪的图重建出干净图的神经网络)。像 DDGI(动态漫反射 GI) 这样的方案更进一步:保留 §4 的光照探针思想,但每帧用少量追踪光线更新探针,于是光照贴图那份烘焙好的间接光变得完全动态——光源与几何都能移动。这正是通往第 15 课的桥,在那里,第 01 课分开的实时与离线渲染,终于开始融合。
Where this points next接下来指向何处
We now have the real-time toolbox as a coherent set rather than a bag of tricks: every technique buys one term of the rendering equation by precomputing, approximating, or amortizing it, and the physically-correct path tracer of lesson 11 is the ground truth they all approximate. But everything so far has assumed a frozen scene — one instant, held still while we shade it. Real content moves: characters walk, cameras pan, cloth billows. Lesson 13 leaves shading for animation and motion — how positions change over time, interpolation and easing, skeletal rigs and skinning, and why motion introduces its own sampling problem in time (the temporal cousin of the aliasing from lesson 06). The lighting we just learned to fake will now have to hold up while the whole scene is in motion.
现在我们把实时工具箱看成一套连贯的体系、而非一袋子技巧:每种技术都通过预计算、近似或摊销买下渲染方程的某一项,而第 11 课那个物理正确的路径追踪器,是它们共同逼近的“地面真值”。但到目前为止,一切都假设场景是冻结的——一个瞬间,被定住让我们着色。真实内容会动:角色行走、相机横摇、布料翻卷。第 13 课离开着色,转向动画与运动——位置如何随时间变化、插值与缓动、骨骼绑定与蒙皮,以及为什么运动会在时间上引入它自己的采样问题(第 06 课那种走样在时间上的表亲)。我们刚学会伪造的光照,现在得在整个场景运动时依然站得住脚。
Real-time rendering cannot converge a Monte Carlo integral per frame — path tracing's 1/√N noise needs far more rays than 16 ms allows. So it never solves the rendering equation; it buys each term cheaply by one of three verbs: precompute (bake), approximate (screen-space), amortize (temporal). Shadow maps precompute the light's visibility as a depth buffer from the light's view, then test d(x) ≤ zmap + bias; too little bias gives acne (self-shadowing from quantization), too much gives peter-panning (the shadow detaches) — PCF softens edges, cascades handle scale. SSAO approximates ambient occlusion from the depth buffer. Lightmaps bake indirect light onto static surfaces; light probes + spherical harmonics bake it into space so dynamic objects can read and interpolate it. SSR reflects by marching the depth buffer — but every screen-space method is blind to what is off-screen. Hardware ray tracing + ML denoising + DDGI lift that limit and begin converging real-time toward offline (lesson 15).
实时渲染没法逐帧让蒙特卡洛积分收敛——路径追踪的 1/√N 噪声所需的光线数远超 16 毫秒 所能允许。所以它从不解渲染方程;它用三个动词之一便宜地买下每一项:预计算(烘焙)、近似(屏幕空间)、摊销(时序)。阴影贴图把光源的可见性预计算成从光源视角看的深度缓冲,再测试 d(x) ≤ zmap + 偏置;偏置太小得痤疮(量化引起的自遮蔽),太大得彼得潘(阴影脱开)——PCF 柔化边缘,级联应对尺度。SSAO 从深度缓冲近似环境光遮蔽。光照贴图把间接光烘焙到静态表面上;光照探针 + 球谐把它烘焙进空间,好让动态物体读取并插值。SSR 通过步进深度缓冲来反射——但每种屏幕空间方法都对屏幕外的东西视而不见。硬件光线追踪 + ML 去噪 + DDGI 抬掉那道限制,开始让实时向离线收敛(第 15 课)。
Interview prompts面试题
- Why can't a real-time renderer just path-trace each frame like an offline one? (§1 — Monte Carlo noise falls only as 1/√N, so a converged frame needs hundreds–thousands of rays per pixel; that many ray-scene queries cannot fit in ~16 ms, so you must precompute, approximate, or amortize instead.) 为什么实时渲染器不能像离线那样逐帧做路径追踪?(§1 — 蒙特卡洛噪声只以 1/√N 下降,收敛一帧需每像素几百到几千条光线;这么多光线—场景查询塞不进 ~16 毫秒,所以只能改用预计算、近似或摊销。)
- Describe the shadow-mapping algorithm end to end. (§2 — pass 1: rasterize depth from the light's viewpoint into a shadow map; pass 2: for each shaded point, project into light space and compare its true depth d(x) to the stored texel depth + bias; shadowed if it is meaningfully farther.) 从头到尾描述阴影贴图算法。(§2 — 第一遍:从光源视角把深度光栅化进阴影贴图;第二遍:对每个着色点投影到光源空间,把它真实深度 d(x) 与存储的纹素深度 + 偏置 相比;若明显更远则在阴影里。)
- What are shadow acne and peter-panning, and how does the bias trade between them? (§2 — acne is stripes of self-shadow from a lit surface reading d > zmap due to depth quantization; too much bias overshoots and lifts the shadow off the contact point, detaching it — peter-panning; bias is the single knob between the two.) 什么是阴影痤疮与彼得潘,偏置如何在二者间权衡?(§2 — 痤疮是被照亮表面因深度量化读出 d > zmap 而产生的条纹状自阴影;偏置过大则矫枉过正,把阴影从接触点抬起、使其脱开——即彼得潘;偏置是二者之间唯一的旋钮。)
- What does PCF do, and why do we want it? (§2 — percentage-closer filtering samples several neighboring shadow-map texels, runs the depth test on each, and averages pass/fail to produce a soft, anti-aliased shadow edge instead of a jagged binary one.) PCF 做什么,我们为什么要它?(§2 — 百分比渐近滤波对邻近的几个阴影贴图纹素采样、对每个都跑深度测试、再把通过/未通过平均,从而得到柔和的、抗锯齿的阴影边缘,而非锯齿状的非黑即白。)
- Lightmaps vs light probes — when do you reach for each? (§4 — lightmaps bake indirect light onto static surfaces and are correct only for static geometry & lights; probes bake incoming light into points in space so dynamic objects can interpolate it — static walls use lightmaps, moving characters use probes.) 光照贴图 vs 光照探针——各自在什么时候用?(§4 — 光照贴图把间接光烘焙到静态表面,只对静态几何与光源正确;探针把入射光烘焙到空间中的点,好让动态物体插值——静态墙面用光照贴图,移动角色用探针。)
- Why store probe irradiance in spherical harmonics, and what is SSR's fundamental limitation? (§4–5 — incoming light over a sphere is smooth/low-frequency, so a few SH coefficients (often 9) reconstruct diffuse irradiance compactly; SSR can only reflect what is on screen — the depth buffer holds visible surfaces only, so off-screen and hidden geometry, and screen edges, have no data.) 为什么把探针辐照度存成球谐,SSR 的根本局限又是什么?(§4–5 — 球面上的入射光平滑、低频,故寥寥几个 SH 系数(常为 9 个)就能紧凑重建漫反射辐照度;SSR 只能反射屏幕上有的东西——深度缓冲只存可见表面,故屏幕外、被遮挡的几何以及屏幕边缘都没有数据。)