
一、一个让我开窍的雨伞故事我永远忘不了大学时那个让我彻底理解四面八方求和的下午。那天是梅雨季我撑着一把透明的雨伞走在校园里雨下得不大不小是那种让人有些烦躁的细雨。走到一半我突然停了下来注意到了一件以前从未留意的事情雨滴不是从一个方向打到我的伞上的。有的雨滴垂直落下打在伞的正中央有的被风吹着从右边斜飞过来有的从前方飘来打在伞的前缘还有的从后方飘来打在伞的后缘。整把伞几乎从每一个方向都在接收雨滴。我突然想到了那个我学了很久但一直没真正理解的问题怎么计算一个点接收到的所有光答案突然变得无比清晰——就像我的雨伞接收来自四面八方的雨滴一样一个点也接收来自四面八方的光。要计算这个点接收到的总光照需要做的就是把每一个方向射来的光全部加起来。那一刻我恍然大悟原来对所有方向求和不是抽象的数学游戏而是一件极其直观的事情就像数雨滴一样数完所有方向的雨就知道这把伞接到了多少水。今天这篇文章我想用最生动的方式带你彻底理解怎么计算一个点从四面八方接收的光被反射出去的总和。这是渲染方程的核心计算也是计算机图形学中最美的求和艺术。二、先想清楚问题我们到底在求什么在开始计算之前先把问题想清楚。我们要求的是一个点从四面八方接收的光被反射出去的总和。这句话拆开来看包含五个关键信息一个点—— 空间中的某个特定位置四面八方—— 所有可能的入射方向接收的光—— 从每个方向射来的光被反射出去—— 这些光按材质的反射规律被弹出去总和—— 把所有方向的贡献加起来所以我们的任务很明确枚举所有方向计算每个方向的贡献加起来。三、最朴素的想法和它的难点假设你站在一个房间里想知道某面墙上某一点接收的总光照。最朴素的想法是把所有可能的方向都试一遍对每个方向看看从这个方向射来多少光看看这部分光按墙的材质能反射出去多少然后把所有方向的贡献加起来——这就是答案。听起来很简单对吧问题是方向有多少个无限多个想象你站在一个点上向四周看你可以看正前方、可以看正前方稍微偏右一点、可以看正前方稍微偏右再偏右一点……每两个方向之间你都能找到一个中间的方向。方向是连续的不是离散的。数学上这种所有可能方向的集合叫做球面光可以从任何方向来或半球面光只能从物体上方来球面上有无穷多个点所以方向也是无穷多个。四、连续怎么加积分登场无穷多个东西怎么加起来数学家给了我们一个强大的工具积分。积分的本质就是对连续的东西求和。如果你只想加 3 个数用加法就行1 2 3 6 1 2 3 61236如果你想加无穷多个连续分布的数就需要积分∫ f ( x ) d x \int f(x) dx∫f(x)dx。积分的符号∫ \int∫是一个拉长的 S来自 Sum求和它的物理意义就是把所有的小贡献加起来。所以对所有方向求和在数学上就写成∫ Ω ( 每个方向的贡献 ) d ω \int_{\Omega} (\text{每个方向的贡献}) \, d\omega∫Ω(每个方向的贡献)dω其中Ω \OmegaΩ是所有方向的集合半球面d ω d\omegadω是一个无穷小的方向单元整个积分的意思就是把每个方向的贡献加起来。不要被这个符号吓到它的本质就是求和只不过是对无穷多个连续的东西求和。五、每个方向的贡献由三部分组成明确了求和的工具再来想每个方向具体贡献了多少。对于某一个方向ω i \omega_iωii 代表 incoming入射它贡献的光照由三部分组成第一部分从这个方向射来多少光这就是入射光强度L i ( ω i ) L_i(\omega_i)Li(ωi)它告诉我们从ω i \omega_iωi这个方向有多少光射到我们关心的点上。第二部分这些光被反射出去多少这就是 BRDF双向反射分布函数f r ( ω i , ω o ) f_r(\omega_i, \omega_o)fr(ωi,ωo)它告诉我们从ω i \omega_iωi方向射来的光有多大比例被反射到ω o \omega_oωo出射方向。第三部分角度衰减。入射角度对接收光的多少有影响——正面来的光全部打在表面上侧面来的光被摊开了效果减弱。这个衰减用cos θ i \cos\theta_icosθi描述其中θ i \theta_iθi是入射方向和表面法线的夹角。把三部分合起来每个方向的贡献就是f r ( ω i , ω o ) ⋅ L i ( ω i ) ⋅ cos θ i f_r(\omega_i, \omega_o) \cdot L_i(\omega_i) \cdot \cos\theta_ifr(ωi,ωo)⋅Li(ωi)⋅cosθi六、完整的求和公式把所有方向的贡献加起来就得到了著名的反射方程L 反射 ( ω o ) ∫ Ω f r ( ω i , ω o ) ⋅ L i ( ω i ) ⋅ cos θ i d ω i L_{反射}(\omega_o) \int_{\Omega} f_r(\omega_i, \omega_o) \cdot L_i(\omega_i) \cdot \cos\theta_i \, d\omega_iL反射(ωo)∫Ωfr(ωi,ωo)⋅Li(ωi)⋅cosθidωi看起来吓人但意思非常清晰——对每个入射方向ω i \omega_iωi算出从这个方向射来的光L i L_iLi乘以这部分光被反射到出射方向的比例f r f_rfr乘以角度衰减cos θ i \cos\theta_icosθi然后全部加起来积分。这就是我们要求的从四面八方接收的光被反射出去的总和。七、一个生动的比喻太阳能板的账本让我用一个生动的比喻帮你彻底理解。想象你是一家太阳能发电厂的会计你需要计算今天这个发电厂收到了多少阳光你怎么算第一步你看看一天中每个时刻太阳的位置每个时刻太阳从不同方向照射。第二步你记录每个方向的阳光强度正午太阳直射时最强早晚斜射时较弱。第三步你还要考虑角度太阳直射时光线垂直打在板上效率最高太阳斜射时光线摊开了效率较低。第四步你还要看你的板的吸收率不同的板对不同角度、不同波长的光有不同的吸收效率。第五步把所有时刻的贡献加起来这就是今天总共收到了多少阳光。这个过程和我们求从四面八方接收的光被反射出去的总和完全一样只不过太阳能板是按时间求和而我们的点是按方向求和。八、计算机怎么实际计算蒙特卡洛积分登场理解了数学上的求和再来看看计算机是怎么实际算的。我们说过方向是连续的、无穷多个但计算机不能真的算无穷次怎么办答案是用采样代替积分。这是一种天才的方法叫蒙特卡洛积分。它的核心思想是不需要算所有方向只需要随机抽样足够多的方向求平均。这个平均值可以近似真实的积分值样本越多估计越准确。具体步骤是随机选 N 个入射方向ω 1 , ω 2 , . . . , ω N \omega_1, \omega_2, ..., \omega_Nω1,ω2,...,ωN对每个方向计算它的贡献f r ⋅ L i ⋅ cos θ i f_r \cdot L_i \cdot \cos\theta_ifr⋅Li⋅cosθi求平均然后乘以方向空间的总大小半球的立体角是2 π 2\pi2π。数学表达为L 反射 ≈ 2 π N ∑ k 1 N f r ( ω k , ω o ) ⋅ L i ( ω k ) ⋅ cos θ k L_{反射} \approx \frac{2\pi}{N} \sum_{k1}^{N} f_r(\omega_k, \omega_o) \cdot L_i(\omega_k) \cdot \cos\theta_kL反射≈N2πk1∑Nfr(ωk,ωo)⋅Li(ωk)⋅cosθk一个生动的比喻民意调查。蒙特卡洛积分就像做民意调查你想知道全国人民对某个问题的态度不可能问每一个人14 亿人但你可以随机抽样 1000 人问1000 人的平均态度可以近似 14 亿人的整体态度样本越多结论越可靠。蒙特卡洛积分也是一样——随机抽样足够多的方向平均贡献可以近似真实的总和。九、一个具体的例子让你感受全过程假设我们要计算一面墙上某一点接收的反射光整个过程是这样的第一步随机抽样。我们随机生成 100 个方向覆盖墙上方的半球。第二步对每个方向追踪光线。对每个方向我们反过来看——如果光从这个方向射到墙上光是从哪里来的我们从墙的这个点沿这个方向发出一条光线看看它击中了什么。可能击中了天花板接收到天花板的反射光、旁边的桌子接收到桌子的反射光、一盏灯接收到灯的直接光、窗外接收到天空的光或者什么都没击中接收到环境光。第三步累加贡献。对每个方向算出f r ⋅ L i ⋅ cos θ i f_r \cdot L_i \cdot \cos\theta_ifr⋅Li⋅cosθi全部加起来。第四步求平均并乘以总立体角得到这个点的反射光估计值。第五步重复多次提高精度。如果觉得 100 个方向不够准图像有噪点就增加到 1000 个、10000 个。十、为什么蒙特卡洛方法这么聪明蒙特卡洛积分有几个让人惊叹的特性。特性一维度独立。传统的数值积分方法比如把空间分成网格在高维空间会爆炸叫维度诅咒但蒙特卡洛积分在高维空间表现得几乎和低维一样好这对图形学至关重要因为渲染方程不只是一次反弹还有多次反弹每次反弹都增加维度。特性二天然并行。每个采样方向都是独立计算的这意味着可以用 GPU 并行处理——一次同时计算几千、几万个采样这就是为什么现代显卡能实时渲染复杂场景。特性三渐进收敛。样本越多结果越准这意味着你可以先快速预览少样本有噪点然后慢慢细化多样本干净这就是为什么电影 CG 渲染一帧可能要花几个小时而游戏渲染要在几毫秒内完成——它们用了不同数量的样本。十一、聪明的优化重要性采样随机抽样虽然有效但有个问题很多方向的贡献可能很小。比如某个房间里只有一盏灯那么指向灯的方向贡献很大其他方向贡献很小如果我们完全随机抽样大部分样本都浪费在贡献小的方向上。怎么办让重要的方向被更多抽到这就是重要性采样Importance Sampling的核心思想。一个生动的比喻钓鱼的智慧。想象你是一个钓鱼人想知道一个湖里有多少鱼你可以随机在湖上撒网蒙特卡洛但更聪明的做法是多在鱼多的地方撒网——你知道哪里水草丰美、哪里水温合适就优先在那些地方采样再根据采样的偏向做数学修正这样能用更少的采样得到更准的结果。在渲染中我们有几种重要性采样策略按 BRDF 采样对镜面反射强烈的材质沿镜面反射方向多采样、按光源采样直接朝光源方向采样确保不会错过光源的贡献、多重重要性采样MIS结合多种采样策略取各自的优点。这些技术让渲染的效率提高了几十倍甚至上百倍。十二、还有一个深刻的问题递归到这里我们已经能算出一个点从四面八方接收的光被反射出去的总和但仔细想想还有一个深刻的问题。我们说对每个方向看看从这个方向射来多少光但从这个方向射来的光本身就是另一个点反射的光。而那个点的反射光又依赖于它接收到的光而它接收到的光又依赖于更多其他点的反射光……这是一个无限递归的问题。实际中我们不能无限递归所以会限制反弹次数游戏中通常只算 1-2 次反弹实时性优先电影 CG 中可能算 8-16 次反弹质量优先。每多一次反弹画面会更真实但计算时间也会增加。一个生动的比喻消息传递。想象一个谣言在朋友圈传播第一手消息直接光照是你直接听到第二手消息一次反弹是你的朋友告诉你第三手消息二次反弹是你朋友的朋友告诉你……理论上消息可以传播无数次但实际中传播 3-5 次后影响就很小了。光也是一样反弹几次后衰减得很厉害再多算也没什么意义。十三、计算这个总和的哲学启示讲到这里我想分享一些更深的感受。求从四面八方接收的光的总和不只是一个数学问题它蕴含着深刻的人生智慧。启示一完整的认识来自全面的观察。要知道一个点接收的总光照必须考虑所有方向人生也是如此要全面理解一件事不能只听一面之词要听来自各方的声音。