1. 光照
1.1. 光源
光源类型 | 特点 | 优点 | 缺点 |
---|---|---|---|
环境光 | 整个场景均匀受光,无方向和位置。 | 模拟全局光照,避免完全黑暗的区域。 | 缺乏方向性和真实感,无法产生阴影。 |
平行光 | 光线方向平行,无位置,仅有方向。 | 计算简单,适合模拟太阳光等远距离光源。 | 无法模拟局部光照,缺乏真实感。 |
点光源 | 光线从一个点向四周发散,具有位置和方向。 | 能模拟局部光照,产生阴影和立体感。 | 计算复杂,光照强度随距离衰减,性能开销较大。 |
聚光灯 | 光线从一个点沿特定方向发散,形成锥形光照区域。 | 能模拟手电筒、舞台灯等聚焦光源,光照区域可控。 | 计算复杂,需要额外判断光照角度和范围。 |
半球光 | 模拟天空光照,顶部光线为一种颜色,底部光线为另一种颜色。 | 能模拟自然光的渐变效果,适合户外场景。 | 计算复杂度较高,通常需要结合其他光源使用。 |
区域光 | 光线从一个平面或区域发散,具有方向和范围。 | 能模拟窗户、屏幕等光源,光照效果更真实。 | 计算复杂度高,通常需要光照贴图或全局光照算法支持。 |
1.1.1. 环境光
环境光 基本原理是,场景中所有方向上都有光,只是强度不同。全局环境光可以定义如下:
float globalAmbient[4] = { 0.6f, 0.6f, 0.6f, 1.0f };
1.1.2. 平行光
平行光的基本原理是,所有的光都从同一个方向照射到物体上,这个方向就是平行光的方向。
平行光无位置,只有方向。它可以用来模拟光源距离非常远,以至于光线接近平行的情况,例如阳光
指向 z 轴负方向的红色定向光可以指定如下:
float dirLightAmbient[4] = { 0.1f, 0.0f, 0.0f, 1.0f };
float dirLightDiffuse[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
float dirLightSpecular[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
float dirLightDirection[3] = { 0.0f, 0.0f, -1.0f };
在已经有全局环境光的情况下,定向光的环境光分量看起来似乎是多余的。然而,当光源“开启”或“关闭”时,全局环境光和定向光的环境光分量的区别就很明显了。当“开启”时,总环境光分量将如预期的那样增加。在上面的例子中,我们只使用了很小的环境光分量。在实际场景中,应当根据场景的需要平衡两个环境光分量
1.1.3. 点光源
点光源的基本原理是,所有的光都从一个点向各个方向照射到物体上,这个点就是点光源的位置。
1.1.3.1. 点光源的特点
点光源是计算机图形学和现实照明中常见的一种光源类型,它模拟了从一个点向各个方向均匀发射光线的光源。以下是点光源的主要特点:
1.1.3.1.1. 物理特性方面
- 位置决定性:点光源有明确的位置坐标,所有光线从该点向四面八方发射。在三维空间中,其位置可以用一个三维向量
(x, y, z)
来精确表示。在场景中移动点光源的位置,会直接改变物体受光的区域和强度分布。 - 光线发散性:光线从点光源出发,呈辐射状向周围空间发散传播。随着传播距离的增加,光线覆盖的面积会逐渐增大,这符合光的传播规律。
- 光照衰减:点光源的光照强度会随着距离的增加而减弱。根据平方反比定律,光照强度与距离的平方成反比,即距离点光源越远,物体接收到的光照越弱。
1.1.3.1.2. 渲染效果方面
- 产生阴影:由于点光源的光线是从一个点发出的,物体被其照射时会在背后产生明显的阴影。阴影的形状和范围取决于物体的形状、位置以及点光源的位置和强度。
- 多角度照明:能从多个角度照亮物体,使物体表面产生丰富的明暗变化,增强物体的立体感和层次感。物体朝向点光源的面会被照亮,而背向的面则处于阴影中。
- 局部照明效果:点光源通常用于突出场景中的特定物体或区域,营造局部照明效果。例如,在游戏场景中,可以用点光源模拟火把、吊灯等,使这些发光物体周围的环境更加生动。
1.1.3.1.3. 计算复杂度方面
- 计算量较大:与平行光等简单光源相比,点光源的光照计算更为复杂。因为需要考虑光源位置、物体与光源的距离、光照衰减等多个因素,所以在实时渲染中,大量使用点光源可能会对性能产生较大影响。
- 可优化性:为了提高渲染效率,有多种针对点光源的优化算法,如光照探针、光照贴图等。这些方法可以在一定程度上减少点光源的计算量,同时保持较好的光照效果。
1.1.3.2. 点光源的实现步骤
点光源的实现步骤如下:
- 计算点光源到物体表面的距离。
- 计算点光源到物体表面的方向向量。
- 计算点光源到物体表面的光照强度。
- 将光照强度应用到物体表面。
点光源具有指定为 RGBA 值的环境光反射、漫反射和镜面反射特性。
位置(5,2,−3)处的红色位置光可以指定如下:
float posLightAmbient[4] = { 0.1f, 0.0f, 0.0f, 1.0f };
float posLightDiffuse[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
float posLightSpecular[4] = { 1.0f,0.0f, 0.0f, 1.0f };
float posLightLocation[3] = { 5.0f, 2.0f, -3.0f };
float radius=1.0f;
常见的点光源衰减因子的公式及其特点:
衰减因子公式 | 名称 | 特点 | 优点 | 缺点 |
---|---|---|---|---|
attenuation = 1.0 | 无衰减 | 光照强度恒定,不随距离变化。 | 计算简单,适合小范围光照。 | 不符合物理规律,无法模拟真实光照效果。 |
attenuation = 1.0 / distance | 线性衰减 | 光照强度与距离成反比。 | 计算简单,能模拟一定的衰减效果。 | 衰减过快,远距离光照效果较弱。 |
attenuation = 1.0 / (distance * distance) | 平方反比衰减 | 光照强度与距离的平方成反比,符合物理规律。 | 模拟真实光照效果,适合大范围光照。 | 距离较近时光照强度过强,可能导致过曝。 |
attenuation = 1.0 / (1.0 + k * distance) | 线性修正衰减 | 在线性衰减的基础上加入常数项,避免距离为零时光照强度无限大。 | 计算简单,适合实时渲染。 | 衰减效果可能不够真实。 |
attenuation = 1.0 / (1.0 + k1 * distance + k2 * distance * distance) | 线性+平方修正衰减 | 综合线性衰减和平方法,加入常数项,常用于实时渲染。 | 平衡了近距离和远距离的光照强度,适合大多数场景。 | 参数调整较复杂,需要根据场景手动调节 k1 和 k2 。 |
attenuation = max(0.0, 1.0 - (distance / radius)) | 截断式衰减 | 光照强度在一定范围内线性衰减,超出范围后强度为零。 | 适合模拟局部光照,计算简单。 | 不符合物理规律,光照范围外会突然变暗。 |
attenuation = exp(-k * distance) | 指数衰减 | 光照强度随距离指数级减弱。 | 模拟柔和的光照衰减效果,适合特定场景。 | 衰减过快,远距离光照效果几乎不可见。 |
attenuation = 1.0 / (1.0 + k1 * distance + k2 * distance * distance + k3 * distance^3) | 高阶多项式衰减 | 在线性+平方修正衰减的基础上加入三次项,模拟更复杂的光照衰减效果。 | 能模拟更真实的光照效果,适合高质量渲染。 | 计算复杂度较高,通常用于离线渲染。 |
线性衰减:
float distance=length(posLightLocation-fragPos);
//float attenuation=1.0/(1.0+radius*distance+radius*radius);
float attenuation=max(0.0,1.0-(distance/radius));
vec3 adjLightColor=lightColor*attenuation;
1.1.4. 聚光灯
聚光灯是一种特殊的光源,它从某个方向照射物体,并在物体表面产生锥形的光照区域。
位于(5,2,−3)向下照射 z 轴负方向的红色聚光灯可以表示为:
float spotLightAmbient[4] = { 0.1f, 0.0f, 0.0f, 1.0f };
float spotLightDiffuse[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
float spotLightSpecular[4] = { 1.0f,0.0f, 0.0f, 1.0f };
float spotLightLocation[3] = { 5.0f, 2.0f, -3.0f };
float spotLightDirection[3] = { 0.0f, 0.0f, -1.0f };
float spotLightCutoff = 20.0f;
float spotLightExponent = 10.0f;
常见的聚光灯衰减因子的公式及其特点: 注意:光离轴角必须小于截光角,超过则聚光灯将不会产生光照效果。
衰减因子公式 | 名称 | 特点 | 优点 | 缺点 |
---|---|---|---|---|
attenuation = 1.0 | 无衰减 | 光照强度恒定,不随距离变化。 | 计算简单,适合小范围光照。 | 不符合物理规律,无法模拟真实光照效果。 |
attenuation = 1.0 / distance | 线性衰减 | 光照强度与距离成反比。 | 计算简单,能模拟一定的衰减效果。 | 衰减过快,远距离光照效果较弱。 |
attenuation = 1.0 / (distance * distance) | 平方反比衰减 | 光照强度与距离的平方成反比,符合物理规律。 | 模拟真实光照效果,适合大范围光照。 | 距离较近时光照强度过强,可能导致过曝。 |
attenuation = 1.0 / (1.0 + k * distance) | 线性修正衰减 | 在线性衰减的基础上加入常数项,避免距离为零时光照强度无限大。 | 计算简单,适合实时渲染。 | 衰减效果可能不够真实。 |
attenuation = 1.0 / (1.0 + k1 * distance + k2 * distance * distance) | 线性+平方修正衰减 | 综合线性衰减和平方法,加入常数项,常用于实时渲染。 | 平衡了近距离和远距离的光照强度,适合大多数场景。 | 参数调整较复杂,需要根据场景手动调节 k1 和 k2 。 |
attenuation = max(0.0, 1.0 - (distance / radius)) | 截断式衰减 | 光照强度在一定范围内线性衰减,超出范围后强度为零。 | 适合模拟局部光照,计算简单。 | 不符合物理规律,光照范围外会突然变暗。 |
attenuation = exp(-k * distance) | 指数衰减 | 光照强度随距离指数级减弱。 | 模拟柔和的光照衰减效果,适合特定场景。 | 衰减过快,远距离光照效果几乎不可见。 |
attenuation = cos(theta) | 角度衰减 | 光照强度与光线方向和片段方向的夹角余弦值成正比。 | 模拟聚光灯的锥形光照区域,适合手电筒、舞台灯等场景。 | 需要额外判断角度范围,计算复杂度较高。 |
attenuation = pow(cos(theta), exponent) | 指数角度衰减 | 在角度衰减的基础上加入指数项,控制光照强度的分布。 | 能模拟更柔和的聚光灯光照效果,适合高质量渲染。 | 参数调整复杂,计算复杂度较高。 |
聚光灯的实现步骤如下:
- 计算聚光灯到物体表面的距离。
- 计算聚光灯到物体表面的方向向量。
- 计算聚光灯到物体表面的光照强度。
- 计算聚光灯到物体表面的方向向量与聚光灯方向的角度,可以转换为余弦值。即:cosθ = dot(lightDir,lightDirToFragment)
- 判断聚光灯是否能照射到物体上,即cosθ是否大于聚光灯的lightCutoff值(角度<聚光灯的角度)。
- 将光照强度应用到物体表面。
以下是运行效果:
从中可以看到,聚光灯的光照区域是一个圆形的区域。
1.2. 光照模型
光照模型 | 特点 | 优点 | 缺点 |
---|---|---|---|
环境光 | 整个场景均匀受光,无方向和位置。 | 模拟全局光照,避免完全黑暗的区域。 | 缺乏方向性和真实感,无法产生阴影。 |
漫反射光 | 光线从光源照射到物体表面,光强与光线与表面法线夹角的余弦值成正比。 | 模拟物体表面的基本光照效果,增强立体感。 | 无法模拟高光效果,缺乏真实感。 |
镜面反射光 | 光线从光源照射到物体表面后,沿特定方向反射,产生高光效果。 | 模拟光滑表面的高光效果,增强真实感。 | 计算复杂度较高,依赖视角位置。 |
Phong 光照模型 | 结合环境光、漫反射光和镜面反射光,模拟物体表面的综合光照效果。 | 光照效果较为真实,适合大多数场景。 | 计算复杂度较高,性能开销较大。 |
Blinn-Phong 光照模型 | Phong 模型的优化版本,使用半程向量计算镜面反射光。 | 性能较高,适合实时渲染。 | 高光效果可能不如 Phong 模型精确。 |
Cook-Torrance 光照模型 | 基于物理的光照模型,考虑微表面结构和 Fresnel 效应。 | 光照效果非常真实,适合高质量渲染。 | 计算复杂度极高,通常用于离线渲染。 |
Toon 光照模型 | 非真实感光照模型,使用离散的光照强度模拟卡通风格。 | 模拟卡通风格,适合特定艺术风格的场景。 | 不适合真实感场景,光照效果较为简单。 |
Oren-Nayar 光照模型 | 模拟粗糙表面的漫反射光,考虑表面微结构的影响。 | 更真实地模拟粗糙表面的光照效果。 | 计算复杂度较高,性能开销较大。 |
Minnaert 光照模型 | 模拟暗表面或背光场景的光照效果,光强与视角和光源方向相关。 | 适合模拟月球表面等特殊场景的光照效果。 | 不适合一般场景,光照效果较为特殊。 |
1.3. 材质
每个物体都有其独特的材质,材质决定了物体对光的各种特性。通党我们用 四个参数:环境光反射、漫反射、镜面反射、光泽来描述材质。要模拟锡铅合金的效果,可以指定如下值:
float pewterMatAmbient[4] = { .11f, .06f, .11f, 1.0f };
float pewterMatDiffuse[4] = { .43f, .47f, .54f, 1.0f };
float pewterMatSpecular[4] = { .33f, .33f, .52f, 1.0f };
float pewterMatShininess = 9.85f;
1.4. Phong光照
这是一个运行效果:
Phong光照模型是一种经典的光照模型,用于模拟真实世界中的光照效果,它将光照分为环境光(Ambient)、漫反射光(Diffuse)和镜面反射光(Specular)三个部分。最终的光照颜色是这三部分光照颜色的总和。
Phong Reflection Model 来自 维基百科上的数据
下面详细介绍各部分的数学公式及整体公式。
1.4.1. 环境光(Ambient)
环境光模拟了场景中全局的、均匀的光照,它不依赖于光源的位置和物体的朝向。环境光的计算公式如下:
I a m b i e n t = k a × I a × M a I_{ambient} = k_a \times I_a \times M_a Iambient=ka×Ia×Ma
其中:
- I a m b i e n t I_{ambient} Iambient 是环境光的颜色强度。
- k a k_a ka 是物体的环境光反射系数,取值范围通常在 [ 0 , 1 ] [0, 1] [0,1] 之间,它表示物体对环境光的反射能力。
- I a I_a Ia 是环境光的颜色强度。
- M a M_a Ma 是材质的环境光颜色强度。
1.4.2. 漫反射光(Diffuse)
漫反射光模拟了光线在物体表面的均匀散射,它取决于光线的方向和物体表面法线的夹角。漫反射光的计算公式基于 Lambert 余弦定律:
I d i f f u s e = k d × I d × M d × max ( 0 , N ⋅ L ) I_{diffuse} = k_d \times I_d \times M_d \times \max(0, \mathbf{N} \cdot \mathbf{L}) Idiffuse=kd×Id×Md×max(0,N⋅L)
其中:
- I d i f f u s e I_{diffuse} Idiffuse 是漫反射光的颜色强度。
- k d k_d kd 是物体的漫反射系数,取值范围通常在 [ 0 , 1 ] [0, 1] [0,1] 之间,它表示物体对漫反射光的反射能力。
- I d I_d Id 是光源的颜色强度。
- M d M_d Md 是材质的漫反射颜色强度。
- N \mathbf{N} N 是物体表面的法线向量,且为单位向量。
- L \mathbf{L} L 是从物体表面指向光源的单位向量。
- N ⋅ L \mathbf{N} \cdot \mathbf{L} N⋅L 是 N \mathbf{N} N 和 L \mathbf{L} L 的点积, max ( 0 , N ⋅ L ) \max(0, \mathbf{N} \cdot \mathbf{L}) max(0,N⋅L) 确保结果不会为负数。
1.4.3. 镜面反射光(Specular)
镜面反射光模拟了光线在物体表面的镜面反射效果,它取决于观察者的位置。镜面反射光的计算公式如下:
I s p e c u l a r = k s × I s × M s × ( max ( 0 , R ⋅ V ) ) n I_{specular} = k_s \times I_s \times M_s \times (\max(0, \mathbf{R} \cdot \mathbf{V}))^n Ispecular=ks×Is×Ms×(max(0,R⋅V))n
其中:
- I s p e c u l a r I_{specular} Ispecular 是镜面反射光的颜色强度。
- k s k_s ks 是物体的镜面反射系数,取值范围通常在 [ 0 , 1 ] [0, 1] [0,1] 之间,它表示物体对镜面反射光的反射能力。
- I s I_s Is 是光源的颜色强度。
- M s M_s Ms 是材质的反射光颜色强度。
- R \mathbf{R} R 是反射光线的单位向量,可以通过公式 R = 2 ( N ⋅ L ) N − L \mathbf{R} = 2(\mathbf{N} \cdot \mathbf{L})\mathbf{N} - \mathbf{L} R=2(N⋅L)N−L 计算得到 。GLSL 中采用函数reflect()来计算。
- V \mathbf{V} V 是从物体表面指向观察者的单位向量。
- n n n 是高光指数(Shininess),它控制了镜面反射的范围,值越大,高光越集中。
以余弦指数建模的反光度
1.4.3.1. Phong光照模型整体公式
最终的光照颜色 I t o t a l I_{total} Itotal 是环境光、漫反射光和镜面反射光的总和:
I t o t a l = I a m b i e n t + I d i f f u s e + I s p e c u l a r I_{total} = I_{ambient} + I_{diffuse} + I_{specular} Itotal=Iambient+Idiffuse+Ispecular
将前面的公式代入可得:
I t o t a l = k a × I a × M a + k d × I d × M d × max ( 0 , N ⋅ L ) + k s × I s × M s × ( max ( 0 , R ⋅ V ) ) n I_{total} = k_a \times I_a \times M_a + k_d \times I_d \times M_d \times \max(0, \mathbf{N} \cdot \mathbf{L}) + k_s \times I_s \times M_s \times (\max(0, \mathbf{R} \cdot \mathbf{V}))^n Itotal=ka×Ia×Ma+kd×Id×Md×max(0,N⋅L)+ks×Is×Ms×(max(0,R⋅V))n
上述是一个非常简单的 Phong 光照模型,它只考虑了环境光、漫反射光和镜面反射光三个部分,且漫反射光和镜面反射光均只有一个光源。
如果是有多个光源,则需要分别计算每个光源对物体的光照贡献,然后将它们相加得到最终的光照颜色。
代码之间的简要逻辑关系如下:
法线矩阵(Normal Matrix)是用于将法线向量从模型空间(Model Space)变换到世界空间(World Space)的矩阵。
由于法线向量表示的是表面的方向,它的变换规则和普通的位置向量有所不同。当模型矩阵包含非均匀缩放时,直接使用模型矩阵变换法线向量会导致法线方向错误,因此需要使用法线矩阵来确保法线向量在变换后仍然垂直于物体表面。
法线矩阵是模型矩阵的逆转置矩阵的左上角 3x3 子矩阵。数学表达式如下:
[ M n o r m a l = ( M m o d e l − 1 ) T ] [ M_{normal} = (M_{model}^{-1})^T ] [Mnormal=(Mmodel−1)T]
可以使用 glm 库中的函数来计算法线矩阵。以下是一个示例代码:
mat3 normalMatrix = transpose(inverse(mat3(model)));
在实际应用中,特别是在使用4x4矩阵处理变换(如模型视图矩阵或模型视图投影矩阵)的情况下,有时会将3x3的法线矩阵扩展为4x4矩阵以适应现有的数学库或者为了与位置变换统一起来处理。这种情况下,通常是将3x3的法线矩阵放在4x4矩阵的左上角,并将第四行和第四列设置为(0, 0, 0, 1)。但请注意,这样做主要是出于方便的考虑,实际上只有左上角的3x3部分对法线向量的变换有效。
从顶点着色器到片段着色器的向量,因为涉及到插值,可以不是单位向量,所以在片段着色器中使用前需要归一化
Blinn-Phong 光照模型
Blinn-Phong 光照模型是 Phong 光照模型的一个优化版本,它使用半程向量(Halfway Vector)来计算镜面反射光,从而减少了计算量。半程向量是光线和视线之间的中点,它与视线方向和光线方向都垂直。
Blinn 发现向量 R 在计算过程中并不是必需的——R 只是用来计算角 φ 的手段。角 φ 的计算
可以不使用向量 R,而通过 L 与 V 的角平分线向量 H 得到。如图 7.13 所示,H 和 N 之间的角 α
刚好等于 1/2(φ)。虽然 α 与 φ 不同,但 Blinn 展示了使用 α 代替 φ 就已经可以获得足够好的结果。
角平分线向量可以简单地使用 L+V 得到(见图 7.14)
1.5. 参考
- OpenGL shader开发实战学习笔记:第九章 第一个光照模型-CSDN博客
- OpenGL shader开发实战学习笔记:第十二章 深入光照_shader 平行光-CSDN博客
- 学习笔记完整代码下载