多光源
之前的章节中我们学习了如何设置定向光、点光源以及聚光效果。而为了在场景中使用多个光源,我们就需要在GLSL中对不同种类似光照计算进行封装。若不进行封装,当场景中有多个光源时,每一种光源都需要一种不同的光照计算,就会使代码变得极为复杂和难读。
GLSL函数语法与C很像,需要注意的几点是若函数不是在main函数之前声明的,则必须要在代码顶部声明一个原型。
简单来说我们只是需要一个输出的片段颜色,因此我们将场景内所有光源对某一片段作用相加,就可以得到该片段最终的输出值。所以我们会用不同的函数来对不同的光照类型进行计算,并在main函数中进行结合输出。
out vec4 FragColor;
void main()
{
//定向光计算
vec3 result = CalcDirLight(...);
//点光源计算
result += CalcPointLight(...);
//聚光计算
result += CalcSpotLight(...);
FragColor = vec4(result,1.0);
}
定向光
我们在片段着色器中定义一个函数CalcDirLight用来计算定向光照,与前几章中的定向光计算无异。
因为不同类型光照需要不同的参数,所以先定义一个定向光结构体,并设置uniform。
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 reflection;
};
uniform DirLight dirLight;
接下来定义一下CalcDirLight原型函数
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
在main函数后面实现CalcDirLight函数(若在main函数之前,则可不用声明原型,与C类似)。
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.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);
}
实现步骤与之前无异,最终返回的是定向光对片段颜色值的贡献。
点光源
同样,定义一个点光源结构体,并设置uniform。
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 reflection;
};
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];
这里我们想要使用多个点光源,在GLSL中使用了预处理指令来定义了我们场景中点光源的数量。接着我们使用了这个NR_POINT_LIGHTS常量来创建了一个PointLight结构体的数组。GLSL中的数组和C数组一样,可以使用一对方括号来创建。现在我们有四个待填充数据的PointLight结构体。
定义CalcPointLight原型函数
vec3 CalcPointLight(PointLight light,vec3 normal,vec3 viewDir,vec3 FragPos);
实现CalcPointLight函数
vec3 CalcPointLight(PointLight light,vec3 normal,vec3 viewDir,vec3 FragPos)
{
vec3 lightDir = normalize(light.position - FragPos);
//漫反射
float diff = max(dot(lightDir,normal),0.0);
//镜面反射
vec3 reflectDir = reflect(-lightDir,normal);
float ref = pow(max(dot(viewDir,reflectDir),0.0),material.shininess);
vec3 ambient = light.ambient * vec3(texture(material.diffuse,TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse,TexCoords));
vec3 reflection = light.reflection * ref * vec3(texture(material.reflection,TexCoords));
//衰减
float distance = length(light.position - FragPos);
float attenuation = 1.0/(light.constant + light.linear * distance + light.quadratic * (distance * distance));
ambient *= attenuation;
diffuse *= attenuation;
reflection *= attenuation;
return (ambient + diffuse + reflection);
}
聚光
定义一个聚光结构体,并设置uniform。
struct SpotLight
{
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 reflection;
};
uniform SpotLight spotLight;
原型声明
vec3 CalcSpotLight(SpotLight light,vec3 normal,vec3 viewDir,vec3 FragPos);
实现
vec3 CalcSpotLight(SpotLight light,vec3 normal,vec3 viewDir,vec3 FragPos)
{
vec3 lightDir = normalize(light.position - FragPos);
//漫反射
float diff = max(dot(lightDir,normal),0.0);
//镜面反射
vec3 reflectDir = reflect(-lightDir,normal);
float ref = pow(max(dot(viewDir,reflectDir),0.0),material.shininess);
vec3 ambient = light.ambient * vec3(texture(material.diffuse,TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse,TexCoords));
vec3 reflection = light.reflection * ref * vec3(texture(material.reflection,TexCoords));
//衰减
float distance = length(light.position - FragPos);
float attenuation = 1.0/(light.constant + light.linear * distance + light.quadratic * (distance * distance));
//平滑
float theta = dot(lightDir,normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff)/epsilon ,0.0,1.0);
ambient *= attenuation * intensity;
diffuse *= attenuation * intensity;
reflection *= attenuation * intensity;
return (ambient + diffuse + reflection);
}
合并结果
由于我们有多个点光源,所以首先定义各个点光源的位置数组。
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)
};
接下来我们可以通过下标来对各个点光源的uniform进行设置
lightingShader.setVec3("pointLights[0].position", pointLightPositions[0]);
lightingShader.setVec3("pointLights[0].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[0].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[0].reflection", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[0].constant", 1.0f);
lightingShader.setFloat("pointLights[0].linear", 0.09f);
lightingShader.setFloat("pointLights[0].quadratic", 0.032f);
其他几个点光源也是同样的操作。
在着色器main函数中实现合并
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,viewDir,FragPos);
}
//聚光计算
result += CalcSpotLight(spotLight,norm,viewDir,FragPos);
FragColor = vec4(result,1.0);
}
最后,我们使用了多个光源立方体,因此,我们需要对不同的光源立方体进行模型变换,变换到不同的位置上去。
lightCubeShader.use();
lightCubeShader.setMat4("projection", projection);
lightCubeShader.setMat4("view", view);
glBindVertexArray(lightCubeVAO);
for (unsigned int i = 0;i < 4;i++)
{
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, pointLightPositions[i]);
model = glm::scale(model, glm::vec3(0.2f));
lightCubeShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
效果如下
