基础光照模型(Phong光照模型)
Phong光照模型是描述物体的直接光照的简易模型,它认为从物体出发进入摄像机的光由四部分组成:自发光(emissive),环境光(ambient),漫反射(diffuse),高光(specular)。
c
=
c
e
m
i
s
s
i
v
e
+
c
a
m
b
i
e
n
t
+
c
d
i
f
f
u
s
e
+
c
s
p
e
c
u
l
a
r
c = c_{emissive} + c_{ambient} + c_{diffuse} + c_{specular}
c=cemissive+cambient+cdiffuse+cspecular
自发光
物体自身发出的光,使用物体材质的自发光颜色。在未使用全局光照的情况下不会照亮周围的物体,只是让自己看起来更亮。
c
e
m
i
s
s
i
v
e
=
m
e
m
i
s
s
i
v
e
c_{emissive} = m_{emissive}
cemissive=memissive
环境光
模拟物体受到环境中的间接光照,通常是个全局变量。
c
a
m
b
i
e
n
t
=
g
a
m
b
i
e
n
t
c_{ambient} = g_{ambient}
cambient=gambient
漫反射
模拟光线在物体表面随机反射,会根据光源方向和表面法线的不同有强度差异。
漫反射的计算符合兰伯特定律(Lambert’s law),即反射光线的强度与光源方向和表面法线之间的夹角余弦成正比。
c
d
i
f
f
u
s
e
=
c
l
i
g
h
t
⋅
m
d
i
f
f
u
s
e
⋅
m
a
x
(
0
,
n
⃗
⋅
l
⃗
)
c_{diffuse} = c_{light} · m_{diffuse} ·max (0, \vec n · \vec l)
cdiffuse=clight⋅mdiffuse⋅max(0,n⋅l)
高光
模拟表面光滑的物体在某些特定角度发生的强烈反射光。反射光的强度由视线方向和反射方向的夹角大小决定。
反射向量的计算公式如下:
r
=
2
⋅
(
n
⃗
⋅
l
⃗
)
⋅
n
⃗
−
l
⃗
r = 2 · (\vec n · \vec l) · \vec n - \vec l
r=2⋅(n⋅l)⋅n−l
得到反射方向后,高光反射公式如下:
c
s
p
e
c
u
l
a
r
=
c
l
i
g
h
t
⋅
m
s
p
e
c
u
l
a
r
⋅
(
m
a
x
(
0
,
v
⃗
⋅
r
⃗
)
)
m
g
l
o
s
s
c_{specular} = c_{light} · m_{specular} · (max(0, \vec v · \vec r))^{m_{gloss}}
cspecular=clight⋅mspecular⋅(max(0,v⋅r))mgloss
公式中
m
g
l
o
s
s
m_{gloss}
mgloss是材质的光泽度,用于控制高光区域的“亮点”有多大。
m
g
l
o
s
s
m_{gloss}
mgloss越大,视线方向和反射方向的夹角变大时反光强度衰减越快,因而反光面积越小。
在Unity Shader中实现基础光照模型
在shader中使用逐像素的方式计算光照。
首先在Properties语义块中声明材质的参数:漫反射颜色(Diffuse),高光颜色(Specular),光泽度(Gloss)。
Properties
{
_Diffuse("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0)
_Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
_Gloss("Gloss", Range(8.0, 256)) = 20
}
其中,_Diffuse是在该shader中的变量名,Diffuse是在Unity的Shader面板上显示的名字,Color是变量类型,等号右边的值是初始值,可在Unity的Shader面板上修改。
接下来,在SubShader语义块中定义一个Pass语义块,在里面设置光照模式。
SubShader
{
Pass
{
Tags {"LightMode" = "ForwardBase"} // 此处含义和作用暂不深究,等学到了再说
}
}
然后使用CGPROGRAM和ENDCG来包围CG代码片段,我们要在其中定义顶点着色器和片元着色器。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 包含了需要使用的Unity内置变量
ENDCG
为了使用Properties中声明的属性,我们需要定义和这些属性类型相匹配的变量。
fixed4 _Specular;
fixed4 _Diffuse;
float _Gloss;
接下来定义顶点着色器的输入和输出结构体。顶点着色器的输出结构体同时也是片元着色器的输入结构体。
struct a2v
{
float4 pos : POSITION;
fixed3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
在顶点着色器中计算顶点在裁剪空间的坐标和法线。
v2f vert(a2v i)
{
v2f o;
o.pos = UnityObjectToClipPos(i.pos);
o.worldPos = mul(unity_ObjectToWorld,i.pos);
o.worldNormal = normalize(UnityObjectToWorldNormal(i.normal));
return o;
}
在片元着色器中计算光照。
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldReflect = normalize(2 * dot(worldNormal, worldLight) * worldNormal - worldLight);
fixed3 worldView = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldReflect, worldView)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
完整代码如下:
Shader "Custom/Chapter6-SpecularPixelLevel"
{
Properties
{
_Diffuse("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0)
_Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
_Gloss("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Specular;
fixed4 _Diffuse;
float _Gloss;
struct a2v
{
float4 pos : POSITION;
fixed3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};
v2f vert(a2v i)
{
v2f o;
o.pos = UnityObjectToClipPos(i.pos);
o.worldPos = mul(unity_ObjectToWorld, i.pos);
o.worldNormal = normalize(UnityObjectToWorldNormal(i.normal));
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldReflect = normalize(2 * dot(worldNormal, worldLight) * worldNormal - worldLight);
fixed3 worldView = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldReflect, worldView)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果图如下:
光照模型优化
漫反射半兰伯特(Half Lambert)模型
半兰伯特模型用于提高物体亮度。
c
d
i
f
f
u
s
e
=
c
l
i
g
h
t
⋅
m
d
i
f
f
u
s
e
⋅
(
α
⋅
m
a
x
(
0
,
n
⃗
⋅
l
⃗
)
+
β
)
c_{diffuse} = c_{light} · m_{diffuse} ·(\alpha · max (0, \vec n · \vec l) + \beta)
cdiffuse=clight⋅mdiffuse⋅(α⋅max(0,n⋅l)+β)
α
\alpha
α 和
β
\beta
β 是人为赋予的常数,调整它们可以改变物体整体亮度。此处取0.5为默认值,在Properties语义块中声明这两个值,并在Pass语义块中定义它们:
_Alpha("Alpha",Range(0.0, 1.0)) = 0.5
_Beta("Beta", Range(0.0, 1.0)) = 0.5
float _Alpha;
float _Beta;
在片元着色器代码作以下修改:
fixed3 halfLambert = fixed4(0.5, 0.5, 0.5, 1.0) + dot(worldNormal, worldLight) * fixed4(0.5, 0.5, 0.5, 1.0);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(halfLambert);
修改后效果如下:
Blinn-Phong 光照模型
Blinn在原有的基础光照模型基础上优化了高光反射的算法。这种算法不用计算反射光线,而是用到一个新的半程向量
h
⃗
\vec h
h,由视角方向和光线方向相加后归一化所得:
h
⃗
=
v
⃗
+
l
⃗
∣
v
⃗
+
l
⃗
∣
\vec h = \frac {\vec v + \vec l}{\vert \vec v + \vec l \vert}
h=∣v+l∣v+l
Blinn模型计算高光反射的公式如下:
c
s
p
e
c
u
l
a
r
=
c
l
i
g
h
t
⋅
m
s
p
e
c
u
l
a
r
⋅
(
m
a
x
(
0
,
n
⃗
⋅
h
⃗
)
)
m
g
l
o
s
s
c_{specular} = c_{light} · m_{specular} · (max(0, \vec n · \vec h))^{m_{gloss}}
cspecular=clight⋅mspecular⋅(max(0,n⋅h))mgloss
Shader中只需修改片元着色器:
float3 worldHalf = normalize(worldView + worldLight);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(i.worldNormal, worldHalf)), _Gloss);
坑点总结
1. 高光反射
r
⃗
\vec r
r 的推导
总是觉得反射公式应该是
r
⃗
+
l
⃗
2
=
l
⃗
⋅
c
o
s
θ
\frac {\vec r + \vec l} {2} = \vec l ·cos \theta
2r+l=l⋅cosθ,但
l
⃗
\vec l
l是向量,算出来的投影不与
n
⃗
\vec n
n一个方向,所以应该是
n
⃗
⋅
c
o
s
θ
\vec n · cos \theta
n⋅cosθ。文章来源:https://www.toymoban.com/news/detail-773823.html
2. 逐像素计算高光时v2f传入worldPos
SV_POSITION的语义是裁剪空间的坐标,和世界坐标是两个东西,因此在 v2f 结构中需要用一个 TEXCOORD1 额外存储顶点的世界空间坐标,用于计算视线方向。文章来源地址https://www.toymoban.com/news/detail-773823.html
到了这里,关于Unity Shader学习1:基础光照模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!