欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > OpenGL学习笔记(平行光、点光源、聚光、多光源)

OpenGL学习笔记(平行光、点光源、聚光、多光源)

2025/4/17 15:07:03 来源:https://blog.csdn.net/lzh804121985/article/details/147041247  浏览:    关键词:OpenGL学习笔记(平行光、点光源、聚光、多光源)

目录

  • 投光物
    • 平行光
    • 点光源
    • 聚光
  • 多光源
    • 封装定向光
    • 封装点光源

GitHub主页:https://github.com/sdpyy
OpenGL学习仓库:https://github.com/sdpyy1/CppLearn/tree/main/OpenGLtree/main/OpenGL):https://github.com/sdpyy1/CppLearn/tree/main/OpenGL

投光物

我们目前对光源的定义是

glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

只是一个点,但实际上光源有很多种。将光投射(Cast)到物体的光源叫做投光物(Light Caster)。在这一节中,我们将会讨论几种不同类型的投光物。

平行光

当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论物体和/或者观察者的位置,看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的。太阳就是一个很好的例子
在这里插入图片描述
因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。即对任何一点计算光照时,lightDir都是一样的。修改Light的定义

// 光源结构体
struct Light {// vec3 position; // 使用定向光就不再需要了vec3 direction;vec3 ambient;vec3 diffuse;vec3 specular;...
};
void main()
{vec3 lightDir = normalize(-light.direction);...
}

记得光源方向计算时要反向,这主要是因为计算需要。
为了更好体现光源对多个物体具有相同的效果,生成多个模型

void drawCube(const shader &cubeShader, GLuint cubeVAO, GLuint diffuseTexture, GLuint specularTexture) {// 画正方形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)};GL_CALL(glBindVertexArray(cubeVAO));cubeShader.use();cubeShader.setVec3("viewPos", camera.Position);
//    cubeShader.setVec3("light.position", lightPos);// Material propertiesGL_CALL(glActiveTexture(GL_TEXTURE0));GL_CALL(glBindTexture(GL_TEXTURE_2D, diffuseTexture));GL_CALL(glActiveTexture(GL_TEXTURE1));GL_CALL(glBindTexture(GL_TEXTURE_2D, specularTexture));cubeShader.setInt("material.diffuse", 0);cubeShader.setInt("material.specular", 1);cubeShader.setFloat("material.shininess", 64.0f);// light propertiescubeShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);cubeShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);cubeShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);cubeShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);for(unsigned int i = 0; i < 10; i++){glm::mat4 model(1.0f);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));setMVP(cubeShader,model);GL_CALL(glDrawArrays(GL_TRIANGLES, 0, 36));}GL_CALL(glDrawArrays(GL_TRIANGLES, 0, 36));
}

在这里插入图片描述
漫反射和镜面光分量的反应都好像在天空中有一个光源的感觉

点光源

定向光对于照亮整个场景的全局光源是非常棒的,但除了定向光之外我们也需要一些分散在场景中的点光源(Point Light)。点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。想象作为投光物的灯泡和火把,它们都是点光源。
在这里插入图片描述
之前实现的就是一个点光源,但是忽略了一个问题,光照强度实际上应该随着距离衰减。这种衰减并不是线性的。在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。
有人想到用
在这里插入图片描述

  • Kc始终为1,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
  • 一次项会与距离值相乘,以线性的方式减少强度。
  • 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。

如下图所示,随着距离增加衰减会变慢
在这里插入图片描述
至于如何选择这些系数,就是经验所得了
在这里插入图片描述
为了实现它,需要把三个系数传入

struct Light {vec3 position;  vec3 ambient;vec3 diffuse;vec3 specular;float constant;float linear;float quadratic;
};
lightingShader.setFloat("light.constant",  1.0f);
lightingShader.setFloat("light.linear",    0.09f);
lightingShader.setFloat("light.quadratic", 0.032f);
// 计算比例
float distance    = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// 对三种光都带上比例
ambient  *= attenuation; 
diffuse  *= attenuation;
specular *= attenuation;

可以看出来近处比较亮,远处就暗多了
在这里插入图片描述

聚光

聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒
在这里插入图片描述
可以用手电筒来举例,在摄像机位置架设手电筒

struct Light {vec3  position;// 这个方向是指手电筒指向的方向vec3  direction;// 手电筒能照到的范围float cutOff;vec3 ambient;vec3 diffuse;vec3 specular;float constant;float linear;float quadratic;
};
lightingShader.setVec3("light.position",  camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff",   glm::cos(glm::radians(12.5f)));

片段着色器要设置在范围内才执行光照计算,否则就只给一个环境光

void main()
{float distance    = length(light.position - FragPos);float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));// 手电筒与当前像素的夹角vec3 lightDir = normalize(light.position - FragPos);float theta = dot(lightDir, normalize(-light.direction));// 如果在范围内if(theta > light.cutOff){// 环境光vec3 ambient  = light.ambient * texture(material.diffuse, TexCoords).rgb;// 漫反射vec3 norm = normalize(Normal);vec3 lightDir = normalize(light.position - FragPos);float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb * attenuation;// 镜面光vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, norm);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)) * attenuation;// 混合vec3 result = ambient + diffuse + specular;FragColor = vec4(result, 1.0);} else {  // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗FragColor = vec4(light.ambient * texture(material.diffuse, TexCoords).rgb, 1.0);}}

下面是实现的手电筒效果
在这里插入图片描述
在这里插入图片描述
但是实现的效果过于生硬了,边缘是一个标准的圆形,需要进行平滑软化边缘。可以通过给聚光一个外圆锥,一个内圆锥,在内圆之内,强度为1,外圆和内圆直接进行一个过渡,最终为0
在这里插入图片描述在这里插入图片描述
基本是在内外余弦值之间根据θ插值

#version 330 core
out vec4 FragColor;// 材质结构体
struct Material {sampler2D diffuse;sampler2D specular;float     shininess;
};
// 光源结构体
struct Light {vec3  position;// 这个方向是指手电筒指向的方向vec3  direction;float cutOff;float outerCutOff;vec3 ambient;vec3 diffuse;vec3 specular;float constant;float linear;float quadratic;
};// 法线
in vec3 Normal;
// 像素对应空间位置
in vec3 FragPos;
// 纹理坐标
in vec2 TexCoords;// 摄像机位置
uniform vec3 viewPos;
// 材质
uniform Material material;
// 光源属性
uniform Light light;void main()
{// 环境光vec3 ambient  = light.ambient * texture(material.diffuse, TexCoords).rgb;// 漫反射vec3 norm = normalize(Normal);vec3 lightDir = normalize(light.position - FragPos);float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;// 镜面光vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, norm);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;// 手电筒与当前像素的夹角float theta     = dot(lightDir, normalize(-light.direction));float epsilon   = light.cutOff - light.outerCutOff;// 内圆之内为1,内外之间为0-1,外圆之外为0float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);// 手电筒范围内外diffuse  *= intensity;specular *= intensity;// 衰减float distance    = length(light.position - FragPos);float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));// 手电筒的距离ambient  *= attenuation;diffuse  *= attenuation;specular *= attenuation;// 混合vec3 result = ambient + diffuse + specular;FragColor = vec4(result, 1.0);
}

实现下来,边缘就消失了,更加柔和
在这里插入图片描述

另外如果把当前代码中关于衰减和聚光范围的判断删掉,效果可以当作移动点光源使用了!

多光源

相当于综合练习,在这一节中,我们将结合之前学过的所有知识,创建一个包含六个光源的完全照明场景。我们将模拟一个类似太阳的定向光(Directional Light)光源,四个分散在场景中的点光源(Point Light),以及一个手电筒(Flashlight)
学习一下glsl的封装

封装定向光

定向光只需要告诉光的方向即可

struct DirLight {vec3 direction;vec3 ambient;vec3 diffuse;vec3 specular;
};  
uniform DirLight dirLight;
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);
}

封装点光源

点光源需要光源位置,和用于距离判断的参数

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];
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{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.0 / (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);
}

封装聚光同理。
接下来就是各种赋值操作,具体代码就直接看我仓库把,找到
可以看出场景亮了很多
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词