光照模型详解:定向光、点光源与聚光
一、平行光(定向光)
当光源距离极远时,光线近似为平行,这种理想化的光源称为定向光。此时,无论物体或观察者的位置如何,场景中的所有光线都来自同一方向,与光源具体位置无关。
- 典型例子:太阳。虽然太阳不是无限远,但在光照计算中可视为无限远,因此可将太阳光近似为平行光线。
定向光的特点:
- 所有光线方向一致,物体与光源的相对位置不影响光照方向。
- 着色时可直接定义光的方向向量,无需光源位置。
实现方式:
- 在着色器中定义光的方向向量(direction),而非位置向量。
- 计算光照时,需将方向向量取反,并进行标准化。
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 * vec3(texture(material.diffuseMap, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuseMap, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specularMap, TexCoords));
return (ambient + diffuse + specular);
}
注意事项:
- 光线方向需取反(从片段指向光源)。
- 向量需标准化,避免错误计算。
- 使用vec4表示向量时,方向向量的w分量应为0.0,位置向量为1.0,可用于区分光照类型。
旧OpenGL通过检测w分量判断光源类型:w=0为定向光,w=1为位置光源。
二、点光源
点光源位于世界中的某一点,向四周均匀发射光线,光强随距离递减。常见的点光源包括灯泡、火把等。
点光源特点:
- 光照随距离衰减,称为Attenuation(衰减)。
- 实际中,采用二次方程进行光强衰减以更真实地模拟物理现象。
衰减参数说明:
- 常数项(Kc):通常为1.0,保证分母不小于1,防止光强异常。
- 一次项(Kl):与距离成正比,线性递减光强。
- 二次项(Kq):与距离的平方成正比,远距离时影响更大。
点光源结构体定义:
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
实现方式:
- 计算片段与光源距离,代入衰减公式,分别作用于环境光、漫反射和镜面光分量。
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir){
vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(normal, lightDir), 0.0);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
vec3 ambient = light.ambient * vec3(texture(material.diffuseMap, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuseMap, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specularMap, TexCoords));
return (ambient + diffuse + specular) * attenuation;
}
三、聚光(Spotlight)
聚光是一种只在某一方向、特定角度范围内发射光线的光源,类似于路灯或手电筒。只有位于聚光锥体内的物体会被照亮,其余部分保持黑暗。
聚光参数说明:
- LightDir:从片段指向光源的向量。
- SpotDir:聚光方向向量。
- Cutoff Angle(切光角):决定聚光锥体的半径。
- Theta:LightDir与SpotDir的夹角,判断片段是否在锥体内。
结构体定义:
struct SpotLight {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
边缘平滑处理:
- 定义内圆锥(cutOff)和外圆锥(outerCutOff),在二者之间,光强平滑过渡。
- 通过计算theta与cutOff、outerCutOff的关系,利用clamp函数将强度约束在0到1之间。
聚光实现示例:
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir){
vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(normal, lightDir), 0.0);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
vec3 ambient = light.ambient * vec3(texture(material.diffuseMap, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuseMap, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specularMap, TexCoords));
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
return (ambient + diffuse + specular) * attenuation * intensity;
}
提示:clamp函数确保强度值始终在0~1之间,保证光照过渡自然。