LearnGL - 学习笔记目录
前些篇:
- LearnGL - 11.1 - 实现简单的Gouraud-Phong光照模型
- LearnGL - 11.2 - 实现简单的Phong光照模型
- LearnGL - 11.3 - 实现简单的Blinn-Phong光照模型
- LearnGL - 11.4 - 实现简单的Flat BlinnPhong光照模型
这些演示光照计算中,都是对 “方向光” 类型的光源来实现的
这篇:我们尝试给 “点光源” 类型的光源
本人才疏学浅,如有什么错误,望不吝指出。
先看一下我们的效果静态图:
点光源
与方向光比较
- 方向光 : 只 考虑方向,不考虑位置,而且他的光强度假设是不衰减的
- 点光源 : 刚好相反,不考虑方向,但需要 考虑光源位置,而且它的光强度会随距离而衰减
光衰减
距离衰减
目前参考了网上提供的衰减曲线,因为直线型的衰减效果不太好
Y轴数值我放大了100倍,因为实际 shader 中使用是 0~1 的一致衰减值
衰减公式为:
a
t
t
e
n
d
(
d
)
=
1
K
c
+
K
l
⋅
d
+
K
q
⋅
d
2
atten_d(d)=\frac{1}{K_c+K_l \cdot d + K_q \cdot d^2}
attend(d)=Kc+Kl⋅d+Kq⋅d21
- d d d 是距离
- K c K_c Kc 是常数项系数,通常为1,为了避免除以零
- K l K_l Kl 是一次项系数
- K q K_q Kq 是二次项系数
作用范围衰减
但是因为我的点光源为了优化,添加了一个 range
参数,即:点光源的影响范围,所以我给上面的衰减模型公式添加了另一个衰减值:atten_r
,
r
r
r 是光源指定的有效的范围值
a
t
t
e
n
r
(
r
)
=
1
−
1
r
,
(
0
≤
r
≤
指
定
的
范
围
值
)
atten_r(r)=1-\frac{1}{r}, (0 \le r \le 指定的范围值)
attenr(r)=1−r1,(0≤r≤指定的范围值)
所以我的
a
t
t
e
n
(
x
)
atten(x)
atten(x) 调整为:
a
t
t
e
n
(
d
,
r
)
=
a
t
t
e
n
d
(
d
)
⋅
a
t
t
e
n
r
(
r
)
atten(d,r)=atten_d(d) \cdot atten_r(r)
atten(d,r)=attend(d)⋅attenr(r)
用 GGB 可以调整系数查看效果:
对应的 Inspector 面板参数
首先是需要选择为 Point 点光源
- Atten Kc 对应的常数项系数
- Atten Kl 一次项系数
- Atten Kq 二次项系数
- Range 就是我们指定该点光源能作用的范围大小
演示
为了方便查看点光源的效果,先将镜头的 clearColor 设置为纯黑色
再关闭环境光: AmbientIntensity = 0
调整 Range 参数查看效果,并调整一下灯光颜色
光源的切换
我给编辑器中的光源作了类似 Gizmos 的显示切换
- 方向光 目前显示的是一个 Cube,从黑色指定有颜色的方向就是光源入射方向
- 点光源 目前实现的是一个 Sphere,不用考虑方向,但需要考虑位置
Shader
主要是查看:my_lighting.glsl 的引用文件(GLSL 中如何实现 include 可以查看我之前的一篇:LearnGL - 12 - GLSL include - GL_ARB_shading_language_include (Extensions扩展) - 各种踩坑)
// 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 LightDir; // 灯光类型为聚光灯的方向
// point light
uniform float PL_Kc; // 点光源 常数项系数
uniform float PL_Kl; // 点光源 一次项系数
uniform float PL_Kq; // 点光源 二次项系数
uniform vec2 PL_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) { // 获取距离衰减
return 1.0 / (PL_Kc + PL_Kl * dist + PL_Kq * (dist * dist));
}
float getRangeAtten(float dist) { // 获取范围衰减
return clamp(dist * PL_Range.y == 0 ? 0 : 1 - dist * PL_Range.y, 0, 1);
}
void phong_illumination(
in vec3 worldNormal,
in vec3 viewDir,
in vec3 worldPos,
out vec3 diffuse,
out vec3 specular
) {
vec3 lightDir;
float atten = 1;
if (LightPos.w == 0) {
// 下面使用的是Phong 光照模型
// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向
lightDir = LightPos.xyz;
} else {
// 点光源 或是 聚光灯
if (LightPos.w == 1) {
// 点光
lightDir = LightPos.xyz - worldPos;
float dist = length(lightDir);
lightDir *= dist == 0 ? 1 : 1.0 / dist;
// lightDir = normalize(lightDir);
atten = getDistanceAtten(dist) * getRangeAtten(dist);
} else { // LightPos.w == 0.5,即:LightPos.w !=0 && LightPos.w != 1
// 聚光灯
}
}
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
my_global.glsl
// jave.lin - my_global.glsl
#ifndef _MY_GLOBAL__GLSL__
#define _MY_GLOBAL__GLSL__
// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标
// local uniform
uniform mat4 mMat; // m 矩阵
uniform mat4 vMat; // v 矩阵
uniform mat4 pMat; // p 矩阵
uniform mat4 mvpMat; // m.v.p 矩阵
uniform mat4 IT_mMat; // Model Matrix 的逆矩阵的转置矩阵
// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
return normalize(mat3(IT_mMat) * n); // 等价于:transpose(I_mMat) * vec4(n, 0)
}
vec3 getWorldViewDir(vec3 worldPos) {
return normalize(_CamWorldPos - worldPos);
}
#endif /* _MY_GLOBAL__GLSL__ */ // 这里一定要加 /* 你的头文件宏 */,否则会报错,太无语了
接着是使用渲染对象的 shader
// jave.lin - testing_includes.vert
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"
// vertex data
in vec3 vPos; // 顶点坐标
in vec2 vUV0; // 顶点纹理坐标
in vec3 vNormal; // 顶点法线
// vertex data - interpolation
out vec2 fUV0; // 给 fragment shader 传入的插值
out vec3 fNormal; // 世界坐标顶点法线
out vec3 fWorldPos; // 世界坐标
void main() {
vec4 worldPos = mMat * vec4(vPos, 1.0); // 世界坐标
fUV0 = vUV0; // UV0
fNormal = ObjectToWorldNormal(vNormal); // 世界坐标顶点法线
fWorldPos = worldPos.xyz; // 世界坐标
gl_Position = pMat * vMat * worldPos; // Clip pos
}
// jave.lin - testing_includes.frag
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"
#include "/Include/my_lighting.glsl"
// interpolation - 插值数据
in vec2 fUV0; // uv 坐标
in vec3 fNormal; // 顶点法线
in vec3 fWorldPos; // 世界坐标
uniform sampler2D main_tex;
void main() {
vec3 albedo = texture(main_tex, fUV0).rgb;
vec3 worldNormal= normalize(fNormal); // 世界坐标法线再次归一化一次,因为插值之后可能会导致不是归一化的值
vec3 viewDir = getWorldViewDir(fWorldPos); // 顶点坐标 指向 镜头坐标 的方向
vec3 diffuse = vec3(0);
vec3 specular = vec3(0);
vec3 ambient = getAmbient(albedo);
phong_illumination(worldNormal, viewDir, fWorldPos, diffuse, specular);
gl_FragColor = vec4(ambient + diffuse * albedo + specular, 1.0);
}