标准光照模型
本文最后更新于:2025年4月17日 下午
标准光照模型
虽然光照模型有很多种类,但在早期游戏引擎中往往只使用一个光照模型,这个模型被称为标准光照模型。标准光照模型在BRDF理论被提出之前就已经被广泛使用了。标准光照模型只关心直接光照(direct light),也就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线
它的基本方法是,把进入到摄像机内的光线分为4个部分,每个部分使用一种方法来计算它的贡献度。这四个部分是
- **自发光(emissive)**部分,这个部分用于描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。需要注意的是,如果没有使用全局光照(global illumination)技术,这些自发光的表面并不会真的照亮周围的物体,而是它本身看起来更亮了而已。
- **高光反射(specular)**部分,这个部分用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
- **漫反射(diffuse)**部分,这个部分用于描述当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
- **环境光(ambient)**部分,它用于描述其他所有的间接光照。
环境光
虽然标准光照模型的重点在于描述直接光照,但在真实的世界中,物体也可以被**间接光照(indirect light)**所照亮。间接光照指的是,光线通常会在多个物体之间反射,最后进入摄像机,也就是说,在光线进入摄像机前,经过了不止一次的物体反射。
在标准光照模型中,我们使用了一种被称为环境光的部分来近似模拟间接光照。环境光的计算非常简单,它通常是一个全局变量,即场景中的所有物体都使用这个环境光。下面的等式给出了计算环境光的部分:
$$
c_{ambient} = g_{ambient}
$$
自发光
光线也可以直接由光源发射进入摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光来计算这个部分的贡献度。它的计算也很简单,就是直接使用了该材质的自发光颜色:
$$
c_{emissive} = m_{emissive}
$$
通常在实时渲染中,自发光的表面往往并不会照亮周围的表面,也就是说,这个物体并不会被当成一个光源。而使用全局光照系统则可以模拟这类自发光物体对周围物体的影响。
漫反射
漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的,因为反射是完全随机的,因此可以认为在任何反射方向上的分布都是一样的。但是,入射光线的角度很重要。
漫反射光照符合兰伯特定律(Lambert’s law):反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比。因此,漫反射部分的计算如下:
$$
c_{diffuse} = (c_{light} \cdot m_{diffuse})\max(0, n \cdot I)
$$
其中,$n$是表面法线,$I$是指向光源的单位矢量,$m_{diffuse}$是材质的漫反射颜色,$c_{light}$是光源颜色。需要注意的是,我们需要防止法线和光源方向点乘的结果为负值,为此,我们使用取最大值的函数来将其截取到0,这可以防止物体被从后面来的光源照亮。
高光反射
这里的高光反射是一种经验模型,也就是说,它并不完全符合真实世界中的高光反射现象。它可用于计算那些沿着完全镜面反射方向被反射的光线,这可以让物体看起来是有光泽的,例如金属材质。
计算高光反射需要知道的信息比较多,如表面法线、视角方向、光源方向、反射方向等。我们假设这些矢量都是单位矢量。下图给出了这些方向矢量。
在这四个矢量中,我们实际上只需要知道其中3个矢量即可,而第四个矢量——反射方向可以通过其他信息计算得到:
$$
r = 2(n \cdot I)n - I
$$
这样,我们就可以利用Phong模型来计算高光反射的部分:
$$
c_{specular} = (c_{light} \cdot m_{specular})\max(0, v \cdot r)^{m_{gloss}}
$$
其中,$m_{gloss}$ 是材质的光泽度(gloss),也被称为反光度(shininess)。它用于控制高光区域的“亮点”有多宽,$m_{gloss}$ 越大,亮点就越小。$m_{specular}$ 是材质的高光反射颜色,它用于控制该材质对于高光反射的强度和颜色。$c_{light}$ 则是光源的颜色和强度。同样,这里也需要防止$v \cdot r$的结果为负数。
和上述的Phong模型相比,Blinn提出了一个简单的修改方法来得到类似的效果。它的基本思想是,避免计算反射方向$r$。为此,Blinn模型引入了一个新的矢量$h$,它是通过对$v$和$I$的取平均后再归一化得到的。即
$$
h = \cfrac{v + I}{\vert v + I \vert}
$$
然后,使用$n$和$h$之间对夹角进行计算,而非$v$和$r$之间的夹角,如下图所示。
总结一下,Blinn模型的公式如下:
$$
c_{specular} = (c_{light} \cdot m_{specular})\max(0,n \cdot h)^{m_{gloss}}
$$
在硬件实现时,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,这是因为,此时可以认为$v$和$I$都是定值,因此$h$将是一个常量。但是,当$v$或者$I$不是定值时,Phong模型可能反而更快一些。需要注意的是,这两种光照模型都是经验模型,也就是说,我们不应该认为Blinn模型是对“正确的”Phong模型的近似。实际上,在一些情况下,Blinn模型更符合实验结果。
半兰伯特模型
广义的半兰伯特光照模型的公式如下:
$$
c_{diffuse} = (c_{light} \cdot m_{diffuse})(\alpha(n \cdot I) + \beta)
$$
可以看出,与原兰伯特模型相比,半兰伯特光照模型没有使用 $\max$ 操作来防止$n$和$I$的点积为负值,而是对其结果进行了一个$\alpha$倍的缩放再加上一个$\beta$大小的偏移。绝大多数情况下,$\alpha$和$\beta$的值均为0.5,即公式为:
$$
c_{diffuse} = (c_{light} \cdot m_{diffuse})(0.5(n \cdot I) + 0.5)
$$
通过这样的方式,我们可以把 $n \cdot l$ 的结果范围从[-1, 1]映射到[0, 1]范围内。也就是说,对于模型的背光面,在原兰伯特光照模型中点积结果将映射到同一个值,即0值处;而在半兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。
需要注意的是,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术。
逐像素还是逐顶点
上面,我们给出了基本光照模型使用的数学公式,那么我们在哪里计算这些光照模型呢?通常来讲,我们有两种选择:在片元着色器中计算,也被称为逐像素光照(per-pixel lighting);在顶点着色器中计算,也被称为逐顶点光照(per-vertex lighting)。
在逐像素光照中,我们会以每个像素为基础,得到它的法线(可以是对顶点法线插值得到的,也可以是从法线纹理中采样得到的),然后进行光照模型的计算。这种在面片之间对顶点法线进行插值的技术被称为Phong着色(Phong shading),也被称为Phong插值或法线插值着色技术。这不同于我们之前讲到的Phong光照模型。
与之相对的是逐顶点光照,也被称为高洛徳着色(Gouraud shading)。在逐顶点光照中,我们在每个顶点上计算光照,然后会在渲染图元内部进行线性插值,最后输出成像素颜色。由于顶点数目往往小于像素数目,因此,逐顶点光照的计算量往往要小于像素数目。但是,由于逐顶点光照依赖于线性插值来得到像素光照,因此,当光照模型中有非线性的计算(例如计算高光反射时)时,逐顶点光照就会出现问题。而且,由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的棱角现象。
参考
《Unity Shader入门精要》