目录
一、前言
二、平行光
2.1 片段着色器
2.2 app渲染
三、点光源
3.1 距离衰减
3.2 衰减片段着色器
四、聚光
4.1 片段着色器
4.2 光照入射方向
4.3 平滑边缘
一、前言
Light Caster :光投射(Cast)到物体的光源。现实世界中通常多种不同的光源类型共同作用在物体表面,包括平行光(定向光)、点光源、聚光。
二、平行光
当光源处于一个很远地方,在物体上的光线可以看作是近似相互平行的。这种光线被称为定向光或者平行光,由于光线是相同的方向,所有它与光源位置是无关的。因此直接使用光线向量来替代位置向量来计算light Direction。
2.1 片段着色器
将光结构体的位置向量去掉,使用direction变量,然后重新计算lightDir即可
#version 330 core
struct Material{
sampler2D diffuse;//漫反射纹理贴图
sampler2D specular;
float shininess;
};
struct Light{
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
out vec4 FragColor;
in vec3 Normal; //法向量:垂直于顶点表面的向量
in vec3 FragPos; //片段位置向量:世界坐标系下
in vec2 TexCoords;
uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量
uniform vec3 lightColor;
uniform Material material;
uniform Light light;
void main()
{
// 环境光照
vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
// 漫反射
vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
// 镜面反射 + 反光强度Shininess(镜面高光散射半径)
vec3 viewDir = normalize(viewPos - FragPos);//观察者位置到物体位置,视线向量
vec3 reflectDir = reflect(-lightDir, norm); //计算沿着法线轴的反射向量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//反射光强度:视线方向与反射方向的点乘
vec3 specular = light.specular*(spec *texture( material.specular,TexCoords).rgb);
FragColor = vec4(ambient + diffuse + specular, 1.0);
}
2.2 app渲染
为了体现光照对不同姿态物体的影响,设置物体的全局位置和旋转角度,计算模型矩阵(从局部到世界空间)
//箱子全局位置
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
//设置光源向量
ObjShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
//模型矩阵, 世界坐标不同位置的立方体
glBindVertexArray(ObjVAO);
for (unsigned int i = 0; i < 10; i++)
{
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[i]);
float angle = 20 * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
ObjShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
三、点光源
3.1 距离衰减
点光源是世界坐标中定义某一个位置的光源,它朝所有方向发光,但是光线会随着距离逐渐衰减。
使用一个方程来表示随距离增长而线性减少光强度:
d表示片段光源的距离,k是可配置的衰减常数项系数。Kc通常为1;一次项保证以线性方式减少强度,二次项在距离近时影响比一次项小很多,当距离远时影响比较大。下表是在指定3项的情况下能覆盖的距离:
3.2 衰减片段着色器
- 在光源结构体中添加衰减项系数
- 计算光源位置到片段的距离
- 计算衰减值
- 计算环境光照、漫反射、镜面反射强度
- 使用衰减值和强度值计算最终颜色
#version 330 core
struct Material{
sampler2D diffuse;//漫反射纹理贴图
sampler2D specular;
float shininess;
};
struct Light{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
out vec4 FragColor;
in vec3 Normal; //法向量:垂直于顶点表面的向量
in vec3 FragPos; //片段位置向量:世界坐标系下
in vec2 TexCoords;
uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量
uniform vec3 lightColor;
uniform Material material;
uniform Light light;
void main()
{
// 环境光照
vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
// 漫反射
vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
//vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
vec3 lightDir = normalize(FragPos - light.position);//从片段到光源的光线方向
float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
// 镜面反射 + 反光强度Shininess(镜面高光散射半径)
vec3 viewDir = normalize(viewPos - FragPos);//观察者位置到物体位置,视线向量
vec3 reflectDir = reflect(-lightDir, norm); //计算沿着法线轴的反射向量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//反射光强度:视线方向与反射方向的点乘
vec3 specular = light.specular*(spec *texture( material.specular,TexCoords).rgb);
//衰减
float distance = length(light.position - FragPos );
float attenuate = 1/(light.constant + light.linear*distance + light.quadratic*distance*distance);
FragColor = vec4((ambient + diffuse + specular)*attenuate, 1.0);
}
在app中设置衰减参数
//ObjShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
ObjShader.setVec3("lightPos", lightPos);
//ObjShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
ObjShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
ObjShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); // 将光照调暗了一些以搭配场景
ObjShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
//衰减
ObjShader.setFloat("light.constant", 1.0f);
ObjShader.setFloat("light.linear", 0.09f);
ObjShader.setFloat("light.quadratic", 0.032f);
四、聚光
聚光是环境中某个位置的光源,它只朝某一个特定方向而不是所有方向照射光线。效果是只有在聚光方向的特定半径内的物体才会被照亮,其它物体都会保持黑暗,类似路灯或者手电筒。
- LightDir是片段到光源的向量
- SpotDir是聚光所指向的方向
- Φ是聚光半径的切光角,落在这个角度之外的物体都不会被这个聚光所照亮
- θ是LightDir和SpotDir夹角
计算LightDir和SpotDir向量之间的点击,得到的结果与切光角对比。LightDir与direction向量之间的点积得到夹角余弦值。
越接近90度表示的是0的余弦值
4.1 片段着色器
在光照中定义聚光信息,然后对比lightDir和SpotDir夹角是否在切光角范围内执行不同光照算法:
#version 330 core
struct Material{
sampler2D diffuse;//漫反射纹理贴图
sampler2D specular;
float shininess;
};
struct Light{
vec3 position;
vec3 direction;
float cutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
out vec4 FragColor;
in vec3 Normal; //法向量:垂直于顶点表面的向量
in vec3 FragPos; //片段位置向量:世界坐标系下
in vec2 TexCoords;
uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量
//uniform vec3 lightColor;
uniform Material material;
uniform Light light;
void main()
{
vec3 lightDir = normalize(light.position - FragPos);//从片段到光源的光线方向
// 检查光照是否在切光角内
float theta = dot(lightDir, normalize(-light.direction));
if(theta > light.cutOff)
{
// 环境光照
vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
// 漫反射
vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
//vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
// 镜面反射 + 反光强度Shininess(镜面高光散射半径)
vec3 viewDir = normalize(viewPos - FragPos);//观察者位置到物体位置,视线向量
vec3 reflectDir = reflect(-lightDir, norm); //计算沿着法线轴的反射向量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//反射光强度:视线方向与反射方向的点乘
vec3 specular = light.specular*(spec *texture( material.specular,TexCoords).rgb);
//衰减
float distance = length(light.position - FragPos );
float attenuate = 1/(light.constant + light.linear*distance + light.quadratic*distance*distance);
//ambient *= attenuate;//导致较远的距离,在切光角内更黑
diffuse *= attenuate;
specular *= attenuate;
FragColor = vec4(ambient + diffuse + specular, 1.0);
}
else
{
//使用环境光,让场景在聚光之外时不至于完全黑暗
FragColor = vec4(light.ambient * texture(material.diffuse, TexCoords).rgb, 1.0);
}
}
在app中对聚光信息设值:
ObjShader.setVec3("light.position", lightPos);
ObjShader.setVec3("light.direction", camera.Front);
ObjShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
4.2 光照入射方向
关于光源LightDir的疑惑,到底是light.position - FragPos还是 FragPos -light.position,取决于场景,如漫反射是从物体到光源方向的反射所以是light.position - FragPos,
模拟平行光是从光源出发的向量lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f); 光的方向是朝下的,要参考右手定则,设为vec3 lightDir = normalize(-light.direction);对向量取反,得到一个从光源出发的全局方向。
4.3 平滑边缘
为了创建一个边缘平滑的聚光,模拟聚光有一个内圆锥、外圆锥。将内圆锥设置为上一部分中的那个圆锥,外圆锥来让光从内圆锥逐渐减暗,直到外圆锥的边界。
定义一个余弦值来代表聚光方向向量 和 外圆锥向量(等于它的半径)夹角。
如果一个片段处于内外圆锥之间,将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内,它的强度就是1.0,如果在外圆锥之外强度值就是0.0。
ϵ(Epsilon)是内(ϕ)和外圆锥(γ)之间的余弦值差(ϵ=ϕ−γ,I就是在当前片段聚光的强度
片段着色器:
#version 330 core
struct Material{
sampler2D diffuse;//漫反射纹理贴图
sampler2D specular;
float shininess;
};
struct Light{
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
out vec4 FragColor;
in vec3 Normal; //法向量:垂直于顶点表面的向量
in vec3 FragPos; //片段位置向量:世界坐标系下
in vec2 TexCoords;
uniform vec3 lightPos; //光源位置向量
uniform vec3 viewPos; //观察者位置向量
//uniform vec3 lightColor;
uniform Material material;
uniform Light light;
void main()
{
// 环境光照
vec3 ambient =light.ambient*texture(material.diffuse,TexCoords).rgb;
// 漫反射
vec3 norm = normalize(Normal);//世界坐标系法向量归一化,单位向量
//vec3 lightDir = normalize(-light.direction);//计算光源向量,取反表示从光源出发的全局方向
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0); //计算光源对当前片段影响
vec3 diffuse = light.diffuse*diff * texture(material.diffuse,TexCoords).rgb ;
// 镜面反射 + 反光强度Shininess(镜面高光散射半径)
vec3 viewDir = normalize(viewPos - FragPos);//观察者位置到物体位置,视线向量
vec3 reflectDir = reflect(-lightDir, norm); //计算沿着法线轴的反射向量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//反射光强度:视线方向与反射方向的点乘
vec3 specular = light.specular*spec *texture( material.specular,TexCoords).rgb;
//聚光+平滑
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;
//衰减
float distance = length(light.position - FragPos );
float attenuate = 1/(light.constant + light.linear*distance + light.quadratic*distance*distance);
ambient *= attenuate;
diffuse *= attenuate;
specular *= attenuate;
FragColor = vec4(ambient + diffuse + specular, 1.0);
}
app设置外圆锥切角:
ObjShader.setFloat("light.outerCutOff", glm::cos(glm::radians(17.5f)));