跳转到内容

06 · 图形学:光照、阴影与后处理

定位:管线题之后,图形学面试的第二梯队就是**光照(PBR)、阴影(Shadow Map)、渲染路径(前向/延迟)、抗锯齿(TAA)**四大件。这章每块都给“原理 + 面试答法”,学完你就能接住“你们渲染这块了解多少”这类开放题,并把话题引到引擎实践上。 学完标准:能从 Phong 讲到 BRDF 再到 PBR 的演进逻辑(为什么要能量守恒);能讲菲涅尔、IBL 三张预计算图和法线贴图为什么偏蓝;Shadow Map 两大病和药能说;前向/延迟按光源数和透明物讨论,并能解释手机上的 TBDR 为什么讨厌延迟渲染;AA 三家族按“采样在哪做”区分。


1. 光照:从 Phong 到 PBR(演进逻辑比公式重要)

Section titled “1. 光照:从 Phong 到 PBR(演进逻辑比公式重要)”

Phong(1975,理解光照的分型):颜色 = 环境光 Ambient + 漫反射 Diffuse(max(0, N·L),与视点无关)+ 高光 Specular(max(0, R·V)^shininess,反光“亮点”)。 Blinn-Phong(改进,游戏用了二十年):高光改用半程向量 H = normalize(L+V) 与法线的夹角——视点离物体远近时高光形态更稳定,且省一次反射向量计算。两者都是经验模型:亮不亮“看着像”,不保证能量守恒。

BRDF(承上启下的概念,必考):Bidirectional Reflectance Distribution Function——给定“入射方向、出射方向”,返回“这一束光有多少被反射到观察方向”。它是所有光照模型的数学骨架:Phong 是一个玩具 BRDF,PBR 是“满足物理约束的 BRDF 家族”。两个约束:

  • 能量守恒:出射能量 ≤ 入射能量(漫反射多了镜面就得少—— Phong 违反这条,所以会“过曝感”);
  • 微表面理论:物体表面理想上是镜面,粗糙度 = 微小镜面法向的分散程度:光滑 → 反射集中(清晰高光);粗糙 → 反射散开(模糊高光)。

PBR(现代引擎标配):基于物理的渲染 = 能量守恒的 BRDF(Cook-Torrance)+ 线性空间计算 + 基于 HDR 光照 + IBL 环境光照。金属度/粗糙度工作流(游戏主流)的两个参数:

参数 含义 直觉
Metallic 金属度 电子是否自由(吸折射还是全反射) 金属=1:没有漫反射,颜色来自反射(带 F0 色调);非金属=0:颜色来自漫反射,镜面反射是无色的
Roughness 粗糙度 微表面法向的混乱度 0=镜面(镜子),1=哑光(石灰墙)

为什么要转 PBR(面试答法):①不同光照/材质下结果可预期(参数物理含义明确,美术不用逐灯调);②IBL——把环境预卷积成探针,反射和环境光统一进同一套 BRDF;③跨引擎/DCC 工具链材质语义统一。一句话:“PBR 不是更真实,而是更可控、可复用、可跨光照环境。”

三个必追问(PBR 答完会被立刻戳):

菲涅尔(Fresnel):同一表面,正视时反射弱,掠射(看向边缘)时反射强。水面远处变成镜子就是这个。Schlick 近似:F = F0 + (1 - F0) · (1 - cosθ)^5。F0 由材质决定:非金属大约 0.04,而且无色;金属用 albedo 当 F0,所以金属的颜色在反射里。能量守恒靠它分配:反射走掉的那一部分,折射和漫反射不能再要。

IBL(基于图像的光照):没有一盏盏的灯,环境是一张环境贴图。不能每个像素对整张贴图做积分,所以离线预计算三份东西:

产物 给谁用
辐照度图 Irradiance 漫反射。低频,把半球积分提前做好
预滤波环境贴图 Prefiltered Env 镜面。按粗糙度选 mip,越粗糙采越糊的那一层
BRDF LUT(一张很小的 2D 表) 把视角和粗糙度对应的菲涅尔积分查出来,运行时只读一次纹理

运行时:漫反射采辐照度图,镜面采对应粗糙度的那层预滤波图,再乘 LUT。一句话:IBL 是把环境光照的积分挪到离线,运行时做两次贴图采样。

法线贴图为什么是蓝的(切线空间):贴图存的不是世界空间法线,而是相对表面的切线空间法线。基准是 (0, 0, 1) 朝外,写进 [0, 1] 时 z 从 0.5 偏到接近 1,B 通道最大,所以偏蓝。好处是同一张贴图能贴到任意朝向的网格上;还可以只存 xy,z 用 sqrt(1 - x² - y²) 还原,省带宽。着色时用法线从切线空间转回世界空间(TBN 矩阵)。非均匀缩放下,法线要用逆转置,见 05 章。

面试金句:“菲涅尔决定掠射角有多亮,金属度决定这点亮是什么颜色;IBL 把环境积分预计算成辐照度图、预滤波 mip 和一张 LUT;法线贴图偏蓝,是因为切线空间的 z 朝外。”

原理(两 pass):

Pass 1 · 从灯看过去 光源 深度图 只记下谁离灯最近 Pass 2 · 正常渲染时对表 像素 变回光源空间 比深度图更深 → 在影子里

两大病 + 药(面试必问“shadow map 有什么问题”):

病 现象 药
Shadow Acne(阴影粉刺) 亮面出现斑马纹/噪点(深度图分辨率不够+数值精度,自遮挡误判) Depth Bias 偏移(比较时加容差;normal-based bias 更稳)
Peter-Panning(彼得潘/悬浮) 加了 bias 后阴影和物体脱开、脚底悬空 bias 别太大;或 front-face culling 渲染深度图;根本解法提分辨率
锯齿/硬边 阴影边缘生硬 PCF:比较时多采几个深度值平均 → 软阴影(PCSS 按遮挡距离自适应宽度)
大场景一套分辨率不够用 远处阴影糊 CSM 级联:视锥切成近中远几段,各配一张 Shadow Map,近处细远处粗

点光源要六张图(立方体六个面)= CubeMap Shadow;动态阴影每帧重画深度图是大开销——追问“阴影优化”可答:级联+按需更新、稳定投影(Texel Snapping 防抖)、静态物烘进 lightmap。

面试金句:“Shadow Map 一句话:从灯的眼睛拍张’谁离我最近’的深度照片,渲染时拿像素和照片对表——被挡的就是影子。Acne 和 Peter-Pan 是同一剂药的剂量问题:bias 小了粉刺、大了悬空。”

前向渲染 Forward 延迟渲染 Deferred 前向+ Forward+
思路 每物体×每光源直接算光 先把几何信息烘进 G-Buffer,再逐屏幕像素×光源算光 前向 + 光源剔除(每 tile 只算影响它的灯)
复杂度 O(物体×光源) O(屏幕像素×光源)(光源当屏幕空间点/球算贡献) O(像素×局部少量光源)
多光源 差(灯多了 DrawCall 和 shader 排列爆炸) 好(百盏灯也稳) 好
透明物体 ✅ 天然支持 ❌ G-Buffer 只有一个表面,透明得回退前向 ✅
MSAA ✅ 友好 ❌ 难(G-Buffer 爆显存) ✅
带宽 低 高(G-Buffer 读写几张 RT) 中
代表 移动端、UE 移动管线 寒霜、UE 默认、多数 3A UE 新世代、氧化 SNAP

G-Buffer 里存什么(追问点):法线(RGB 压缩)、反照率 Albedo、材质参数(金属度/粗糙度)、深度——几张 RenderTarget 打包。“延迟的瓶颈是带宽,透明的死穴是 G-Buffer 单表面”两句就是满分答案的两条腿。

选型答法:光源多且不透明为主 → 延迟;透明多/移动端带宽敏感/要 MSAA → 前向+;VR(带宽敏感)多走前向+。

为什么手机少用延迟(TBDR):Mali、Adreno、Apple 这类移动 GPU 多是 TBR/TBDR。画面切成小块,一块的颜色和深度先留在片上,画完再写出主存一次。延迟渲染要写好几张全屏 G-Buffer,片上放不下,只好反复读写主存,带宽和发热一起上去。所以不是“手机算力不够”,是这套架构讨厌全屏多目标。移动端因此默认前向或前向+。

抗锯齿(按“在哪一步采样”分类,面试标准答法):

家族 代表 原理 优缺点
硬件/几何阶段 MSAA 光栅化时每像素多个子采样点着色判定覆盖 质量高、只解决几何边;显存+带宽贵;延迟渲染下难用
着色后图像空间 FXAA 对最终图像找高对比边做模糊 极便宜、全平台;会糊掉细节(文字遭殃)
时间 TAA 多帧抖动采样(每帧子像素偏移)+ 历史帧混合 便宜且边缘质量最好——现代 3A 默认;代价:运动重影/鬼影(要 motion vector 补偿)、透明/粒子的历史失效问题
重构 DLAA/DLSS/FSR2 低分辨率渲染 + AI/时序重构升回高分辨率 顺便白送性能;生态依赖

面试金句:“MSAA 在几何阶段多采样子像素,FXAA 在图像上补锅,TAA 把采样摊到时间轴上——用’过去几帧凑一个高采样’,所以便宜又干净,但要用运动矢量管好重影。”

Bloom(辉光):亮度提取(阈值)→ 多级降采样模糊(硬件双线性降采样近似高斯,快)→ 叠回原图。没有它 HDR 的高亮无法“晕开”。

HDR 与色调映射(Tone Mapping):光照在线性 HDR(亮度可 >1)里计算,最后色调映射(ACES 曲线为标配)压回 [0,1] 输出——高亮优雅滚降而不是截断死白、整体对比更“电影感”。链条答法:“线性空间算光 → HDR 累积 → Bloom 加辉 → ACES 压回 LDR → gamma 输出”(gamma 校正衔接 12 章:显示器的非线性,颜色计算必须先转线性)。

5. 高频面试题 Q&A(合上书能讲)

Section titled “5. 高频面试题 Q&A(合上书能讲)”

Q1:Phong 和 Blinn-Phong 的区别? 高光项:Phong 用反射向量 R 和视向 V 的夹角;Blinn-Phong 用半程向量 H=(L+V) 与法线的夹角。后者在视角贴近平行表面时高光形态稳定、计算更省(不用算反射)。两者都是经验光照,不保证能量守恒。

Q2:什么是 BRDF? 双向反射分布函数:输入入射方向和观察方向,输出该入射光被反射到观察方向的比例。一切光照模型的数学形式;物理合理的 BRDF 要满足亥姆霍兹互易和能量守恒。Phong 是玩具 BRDF,PBR 的 Cook-Torrance 是满足约束的微表面 BRDF。

Q3:为什么要用 PBR?金属度和粗糙度控制什么? 因为可控可复用:能量守恒让任何光照下不出戏,参数有物理含义跨工具链统一,配合 IBL 把环境光和反射纳入同一框架。Metallic 决定“颜色来自反射(金属)还是漫反射(非金属)”;Roughness 是微表面混乱度——0 镜面高光锐利,1 哑光高光弥散。

Q4:Shadow Map 的原理和问题? 两 pass:先从光源渲染深度图;正式渲染时像素投影到光源空间比深度,被挡即在阴影。问题:acne(自遮挡斑马纹,因分辨率/精度——加 bias)、bias 过大导致 peter-panning(阴影脱脚)、边缘锯齿(PCF 软化、PCSS 自适应)、大场景分辨率分配(CSM 级联:近细远粗多张)、点光源要六面立方体图、每帧重绘的开销。

Q5:前向渲染和延迟渲染的区别?怎么选? 前向:物体×光源直接算,O(物×灯),透明友好、带宽低,但多光源爆炸;延迟:先写 G-Buffer(法线/Albedo/材质/深度),再逐像素×灯算,O(像素×灯),多光源强,但带宽高、透明物和 MSAA 是死穴(回退前向)。光源多的 3A 不透明场景选延迟;透明多、移动端、要 MSAA 选前向+(分 tile 剔除光源的前向)。

Q6:G-Buffer 里存什么? 屏幕空间几何与材质摘要:法线、Albedo(反照率)、金属度/粗糙度、深度,可能加运动矢量/材质 ID——多张 MRT。本质是“把最表面的着色输入缓存下来”,这也是透明做不了的原因:一个像素只记了一个表面。

Q7:MSAA、FXAA、TAA 分别怎么工作?优缺点? MSAA:光栅化每像素多子采样点(只对几何边有效),质量高但贵、延迟管线难用;FXAA:对成图检测高对比边模糊,极便宜但糊细节;TAA:每帧抖动子像素偏移+历史帧累积,等效超采样,边缘最好且免费,但要 motion vector 防运动重影,透明/粒子历史不稳。

Q8:Bloom 怎么实现? 阈值提取高亮 → 多级降采样+升采样模糊(用双线性降采样近似高斯,快于直接大核卷积)→ 与原图叠加。前提是 HDR:亮度可以超 1 才有“值得晕开”的高光。

Q9:为什么要 Tone Mapping?ACES 是什么? 光照在 HDR 线性空间算出 >1 的值,直接 clamp 会死白丢层次;tone mapping 把高动态优雅压进 [0,1](高亮滚降、保持对比)。ACES 曲线是电影业色彩科学,UE/Unity 默认,“顺便”给画面统一的调色风格。

Q10:为什么颜色计算要在线性空间? 显示器输出时做 gamma(≈2.2)幂运算;纹理若按 sRGB 直接参与光照,等于在非线性空间做乘加,结果物理错误(光照偏暗、混合过饱和)。正确链:sRGB 纹理采样时转线性 → 线性算光 → 输出前 gamma 编码。

Q11:法线贴图为什么偏蓝色? 存的是切线空间法线:z 轴恒朝外(基准 0.5 偏到 ~1.0 → B 通道大),xy 表示凹凸偏移——所以整体偏蓝。切线空间法线贴图可以跨物体复用、方便压缩(只存 xy,z 重构)。变换回世界空间用 TBN;非均匀缩放时法线仍要逆转置。

Q12:什么是菲涅尔?Schlick 近似在说什么? 同一表面越接近掠射角,反射越强,水面远处像镜子就是这个。Schlick:F = F0 + (1-F0)(1-cosθ)^5。非金属的 F0 大约 0.04,而且无色;金属的 F0 用反照率着色。它也是能量守恒的分配器:反射掉的比例,漫反射不能再拿。

Q13:IBL 运行时为什么只要采几张图? 环境光的半球积分在离线做完了。漫反射查辐照度图(低频),镜面按粗糙度查预滤波环境贴图的对应 mip(越糙越糊),再乘一张 BRDF LUT(视角 × 粗糙度 → 菲涅尔积分)。运行时不做积分,只做采样。

Q14:为什么手机上很少用延迟渲染? 移动 GPU 多是 TBDR:按小块把颜色和深度留在片上,画完再写一次主存。延迟渲染的 G-Buffer 是好几张全屏目标,片上放不下,带宽和发热都会爆。所以移动端用前向或前向+,不是单纯因为算力小。

6. 本章自测(10 题,限时 20 分钟,先自己答再看答案)

Section titled “6. 本章自测(10 题,限时 20 分钟,先自己答再看答案)”

1. Phong 三项分别模拟什么现象?

2. Blinn-Phong 改了哪一项?为什么更好?

3. 能量守恒在 PBR 里怎么体现?

4. 金属度 1 和 0 的材质,颜色分别来自哪?

5. Shadow Acne 和 Peter-Panning 的因果链?

6. CSM 解决什么问题?

7. 延迟渲染为什么做不了透明?

8. TAA 为什么便宜又好?它的代价?

9. HDR 光照后直接输出会怎样?

我的原答:ACES 做了什么?

10. sRGB 纹理不转线性直接算光,错在哪?

📖 答案(先自己答完再展开)
  1. 环境光(间接光的粗略替代)、漫反射(粗糙表面朝各向均匀反射,N·L)、高光(光滑表面镜面反射亮点,R·V^n)。
  2. 高光项换成半程向量 H·N;视点远近变化时形态稳定且省反射向量计算。
  3. 漫反射+镜面反射的能量分配此消彼长(菲涅尔按角度分配),出射总量不超过入射。
  4. 金属 1:无漫反射,颜色来自带 F0 着色的镜面反射;非金属 0:颜色来自漫反射(albedo),镜面是无色低强度。
  5. 深度图分辨率/精度不足→自遮挡误判出斑马纹(acne)→ 加 bias 容差→bias 过大把本该贴着的接触阴影推开→阴影和脚脱离(peter-panning)。
  6. 大场景一张 Shadow Map 无法近处够细远处够全——视锥分段各配一张,近段高分辨率、远段低分辨率。
  7. G-Buffer 每像素只记录最表面的一个层;透明需要“看到后面的表面并混合”,延迟管线里后面那些信息已被覆盖丢弃——只能回退前向单独画。
  8. 采样摊到时间轴:每帧抖动半个像素,历史帧累积等效每像素多采样,几乎零额外算力;代价是运动物体与历史错位(要 motion vector 重投影)→ 重影,透明/粒子无深度难以重投影会闪烁。
  9. 1 的亮度被 clamp 成死白、高光层次全丢;ACES 把 HDR 优雅压缩进 [0,1],高亮平滑滚降、保持整体对比与色调。

  10. 显示/纹理是 gamma 空间(非线性),在非线性数值上做乘加与光的物理混合不符——光照偏暗、混合过饱和;应先解码到线性再计算,输出时再编码。

7. 进阶追问(答不上来就回来复习)

Section titled “7. 进阶追问(答不上来就回来复习)”
  • IBL 怎么预卷积?见本章 §1(辐照度图给漫反射,预滤波 mip 给镜面,BRDF LUT 给菲涅尔积分)
  • 菲涅尔和 Schlick 近似?见本章 §1(掠射角反射率飙升,水面远处变镜子;F0 区分金属和非金属)
  • 体积雾 / 云怎么和延迟管线配合?(后处理阶段按深度重建位置做 raymarch)
  • 与 13 章贯通:DrawCall 合批、实例化与渲染路径怎么配合减少 CPU/GPU 两头的浪费?
  • TAA 的历史帧混合发生在哪?见本章 §4(后处理全屏 pass,需要前一帧的 motion vector)