深入 LTC 实时面光源渲染:从微积分原理到 URP 落地
💡核心思路
把昂贵的「BRDF 在球面多边形上的二维积分」,先用一个 acos 调用。
LTC(Linearly Transformed Cosines)是 Eric Heitz 等人 2016 年在 SIGGRAPH 上推出的一套实时面光源算法 [1]。它在物理正确性、运行时开销、实现复杂度三者间找到了一个非常优雅的平衡点,目前已经是 Unity HDRP、Unreal Engine、以及众多自定义管线(VRChat 的 LTCGI 等)的事实标准实现。
本文沿着 「数学动机 → 数学工具 → 工程落地」 的脉络,把 2016 原始论文 + 2017 年三篇扩展(线光源 / 圆盘光源 / 椭球立体角)梳理成一份可以直接对照源码阅读的笔记。
一、引言:为什么实时面光源这么难?
在传统点光源 / 方向光中,光照贡献只需要计算 BRDF 在单一方向上的值;可是真实世界几乎不存在数学意义上的点光源 —— 灯管、灯箱、显示屏,本质上都是 面光源(Area Light)。物理正确的面光源着色,意味着我们要对一个二维空间区域上的所有光线方向求积分:
其中
Problem 1:球面多边形上的参数球分布积分,即使最简单的分布也很难。
Problem 2:现代 PBR 材质(GGX 微表面 BRDF)的形状非常复杂 —— 各向异性拉伸、偏斜(skewness),数值积分在实时场景里成本爆炸。
LTC 的破局思路非常工程化:与其在原 BRDF 空间积分,不如把它”扭”回一个我们解析上有解的简单分布。下图是论文给出的著名示意(GGX 与近似 LTC 的逐角度对比):

二、数学基石:线性变换余弦分布
2.1 从一个简单的”种子”开始
LTC 的母分布是一个 clamped cosine 分布:
它只是上半球的余弦衰减,朗伯漫反射的 BRDF 就是它本人。这个分布有两个关键的”好性质”:
| 性质 | 是否成立 |
|---|---|
| 球面多边形上的积分有解析闭式解 | ✅ 这就是经典的 Lambert form factor |
| 重要性采样有解析公式 | ✅ 余弦半球采样 |
2.2 用矩阵 给余弦分布”动整形手术”
对任意方向向量
其中第二项是球面变换的 Jacobian(论文附录有完整推导)。换句话说:对方向向量做线性变换,分布就被对应地拉伸 / 旋转 / 偏斜了。
通过控制
| 参数 | 矩阵表现 | 几何效果 |
|---|---|---|
| 粗糙度 roughness | lobe 等比例展宽 / 收紧 | |
| 各向异性 anisotropy | lobe 在切线方向呈椭圆形 | |
| 偏斜性 skewness | lobe 偏离法线方向倾斜 |
2.3 关于 “Skewness” 的一些澄清
skewness 在图形学中并不是一个统一专指的术语[2],必须看上下文。在 LTC 这里,它指的是:矩阵
几何上:
中的非正交成分 shear;
统计上:变换后的概率分布不再左右对称;
渲染上:高光在掠射时呈”拉长且偏移的彗尾”。
LTC 的精妙之处是 —— 这三件事可以用同一个
2.4 交互式可视化
下面这个 Three.js 组件复刻了原作者博客里的几张 GIF,但现在可以任意组合这三类形变。距离原点的半径
鼠标可以拖动旋转视角;球面颜色 =
强度(蓝→白→红),黑色细线 = 重要性采样得到的 LTC 主导方向,右下角的红色立方体 = 矩阵 作用于单位立方体的视觉化。试着先调 roughness 看红色 lobe 收缩成一点,再加 skew 看它如何向一侧倾斜 —— 这正是 GGX 在 grazing angle 下的真实行为。
三、降维打击:从面积分到线积分
LTC 的第二个支柱是把球面多边形的面积分变成边的线积分。这一节先解释几何直觉,再展示对应的着色器实现。
3.1 多边形辐照度的几何直觉
Heitz 在 2017 年的报告 [3] 给出了一个非常漂亮的几何推导。结论先放:球面多边形
它的几何含义是:
多边形在单位圆盘上的投影面积 可以分解为多条边各自对应的 disk sector(披萨切片)的有符号投影面积之和。
每条边
下图把这个几何分解展示得很清晰:
3.2 这其实是 Stokes 定理在做事
经典 Stokes 定理告诉我们:
在我们这里,原本的 球面多边形上的二维面积分(
- 几何派:把多边形的投影面积分解为以原点为顶点、以边为底的”扇形”贡献;
- 分析派:用散度定理把面积分写成边界上的线积分,每条边算一次
acos + cross + dot。
工程上最终落到 shader 里的 IntegrateEdge 就是这条边的解析贡献:
1 | |
每条边的输出是一个 3D 向量;最后把所有边的向量累加,再 dot 上
1 | |
3.3 地平线裁剪:把光源砍到上半球
由于积分定义在 上半球,必须先把光源在地平面以下的部分剪掉。在变换后的余弦空间这一步可以非常便宜地做:
裁剪后的多边形最多变成 5 个顶点(一个四边形最多被一条直线切出 5 个新顶点的情况),shader 里通常用查表的方式分支处理:
1 | |
四、扩展与进阶:2017 年的三篇增量论文
LTC 2016 主要面向多边形光源;2017 年 Heitz 团队又陆续放出三篇文章,把同一套思想推广到更多形状。
4.1 线光源(Line Lights)—— GPU Zen 2017
把”半径无限小的圆柱”近似成线段,避免对
其中
工程上的关键是 invariance 仍然成立:把线段端点用
1 | |
适用条件(来自论文测试):
- ✅ 圆柱半径较小、距离较远、材质较粗糙
- ❌ 高镜面 + 大半径 + 近距离 — 此时线段近似明显失真,需要回退到圆柱 / 圆盘端帽建模
4.2 圆盘光源 & 椭球立体角 [5]
2017 论文 Analytical calculation of the solid angle subtended by an arbitrarily positioned ellipsoid 给出一个让我反复称赞的几何巧思:
任意椭球对原点张成的立体角域
把椭球用 变成单位球后取球冠 把球冠对应的圆盘再用 反变换回去得到一个椭圆。
下面是论文里的核心流程图:
这个性质在 LTC 框架里特别有用:所有”圆盘光源” / “球状光源”都可以归约为一次”椭圆光源积分”,再走 LTC 标准流程,避免对椭球做昂贵的解析积分。
4.3 球冠保形参数化(Spherical Cap Preserving)[6]
进一步推广到任意球面分布,处理分布之间的重叠 / 相交问题,是 LTC 后续做 多光源混合 / 阴影体积近似 的理论基础,本文不展开。
五、工程落地:Unity URP 中的 LTC
5.1 LUT 生成(离线预计算)
实时阶段我们不解非线性方程,只查表。需要预计算的 LUT 由两张 RGBA32F 纹理组成:
| LUT | 内容 | 取值意义 |
|---|---|---|
| LUT_M | 矩阵 |
|
| LUT_AmpFresnel | 振幅 + 菲涅尔 |
索引方式:
1 | |
⚠️ LUT_SCALE / LUT_BIAS 的设置非常关键 —— 64×64 的 LUT 时通常是
LUT_SCALE = 63.0/64.0; LUT_BIAS = 0.5/64.0;。
5.2 URP 集成的最小骨架
下面是一个在 URP 自定义 Pass 里集成 LTC 矩形面光源的关键片段(仅展示关节点):
1 | |
C
1 | |
5.3 双重贡献:漫反射 + 镜面反射
最终的 BRDF 评估需要分两路调用:漫反射用恒等矩阵
1 | |
5.4 性能数据
来自 GPU Zen 论文的测试(NVIDIA Quadro M6000,1920×1080,Sponza 场景):
| 光源类型 | 主光照 Pass 耗时 |
|---|---|
| 四边形 LTC | 0.58 ms |
| 线光源 LTC | 0.42 ms |
线光源因为只走一维线积分,比多边形便宜约 28%,对于走廊里大量灯管的场景非常划算。
六、实现踩坑清单
下面这些点是我自己集成 LTC 时反复掉过的坑:
- TBN 顺序:
Minv * transpose(TBN)还是transpose(TBN) * Minv取决于你用的是行向量还是列向量约定,selfshadow 参考实现是后者。 - LUT 上下颠倒:原作者的预计算脚本生成的 LUT 是
沿 V 轴递增,但 Unity 的 RT 默认 V 轴朝下,记得 1.0 - uv.y或在脚本中翻一次。 - 裁剪后顶点数判断:5 边形情况下要多一次
IntegrateEdge调用,不能写死成 4 次循环。 - double-sided:默认 LTC 是单面光源(朝法线方向发光),双面要把
max(0, sum.z)改为abs(sum.z)。 - Fresnel 拟合:LUT 第二张里的两个分量是 Schlick 近似的两个端点,公式是
F0 * f1 + (1-F0) * f2,不是F0 + (1-F0)*pow(1-NoV, 5)。
七、总结
LTC 的工程价值,本质是把”积分难点”在三个层级上各打掉一刀:
| 层级 | 难点 | LTC 的对策 |
|---|---|---|
| 分布层 | GGX 形状复杂 | 用 |
| 积分层 | 球面多边形面积分 | Stokes 定理 → 边界线积分 |
| 运行时 | 实时不能解非线性 | 预计算 |
它不是数值最精确的方法,但在 “看上去对” + “够快” + “易实现” 三个维度上几乎是当下最优解。Unity HDRP 的所有 area light、Unreal 的 Rect Light、VRChat 的 LTCGI 都基于此,足以说明它在产业化上的稳健。
一个有趣的延伸思考:LTC 的”线性变换 + 解析积分”思想在很多地方都能复用,比如:
- 光锥(cone-traced)GI 的 cone footprint 拟合
- 法线分布在 mip 链中的 NDF prefilter
- 体积云中 ray march 的相函数近似
如果你对这些延伸有兴趣,可以从 A Spherical Cap Preserving Parameterization for Spherical Distributions(同一团队 2017)开始,它是 LTC 在更通用球面分布上的推广。
LTC 核心理论 (Theory)
Heitz E., Dupuy J., Hill S., Neubelt D. Real-Time Polygonal-Light Shading with Linearly Transformed Cosines. SIGGRAPH 2016.
偏斜性 (skewness) 在不同图形学语境下的四种含义 — 详见笔者另一篇笔记。
Heitz E. Geometric Derivation of the Irradiance of Polygonal Lights. Research Report, Unity Technologies, 2017. (HAL: hal-01458129)
Heitz E., Hill S. Linear-Light Shading with Linearly Transformed Cosines. GPU Zen 2017, Chapter 1.
Heitz E. Analytical calculation of the solid angle subtended by an arbitrarily positioned ellipsoid to a point source. Nuclear Inst. and Methods in Physics Research, A 852 (2017): 10-14.
Dupuy J., Heitz E., Belcour L. A Spherical Cap Preserving Parameterization for Spherical Distributions. SIGGRAPH 2017.
配套开源实现:
selfshadow/ltc_code — 原作者参考实现
guiqi134/LTC-Area-Lights — 完整 paper 复现
nscTechArt/URP-LTC-AreaLight — Unity 2022 URP 集成
PiMaker/ltcgi — VRChat 生产级实现