6.1 我们是如何看到这个世界的
要模拟真实的光照环境来生成一张图像,需要考虑 3 种物理现象:
光线
从光源
中被发射出来光线
和场景中的一些物体相交:一些光线被吸收
了,另外的被散射
到其他方向- 最后,
摄像机
吸收了一些光,产生了一张图像
6.1.1 光源
-
通常把光源当成一个没有体积的点,用 <math xmlns="http://www.w3.org/1998/Math/MathML"> l l </math>l 来表示它的方向
-
使用
辐照度(irradiance)
来量化光------发射出了多少光- 对于平行光,通过计算在垂直于 <math xmlns="http://www.w3.org/1998/Math/MathML"> l l </math>l 的单位面积上单位时间内穿过的能量来得到
- 若物体表面和 <math xmlns="http://www.w3.org/1998/Math/MathML"> l l </math>l 不垂直,可以使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> l l </math>l 和表面法线 n 之间的余弦值来得到
-
<math xmlns="http://www.w3.org/1998/Math/MathML"> c o s θ cos\theta </math>cosθ 可用
光源方向 l
和表面法线 n
的点积
来得到,所以点积
可用来计算辐照度
6.1.2 吸收和散射
光线与物体相交的结果:散射
和吸收
散射
只改变光线的方向
,但不改变光线的密度
与颜色
吸收
只改变光线的密度
和颜色
,不改变方向
- 散射后有两种方向:
- 散射到物体内部,被称为
折射(refraction)
和透射(transmission)
- 散射到物体外部,被称为
反射(reflection)
- 散射到物体内部,被称为
- 对于不透明物体 ,
折射
进物体内部的光线会继续和内部的颗粒相交,其中一些光线最后会重新发射出物体表面,而另一些则被物体吸收
- 那些从物体表面重新发射出的光线将具有和入射光线不同的方向分布和颜色

- 为了区分这两种不同的散射方向,光照模型中使用不同的部分来计算它们
高光反射(specular)
:物体表面如何反射光线漫反射(diffuse)
:有多少光线会被折射、吸收和散射出表面
- 根据入射光线的数量和方向,可以计算出射光线的数量和方向,通常使用
出射度(exitance)
来描述它
6.1.3 着色
根据材质属性(如漫反射等)、光源信息,使用一个等式 去计算沿某个观察方向的出射度的过程
- 这个等式被称为
光照模型(Lighting Model)
- 不同的光照模型有不同的目的,有的用于描述粗糙物体表面,有的用于描述金属表面
6.1.4 BRDF 光照模型
BRDF(Bidirectional Reflectance Distribution Function)包含了给定模型表面上的一个点 的外观的完整描述,用于计算出射方向上的光照能量分布
- 本章涉及的 BRDF 都是对真实场景进行理想化和简化后的模型,不能真实反映现实的交互效果
6.2 标准光照模型
- 在 BRDF 理论被提出之前,标准光照模型已被广泛使用
- 基本理念:只关心直接光照------那些直接从光源发射出来照射到物体表面后,经一次反射直接进入摄像机的光线
- 基本方法,将进入摄像机的光线分为 4 部分,每部分使用一种方法计算其
贡献度
自发光(emissive)
:描述一个表面会向指定方向发射多少辐射量高光反射(specular)
:描述当光线照到表面时,该表面会在完全镜面反射方向散射多少辐射量漫反射(diffuse)
:描述当光线照到表面时,该表面会向每个方向散射多少辐射量环境光(ambient)
:描述其他所有间接光照
6.2.1 环境光
物体会被
间接光照
所照亮,光线通常会在多个物体之间反射,最后进入摄像机
- 在标准光照模型中,使用一种被称为
环境光
的部分来近似模拟间接光照 - 它通常是一个全局变量,即场景中所有物体都使用这个。计算环境光公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> c a m b i e n t = g a m b i e n t c_{ambient}=g_{ambient} </math>cambient=gambient
6.2.2 自发光
光线不经任何物体反射,直接由光源发射入摄像机
它的计算很简单:
<math xmlns="http://www.w3.org/1998/Math/MathML"> c e m i s s i v e = m e m i s s i v e c_{emissive}=m_{emissive} </math>cemissive=memissive
6.2.3 漫反射
漫反射是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的
- 视角位置不重要,可以认为在任何反射方向上的颁都是一样随机的
- 入射光线的角度很重要
- 漫反射光照符合兰伯特定律 :反射光线的强度与
表面法线
和光源方向
之间夹角的余弦成正比:
<math xmlns="http://www.w3.org/1998/Math/MathML"> c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ) m a x ( 0 , n ⋅ I ) c_{diffuse}=(c_{light}\cdot m_{diffuse})max(0,n\cdot I) </math>cdiffuse=(clight⋅mdiffuse)max(0,n⋅I)
- <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是
表面法线
, <math xmlns="http://www.w3.org/1998/Math/MathML"> I I </math>I 是指向光源的单位矢量
, <math xmlns="http://www.w3.org/1998/Math/MathML"> m d i f f u s e m_{diffuse} </math>mdiffuse 是材质的漫反射颜色
, <math xmlns="http://www.w3.org/1998/Math/MathML"> c l i g h t c_{light} </math>clight 是光源颜色
6.2.4 高光反射
用于计算那些沿着完全镜面反射方向被反射的光线,以让物体看起来是有光泽的
- 这里的高光反射是一种经验模型,并不完全符合真实世界中的高光反射现象
利用 Phong 模型来计算
<math xmlns="http://www.w3.org/1998/Math/MathML"> c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , v ^ ⋅ r ^ ) m g l o s s c_{specular}=(c_{light}\cdot m_{specular})max(0,\hat{v}\cdot \hat{r})^{m_{gloss}} </math>cspecular=(clight⋅mspecular)max(0,v^⋅r^)mgloss

- <math xmlns="http://www.w3.org/1998/Math/MathML"> m g l o s s m_{gloss} </math>mgloss 是
材质的光泽度
,也称为反光度
,用于控制高光区域的"亮点"有多宽,值越大,亮点越小 - <math xmlns="http://www.w3.org/1998/Math/MathML"> m s p e c u l a r m_{specular} </math>mspecular 是
材质的高光反射颜色
,用于控制材质对于高光反射的强度和颜色 - <math xmlns="http://www.w3.org/1998/Math/MathML"> c l i g h t c_{light} </math>clight 是
光源的颜色和强度
使用 Blinn 模型来计算
Blinn 提出一个简单的修改方法来得到类似效果。基本思想是,避免计算反射方向 <math xmlns="http://www.w3.org/1998/Math/MathML"> r ^ \hat{r} </math>r^
引入新矢量 <math xmlns="http://www.w3.org/1998/Math/MathML"> h ^ \hat{h} </math>h^,通过对 <math xmlns="http://www.w3.org/1998/Math/MathML"> v ^ \hat{v} </math>v^ 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> I ^ \hat{I} </math>I^ 平均后再归一化得到。即:
<math xmlns="http://www.w3.org/1998/Math/MathML"> h ^ = v ^ + I ∣ v ^ + I ∣ \hat{h}=\frac{\hat{v}+I}{|\hat{v}+I|} </math>h^=∣v^+I∣v^+I
然后再使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ^ \hat{n} </math>n^ 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> h ^ \hat{h} </math>h^ 之间夹角进行计算
<math xmlns="http://www.w3.org/1998/Math/MathML"> c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , n ^ ⋅ h ^ ) m g l o s s c_{specular}=(c_{light}\cdot m_{specular})max(0,\hat{n}\cdot \hat{h})^{m_{gloss}} </math>cspecular=(clight⋅mspecular)max(0,n^⋅h^)mgloss

- 若摄像机和光源距离足够远的话,Blinn 模型会快于 Phong 模型
- 实际上,在一些情况下,Blinn 模型更符合实验结果
6.2.5 逐像素还是逐顶点
我们在哪计算这些光照模型呢?
在片元着色器中,也被称为逐像素光照
以每个像素为基础,得到它的法线(可对顶点法线插值得到,也可从法线纹理中采样得到),然后进行光照模型的计算
- 这种在面片之间对顶点法线进行插值的技术称为
Phone 着色
在顶点着色器中,也被称为逐顶点光照
在每个顶点上计算光照,然后在渲染图元内部进行线性插值,最后输出像素颜色
- 被称为
高洛德着色
- 顶点数往往小于像素数,因此
逐顶点光照
的计算量往往要小于逐像素光照
- 会出现的问题
- 由于光照结果依赖于线性插值来得到,当光照模型中有非线性计算(如高光)时,逐顶点光照会出问题
- 由于会在渲染图元内部对顶点颜色进行插值,会导致渲染图元内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的棱角现象
6.2.6 总结
- 标准光照模型由于它的易用性、计算速度和得到的效果都比较好,因此一直被广泛使用,尽管它并不完全符合真实世界中的光照现象
- 但这种模型有很多局限性,很多重要的物理现象无法用这个模型表现出来,如
菲涅耳反射
,和有些表面是具有各向异性
反射性质的物体旋转时的光照效果
6.3 Unity 中的环境光和自发光
Window - Lighting - Ambient Source/Ambient Color/Ambient Intensity
6.4 在 Unity Shader 中实现漫反射光照模型
6.4.1 实践:逐顶点光照
注意:若物体显示不正常,尝试将 PASS 的标签改为
js
Tags { "LightMode"="UniversalForward" }
6.4.2 实践:逐像素光照
注意事项同上
问题:光照无法到达的区域,模型的外观通常是全黑的,使该区域看起来像一个平面。半兰伯特光照模
型可以改善这个问题
6.4.3 半兰伯特模型
《半条命》游戏开发公司 Valve 提出的一种技术,在原兰伯特光照模型基础上进行了一个简单的修改,因此得名
广义的半兰伯特光照模型公式:
<math xmlns="http://www.w3.org/1998/Math/MathML"> c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ( α ( n ^ ⋅ I ) + β ) ) c_{diffuse}=(c_{light}\cdot m_{diffuse}(\alpha (\hat{n}\cdot I)+\beta)) </math>cdiffuse=(clight⋅mdiffuse(α(n^⋅I)+β))
- 与原模型相比,半兰伯特模型没使用 max 操作来防止 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ^ \hat{n} </math>n^ 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> I I </math>I 的点积为负,而是对结果进行了一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> α \alpha </math>α 倍的缩放再加上一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> β \beta </math>β 大小的偏移
- 绝大多数情况下, <math xmlns="http://www.w3.org/1998/Math/MathML"> a l p h a alpha </math>alpha 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> β \beta </math>β 的值均为 0.5
<math xmlns="http://www.w3.org/1998/Math/MathML"> c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ( 0.5 ( n ^ ⋅ I ) + 0.5 ) ) c_{diffuse}=(c_{light}\cdot m_{diffuse}(0.5 (\hat{n}\cdot I)+0.5)) </math>cdiffuse=(clight⋅mdiffuse(0.5(n^⋅I)+0.5))
修改一下 6.4.2 小节的代码就可实现:

效果图(逐顶点、逐像素、半兰伯特)

6.5 在 Unity Shader 中实现高光反射光照模型
6.5.1 实践:逐顶点光照
效果:

问题:高光部分不平滑。原因是高光反射部分的计算是非线性 的,而在顶点着色器中计算光照再进行插值的过程是线性 的,破坏了原计算的非线性关系,就会出现较大的视觉问题 (详解)
6.5.2 实践:逐像素光照
效果(右边的是逐像素):

6.5.3 Blinn-Phong 光照模型
另一种高光反射模型------Blinn,它没有使用反射方向,而是引入一个新的矢量 <math xmlns="http://www.w3.org/1998/Math/MathML"> h ^ \hat{h} </math>h^,它是通过下面公式计算得到:
<math xmlns="http://www.w3.org/1998/Math/MathML"> h ^ = v ^ + I ∣ v ^ + I ∣ \hat{h}=\frac{\hat{v}+I}{|\hat{v}+I|} </math>h^=∣v^+I∣v^+I
Blinn 模型计算高光反射的公式是:
<math xmlns="http://www.w3.org/1998/Math/MathML"> c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , n ^ ⋅ h ^ ) m g l o s s c_{specular}=(c_{light}\cdot m_{specular})max(0,\hat{n}\cdot \hat{h})^{m_{gloss}} </math>cspecular=(clight⋅mspecular)max(0,n^⋅h^)mgloss
效果(最右的):

可以看出 Blinn-Phong 光照模型的高光反射部分更大、更亮一些。在实际渲染中,绝大多数情况都会选择这个模型
6.6 召唤神龙:使用 Unity 内置函数

效果跟原来的一致
- 注意:内置函数得到的方向是没有归一化的,需要我们自己补充