多光源(Multiple Lights)
前面我们学习一些关于光照的知识,这一章节我们结合所获得的知识来创建一个包含6个光源的场景,分别是一个模拟太阳光的定向光源,4个分散在场景中的点光源和一个聚光灯光源。
- 当我们在片元着色器中进行多光源场景下片元颜色的计算,我们最好将不同类型的光源对颜色的影响封装到不同的GLSL函数中,避免代码太过混乱。GLSL函数与C函数相似,我们需要一个函数名和一个返回类型,且如果函数在主函数后面定义我们需要声明一个函数原型。引入GLSL函数后,片元着色器的大致结构如下所示:
out vec4 FragColor;
void main()
{
// 定义一个输出颜色
vec3 output = vec3(0.0);
// 添加定向光源的颜色贡献
output += someFunctionToCaculateDirectionalLight();
// 添加点光源的颜色贡献
for(int i = 0; i < nr_of_point_lights; i++)
{
output += someFunctionToCaculatePointLight();
}
// 添加聚光灯光源的颜色贡献(如聚光灯)
output += someFunctionToCaculateSpotlight();
FragColor = vec4(output, 1.0);
}
1. 定向光源
- 1.1 我们首先将计算定向光源的参数定义到一个结构体内,并声明一个uniform变量来接收参数值。
struct DirLight
{
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
- 1.2 声明计算定向光源函数的原型。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
- 1.3 实现计算定向光源的函数。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
// diffuse
float diff = max(dot(normal, lightDir), 0.0);
// specular
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// combine
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
return (ambient + diffuse + specular);
}
2. 点光源
- 2.1 定义点光源参数结构体。
struct PointLight
{
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
- 2.2 因为我们的场景中包含四个点光源,因此我们先使用预处理指令定义一个常量,然后声明一个上述结构类型的uniform变量数组。
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];
- 2.3 声明点光源函数原型。
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
- 2.4 实现点光源计算函数。
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse
float diff = max(dot(normal, lightDir), 0.0);
// specular
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// combine
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
3. 聚光灯光源
- 3.1 定义聚光灯光源结构体,并声明uniform变量。
struct SpotLight
{
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform SpotLight spotLight;
- 3.2 声明聚光灯光源函数原型。
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
- 3.3 实现聚光灯光源计算函数。
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse
float diff = max(dot(normal, lightDir), 0.0);
// specular
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// spotlight intensity
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
// combine
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation * intensity;
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
return (ambient + diffuse + specular);
}
4. 组合到一起
- 将上述3种类型光源的计算组合到主函数中,计算出片元最终的颜色。
void main()
{
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 result = CalcDirLight(dirLight, norm, viewDir);
for(int i = 0; i < NR_POINT_LIGHTS; i++)
{
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
}
result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
- 注意:上述光源的计算函数存在很多重复计算(因为基本是参照前面章节的计算过程进行封装),如反射矢量,扩散光和镜面光分量和纹理采样等的计算,因此后续我们可以对这些进行优化。
- 前面我们定义了一个点光源结构数组,数组元素值的设置与普通变量相似。
objectShader.setFloat("pointLights[0].constant", 1.0f);
- 对于四个电源,我们需要定义四个位置,并使用模型矩阵将其放置到空间中的不同位置。下面是四个点光源的位置数组。
glm::vec3 pointLightPositions[] = {
glm::vec3( 0.7f, 0.2f, 2.0f),
glm::vec3( 2.3f, -3.3f, -4.0f),
glm::vec3(-4.0f, 2.0f, -12.0f),
glm::vec3( 0.0f, 0.0f, -3.0f)
};
-
场景的渲染效果。
多光源场景1 -
调整光源参数后的一些渲染效果。
多光源场景2
网友评论