当前位置: 代码迷 >> 综合 >> LearnGL - 13.1 - SpotLight - 聚光灯
  详细解决方案

LearnGL - 13.1 - SpotLight - 聚光灯

热度:81   发布时间:2024-02-02 12:40:45.0

文章目录

  • 演示
  • 完整 Shader
  • Spot Light Gizmos - 聚光灯的 Gizmos 绘制
    • C++ 生成网格代码

LearnGL - 学习笔记目录

前一篇:

  • LearnGL - 13 - PointLight - 点光源 实现了 点光源 的最简单的着色方式

这篇:我们尝试给 “聚光灯” 类型的光源

本人才疏学浅,如有什么错误,望不吝指出。


其实 聚光灯点光源 很类似,以为区别比较大的是: 聚光灯 有光照的张角范围,而不像 点光源 的张角是全方位的范围。

所以相比上一篇来说,代码的添加与调整也是很少的:

void phong_illumination(in vec3 worldNormal,in vec3 viewDir,in vec3 worldPos,out vec3 diffuse,out vec3 specular,out float atten) {vec3 lightDir;atten = 1;if (LightPos.w == 0) {// 下面使用的是Phong 光照模型// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向lightDir = LightPos.xyz;} else {// 点光源 或是 聚光灯 都需要处理的lightDir = LightPos.xyz - worldPos; // 片段到光源的方向,简称:灯光方向float dist = length(lightDir);		// 片段到光源的距离lightDir *= dist == 0 ? 1 : 1.0 / dist; // 归一化atten = getDistanceAtten(dist);		// 获取距离衰减// 聚光灯if (LightPos.w != 1) {float LdotSD = dot(-lightDir, SpotLightDir); // LightDir dot Spot Light Dirfloat angle = acos(LdotSD);if (angle < SpotLightFOL) {atten = 1;} else {atten = 0;}}}float D = max(0, dot(lightDir, worldNormal));diffuse = LightColor.rgb * LightColor.a * D * DiffuseK * atten;vec3 H = normalize(lightDir + viewDir);float S = 0;if (D > 0) S = pow(max(0, dot(H, worldNormal)), Glossy);specular = LightColor.rgb * LightColor.a * S * SpecularK * atten;
}

主要看:

		// 聚光灯if (LightPos.w != 1) {float LdotSD = dot(-lightDir, SpotLightDir); // LightDir dot Spot Light Dirfloat angle = acos(LdotSD);if (angle < SpotLightFOL) {atten = 1;} else {atten = 0;}}

主要判断的是 -lightDir 光源位置到片段位置的方向,与 SpotLightDir 聚光灯的照射方向 的角度的 SpotLightFOL 张角 范围内就算是照亮范围内:

即:SpotLightDir 与 -lightDir 之间的夹角 小于 (FOL / 2),那就说明是在 聚光灯照射范围内
在这里插入图片描述

我们直接将 atten 输出,可以查看到效果:

gl_FragColor = vec4(atten);

在这里插入图片描述
(上面的效果中,可以发现 气球猫 的模型部分被 球体 挡住的部分,还是有光照,这部分先不用纠结,以后学习到 阴影 部分就可以处理这部分内容,如果需要,你也可以查看我之前 在 Unity 中实现自定义的方向光阴影 ShadowMap 的方式来实现)

但是这个张角的照射边缘的过渡很生硬,我们提供另一个:

		// 聚光灯if (LightPos.w != 1) {float LdotSD = dot(-lightDir, SpotLightDir); // LightDir dot Spot Light Dirfloat angle = acos(LdotSD);if (angle < SpotLightFOL) {if (angle < SpotLightFOL_FadeOut) {atten = 1;} else {atten = 1 - smoothstep(SpotLightFOL_FadeOut, SpotLightFOL, angle);}// atten *= pow(LdotSD, SpotLightExp);} else {atten = 0;}}

在代码中,可以看到我们使用了 atten = 1 - smoothstep(SpotLightFOL_FadeOut, SpotLightFOL, angle); 这么一句代码来平滑

其中:

  • SpotLightFOL 还是之前那个 张角
  • SpotLightFOL_FadeOut 就是我们的 开始淡出的张角角度
  • angle 是当前的光源指向片段的角度

这里头,smoothstep 函数用法是:smoothstep(min_val, max_val, val); 指定 最小范围 min_val 和最大范围 max_val,如果 valmin_valmax_val 之间,将会是 0~1 之间的值,valmin_val 更小,则返回 0,比 max_val 大, 则返回 1,想 查看 smoothstep 曲线函数 的可以查看我之前的一篇:CG cosh, sinh, smoothstep, tanh, perlin_easeCurve1/2 曲线 - smoothstep

在这里插入图片描述

演示

在这里插入图片描述

调整张角效果
在这里插入图片描述

还可以对 Spot Light Gizmos - Cone 圆锥体调整渲染状态
在这里插入图片描述

完整 Shader

shader 也是相当简单

// jave.lin - my_lighting.glsl - 光照模型处理#include "/Include/my_global.glsl"#ifndef _MY_LIGHTING__GLSL__
#define _MY_LIGHTING__GLSL__// scene uniform
uniform vec4 _Ambient;		// .xyz 环境光颜色, .w 环境光系数
uniform int AmbientType;	// 环境光类别,[测试用]// object uniform
uniform float Glossy;		// 光滑度
uniform vec3 DiffuseK;		// 漫反射系数
uniform vec3 SpecularK;		// 高光系数// light uniform
uniform vec4 LightPos;		// 灯光世界坐标位置,w==0,或名是方向光,w==1说明是点光源,w == 0.5 是聚光灯
uniform vec4 LightColor;	// 灯光颜色,.xyz 顔色,.w 强度
uniform vec3 SpotLightDir;	// 聚光灯照射方向
uniform float SpotLightFOL; // 聚光灯的张角量,Field Of Light(弧度)
uniform float SpotLightFOL_FadeOut; // 这个边缘淡出的角度,必须小于 SpotLightFOL,否则没有效果// point or spot light
uniform float ATTEN_Kc;		// 点光源 常数项系数
uniform float ATTEN_Kl;		// 点光源 一次项系数
uniform float ATTEN_Kq;		// 点光源 二次项系数
uniform vec2 ATTEN_Range;	// 点光源 有效范围, .x == range, .y == 1.0 / range// TODO : 实现多光源时使用,现在单光源先不写
// struct LightData_t {
// };// const uint MaxLightNum = 10;
// uniform LightData_t Lights[MaxLightNum];// ambient
vec3 getAmbient(vec3 albedo) {if (AmbientType == 0) {return _Ambient.rgb * _Ambient.a;} else {return mix(_Ambient.rgb * _Ambient.a, albedo, _Ambient.a);}
}// point light
float getDistanceAtten(float dist) {		// 获取距离衰减// method1:// return (1.0 / (ATTEN_Kc + ATTEN_Kl * dist + ATTEN_Kq * (dist * dist)))// // ;// * clamp(dist * ATTEN_Range.y == 0 ? 0 : 1 - dist * ATTEN_Range.y, 0, 1);// method2:return 1 - smoothstep(0, ATTEN_Range.x, dist);
}void phong_illumination(in vec3 worldNormal,in vec3 viewDir,in vec3 worldPos,out vec3 diffuse,out vec3 specular// ,out float atten) {vec3 lightDir;float atten = 1;if (LightPos.w == 0) {// 下面使用的是Phong 光照模型// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向lightDir = LightPos.xyz;} else {// 点光源 或是 聚光灯 都需要处理的lightDir = LightPos.xyz - worldPos; // 片段到光源的方向,简称:灯光方向float dist = length(lightDir);		// 片段到光源的距离lightDir *= dist == 0 ? 1 : 1.0 / dist; // 归一化atten = getDistanceAtten(dist);		// 获取距离衰减// 聚光灯if (LightPos.w != 1) {float LdotSD = dot(-lightDir, SpotLightDir); // LightDir dot Spot Light Dirfloat angle = acos(LdotSD);if (angle < SpotLightFOL) {if (angle > SpotLightFOL_FadeOut) {atten *= 1 - smoothstep(SpotLightFOL_FadeOut, SpotLightFOL, angle);}} else {atten = 0;}}}float D = max(0, dot(lightDir, worldNormal));diffuse = LightColor.rgb * LightColor.a * D * DiffuseK * atten;vec3 H = normalize(lightDir + viewDir);float S = 0;if (D > 0) S = pow(max(0, dot(H, worldNormal)), Glossy);specular = LightColor.rgb * LightColor.a * S * SpecularK * atten;
}#endif

Spot Light Gizmos - 聚光灯的 Gizmos 绘制

如果你好奇我的 Spot Light 的调试网格是怎么来的,可以看一下,否则可以不开这部分内容

前面我们有对 方向光(一个 Cube),点光源(一个 Sphere)来绘制 Gizmos

这次我们的 聚光灯 使用的是一个 Cone,圆锥体

这个圆锥体是实时程序控制的顶点坐标

C++ 生成网格代码

				const size_t segment = 36;const float startDeg = 0;const float intervalDeg = 360.0f / segment;const float fol = D2R(15.0f); // field of light,聚光灯的张角/*tan(a)=D/La=15 degsD=?L=1tan(15 degs)=D/1tan(15 degs)=D*/// 第一个圆锥点// 顶点std::vector<vec3> vec_vertices;// 第一个锥顶点vec_vertices.push_back(vec3(0));// 聚光灯的张角作为半径GLfloat radius = tanf(fol);// 画一个原型,从x轴正方向 开始 顺时针 生成for (size_t i = 0; i < segment; i++) {vec3 v = vec3(cosf(D2R(startDeg + intervalDeg * i)) * radius,sinf(D2R(startDeg + intervalDeg * i)) * radius,-1.0f);vec_vertices.push_back(v);//std::cout << v.x << "," << v.y << "," << v.z << "\n";}// 最后一个闭合点,与起点重合vec_vertices.push_back(vec3(cosf(D2R(startDeg))* radius,sinf(D2R(startDeg))* radius,-1.0f));size_t vertices_count = vec_vertices.size() * 3;//std::cout << "vec_vertices.size() : " << vec_vertices.size() << "\n";GLfloat* vertices = new GLfloat[vertices_count];for (size_t i = 0; i < vec_vertices.size(); i ++) {vec3 v = vec_vertices[i];vertices[i * 3 + 0] = v.x;vertices[i * 3 + 1] = v.y;vertices[i * 3 + 2] = v.z;}// 颜色size_t colors_count = vec_vertices.size() * 4;//std::cout << "colors_count : " << colors_count << "\n";GLfloat* colors = new GLfloat[colors_count];colors[0] = 1.0f;colors[1] = 1.0f;colors[2] = 1.0f;colors[3] = 1.0f;for (size_t i = 4; i < colors_count; i += 4) {colors[i + 0] = 1.0f;colors[i + 1] = 1.0f;colors[i + 2] = 1.0f;colors[i + 3] = 0.0f;}// 索引size_t size1 = (vec_vertices.size() - 2) * 3;size_t size2 = (vec_vertices.size() - 1 - 2) * 3;size_t indices_count = size1 + size2;GLuint* indices = new GLuint[indices_count];//std::cout << "indices_count : " << indices_count << "\n";size_t idx = 0;size_t v_idx = 0;for (size_t i = 0; i < size1 - 2; i += 3) {indices[idx++] = 0;indices[idx++] = v_idx + 1;indices[idx++] = v_idx + 2;std::cout << 0 << "," << (v_idx + 1) << "," << (v_idx + 2) << "\n";v_idx++;}v_idx = 0;for (size_t i = 0; i < size2 - 2; i += 3) {indices[idx++] = 1;indices[idx++] = v_idx + 3;indices[idx++] = v_idx + 2;//std::cout << 1 << "," << (v_idx + 2) << "," << (v_idx + 3) << "\n";v_idx++;}//std::cout << "idx : " << idx << "\n";SL_mesh = new Mesh("Cone Mesh");SL_mesh->pos_copy_from(vertices, vertices_count);SL_mesh->color_copy_from(colors, colors_count);SL_mesh->indices_copy_from(indices, indices_count);delete[] vertices;delete[] colors;delete[] indices;rawPos = SL_mesh->getRawPos();

我的圆锥共有38个顶点,第一个顶点都是0点,其他的点都是一个圆形上分36分段,加上最后一个闭合点,所以共38个。

其中 索引 的部分有两个大小 size1, size2 这两部分我是先画了一下图形,总结了一个规律算出来的索引数量,具体可以参考下图:
在这里插入图片描述

然后你可以看到有一句:

				// 聚光灯的张角作为半径GLfloat radius = tanf(fol);

这部分就的 fol 就是编辑器中的 Spot Light FOL 参数,就是聚光灯的 张角 参数。