光照
光照是3D图形中最重要的主题之一,它决定了场景的真实感和视觉效果。本章将介绍OpenGL中的光照模型和实现方法。
光照基础
现实世界中的光照非常复杂,OpenGL使用简化的模型来模拟光照效果。最常用的模型是冯氏光照模型(Phong Lighting Model)。
冯氏光照模型
冯氏光照模型由三个分量组成:
- 环境光(Ambient):模拟间接光照,使物体不会完全黑暗
- 漫反射(Diffuse):模拟光线直接照射到物体表面的效果
- 镜面反射(Specular):模拟光滑表面的高光效果
最终颜色 = 环境光 + 漫反射 + 镜面反射
环境光
环境光是最简单的光照分量,它假设场景中有一些基础光照,使物体不会完全黑暗。
// 片段着色器
void main() {
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
漫反射
漫反射模拟光线照射到粗糙表面的效果。光照强度取决于光线方向与表面法线之间的夹角。
法线向量
法线是垂直于表面的向量,用于计算光照:
// 顶点数据包含法线
float vertices[] = {
// 位置 // 法线
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
// ...
};
漫反射计算
漫反射强度 = max(dot(法线, 光线方向), 0)
// 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
// 片段着色器
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main() {
// 环境光
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// 漫反射
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);
}
法线矩阵
当模型发生非均匀缩放时,法线会变形。需要使用法线矩阵来正确变换法线:
Normal = mat3(transpose(inverse(model))) * aNormal;
镜面反射
镜面反射模拟光滑表面的高光效果,取决于观察者的位置。
// 片段着色器
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main() {
// 环境光
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// 漫反射
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// 镜面反射
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;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}
高光指数
高光指数(shininess)控制高光的大小和锐利程度:
- 较小的值(如2):大而模糊的高光
- 较大的值(如256):小而锐利的高光
光源类型
点光源
点光源从一点向所有方向发射光线,光线强度随距离衰减:
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
// 衰减计算
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * distance * distance);
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
衰减参数参考:
| 距离范围 | constant | linear | quadratic |
|---|---|---|---|
| 7 | 1.0 | 0.7 | 1.8 |
| 13 | 1.0 | 0.35 | 0.44 |
| 20 | 1.0 | 0.22 | 0.20 |
| 50 | 1.0 | 0.09 | 0.032 |
| 100 | 1.0 | 0.045 | 0.0075 |
方向光
方向光模拟远处的光源(如太阳),所有光线平行:
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) {
vec3 lightDir = normalize(-light.direction);
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
// 镜面反射
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 ambient = light.ambient * material.diffuse;
vec3 diffuse = light.diffuse * diff * material.diffuse;
vec3 specular = light.specular * spec * material.specular;
return ambient + diffuse + specular;
}
聚光灯
聚光灯从一点向特定方向发射锥形光线:
struct SpotLight {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
vec3 lightDir = normalize(light.position - fragPos);
// 检查是否在聚光灯范围内
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
// 衰减
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * distance * distance);
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
// 镜面反射
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 ambient = light.ambient * material.diffuse;
vec3 diffuse = light.diffuse * diff * material.diffuse * intensity;
vec3 specular = light.specular * spec * material.specular * intensity;
return (ambient + diffuse + specular) * attenuation;
}
材质
材质定义了物体如何响应光照:
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
使用纹理作为材质
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
// 片段着色器
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
多光源
组合多种光源:
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
uniform DirLight dirLight;
uniform PointLight pointLights[4];
uniform Material material;
uniform vec3 viewPos;
// 函数声明
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
void main() {
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
// 方向光
vec3 result = CalcDirLight(dirLight, norm, viewDir);
// 点光源
for(int i = 0; i < 4; i++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
光照贴图
使用纹理来定义材质属性:
// 加载漫反射贴图
unsigned int diffuseMap = loadTexture("container2.png");
// 加载镜面反射贴图
unsigned int specularMap = loadTexture("container2_specular.png");
// 设置uniform
glUniform1i(glGetUniformLocation(shader.ID, "material.diffuse"), 0);
glUniform1i(glGetUniformLocation(shader.ID, "material.specular"), 1);
// 绑定纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);
完整示例
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "shader.h"
#include "camera.h"
// 顶点数据
float vertices[] = {
// 位置 // 法线 // 纹理坐标
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
// ... 其他面
};
int main() {
// 初始化...
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
Shader lightingShader("lighting.vs", "lighting.fs");
Shader lampShader("lamp.vs", "lamp.fs");
// 创建VAO、VBO...
// 光源位置
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 绘制物体
lightingShader.use();
lightingShader.setVec3("light.position", lightPos);
lightingShader.setVec3("viewPos", camera.Position);
// 光照属性
lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
// 材质属性
lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightingShader.setFloat("material.shininess", 32.0f);
// 变换矩阵
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom),
800.0f / 600.0f, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 model = glm::mat4(1.0f);
lightingShader.setMat4("projection", projection);
lightingShader.setMat4("view", view);
lightingShader.setMat4("model", model);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
// 绘制光源
lampShader.use();
lampShader.setMat4("projection", projection);
lampShader.setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));
lampShader.setMat4("model", model);
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
小结
光照是3D图形的核心:
- 冯氏光照模型包含环境光、漫反射和镜面反射
- 法线用于计算光照方向
- 不同类型的光源有不同的特性
- 材质定义物体如何响应光照
下一章我们将学习 模型加载,加载复杂的3D模型。
光照调试
如果光照效果不正确,检查以下几点:
- 法线是否正确设置和变换
- 光源位置是否正确
- 观察者位置是否正确传递给着色器
- 向量是否归一化