文章目录
LearnGL - 学习笔记目录
前些篇:
了解了 Gouraud、Phong 光照模型的基本认识。
这篇:我们将对 Blinn-Phong 光照模型实现一个简单的实现。
本人才疏学浅,如有什么错误,望不吝指出。
Blinn-Phong
也是基于 Phong 模型的 “ 改进 ”,注意这里的改进是用了 双引号 的。
以前在学习 Blinn-Phong 的时候,看到很多人都说效率会更好,因为 Blinn-Phong 与 Phong 模型的核心区别在于计算高光的部分,如下,但是真的有高很多效率吗?
- Phong 模型公式:
I
=
I
a
⋅
K
a
+
∑
i
=
0
n
(
I
d
⋅
K
d
⋅
d
o
t
(
N
,
L
i
)
+
I
s
⋅
K
s
⋅
p
o
w
(
d
o
t
(
r
e
f
l
e
c
t
(
−
L
i
,
N
)
,
V
)
,
G
l
o
s
s
y
)
)
I=I_a\cdot K_a+\sum_{i=0}^n(I_d\cdot K_d\cdot dot(N,L_i) + I_s\cdot K_s\cdot pow(dot(\red{reflect(-L_i,N)},\blue{V}),Glossy))
I=Ia⋅Ka+∑i=0n(Id⋅Kd⋅dot(N,Li)+Is⋅Ks⋅pow(dot(reflect(−Li,N),V),Glossy))
简写: I = I a ⋅ K a + ∑ i = 0 n ( I d ⋅ K d ⋅ d o t ( N , L i ) + I s ⋅ K s ⋅ p o w ( d o t ( R , V ) , G l o s s y ) ) I=I_a\cdot K_a+\sum_{i=0}^n(I_d\cdot K_d\cdot dot(N,L_i) + I_s\cdot K_s\cdot pow(dot(\red{R},\blue{V}),Glossy)) I=Ia⋅Ka+∑i=0n(Id⋅Kd⋅dot(N,Li)+Is⋅Ks⋅pow(dot(R,V),Glossy)) - Blinn-Phong 模型公式:
I
=
I
a
⋅
K
a
+
∑
i
=
0
n
(
I
d
⋅
K
d
⋅
d
o
t
(
N
,
L
i
)
+
I
s
⋅
K
s
⋅
p
o
w
(
d
o
t
(
n
o
r
m
a
l
i
z
e
(
L
+
V
)
,
N
)
,
G
l
o
s
s
y
)
)
I=I_a\cdot K_a+\sum_{i=0}^n(I_d\cdot K_d\cdot dot(N,L_i) + I_s\cdot K_s\cdot pow(dot(\red{normalize(L+V)},\green{N}),Glossy))
I=Ia⋅Ka+∑i=0n(Id⋅Kd⋅dot(N,Li)+Is⋅Ks⋅pow(dot(normalize(L+V),N),Glossy))
简写: I = I a ⋅ K a + ∑ i = 0 n ( I d ⋅ K d ⋅ d o t ( N , L i ) + I s ⋅ K s ⋅ p o w ( d o t ( H , N ) , G l o s s y ) ) I=I_a\cdot K_a+\sum_{i=0}^n(I_d\cdot K_d\cdot dot(N,L_i) + I_s\cdot K_s\cdot pow(dot(\red{H},\green{N}),Glossy)) I=Ia⋅Ka+∑i=0n(Id⋅Kd⋅dot(N,Li)+Is⋅Ks⋅pow(dot(H,N),Glossy))
Phong 的高光计算量
Phong 当中的 R R R 使用的 GLSL reflect 函数其内部实现是: I − 2 ⋅ d o t ( N , I ) ⋅ N I-2 \cdot dot(N,I) \cdot N I−2⋅dot(N,I)⋅N,其中 I I I是光源入射角向量, N N N 是物体表面法线向量。
这个 reflect 的计算量有:1次减法、2次乘法、和1个 dot,而 dot 内部是就向量点积,举个2维向量例子: d o t ( v e c 2 a , v e c 2 b ) = a . x ∗ b . x + a . y ∗ b . y dot(vec2\space a, vec2\space b) = a.x * b.x + a.y * b.y dot(vec2 a,vec2 b)=a.x∗b.x+a.y∗b.y,所以1个 dot 有2个乘法和1一个加法。
那么以2维的 reflect 的基础计算量有:1次减法(2分量)、1次标量乘法,1次标量与向量N乘法(2分量),1次加法(2分量)(这里还没算个每个分量的计算),运算量可不少了。
Blinn-Phong 的高光计算量
那么接下来介绍的是 Blinn-Phong 中的 H H H,它是由:光源方向 与 观察者方向 之和的单位向量, H = n o r m a l i z e ( L + V ) H=normalize(L+V) H=normalize(L+V),从式子得知,有1次加法,还有1个 normalize 函数,normalize 的计算方式是,以2维向量为例: n o r m a l i z e ( v e c 2 v ) = v s q r t ( d o t ( v , v ) ) = v s q r t ( v . x ∗ v . x + v . y ∗ v . y ) = v ⋅ 1 s q r t ( v . x ∗ v . x + v . y ∗ v . y ) = normalize(vec2\space v) = \frac{v}{sqrt(dot(v,v))}=\frac{v}{sqrt(v.x*v.x+v.y*v.y)}=v\cdot\frac{1}{sqrt(v.x*v.x+v.y*v.y)}= normalize(vec2 v)=sqrt(dot(v,v))v=sqrt(v.x∗v.x+v.y∗v.y)v=v⋅sqrt(v.x∗v.x+v.y∗v.y)1=,有1个dot和一个sqrt,dot 在上面介绍 Phong 时有说明。也就说, H H H的计算量有:2次加法、2次乘法,1次 sqrt,还有1次除法,而其中,sqrt 与除法计算量也是不少的,这两种运算在图形学中基本是能避免就避免,能减少就减少的 (例如:先求某个系数的倒数,再用向量乘以该系数的倒数,来减少除法)。所以 目测 比 Phong 的计算量还高,不过目测什么的也是不准的,需要巨量的运算用时比较才知道呢,那么这些测试这里就不去做了,有兴趣同学可以去测试一下。
但,如果我们将一些必要的向量,先在 VS 中算好,再传到 FS,那么就可以避免片段过多导致的消耗,如上面所将的 H H H 也可以放再 VS 算好再传到 FS
那么 目测 的性能比较就到这吧,接下来看一下他们在视觉上的效果有什么样的区别。
在 GGB 看看数值效果
留意这个值的变化。
其中 d o t dot dot 在 Phong 光照模型我有详细的介绍。
H = n o r m a l i z e ( L + V ) H=normalize(L+V) H=normalize(L+V) 在我移动 L 、 V L、V L、V 过程中可以看到 H H H 的变化,也可以看到 d o t ( H , N ) dot(H,N) dot(H,N) 的变化。
看下 GIF 效果
Blinn Phong in vs
// jave.lin - testing_blinn_phong_only_specular_shading_in_vs.vert
#version 450 compatibility
// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标
// object uniform
uniform float Glossy; // 光滑度
// light uniform
uniform vec4 LightPos; // 灯光世界坐标位置,w==0,或名是方向光,w==1说明是点光源,w == 0.5 是聚光灯
// transform matrix uniform
uniform mat4 mMat; // model matrix
uniform mat4 mvpMat; // m.v.p 矩阵
uniform mat4 IT_mMat; // model matrix 的逆矩阵的转置
// vertex data
attribute vec3 vPos; // 顶点坐标
attribute vec3 vNormal; // 顶点法线
// vertex data - interpolation
varying vec3 fCol; // 片段插值颜色
// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
return normalize(mat3(IT_mMat) * n); // 等价于:transpose(I_mMat) * vec4(n, 0)
}
void main() {
vec3 worldPos = (mMat * vec4(vPos, 1.0)).xyz; // 世界坐标
vec3 worldNormal = ObjectToWorldNormal(vNormal); // 获取世界坐标下的法线
vec3 viewDir = normalize(_CamWorldPos - worldPos); // 顶点坐标 指向 镜头坐标 的方向
float LdotN = dot(LightPos.xyz, worldNormal);
vec3 H = normalize(LightPos.xyz + viewDir); // H = normalize(L + V)
float HdotN = max(0, dot(H, worldNormal)); // 注意这里 Phong 是 dot(R,V),而 BlinnPhong 是 dot(H,N)
float S = 0;
if (LdotN > 0) {
S = pow(HdotN, Glossy);
}
fCol = vec3(S);
// uv0
gl_Position = mvpMat * vec4(vPos, 1.0);
}
// jave.lin - testing_blinn_phong_only_specular_shading_in_vs.frag
#version 450 compatibility
// interpolation - 插值数据
varying vec3 fCol; // 片段插值颜色
void main() {
gl_FragColor = vec4(fCol, 1.0);
}
Blinn Phong in fs
// jave.lin - testing_blinn_phong_only_specular_shading_in_vs.vert
#version 450 compatibility
// transform matrix uniform
uniform mat4 mMat; // m.v.p 矩阵
uniform mat4 vMat;
uniform mat4 pMat;
uniform mat4 IT_mMat; // model matrix 的逆矩阵的转置矩阵
// vertex data
attribute vec3 vPos; // 顶点坐标
attribute vec2 vUV0; // 顶点纹理坐标
attribute vec3 vNormal; // 顶点法线
// vertex data - interpolation
varying vec2 fUV0; // 给 fragment shader 传入的插值
varying vec3 fNormal; // 世界坐标顶点法线
varying vec3 fWorldPos; // 世界坐标
// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
return normalize(mat3(IT_mMat) * n); // 等价于:transpose(I_mMat) * vec4(n, 0)
}
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_blinn_phong_only_specular_shading_in_vs.frag
#version 450 compatibility
// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标
// object uniform
uniform float Glossy; // 光滑度
// light uniform
uniform vec4 LightPos; // 灯光世界坐标位置,w==0,或名是方向光,w==1说明是点光源,w == 0.5 是聚光灯
// interpolation - 插值数据
varying vec2 fUV0; // uv 坐标
varying vec3 fNormal; // 顶点法线
varying vec3 fWorldPos; // 世界坐标
void main() {
vec3 worldNormal= normalize(fNormal); // 世界坐标法线再次归一化一次,因为插值之后可能会导致不是归一化的值
vec3 viewDir = normalize(_CamWorldPos - fWorldPos); // 顶点坐标 指向 镜头坐标 的方向
float S = 0;
if (LightPos.w == 0) {
// 下面使用的是Phong 光照模型
// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向
float LdotN = dot(LightPos.xyz, worldNormal);
vec3 H = normalize(LightPos.xyz + viewDir);
float HdotN = max(0, dot(H, worldNormal));
if (LdotN > 0) {
S = pow(HdotN, Glossy);
}
} else {
// 点光源 或是 聚光灯
if (LightPos.w == 1) {
// 点光
} else { // LightPos.w == 0.5,即:LightPos.w !=0 && LightPos.w != 1
// 聚光灯
}
}
gl_FragColor = vec4(S);
}
运行效果
气球猫的效果
可以看到 Gouraud 的右手臂上的高光没那么平滑, Phong 的高光比较细小,Blinn Phong 的高光比较混大、更加平滑似的。
其实这个气球猫的顶点有 4700+ 个,算比较多的,可以使用 Blinn Phong 在 VS 中处理,效果还不错,性能也还行。
如果顶点数、面数比较少,则在 VS 中处理光照会有比较明显的三角面块的不平滑过渡问题,但这个气球猫面数不算少,所以可以看到 Gouraud 模型中的效果也不会太差,但如果想上面我们实验用的球体,面数不够,那么光照效果用 Gouraud 模型的话,效果就不太好了。
环境光
之前各种 Phong 中,都没有详细看看效果,其实按 Phong 模型的环境光的效果真的不好
Phong 中环境光效果
因为 Phong 中是直接加环境光叠加上结果的,效果极差,如下图:
特别是本身没什么光照的地方,更加的差的效果,如,背光部分,如下GIF:
效果真的是差!
呐,看看ambient的处理 最终是叠加的
gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
在背光的时候,环境光相当于直接赋值颜色,而看不到物体本身的 反射率 和 吸收率 的效果了(即:纹理效果)
稍微调整一下环境光的处理
为了让 ambient 效果好,可是看到 反射率 和 吸收率 的效果(即:纹理效果),我们稍微调整一下 Phong 模型: I = I a ⋅ K a + ∑ i = 0 n ( I d ⋅ K d ⋅ d o t ( N , L i ) + I s ⋅ K s ⋅ p o w ( d o t ( r e f l e c t ( − L i , N ) , V ) , G l o s s y ) ) I=\red{I_a\cdot K_a}+\sum_{i=0}^n(I_d\cdot K_d\cdot dot(N,L_i) + I_s\cdot K_s\cdot pow(dot(reflect(-L_i,N),V),Glossy)) I=Ia⋅Ka+∑i=0n(Id⋅Kd⋅dot(N,Li)+Is⋅Ks⋅pow(dot(reflect(−Li,N),V),Glossy))
调整为: I = l e r p ( I a ⋅ K a , a l b e d o , K a ) + ∑ i = 0 n ( I d ⋅ K d ⋅ d o t ( N , L i ) + I s ⋅ K s ⋅ p o w ( d o t ( r e f l e c t ( − L i , N ) , V ) , G l o s s y ) ) I=\red{lerp(I_a\cdot K_a, albedo, K_a)}+\sum_{i=0}^n(I_d\cdot K_d\cdot dot(N,L_i) + I_s\cdot K_s\cdot pow(dot(reflect(-L_i,N),V),Glossy)) I=lerp(Ia⋅Ka,albedo,Ka)+∑i=0n(Id⋅Kd⋅dot(N,Li)+Is⋅Ks⋅pow(dot(reflect(−Li,N),V),Glossy))
I a ⋅ K a → l e r p ( I a ⋅ K a , a l b e d o , K a ) \red{I_a\cdot K_a} \rightarrow \red{lerp(I_a\cdot K_a, albedo, K_a)} Ia⋅Ka→lerp(Ia⋅Ka,albedo,Ka)
对应代码如下:
vec3 ambient;
if (AmbientType == 0) { // 原始 phong 模型
ambient = _Ambient.rgb * _Ambient.a;
} else {
ambient = mix(_Ambient.rgb * _Ambient.a, albedo, _Ambient.a); // 这个是我们调整过的模型
}
我们在编辑器做了一个开关:Ambient_Tweak
的复选框
那么来看看运行效果
留意上面GIF中 Inspector Panel 中的 Materials 栏中的 Amibnet_Tweak 的 开、关效果,关的时候就是原始 Phong 的效果,开的时候就是调整过的效果。
完整的气球猫 三种 Shader
Phong Shader
// jave.lin - testing_load_balloon_cat_mesh_shading.vert
#version 450 compatibility
// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标
// scene uniform
uniform vec4 _Ambient; // .xyz 环境光颜色, .w 环境光系数
// 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; // 灯光类型为聚光灯的方向
// transform matrix uniform
uniform mat4 mMat; // m.v.p 矩阵
uniform mat4 vMat;
uniform mat4 pMat;
uniform mat4 IT_mMat; // model matrix 的逆矩阵的转置矩阵
// vertex data
attribute vec3 vPos; // 顶点坐标
attribute vec2 vUV0; // 顶点纹理坐标
attribute vec3 vNormal; // 顶点法线
// vertex data - interpolation
varying vec2 fUV0; // 给 fragment shader 传入的插值
varying vec3 fAmbient; // 环境光
varying vec3 fDiffuse; // 漫反射颜色
varying vec3 fSpecular; // 高光颜色
// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
return normalize(mat3(IT_mMat) * n); // 等价于:transpose(I_mMat) * vec4(n, 0)
}
void main() {
vec4 worldPos = mMat * vec4(vPos, 1.0); // 世界坐标
vec3 viewDir = normalize(_CamWorldPos - worldPos.xyz); // 顶点坐标 指向 镜头坐标 的方向
vec3 worldNormal = ObjectToWorldNormal(vNormal); // 获取世界坐标下的法线
// phong shading
// ambient
fAmbient = _Ambient.xyz * _Ambient.w;
if (LightPos.w == 0) {
// 下面使用的是Phong 光照模型
// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向
float D = max(0, dot(LightPos.xyz, worldNormal));
fDiffuse = LightColor.rgb * LightColor.a * D * DiffuseK;
vec3 R = reflect(-LightPos.xyz, worldNormal);
float S = 0;
if (D > 0) S = pow(max(0, dot(R, viewDir)), Glossy);
fSpecular = LightColor.rgb * LightColor.a * S * SpecularK;
} else {
// 点光源 或是 聚光灯
if (LightPos.w == 1) {
// 点光
} else { // LightPos.w == 0.5,即:LightPos.w !=0 && LightPos.w != 1
// 聚光灯
}
}
// uv0
fUV0 = vUV0;
gl_Position = pMat * vMat * worldPos;
}
// jave.lin - testing_load_balloon_cat_mesh_shading.frag
#version 450 compatibility
uniform vec4 _Ambient; // 环境光
uniform int AmbientType; // 环境光类别,测试用
// interpolation - 插值数据
varying vec2 fUV0; // uv 坐标
varying vec3 fAmbient; // 环境光
varying vec3 fDiffuse; // 漫反射颜色
varying vec3 fSpecular; // 高光颜色
// local uniform
uniform sampler2D main_tex; // 主纹理
void main() {
vec3 albedo = texture(main_tex, fUV0).rgb;
vec3 ambient;
if (AmbientType == 0) {
ambient = _Ambient.rgb * _Ambient.a;
} else {
ambient = mix(_Ambient.rgb * _Ambient.a, albedo, _Ambient.a);
}
gl_FragColor = vec4(ambient + albedo * fDiffuse + fSpecular, 1.0);
}
Gouraud Shader
// jave.lin - testing_balloon_cat_gouraud_phong_shading.vert
#version 450 compatibility
// transform matrix uniform
uniform mat4 mMat; // m.v.p 矩阵
uniform mat4 vMat;
uniform mat4 pMat;
uniform mat4 IT_mMat; // model matrix 的逆矩阵的转置矩阵
// vertex data
attribute vec3 vPos; // 顶点坐标
attribute vec2 vUV0; // 顶点纹理坐标
attribute vec3 vNormal; // 顶点法线
// vertex data - interpolation
varying vec2 fUV0; // 给 fragment shader 传入的插值
varying vec3 fNormal; // 世界坐标顶点法线
varying vec3 fWorldPos; // 世界坐标
// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
return normalize(mat3(IT_mMat) * n); // 等价于:transpose(I_mMat) * vec4(n, 0)
}
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_balloon_cat_gouraud_phong_shading.frag
#version 450 compatibility
// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标
uniform vec4 _Ambient; // 环境光
// object uniform
uniform float Glossy; // 光滑度
uniform vec3 DiffuseK; // 漫反射系数
uniform vec3 SpecularK; // 高光系数
uniform int AmbientType; // 环境光类别,测试用
// light uniform
uniform vec4 LightPos; // 灯光世界坐标位置,w==0,或名是方向光,w==1说明是点光源,w == 0.5 是聚光灯
uniform vec4 LightColor; // 灯光颜色,.xyz 顔色,.w 强度
// interpolation - 插值数据
varying vec2 fUV0; // uv 坐标
varying vec3 fNormal; // 顶点法线
varying vec3 fWorldPos; // 世界坐标
uniform sampler2D main_tex;
void main() {
vec3 albedo = texture(main_tex, fUV0).rgb;
vec3 worldNormal= normalize(fNormal); // 世界坐标法线再次归一化一次,因为插值之后可能会导致不是归一化的值
vec3 viewDir = normalize(_CamWorldPos - fWorldPos); // 顶点坐标 指向 镜头坐标 的方向
vec3 ambient;
if (AmbientType == 0) {
ambient = _Ambient.rgb * _Ambient.a;
} else {
ambient = mix(_Ambient.rgb * _Ambient.a, albedo, _Ambient.a);
}
vec3 diffuse = vec3(0);
vec3 specular= vec3(0);
if (LightPos.w == 0) {
// 下面使用的是Phong 光照模型
// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向
float D = max(0, dot(LightPos.xyz, worldNormal));
diffuse = LightColor.rgb * LightColor.a * D * DiffuseK * albedo;
vec3 R = reflect(-LightPos.xyz, worldNormal);
float S = 0;
if (D > 0) S = pow(max(0, dot(R, viewDir)), Glossy);
specular = LightColor.rgb * LightColor.a * S * SpecularK;
} else {
// 点光源 或是 聚光灯
if (LightPos.w == 1) {
// 点光
} else { // LightPos.w == 0.5,即:LightPos.w !=0 && LightPos.w != 1
// 聚光灯
}
}
gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
}
Blinn Phong Shader
// jave.lin - testing_balloon_cat_blinn_phong_shading_in_fs.vert
#version 450 compatibility
// transform matrix uniform
uniform mat4 mMat; // m.v.p 矩阵
uniform mat4 vMat;
uniform mat4 pMat;
uniform mat4 IT_mMat; // model matrix 的逆矩阵的转置矩阵
// vertex data
attribute vec3 vPos; // 顶点坐标
attribute vec2 vUV0; // 顶点纹理坐标
attribute vec3 vNormal; // 顶点法线
// vertex data - interpolation
varying vec2 fUV0; // 给 fragment shader 传入的插值
varying vec3 fNormal; // 世界坐标顶点法线
varying vec3 fWorldPos; // 世界坐标
// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
return normalize(mat3(IT_mMat) * n); // 等价于:transpose(I_mMat) * vec4(n, 0)
}
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_balloon_cat_blinn_phong_shading_in_fs.frag
#version 450 compatibility
// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标
uniform vec4 _Ambient; // 环境光
// object uniform
uniform float Glossy; // 光滑度
uniform vec3 DiffuseK; // 漫反射系数
uniform vec3 SpecularK; // 高光系数
uniform int AmbientType; // 环境光类别,测试用
// light uniform
uniform vec4 LightPos; // 灯光世界坐标位置,w==0,或名是方向光,w==1说明是点光源,w == 0.5 是聚光灯
uniform vec4 LightColor; // 灯光颜色,.xyz 顔色,.w 强度
// interpolation - 插值数据
varying vec2 fUV0; // uv 坐标
varying vec3 fNormal; // 顶点法线
varying vec3 fWorldPos; // 世界坐标
uniform sampler2D main_tex;
void main() {
vec3 albedo = texture(main_tex, fUV0).rgb;
vec3 worldNormal= normalize(fNormal); // 世界坐标法线再次归一化一次,因为插值之后可能会导致不是归一化的值
vec3 viewDir = normalize(_CamWorldPos - fWorldPos); // 顶点坐标 指向 镜头坐标 的方向
vec3 ambient;
if (AmbientType == 0) {
ambient = _Ambient.rgb * _Ambient.a;
} else {
ambient = mix(_Ambient.rgb * _Ambient.a, albedo, _Ambient.a);
}
vec3 diffuse = vec3(0);
vec3 specular= vec3(0);
if (LightPos.w == 0) {
// 下面使用的是Phong 光照模型
// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向
float D = max(0, dot(LightPos.xyz, worldNormal));
diffuse = LightColor.rgb * LightColor.a * D * DiffuseK * albedo;
vec3 H = normalize(LightPos.xyz + viewDir);
float S = 0;
if (D > 0) S = pow(max(0, dot(H, worldNormal)), Glossy);
specular = LightColor.rgb * LightColor.a * S * SpecularK;
} else {
// 点光源 或是 聚光灯
if (LightPos.w == 1) {
// 点光
} else { // LightPos.w == 0.5,即:LightPos.w !=0 && LightPos.w != 1
// 聚光灯
}
}
gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
}