环境光
撰写时间:2026-07-03
修订时间:2026-07-11
环境光的概念及作用
环境光是场景中整个环境的统一的光照。不管场景中有多少个网格物体,也不管各个网格物体具有什么样的颜色,环境光都会影响到所有网格物体的光照。
在一个封闭的房间里,如果没有任意光源,则环境光为全黑,此时场景中的各个网格物体将不可见。若在房间中打开一盏灯泡,则灯泡将为整个场景提供一个统一的环境光。并且,灯泡光源若有特定的颜色,则场景所有网格物体都会受此环境光颜色的影响。
一般来讲,环境光应是较暗的一种光源,以允许场景中另行添加其它的诸如点光、平行光、聚光等光照效果。如果环境光太强,则场景中所有网格物体将不可避免地趋向于统一的光照颜色。
环境光没有位置、没有方向,能均量散布在场景的任何位置。但不同的材质,有着不同的反射率,对同一环境光,应反射出不同强度的光照。
环境光公式
遵循经典Phong反射模型的符号体系,任何材质的表面对环境光的反射颜色值等于材质对环境光的反射系数乘以表面自身颜色乘以环境光的颜色值。公式如下:
\[ I_{ambient} = K_a \cdot C_{obj} ⊗ I_a \]
其中:
- 结果\(I_{ambient}\):物体表面最终反射的环境光颜色,类型为代表RGB通道值的三维向量(r, g, b)。
-
因子\(K_a\):材质对环境光的反射系数,描述表面对环境光的反射能力,取值范围通常在[0, 1]。例如:
- 黑色橡胶:\(K_a = 0.1\) (几乎不反射环境光)
- 白色石膏:\(K_a = 0.8\) (大量反射环境光)
- 因子\(C_{obj}\):材质表面自身颜色。
- 因子\(I_a\):场景中的全局环境光颜色,描述整个场景中恒定存在的背景光。类型为代表RGB通道值的三维向量(r, g, b)。
- 运算符号\(⊗\):逐分量乘法 (Hadamard product,中文译为哈达玛积),即与每个颜色通道值独立相乘。
环境光不需要物体表面的法线参与运算。
下面是该公式的图示。
在最简化的版本中,\(I_{ambient}\)的值可以只取自\(I_a\)的值。此时,场景中所有网格物体所反射的环境光均完全一样。
顶点着色器
之前,我们总是向顶点着色器传送投影矩阵、视图矩阵及模型矩阵,然后在顶点着色器中让它们与顶点相乘:
程序逻辑没问题。但这种方案,将导致在渲染每个顶点时都要重复进行变换矩阵的相乘运算,较为严重地影响了整个应用程序的性能。设想一下,如果一个网格物体有10000个顶点,这种多余的重复运算多耗资源!
因此,上面我们改为在客户端(或称,CPU端)进行变换矩阵的相乘运算,再将结果矩阵传进顶点着色器中的uMVPMatrix,在渲染每个顶点时,让其与aPosition相乘即可。
在每个光照应用,每个顶点都需要法线矢量。它是用于计算光照效果的重要依据。
当我们在客户端为网格对象的各个顶点静态地指定法线后,如果网格对象发生平移、旋转等变换,则要求它们的法线也随之而改变。最简单的方法就是向顶点着色器的uNormalMatrix传入一个法线矩阵,让其乘以aNormal后自动变换。并且,需进行归一化。
尽管如此,需指出的是,在仅需要渲染环境光的应用程序中,并不需要传入法线数据。但很少有应用程序仅渲染环境光而已,因此这里留出其位置,为以后的扩展做好准备。
片断着色器
因为环境光是一种影响全局的光源,因此将代表其的变量uAmbient设置为uniform的数据类型。
与之前代码相比,使用baseColor来存储各网格物体的基本颜色。
然后,变量scatteredLight代表漫反射的光源。在本例中,漫反射光源只有环境光一种光源。
其次,将baseColor的rgb成分与环境光scatteredLight相乘。只让baseColor的rgb成分参与运算的原因是,如果baseColor有alpha值,则该值不受运算结果的影响。
接着,将其结果钳制在[0.0, 1.0]的范围之内并赋值于变量rgb。如果有多层步骤的运算,则这种钳制行为可置于最后的环节进行。在中间运算过程中,rgb的值可允许超过[0.0, 1.0]的范围。
最后,重新加入baseColor的alpha值后,向fragColor赋值,以送至帧缓冲区渲染。
客户端主要代码
ambient的rgb成分值均取值0.2,以营造一种较暗的环境光效果。
为支持光照,应用框架的各个类均做了幅度不小的修改,例如这里的SolidMesh就已经改版为在构造方法可直接传入法线数据。
运行
运行应用。
地面上平躺着一个正方形网格物体,周围有着较暗的环境光照射。
