LearnOpenGL 投光物 学习笔记

137 阅读1分钟

投光物:将光投射到物体的光源

平行光

假设光源处于无限远处的模型——定向光 我们使用光的方向向量而不是位置向量来计算

struct Light {
    // vec3 position; // 使用定向光就不再需要了
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
...
void main()
{
  vec3 lightDir = normalize(-light.direction);
  ...
}

设定光源的方向

lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);

在片段着色器中,原来计算光线方向向量是

vec3 lightDir = normalize(light.position - FragPos);

现在则是

vec3 lightDir = normalize(-light.direction);  

因为我们需要由片段指向光源的向量,所以需要取反

点光源

位于世界中某个位置的光源,朝所有方向发光,但是光线会随着距离增加逐渐衰减 pointLight 之前的联系中的点光源模拟的是永不衰减的光线

实现衰减(Attenuation)

struct Light {
    vec3 position;  

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

然后需要根据公式计算一下衰减值

Fatt=1.0Kc+Kld+Kqd2F_{\text{att}} = \frac{1.0}{K_c + K_l \cdot d + K_q \cdot d^2}

这里有三个可配置项:常数项KcK_c、一次项KlK_l和二次项KqK_q

  • **常数项(常量衰减)**通常用于表示光源在特定范围内的基本强度衰减。
  • **一次项(线性衰减)**用于表示光源随着距离的增加而线性减弱的速率。
  • **二次项(二次衰减)**则用于表示光源随着距离的增加而二次减弱的速率,通常用于模拟更真实的衰减效果,使得远离光源的物体更暗。

对应的片段着色器代码👇

//attenuation
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;

关于常数项,一次项二次项的取值,大多数时候是由经验设定,然后适量调整。

聚光

OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径。 spotlight

  • LightDir:从片段指向光源的向量。
  • SpotDir:聚光所指向的方向。
  • Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
  • Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小

手电筒(聚光的一种)

根据刚才的描述,聚光的表示需要一个位置,一个方向和一个切光角。根据此修改light结构体

struct Light {
    vec3  position;
    vec3  direction;
    float cutOff;
    ...
};

将合适的值传入着色器

lightingShader.setVec3("light.position",  camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff",   glm::cos(glm::radians(12.5f)));

接下来就是计算θ值,将他和切光角ϕ值比较,看片段是否在聚光内部

float theta = dot(lightDir, normalize(-light.direction));

if(theta > light.cutOff) 
{       
  // 执行光照计算
}
else  // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗
  color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);

平滑/软化边缘

刚才那个效果做出来真的好生硬,然后我就发现下一节就是讲如果平滑/软化边缘的。😀 创建边缘平滑的聚光需要一个内圆锥和一个外圆锥

这里ϵ(Epsilon)是内(ϕ)和外圆锥(γ)之间的余弦值差(ϵ=ϕ−γ)。最终的I值就是在当前片段聚光的强度。

实现

在light结构体中新增的内容

struct Light {
    float cutOff;
    float outerCutOff;
    ......
}

在计算颜色时就不用if else,直接乘以强度值即可

float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff-light.outerCutOff;
float intensity = clamp((theta-light.outerCutOff)/epsilon,0.0,1.0);

......

diffuse  *= intensity;
specular *= intensity;

环境光不乘以intensity是因为要留一点环境光