【learnOpenGL学习笔记_13】OpenGL多光源融合 - 综合实战

大家好,我是 同学小张,+v: jasper_8017 一起交流,持续学习AI大模型应用实战案例,持续分享,欢迎大家点赞+关注,订阅我的大模型专栏,共同学习和进步。
现在订阅专栏,+微信私信我 返3元,即将涨价!


前面几个教程,我们已经学习了许多关于 OpenGL 光照的知识,其中包括冯氏照明模型(Phong shading)、光照材质(Materials)、光照贴图(Lighting maps)以及各种投光物(Light casters)。本教程将结合上述所学的知识,创建一个包含六个光源的场景。我们将模拟一个类似阳光的平行光(Directional light)和4个定点光(Point lights)以及一个手电筒(Flashlight)。

1. 多光源框架优化

为了在场景中高效运用多光源,我们需要在 GLSL 中巧妙封装一些函数,用于计算不同光源的光照效果。若为每个光源单独编写光照计算代码,不仅繁琐不堪,还会使 main 函数变得混乱不堪。因此,将相关操作封装为函数是更为明智的选择。

在处理多光源场景时,通常采用以下方法:创建一个用于存储输出颜色的向量,每个光源对输出颜色贡献相应的色彩份额。因此,场景中的每个光源将独立运算,其结果共同影响最终的输出颜色。以下是实现这一目标的通用结构:

out vec4 color;

void main()
{
    // 定义输出颜色
    vec3 output;
    // 将平行光的运算结果颜色添加到输出颜色
    output += someFunctionToCalculateDirectionalLight();
    // 同样,将定点光的运算结果颜色添加到输出颜色
    for(int i = 0; i < nr_of_point_lights; i++)
        output += someFunctionToCalculatePointLight();
    // 添加其他光源的计算结果颜色(如投射光)
    output += someFunctionToCalculateSpotLight();

    color = vec4(output, 1.0);
}

可以看到,我们为每种光源类型创建了一个函数:

  • 平行光:someFunctionToCalculateDirectionalLight()
  • 点光源:someFunctionToCalculatePointLight()
  • 聚光:someFunctionToCalculateSpotLight()

下面我们分别实现三个函数。

2. 平行光(Directional Light)封装

2.1 平行光结构体

首先,定义平行光的结构体,并定义 uniform 变量来传递平行光的数据。

struct DirLight {
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};  
uniform DirLight dirLight;

2.2 平行光计算函数

我们将平行光计算函数命名为 calculateDirectionalLight。其需要的参数包括:

  • 方向光(DirLight)
  • 法线(Normal)
  • 视角(View Direction)

所以,函数声明如下:

vec3 calculateDirectionalLight(DirLight light, vec3 normal, vec3 viewDir);

接下来,我们实现该函数(将之前平行光计算的代码复制过来)。

vec3 calculateDirectionalLight(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);
}  

3. 点光源(Point Light)封装

3.1 点光源结构体

首先,定义点光源的结构体,并定义 uniform 变量来传递点光源的数据。

struct PointLight {
    vec3 position;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
#define NR_POINT_LIGHTS 4  
uniform PointLight pointLights[NR_POINT_LIGHTS];

3.2 点光源计算函数

我们将点光源计算函数命名为 calculatePointLight。其需要的参数包括:

  • 点光源(PointLight)
  • 法线(Normal)
  • 视角(View Direction)
  • 片段位置(Fragment Position)

所以,函数声明如下:

vec3 calculatePointLight(PointLight light, vec3 normal, vec3 viewDir, vec3 fragPos);

接下来,我们实现该函数(将之前点光源计算的代码复制过来)。

vec3 calculatePointLight(PointLight light, vec3 normal, vec3 viewDir, vec3 fragPos)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // 计算漫反射强度
    float diff = max(dot(normal, lightDir), 0.0);
    // 计算镜面反射
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // 计算衰减
    float distance = length(light.position - fragPos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
    // 将各个分量合并
    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);
}

4. 聚光灯(Spot Light)封装

4.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;

4.2 聚光灯计算函数

我们将聚光灯计算函数命名为 calculateSpotLight。其需要的参数包括:

  • 聚光灯(SpotLight)
  • 法线(Normal)
  • 视角(View Direction)
  • 片段位置(Fragment Position)

所以,函数声明如下:

vec3 calculateSpotLight(SpotLight light, vec3 normal, vec3 viewDir, vec3 fragPos);

接下来,我们实现该函数(将之前聚光灯计算的代码复制过来)。

vec3 calculateSpotLight(SpotLight light, vec3 normal, vec3 viewDir, vec3 fragPos)
{
    // 环境光
    vec3 ambient = vec3(texture(material.diffuse, TexCoords)) * light.ambient;
    vec3 lightDir = normalize(light.position - fragPos);

    float theta = dot(lightDir, -light.direction);
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);

    // 漫反射光
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = vec3(texture(material.diffuse, TexCoords)) * diff * light.diffuse;

    // 镜面光
    vec3 reflectDir = reflect(-lightDir, normal);
    float diff_spec = max(dot(viewDir, reflectDir), 0.0);
    float spec = pow(diff_spec, material.shininess);
    vec3 specular = vec3(texture(material.specular, TexCoords)) * spec * light.specular;

    // 衰减
    float distance = length(light.position - fragPos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

    vec3 finalColor = ambient + (diffuse + specular) * attenuation * intensity;
    return finalColor;
}

5. 整合光照计算

main 函数中,整合点光源和聚光灯的计算。

vec3 normal = normalize(outNormal);
vec3 viewDir = normalize(viewPos - outFragPos);

// 将平行光的运算结果颜色添加到输出颜色
vec3 finalColor = calculateDirectionalLight(dirLight, normal, viewDir);
// 同样,将定点光的运算结果颜色添加到输出颜色
for(int i = 0; i < NR_POINT_LIGHTS; i++)
{
    finalColor += calculatePointLight(pointLights[i], normal, viewDir, outFragPos);
}
// 添加其他光源的计算结果颜色(如投射光)
finalColor += calculateSpotLight(spotLight, normal, viewDir, outFragPos);

FragColor = vec4(finalColor, 1.0);

6. 主程序中设置光源数据

(1)先定义四个点光源

glm::vec3 pointLightPositions[] = {
   
   
    glm::vec3( 0.7f,  0.2f,  2.0f</
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

同学小张

如果觉得有帮助,欢迎给我鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值