图形学系列文章:全局光照的上层理解

avatar
@海尔优家智能科技(北京)有限公司

系列文章前言

图形学中的图形渲染分支是一个让人又爱又恨的领域,人们恨它因为太多的数学公式让人崩溃,但人们也爱它因为它能让代码用最浪漫的方式让人看到。不可否认的是,图形渲染学习路线陡峭,入门很难,但一个人在经历过崩溃、绝望、自我怀疑之后,得到的会是自信与对技术无比坚定的认可,你会觉得做技术也是可以干一辈子的。

正是因为这样,入门的阵痛是必须经历的,试图通过看几篇文章来入门是不可能的。因此,本系列文章不会包含入门的知识,它应该交给读者自己来完成,而这些文章则会成为入门过程中翻越门槛的 Helper。

推荐以下网站作为入门的开始:

最后,希望你在遇到困难的时候能从本系列文章中获得帮助。

前言

全局光照算法涉及大量前置知识,如果贸然进入这个领域,往往会被各种公式、概念、约定折磨的头昏脑涨、“斑秃”而费。因此,本系列文章希望以一种深入浅出的方式,从上层理解、到中层实现、再到底层理论基础,帮助对渲染感兴趣的小伙伴迈过前期的门槛,让学习渲染技术的热情不再轻易消散。

需要说明的是,作者也是刚刚入门,也不是数学或者物理专业出身,很多东西也都是现学现卖,但我争取做到对知识保持敬畏,确保文中各种论述都在逻辑上自洽。

1. 什么是全局光照(GI, global illumination)

为了使场景渲染效果更接近现实世界,综合考虑场景中“直接光照“、“间接光照“等全局因素对物体的影响,为渲染画面中每个像素点设置更逼真的颜色的过程,我们称之为全局光照(GI)。如图下图所示:

左侧是只考虑直接光照的渲染结果,光从右上角射出,因此物体右上方被点亮,左下方出现阴影,这完全正确,但是我们会感觉不真实,为什么?因为我们会感觉这个物体并没有跟整个空间融为一体,具体来说这个球没有映射出“左侧绿墙“和“下方粉底”的颜色。而右图则做到了,我们很明显可以看到球的左边有一抹绿,右边有大片粉。

之所以会有这种差异,主要是因为右图在对像素着色时,不仅考虑到光源对物体的直接影响,还考虑到了周围环境(绿墙、粉底)所反射的光对物体的间接影响。如上图右侧所示,光源的光线先照射到墙壁,然后光线反射到地面后再反射到人眼。

同时,因为不同材质的物体对光的吸收程度不同,比如绿色的墙对光中“非绿色”的波段吸收的更多,所以反射后余下的光衰减为绿色的波段,最终绿色就贡献到下一个光线接触的表面上,最终让人眼看到绿色和物体本身颜色灰色叠加的一种颜色。

2. 如何实现全局光照

要把光照点亮物体这一物理世界的现象搬到计算机中实现并非易事,首先我们要能用数学表达出“光照点亮物体“这种物理现象,使其严格遵守物理规律、保证能量守恒。其次我们要权衡无上限的渲染效果与有限的硬件设备之间的取舍问题,尽可能的用更少的硬件,在更短的时间里,实现更好的渲染效果。综合这些因素,要在计算机中实现全局光照的基本步骤如下:

  1. 找到描述物体表面光照强度的数学公式: 这样我们才能把复杂的全局光照问题,转化成一个关于若干个物理量(光强、方向、点位)的函数,从而在计算机中求解该函数得到物体表面正确的符合物理规律的颜色。
  2. 找到在 计算机 中求解该公式结果的方法: 函数的求解方式往往纯粹数学层面的,它不会考虑计算机中如何去实现,比如求一个空间所有方向的光照强度的积分这件事情,方向有无限种可能性,计算机无法直接处理这种问题。因此,我们需要找到一种求解函数积分近似值的方法,把无限的计算,收敛成有限的计算。
  3. 设计在 计算机 中应用该公式的 算法 数学公式给出了关于若干个物理量的函数,但是如何在计算机中获取这些物理量是一个工程算法问题。举个例子,从物理角度之所以人眼能看到物体是因为从光源发出的无数的光线,碰撞到物体并最终反射到人眼,这个过程可称之为“正向光线追踪”,光是从光源出发最终抵达人眼。但是在计算机的角度,正向的光线有无数种可能,但是“经过电脑屏幕”到达人眼的光线,只是其中一少部分,因此对于计算机来说,更高效的方式是从屏幕发出光线,然后尝试计算光线是否最终“抵达“光源,这种思路称之为“逆向光线追踪”。因此,设计一个好的算法,才能在计算机中高效的应用数学公式。

接下来我们从上层角度具体理解一下每个步骤要做的事情。

3. 找到描述物体表面光照强度的数学公式

渲染方程(Render Equation)是一个描述光对物体表面光强(辐射度)的影响的方程。总体来说,渲染方程可以帮助我们计算:从半球(Ω)各个 wi 方向射入的光,接触到物体表面一点 x 后反射到人眼 wo 出射方向的光强,如下图所示:

为了能让小伙伴们对渲染方程有一个基本的认识,我们还是把方程列出来。但不要担心,这里不会用到任何高深的数学知识,记住我们目前只是从上层去理解这个方程。

Lo(wo,x)=Ωfr(wi,wo,x)Li(wi,x)cosθdwiL_{o}(wo,x)=\int_{\Omega}f_{r}(wi,wo,x)L_{i}(wi, x) \cos\theta dwi

可以看到,渲染方程包含多个部分,我们先简单介绍一下每一项的含义:

  1. Lo:光强。在 wo 方向观察物体表面一点 x 时,观察者接收到的,来自各个方向所贡献的,反射到眼睛中的,所有方向光强的总和。(这里光强可以先理解成人眼所看到的颜色)
  2. wo:反射到眼睛的出射光的方向
  3. x:物体表面一点
  4. ∫Ω:算物体表面一点接收到的光强,要把半球范围(Ω)的所有方向上接收的光强都纳入考虑,因此这里是一个积分的过程。
  5. fr:也就是我们常说的 BRDF,它用来模拟光接触到物体表面时的反射、折射、散射等光学现象现象,fr 的计算具体体现在不同材质的渲染效果上,如粗糙、金属、透明玻璃、皮毛等。
  6. Li:也是光强。Lo 表示 wo 方向观察到的光强,而 Li 表示 Wi 方向接收到的光强。这里可以看出,渲染方程一个递归的过程,如下图所示:

  1. cosθ:光强会跟光线跟物体表面垂直方向的夹角θ有关,夹角越大光强越会衰弱。
  2. dwi:每个 wi 方向的微分

没看明白没有关系,目前只需要知道渲染方程的基本原理是:递归的计算物体表面一点,在半球范围内,所有方向的入射光,照射到该点后反射到人眼的光强的累计值。

实际上,基于物理的渲染框架,无论是实时的还是离线的,它们都是基于这个渲染方程。只不过会根据渲染效果的诉求——更追求性能还是更追求质量,简化了方程中的一些计算罢了。比如 Three.js 的物理材质,它舍弃了渲染方程的递归形式以及半球面各个方向的光强积分,同时还简化了 BRDF 的计算。

4. 找到在计算机中求解该公式结果的方法

上文说到渲染方程要对半球范围内的所有方向的入射光做计算,但半球范围有无限个方向,计算机无法直接进行计算。因此,我们需要一些概率的方法,具体来说是蒙特卡洛方法,将无限的计算转化成有限的、逐步收敛到真实值的计算。就像我们不断抛硬币,正面为上的概率最终会收敛到 1/2 一样。

在计算半球范围内所有方向的问题上,概率的作用体现在随机选取方向上。我们要想实现在选取出足够多的随机方向后,渲染方程的计算结果 Lo 能够收敛到真实值,就必须遵守 蒙特卡洛方法 对随机值选取的内在要求,即在半球范围Ω内选取的每个方向的概率必须一样。

本文不会过多讲述蒙特卡洛方法的数学定义,我们只需要明白一个道理:测试样本必须足够随机,才能让测试结果更接近真实值。

因此,如何选出概率一样的随机方向解决当前问题的关键,其方法也十分巧妙。要想得到概率均匀分布的随机方向,首先我们要生成一个随机的浮点数,这是一切的起点。而恰好,一个随机事件的概率范围是 0~1,它跟随机浮点数的范围正好一致,这样思路就有了。

那就是,我们要找到这样一个概率函数 F,它有这样的性质:如果你告诉我它的概率结果,我能反推出它的自变量的取值。因为这个自变量是通过 F 反推出来的,那么这个自变量一定符合 F 的概率分布。具体来说,假设这个函数是一个关于方向的函数 F(w),你告诉我一个 0~1 范围的随机数,我就能反推出这个符合 F 概率分布的方向 w。

幸运的是,的确有这样的概率函数符合上属性值,那就是概率分布函数(CDF) ,它结合概率密度函数(PDF) 的定义如下:

F(x)=xf(t)dt1F(x)=\int_{-∞}^{x}f(t)dt 1

意思是,概率分布函数 F 在 x 处的取值,是概率密度函数 f 在-∞到 x 范围内的积分。这个可能不好理解,但从离散的角度来看,其实就是一种概率的前缀和,不断累加 x 之前的所有概率罢了,就如下图中的正态分布的 CDF 和 PDF 的关系为例,应该很好理解:

更通俗来说,CDF 给出了随机变量 X 取值小于 x 时的概率:F(x)=P(Xx) F(x)=P(X\le x)

So,为什么 CDF 会跟随机方向的选取有关系?这里简单的解释一下:我们知道方向可以通过极坐标θ和φ来表示,如果你找到了两个 CDF(真正的θ和φ的 CDF 推导过程比较繁琐,这里用伪函数替代方便理解):

  1. 一个是关于随机变量θ的 CDF:F(θ) = 1 - θ
  2. 另一个是关于随机变量φ的 CDF:F(φ) = 1 + φ

那么你随机生成两个浮点数 r1 和 r2,并把它们当做上述两个 CDF 的结果,然后反推:

  1. r1 = 1 - θ ====⇒ θ = 1 - r1
  2. r2 = 1 +φ ====⇒ φ = -1 + r2

这样你就得到了两个符合概率均匀 分布函数 的自变量,而它们所表示的极坐标正是符合蒙特卡洛内在要求的随机方向。这样,我们就有办法在计算机中通过有限次的计算得到渲染正确的结果了,伪代码如下:

sampleCount = 100 // 在半球范围内采样100次
l = 0
for (i in 0..sampleCount) {
        r1 = rand()
        r2 = rand()
        dir = sampleDirection(r1, r2) // 生成随机方向
        l += LightEquation(dir, uv) // 计算该随机方向光强并积累起来
}
l /= sampleCount              // 取平均值,结果会逼近真实积分结果(蒙特卡洛方法)

5. 设计在计算机中应用该公式的算法

现在我们已经知道如何应用渲染方程到计算中了,但如何设计一个巧妙地算法,高效的计算渲染方程依然是个问题。正如前面所说,正向的光线追踪(下左图)是低效的,因为光源发射出来的光的方向有无限种可能,而最终进入人眼的只有一部分。因此我们需要逆向光线追踪(下右图),即从眼睛发射出射线,穿过屏幕,经过物体的碰撞,最后回到光源处。

我想右图应该很容易想象并理解,但是站在计算机图形渲染管线的角度,这个逆向光线追踪算法该如何在程序中实现呢?其实并不麻烦,主要分为以下几个步骤:

  1. 构造 N 条初始射线(下称 Ray),射线起点在人眼位置,射线方向指向每个像素点。其中 N 等于像素个数。

  1. 然后判断这条 Ray 是否跟场景内的物体相交

这里的难点主要是如何高效的判断 ray 跟场景内的物体相交以及相交在什么位置,实际代码中会使用 bvh(层次包围体) 数据结构分隔物体,从而加速相交的判断。

  1. 如果有相交点,那么代表要在物体表面反射了

在上文中我们说过,计算物体表面一点的光强,要考虑到半球范围内所有可能的方向。同时,因为考虑到计算机的特点,我们要通过蒙特卡洛方法,给出有限个随机的方向,计算这些方向的累加结果,来逼近真正的积分的结果。因此,这里我们随机生成了一个方向(图中半球体Ω中粗体箭头)作为 wi,此时 wo,wi,x 都有了,就可以代入渲染方程计算该点 x 的光强了。

  1. 当计算完当前这个 Ray 碰撞后的光强的结果后,一方面我们累加其光强到像素点的颜色上,另一方面“向前推进(RayMarching) ”这条 Ray,设置新的 ray 的起点,以及新的 ray 的方向。

这样以新的 ray 为起点,重复上述过程,就是渲染方程中的递归的含义了。

  1. 随着当前这一帧中所有像素点的颜色计算完毕,本轮光线追踪的工作就结束了。如果你设置的采样数(sampleCount)大于 1,那么下一帧又会重复这整个过程。

随着物体表面一点的半球内的随机方向的采样次数增多,屏幕像素点的颜色会(有衰减的)不断累加,最终逐步逼近渲染方程积分的真实结果

这也是为什么,在我们观察全局光照渲染器时,它的渲染画面总会是由“混乱”到“清晰”的原因,因为我们需要发射足够多的 ray 到屏幕中(采样多次),才能还原出物体表面真实的渲染效果,如下图所示,注意左下角的 samples 次数:

gkjohnson.github.io/three-gpu-p…

以上就是逆向光线追踪算法的基本原理。

6. 总结

本文从上层理解的角度,把实现全局光照分成了三步,总结来说是:渲染方程、随机采样和光线追踪。渲染方程告诉我们如何用数学的方式计算物体表面每个点的光强和反射折射情况,随机采样帮助我们在计算机中用有限次数的计算得到逼近真实积分的结果,光线追踪帮助我们在计算机设备硬件有限的情况下用更高效的算法完成渲染方程及其递归的计算。

实际上一个成熟的渲染器还有非常多的细节需要关注,比如:

  1. 渲染方程中 BRDF 的计算,要针对每种材质(金属、绝缘体、玻璃、衣物、车漆等)进行算法设计,它对渲染效果起到的决定性作用,一个成熟的渲染器不仅要支持多种材质,还要把算法写好来实现更好的性能和效果。
  2. 蒙特卡洛方法的内在要求——随机的均匀分布,不仅体现在随机采样方向的选取上,还体现在光强的叠加上。
  3. 如何做到 Ray 与场景内物体高效进行相交判断
  4. 现代的 GPU 硬件(RTX)是如何高效实现光追计算的
  5. 等等…

这些问题会在后续文章中陆续为大家揭晓,敬请期待。

最后,在图形学之路是艰难而漫长的,但这正是其作为程序员三大浪漫之一的原因。每一次攻克难关的瞬间,都仿佛揭开了世界运规律的一角。希望这篇文章能对学习图形学或者三维渲染的你有所帮助和启发。

7. 参考

8. 团队介绍

三翼鸟数字化技术平台-筑巢自研平台」依托实体建模技术与人工智能技术打造面向家电的智能设计平台,为海尔特色的成套家电和智慧场景提供可视可触的虚拟现实体验。智慧设计团队提供全链路设计,涵盖概念化设计、深化设计、智能仿真、快速报价、模拟施工、快速出图、交易交付、设备检修等关键环节,为全屋家电设计提供一站式解决方案。