基础光照
现实世界的光照极其复杂,受多种因素影响,难以用有限的计算资源完整模拟。因此,OpenGL 采用简化模型近似真实光照,既易于实现,视觉效果也较为真实。这些光照模型基于对光的物理特性理解,其中最常用的是 Phong 光照模型。该模型包含三个主要分量:
- 环境光(Ambient)
- 漫反射(Diffuse)
- 镜面光(Specular)
下图展示了这些光照分量的视觉效果:
Phong 光照模型三大分量
- 环境光照(Ambient Lighting)即使在黑暗环境中,世界上也总有一些微弱光线,因此物体几乎不会完全黑暗。通过设置环境光照常量,为物体提供基础色彩。
- 漫反射光照(Diffuse Lighting)模拟光源对物体的方向性影响,是视觉上最显著的部分。物体表面越正对光源,亮度越高。
- 镜面光照(Specular Lighting)模拟有光泽物体表面出现的高光点。镜面光的颜色通常更接近光源色,而非物体本身颜色。
环境光
在现实中,光线通常来自四面八方,经由多次反射间接影响物体。这种现象称为全局照明(Global Illumination),但其计算复杂且资源消耗大。
为简化处理,常用环境光照近似全局照明。只需将一个较小的常量光照颜色添加到物体片段的最终颜色中,即使无直接光源,场景也不会全黑。
实现方式:
void main() {
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
漫反射
环境光不足以展现真实感,漫反射光照可以让物体根据光源方向产生明暗变化。与光线方向越接近的片段,亮度越高。
计算方法:
- 法向量(Normal Vector):垂直于片段表面的单位向量。
- 光照方向向量:光源位置与片段位置的差向量。
注意:向量需标准化为单位向量,才能正确反映夹角余弦。
代码示例:
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
- 点乘值越大,漫反射分量越强。
- 使用 max 保证分量不为负数,避免无定义的负光照。
最终颜色合成:
vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);
关于法向量变换
- 法向量仅表示方向,无空间位置,也无齐次坐标(w 分量)。
- 位移不应影响法向量,变换时应仅保留模型矩阵的左上角 3×3 部分(旋转和缩放)。
- 不等比缩放会破坏法向量的垂直性,需使用法线矩阵(Normal Matrix)进行修正:
法线矩阵定义: 模型矩阵左上角 3×3 的逆矩阵的转置。
代码实现:
Normal = mat3(transpose(inverse(model))) * aNormal;
矩阵求逆开销较大,建议在 CPU 端计算后通过 uniform 传递给着色器。
镜面光
镜面光照不仅依赖光照方向与法向量,还与观察方向相关,决定于表面的反射特性。如下图所示:
- 通过法向量翻折入射光方向,得到反射向量。
- 反射向量与观察方向越接近,镜面高光越明显。
计算步骤:
- 定义镜面强度:
float specularStrength = 0.5;
- 计算视线方向和反射方向:
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
- 计算镜面分量:
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
-
- 32 为反光度(Shininess),值越大高光越集中。
早期光照多在顶点着色器实现,效率高但不够真实。
顶点插值光照称为 Gouraud 着色,片段级光照称为 Phong 着色,后者效果更平滑自然。
观察空间 VS 世界空间 —— 计算 Phong 光照
世界空间
- 全局坐标系:所有物体的统一参考系
- 特点:
-
- 原点固定在世界某一点
- 物体通过模型变换(平移、旋转、缩放)放置其中
- 光源位置通常在世界空间定义
观察空间
- 相机坐标系:以摄像机为中心的坐标系
- 特点:
-
- 原点在摄像机位置
- Z 轴通常指向观察方向(OpenGL 为 -Z,DirectX 为 +Z)
- 通过视图矩阵从世界空间转换而来
| 特性 | 观察空间 | 世界空间 |
| 视线计算 | 简单(相机在原点) | 需要相机位置 |
| 光源处理 | 需转换到观察空间 | 直接使用 |
| 调试便利性 | 较差 | 较好 |
| 与投影结合 | 直接 | 需额外步骤 |
| 性能 | 通常更优 | 略差 |
| 现代应用 | 更常见 | 特定场景 |