投光物:将光投射到物体的光源
平行光
假设光源处于无限远处的模型——定向光 我们使用光的方向向量而不是位置向量来计算
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);
因为我们需要由片段指向光源的向量,所以需要取反
点光源
位于世界中某个位置的光源,朝所有方向发光,但是光线会随着距离增加逐渐衰减
之前的联系中的点光源模拟的是永不衰减的光线
实现衰减(Attenuation)
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
然后需要根据公式计算一下衰减值
这里有三个可配置项:常数项、一次项和二次项。
- **常数项(常量衰减)**通常用于表示光源在特定范围内的基本强度衰减。
- **一次项(线性衰减)**用于表示光源随着距离的增加而线性减弱的速率。
- **二次项(二次衰减)**则用于表示光源随着距离的增加而二次减弱的速率,通常用于模拟更真实的衰减效果,使得远离光源的物体更暗。
对应的片段着色器代码👇
//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)来表示的,切光角指定了聚光的半径。
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是因为要留一点环境光