1 高光反射
是一种经验模型,并不完全符合现实中的高光发射现象。可用于计算那些沿着完全镜面反射方向被反射的光线。可以让物体看起来比较有光泽,比如金属材质。
可以利用Phong光照模型、Blinn-Phong光照模型计算高光
2 Phong光照模型
2.1 Phong模型公式
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 , d o t ( V ⃗ , R ⃗ ) ) M g l o s s C_{specular}=(C_{light} \cdot M_{specular})max(0, dot(\vec{V},\vec{R}))^{M_{gloss}} Cspecular=(Clight⋅Mspecular)max(0,dot(V,R))Mgloss
从以上公式可以看出,要计算高光反射需要知道4个参数:
- 入射光线的颜色和强度 C l i g h t C_{light} Clight
- 材质的高光反射颜色和强度 M s p e c u l a r M_{specular} Mspecular
- 视角方向 V ⃗ \vec{V} V
- 反射方向
R
⃗
\vec{R}
R
除了以上4个参数,还有一个常量 M g l o s s M_{gloss} Mgloss,是材质的光泽度(gloss),也被称为反光度(shininess)。它用于控制高光区域的“亮点”有多宽, M g l o s s M_{gloss} Mgloss越大,亮点就越小。
2.2 反射方向 R ⃗ \vec{R} R计算公式
R ⃗ = 2 ( N ⃗ ⋅ L ⃗ ) N ⃗ − L ⃗ \vec{R}=2(\vec{N} \cdot \vec{L})\vec{N}-\vec{L} R=2(N⋅L)N−L
其中 L ⃗ \vec{L} L为光源方向, N ⃗ 为 平 面 法 线 方 向 \vec{N}为平面法线方向 N为平面法线方向。CG语言中提供了reflect函数可以用来计算反射方向。
2.3 Unity Shader代码
Phong高光反射光照模型(逐像素实现)
// Phong高光反射光照模型 (逐像素实现)
Shader "LShaders/PhongSpecular"
{
Properties
{
_DiffuseColor("Diffuse Color", Color) = (1,1,1,1)
_Specular("Specular Color", Color) = (1,1,1,1)
_Gloss("Gloss", Range(0, 100)) = 1
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Specular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL; // 顶点法线
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 worldNormal: TEXCOORD0;
float3 worldPos: TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 世界空间下的法线
fixed3 worldNormal = i.worldNormal;
// 世界空间下光源方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 半兰伯特光照模型:Clight * Mdiffuse * (dot(worldNormal, worldLightDir) * 0.5 + 0.5)
fixed halfLambert = saturate(dot(worldNormal, worldLightDir) * 0.5 + 0.2);
// 漫反射 = 光源颜色 * 漫反射颜色 * (顶点法线和光源方向的点积,归一化)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * halfLambert;
// 世界空间下的反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 世界空间下的视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// 高光反射 = 光源颜色 * 高光颜色 * (反射方向和视角方向的点积,根据光泽度_Gloss进行幂运算)
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
// 最终颜色 = 环境光 + 漫反射光 + 高光
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
}
3 Blinn-Phong光照模型
Blinn-Phong光照模型与Phong光照模型不同的是,Blinn模型没有使用反射方向,而实引入一个新的矢量 H ⃗ \vec{H} H。
3.1 Blinn-Phong模型公式
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 , d o t ( N ⃗ , H ⃗ ) ) M g l o s s C_{specular}=(C_{light} \cdot M_{specular})max(0, dot(\vec{N},\vec{H}))^{M_{gloss}} Cspecular=(Clight⋅Mspecular)max(0,dot(N,H))Mgloss
公式与Phong模型公式相似,不同的是Phong中是视角方向 V ⃗ \vec{V} V和反射方向 R ⃗ \vec{R} R的点积,Blinn-Phong中则是法线方向 N ⃗ \vec{N} N和矢量 H ⃗ \vec{H} H的点积。
其中矢量 H ⃗ \vec{H} H,是通过对视角方向 V ⃗ \vec{V} V和光照方向 L ⃗ \vec{L} L相加后再归一化得到的。即:
H ⃗ = V ⃗ + L ⃗ ∣ V ⃗ + L ⃗ ∣ \vec{H}= {\vec{V} + \vec{L} \over | \vec{V}+\vec{L}|} H=∣V+L∣V+L
3.2 Unity Shader代码
Blinn-Phong高光反射光照模型(逐像素实现)
// Blinn-Phong高光反射光照模型 (逐像素实现)
Shader "LShaders/BlinnPhongSpecular"
{
Properties
{
_DiffuseColor("Diffuse Color", Color) = (1,1,1,1)
_Specular("Specular Color", Color) = (1,1,1,1)
_Gloss("Gloss", Range(1, 200)) = 50
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Specular;
float _Gloss;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL; // 顶点法线
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 worldNormal: TEXCOORD0;
float3 worldPos: TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 世界空间下的法线
fixed3 worldNormal = i.worldNormal;
// 世界空间下光源方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 半兰伯特光照模型:Clight * Mdiffuse * (dot(worldNormal, worldLightDir) * 0.5 + 0.5)
fixed halfLambert = saturate(dot(worldNormal, worldLightDir) * 0.5 + 0.2);
// 漫反射 = 光源颜色 * 漫反射颜色 * (顶点法线和光源方向的点积,归一化)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * halfLambert;
// 世界空间下的视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// 世界空间下的half方向
fixed3 halfDir = normalize(worldLightDir + viewDir);
// 高光反射 = 光源颜色 * 高光颜色 * (反射方向和视角方向的点积,根据光泽度_Gloss进行幂运算)
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);
// 最终颜色 = 环境光 + 漫反射光 + 高光
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
}