当前位置: 首页 > news >正文

【OpenGL】LearnOpenGL学习笔记10 - 平行光、点光源、聚光灯

上接:https://blog.csdn.net/weixin_44506615/article/details/150443248?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl

之前我们使用的光源都是空间中的一个点,然后使用片段位置和光源位置来计算光照的方向。接下来我们要了解三种常见的投光物,来帮助我们更好的定义和使用光源

一、平行光 (Directional Light)

当一个光源处于非常远的位置时,光线到达我们的场景处就近似平行。这样的光源就是平行光,最典型的代表就是太阳光。在使用平行光的时候,我们只考虑光源的方向而不会考虑光源所处的位置,并且平行光是永不衰减的

首先来改造一下我们的场景,便于之后更清晰地观察,我们将会在场景中绘制十个位置和角度各不相同的立方体
首先定义十个立方体的坐标
Main.cpp

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)
};

接下来在我们的主循环中
Main.cpp

while (!glfwWindowShouldClose(window))
{float currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);ProcessKeyboardInput(window);glm::mat4 view = camera.GetViewMatrix();glm::mat4 projection = glm::mat4(1);projection = glm::perspective(glm::radians(camera.fov), screenWidth / screenHeight, 0.1f, 100.0f);shader.Use();shader.SetMat4("view", view);shader.SetMat4("projection", projection);shader.SetVec3("lightColor", lightColor);shader.SetVec3("viewPos", camera.transform.position);shader.SetVec3("material.ambient", glm::vec3(1.0f, 0.5f, 0.31f));shader.SetInt("material.diffuse", 0);shader.SetInt("material.specular", 1);shader.SetFloat("material.shininess", 32.0f);shader.SetVec3("light.ambient", glm::vec3(0.2f, 0.2f, 0.2f));shader.SetVec3("light.diffuse", glm::vec3(0.5f, 0.5f, 0.5f));shader.SetVec3("light.specular", glm::vec3(1.0f, 1.0f, 1.0f));// 设置平行光的方向shader.SetVec3("light.direction", glm::vec3(-0.2f, -1.0f, -0.3f));glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, boxDiffuseTex.GetTextureID());glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, boxSpecularTex.GetTextureID());// 遍历十个坐标,计算model矩阵for (int i = 0; i < 10; ++i){glm::mat4 model = glm::mat4(1);model = glm::translate(model, cubePositions[i]);float angle = 20.0f * i;model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));shader.SetMat4("model", model);glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 36);}// 去掉光源的绘制//lightShader.Use();//glm::mat4 model = glm::mat4(1.0f);//model = glm::translate(model, lightPos);//model = glm::scale(model, glm::vec3(0.2f));//lightShader.SetMat4("model", model);//lightShader.SetMat4("view", view);//lightShader.SetMat4("projection", projection);//lightShader.SetVec3("objectColor", lightColor);//glBindVertexArray(lightVAO);//glDrawArrays(GL_TRIANGLES, 0, 36);glfwSwapBuffers(window);glfwPollEvents();
}

以上,我们还设置了平行光的方向 light.direction
接下来修改片段着色器
FragmentShader.fs

//...struct Light {// 平行光方向vec3 direction;vec3 ambient;vec3 diffuse;vec3 specular;
};// ...void main()
{// 环境光vec3 ambient = lightColor * vec3(texture(material.diffuse, TexCoords)) * light.ambient;// 漫反射vec3 normal = normalize(Normal);// 平行光方向vec3 directionalLightDir = normalize(light.direction);vec3 diffuse = max(dot(normal, directionalLightDir), 0.0) * lightColor * vec3(texture(material.diffuse, TexCoords)) * light.diffuse;// 镜面反射vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-directionalLightDir, normal);vec3 specular = pow(max(dot(viewDir, reflectDir), 0), material.shininess) * lightColor * vec3(texture(material.specular, TexCoords)) * light.specular;FragColor = vec4(ambient + diffuse + specular, 1.0f);
}

编译运行,顺利的话可以看见以下图像
平行光

二、点光源 (Point Light)

点光源就是处于场景某一处的光源,它会朝着四周发光,但是与我们一开始使用的光源不一样,点光源是会随着距离而衰减的,典型的例子就是生活中的灯泡或是火把

那么如何来计算衰减?
如果直接使用与光源的距离来进行线性衰减的话,虽然便于计算,但是效果会很假
所以这里我们会用到一个非线性的衰减计算公式
点光源非线性衰减
这里的Kc为常数项,通常为1,保证分母不会小于分子而出现强度增大的异常现象
Kl为一次项,来与距离d相乘以获得一个线性的衰减
Kq为二次项,来与距离d的平方相乘以获得一个快速下降的非线性衰减
图片来自于LearnOpenGL
点光源的非线性衰减
那么,我们在计算衰减的时候,KlKq的选择就至关重要,以下表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值
数据来自于Ogre3D的Wiki
衰减系数
接下来实现点光源
首先修改片段着色器
FragmentShader.fs

// ...struct Light {// 点光源位置vec3 position;vec3 ambient;vec3 diffuse;vec3 specular;// 衰减参数float constant;float linear;float quadratic;
};// ...void main()
{// 与光源的距离float distance = length(light.position - FragPos);// 强度float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance);// 环境光vec3 ambient = lightColor * vec3(texture(material.diffuse, TexCoords)) * light.ambient * attenuation;// 漫反射vec3 normal = normalize(Normal);vec3 lightDir = normalize(light.position - FragPos);vec3 diffuse = max(dot(normal, lightDir), 0.0) * lightColor * vec3(texture(material.diffuse, TexCoords)) * light.diffuse * attenuation;// 镜面反射vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, normal);vec3 specular = pow(max(dot(viewDir, reflectDir), 0), material.shininess) * lightColor * vec3(texture(material.specular, TexCoords)) * light.specular * attenuation;FragColor = vec4(ambient + diffuse + specular, 1.0f);
}

接下来将对应参数传递
Main.cpp

// 之前定义的点光源位置
glm::vec3 lightPos(2.5f, 2.0f, -2.0f);// 主循环
while (!glfwWindowShouldClose(window))
{float currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;// 背景调暗,便于观察glClearColor(0.1f, 0.1f, 0.1f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);ProcessKeyboardInput(window);glm::mat4 view = camera.GetViewMatrix();glm::mat4 projection = glm::mat4(1);projection = glm::perspective(glm::radians(camera.fov), screenWidth / screenHeight, 0.1f, 100.0f);shader.Use();shader.SetMat4("view", view);shader.SetMat4("projection", projection);shader.SetVec3("lightColor", lightColor);shader.SetVec3("viewPos", camera.transform.position);shader.SetVec3("material.ambient", glm::vec3(1.0f, 0.5f, 0.31f));shader.SetInt("material.diffuse", 0);shader.SetInt("material.specular", 1);shader.SetFloat("material.shininess", 32.0f);shader.SetVec3("light.ambient", glm::vec3(0.2f, 0.2f, 0.2f));shader.SetVec3("light.diffuse", glm::vec3(0.5f, 0.5f, 0.5f));shader.SetVec3("light.specular", glm::vec3(1.0f, 1.0f, 1.0f));// 设置点光源的位置和衰减系数shader.SetVec3("light.position", lightPos);shader.SetFloat("light.constant", 1.0f);shader.SetFloat("light.linear", 0.09f);shader.SetFloat("light.quadratic", 0.032f);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, boxDiffuseTex.GetTextureID());glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, boxSpecularTex.GetTextureID());for (int i = 0; i < 10; ++i){glm::mat4 model = glm::mat4(1);model = glm::translate(model, cubePositions[i]);float angle = 20.0f * i;model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));shader.SetMat4("model", model);glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 36);}// 绘制点光源的位置lightShader.Use();glm::mat4 model = glm::mat4(1.0f);model = glm::translate(model, lightPos);// 缩放再小点model = glm::scale(model, glm::vec3(0.1f));lightShader.SetMat4("model", model);lightShader.SetMat4("view", view);lightShader.SetMat4("projection", projection);lightShader.SetVec3("objectColor", lightColor);glBindVertexArray(lightVAO);glDrawArrays(GL_TRIANGLES, 0, 36);glfwSwapBuffers(window);glfwPollEvents();
}

编译运行,顺利的话可以看见以下图像
距离光源越近的木箱就会越亮,远处则几乎漆黑一片
点光源

三、聚光灯 (Spot Light)

聚光灯也是位于环境中某个位置的光源,但它只朝向某一个特定的方向发射光线,我们用一个方向和一个**切光角(Cutoff Angle)**来表示
聚光灯
图片来自于LearnOpenGL

这里的 LightDir 表示从片段指向光源的向量
SpotDir 表示聚光灯所指向的方向
Φ 表示切光角
θ 表示LightDir和SpotDir向量之间的夹角
这里如果θ小于Φ的话,则表示将会被聚光灯所照亮

接下来我们把我们的相机作为一个手电筒,它会照亮我们所看向的方向
修改片段着色器
FragmentShader.fs

// ...struct Light {vec3 position;vec3 ambient;vec3 diffuse;vec3 specular;// 聚光灯的方向vec3 direction;// 切光角的cos值float cutOff;
};// ...void main()
{vec3 lightDir = normalize(light.position - FragPos);float theta_cos = dot(lightDir, normalize(-light.direction));// 切光角范围在0 ~ 90度,角度越大,cos值则越小if (theta_cos > light.cutOff){// 环境光vec3 ambient = lightColor * vec3(texture(material.diffuse, TexCoords)) * light.ambient;// 漫反射vec3 normal = normalize(Normal);vec3 diffuse = max(dot(normal, lightDir), 0.0) * lightColor * vec3(texture(material.diffuse, TexCoords)) * light.diffuse;// 镜面反射vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, normal);vec3 specular = pow(max(dot(viewDir, reflectDir), 0), material.shininess) * lightColor * vec3(texture(material.specular, TexCoords)) * light.specular;FragColor = vec4(ambient + diffuse + specular, 1.0f);}else{// 在切光角的范围外则只受到环境光影响vec3 ambient = lightColor * vec3(texture(material.diffuse, TexCoords)) * light.ambient;FragColor = vec4(ambient, 1.0f);}
}

这里为了不计算arccos,我们将在外面直接传入切光角的cos值,着色器计算时直接比较两角的cos值大小即可
接下来修改Main.cpp

// 设置聚光灯的位置和方向为相机的位置和方向
shader.SetVec3("light.position", camera.transform.position);
shader.SetVec3("light.direction", camera.transform.front);
// 设置切光角
shader.SetFloat("light.cutOff", glm::cos(glm::radians(12.5f)));// 隐藏绘制的光源

编译运行,顺利的话可以看见以下图像
聚光灯
效果看起来有点假,这是因为边缘过硬,并且聚光灯也应该像点光源一样有一个距离衰减

优化聚光灯

为了软化边缘,我们需要再增加一个外切光角,那么之前的切光角就变成了内切光角
在内切光角到外切光角这一段范围,我们将对光照进行线性衰减
我们会用到以下公式来计算最终的光照强度
线性插值
这里的ε内外切光角的cos差,这个公式本质上就是再进行线性插值 (Lerp)

修改片段着色器
FragmentShader.fs

// ...struct Light {vec3 position;vec3 ambient;vec3 diffuse;vec3 specular;// 聚光灯的方向vec3 direction;// 内切光角的cos值float cutOff;// 外切光角的cos值float cutOffOuter;
};// ...void main()
{vec3 lightDir = normalize(light.position - FragPos);float theta_cos = dot(lightDir, normalize(-light.direction));float epsilon = light.cutOff - light.cutOffOuter;// 强度,直接作用于漫反射和镜面反射,由于会衰减到0,所以不需要再使用if-else来判断了float intensity = clamp((theta_cos - light.cutOffOuter) / epsilon, 0.0f, 1.0f);// 环境光vec3 ambient = lightColor * vec3(texture(material.diffuse, TexCoords)) * light.ambient;// 漫反射vec3 normal = normalize(Normal);vec3 diffuse = max(dot(normal, lightDir), 0.0) * lightColor * vec3(texture(material.diffuse, TexCoords)) * light.diffuse * intensity;// 镜面反射vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, normal);vec3 specular = pow(max(dot(viewDir, reflectDir), 0), material.shininess) * lightColor * vec3(texture(material.specular, TexCoords)) * light.specular * intensity;FragColor = vec4(ambient + diffuse + specular, 1.0f);
}

Main.cpp

// 设置切光角
shader.SetFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
// 设置外切光角
shader.SetFloat("light.cutOffOuter", glm::cos(glm::radians(17.5f)));

编译运行,可以看见边缘软化了很多
软化边缘的聚光灯
接下来我们再添加一下距离衰减,和点光源类似
FragmentShader.fs

// ...struct Light {vec3 position;vec3 ambient;vec3 diffuse;vec3 specular;// 聚光灯的方向vec3 direction;// 内切光角的cos值float cutOff;// 外切光角的cos值float cutOffOuter;// 衰减参数float constant;float linear;float quadratic;
};// ...void main()
{vec3 lightDir = normalize(light.position - FragPos);float theta_cos = dot(lightDir, normalize(-light.direction));float epsilon = light.cutOff - light.cutOffOuter;// 强度,直接作用于漫反射和镜面反射,由于会衰减到0,所以不需要再使用if-else来判断了float intensity = clamp((theta_cos - light.cutOffOuter) / epsilon, 0.0f, 1.0f);// 衰减系数float distance = length(light.position - FragPos);float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * distance * distance);// 环境光vec3 ambient = lightColor * vec3(texture(material.diffuse, TexCoords)) * light.ambient;// 漫反射vec3 normal = normalize(Normal);vec3 diffuse = max(dot(normal, lightDir), 0.0) * lightColor * vec3(texture(material.diffuse, TexCoords)) * light.diffuse * intensity * attenuation;// 镜面反射vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, normal);vec3 specular = pow(max(dot(viewDir, reflectDir), 0), material.shininess) * lightColor * vec3(texture(material.specular, TexCoords)) * light.specular * intensity * attenuation;FragColor = vec4(ambient + diffuse + specular, 1.0f);
}

Main.cpp

// 设置衰减系数
shader.SetFloat("light.constant", 1.0f);
shader.SetFloat("light.linear", 0.09f);
shader.SetFloat("light.quadratic", 0.032f);

编译运行,可以发现距离远的物体暗了下来
慢慢移动摄像机靠近远距离物体,远距离物体会慢慢亮起来
距离衰减的聚光灯
完整代码请见顶部git仓库

http://www.lryc.cn/news/623065.html

相关文章:

  • 2020/12 JLPT听力原文 问题二 2番
  • CSDN部分内容改为视频转到B站-清单
  • Flink Stream API 源码走读 - print()
  • B3865 [GESP202309 二级] 小杨的 X 字矩阵(举一反三)
  • 矩阵链相乘的最少乘法次数(动态规划解法)
  • 深入了解 swap:作用、局限与分区建立
  • Hadoop面试题及详细答案 110题 (16-35)-- HDFS核心原理与操作
  • 鸿蒙应用开发和Vue网页开发中生命周期的区别
  • (论文速读)ViDAR:视觉自动驾驶预训练框架
  • leetcode hot100数组:缺失的第一个正数
  • Winsows系统去除右键文件显示的快捷列表
  • Win11家庭版docker安装Minio
  • windows环境下使用vscode以及相关插件搭建c/c++的编译,调试环境
  • 93、23种设计模式之抽象工厂模式
  • MySQL建表练习
  • GaussDB 数据库架构师修炼(十三)安全管理(3)-数据库审计
  • 人工智能中的(特征选择)数据过滤方法和包裹方法
  • Linux 下 安装 matlab 2025A
  • 安卓11 12系统修改定制化_____修改系统 解锁system分区 去除data加密 自由删减系统应用
  • python线程学习
  • Leetcode 14 java
  • AI 云电竞游戏盒子:从“盒子”到“云-端-芯”一体化竞技平台的架构实践
  • WSL 配置文件 wsl.conf 设置
  • Windows 基于ACL(访问控制列表)的权限管理
  • LeetCode 55.跳跃游戏:贪心策略下的可达性判断
  • Windows 操作系统 - Windows 恢复浏览器标题栏颜色
  • tensorrt-llm0.20.0:Prometheus3.5.0通过间接采集,进行性能指标分析
  • AirReceiverLite:轻松实现手机隔空投屏
  • 自动驾驶中的传感器技术24.1——Camera(16)
  • 电路方案分析(二十二)适用于音频应用的25-50W反激电源方案