OpenGL学习13——多光源

多光源(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
多光源场景3
多光源场景4
多光源场景5
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容