学习OpenGL——第十一天

38 阅读3分钟

光照模型详解:定向光、点光源与聚光


一、平行光(定向光)

当光源距离极远时,光线近似为平行,这种理想化的光源称为定向光。此时,无论物体或观察者的位置如何,场景中的所有光线都来自同一方向,与光源具体位置无关。

  • 典型例子:太阳。虽然太阳不是无限远,但在光照计算中可视为无限远,因此可将太阳光近似为平行光线。

image.png

定向光的特点:

  • 所有光线方向一致,物体与光源的相对位置不影响光照方向。
  • 着色时可直接定义光的方向向量,无需光源位置。

实现方式:

  • 在着色器中定义光的方向向量(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为位置光源。


二、点光源

点光源位于世界中的某一点,向四周均匀发射光线,光强随距离递减。常见的点光源包括灯泡、火把等。

image.png

点光源特点:

  • 光照随距离衰减,称为Attenuation(衰减)。
  • 实际中,采用二次方程进行光强衰减以更真实地模拟物理现象。

Fatt=1.0Kc+Kid+Kdd2F_{att} = \frac{1.0}{K_c+K_i*d+K_d*d^2}

衰减参数说明:

  • 常数项(Kc):通常为1.0,保证分母不小于1,防止光强异常。
  • 一次项(Kl):与距离成正比,线性递减光强。
  • 二次项(Kq):与距离的平方成正比,远距离时影响更大。

image.png

点光源结构体定义:

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)

聚光是一种只在某一方向、特定角度范围内发射光线的光源,类似于路灯或手电筒。只有位于聚光锥体内的物体会被照亮,其余部分保持黑暗。

image.png

聚光参数说明:

  • 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),在二者之间,光强平滑过渡。

I=θγϵI=\frac{θ-γ}{ϵ}

  • 通过计算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之间,保证光照过渡自然。