WAFILOG

起源引擎的静态光照是怎么算出来的

起源引擎(Source Engine)在 2000 年的 SIGGRAPH 上第一次亮相,是 GoldSource 的继任者。它的静态光照走的是基于辐射度(Radiosity)的预计算路线——也就是先把光烘进贴图,运行时不再实时算。这篇文章想从实现的角度,把 VRAD 这套工具到底怎么干活、光照贴图又是怎么编码的,尽量讲清楚。

辐射度光传输方程

辐射度算法要解的其实是漫反射全局光照。漫反射时 BRDF 退化成常数,渲染方程也就跟着简化成下面这样:

$$L_o = L_e + \sigma_s \int_A L_i F(x,x') dA(x')$$

式子里 σs 是漫反射 Albedo(在 VTF 材质里对应 $reflectivity 参数),F 是几何项,把可见性、距离衰减和角度关系都包了进去。这是第二类 Fredholm 积分方程,没有解析解,只能靠数值方法去逼近。

有限元离散化

VRAD 用的是有限元法(Finite Element Method)把连续的光照方程拆开。大致分这么几步:

  1. 面片细分:地图里每个 brush 表面都被切成一块块 patch,靠 CreateChildPatch() 和 SubdividePatch() 两个函数完成。
  2. 可见性计算:先建一个 transfer list,再用 Ray Casting 判断哪些面片之间真的有光路。VVIS 阶段生成的 PVS 数据可以拿来做个粗筛。
  3. 空间索引:面片用空间哈希表来索引,这种思路在 RTXGI 2.0 这类新方案里也还在用。

离散之后,原来的连续积分就变成了线性方程组:

$$(I - \sigma F)L = L_e$$

这里 F 是 form factor 矩阵,L 是要求解的辐射度向量。VRAD 最多开 16 个线程(threadnum),通过 GatherLight() 并行地把这方程组解出来。

Form Factor计算

Form Factor 是辐射度算法的核心,说白了就是两个面片之间的几何关系。VRAD 里给了两种算的方式:

面片到微分面片(FormFactorPolyToDiff):来自 Philip Dutre 那本 Global Illumination Compendium 的公式 81,适合近距离精细算。

微分面片到微分面片(FormFactorDiffToDiff):基于 Pat Hanrahan 的课件,距离超过 patch 尺寸 5 倍时可以用近似公式。

光照贴图处理

解完之后,光照贴图还是一块块粗糙的方块,得靠 自适应超采样 把它磨平:

ComputeLightmapGradients() 会先算每个采样点的梯度。梯度小于 0.0625,说明这块地方变化很平缓,没必要再超采样;否则就调用 SupersampleLightAtPoint() 做 4 倍超采样。

到了《半条命2》二十周年更新,Valve 头一回把线性插值换成了双三次插值(Bicubic),光照过渡明显柔和了。这也是 2000 年以来这个算法第一次被动。

Radiosity Normal Mapping编码

传统光照贴图只存每个像素的总辐射度,方向信息就丢掉了。表面一旦有法线贴图,那些凹凸细节就吃不到正确的光。

起源引擎用了 Radiosity Normal Mapping(也叫 HL2 Basis),把光照投影到三个正交基底上。运行的时候,采样法线贴图里的 normal 向量,分别和三个基向量做点积,加权加起来就是这个点的静态光照:

$$L_{final} = (n \cdot b_0) L_0 + (n \cdot b_1) L_1 + (n \cdot b_2) L_2$$

它基于的是半球空间(跟球谐函数用的球空间不一样),能保住光照的方向性,于是法线贴图的凹凸和静态光照就能自然叠在一起,自阴影这类细节也出来了。

性能特征

VRAD 的编译时间主要跟这几样有关:

  • 面片数量:patch 越多,线性方程组维度越大。
  • 反弹次数:多反弹就得反复迭代。
  • 光照贴图精度:lightmap scale 参数直接决定采样密度。

地图要是出现 leak(泄漏),VRAD 会去给一个无限大的外部空间算光照,编译时间一下就爆了。多线程在带 -final 的室外场景里还有瓶颈,这个 Valve 在 SDK 2013 里给了个补丁。

社区里有人做 GPU VRAD 想加速,但问题不少。到了起源2,VRAD3 转去了 Path Tracing 框架,还带了 Vulkan 硬件光追扩展,复杂场景编译时间缩了一大截。

技术限制

辐射度算法默认所有表面都是理想漫反射体,镜面反射和折射它就管不了了。碰到高频光照细节(比如很硬的阴影边缘),要么把 patch 密度提上去,要么换别的方法,比如光子映射。

另外光照贴图在运行期是死的,跟不上动态光源的变化。像手电筒这种会动的光,得另接一套动态光照系统才行。

参考文献

  1. Chris Green. "Efficient Self-Shadowed Radiosity Normal Mapping." SIGGRAPH 2007.
  2. Jason Mitchell, Gary McTaggart, Chris Green. "Shading in Valve's Source Engine." SIGGRAPH 2006.
  3. Groal et al. "Modeling the interaction of light between diffuse surfaces." 1984.
  4. Kajiya. "The rendering equation." 1986.
← 返回首页