引言
光照模型是 Shader 编程的核心部分,它决定了物体表面如何与光线交互,从而影响最终的视觉效果。在 Unity 中,常见的光照模型包括 Lambert 漫反射模型、Phong 高光反射模型 和 Blinn-Phong 模型。
本文将详细介绍这些光照模型的原理,并展示如何在 Unity Shader 中实现它们。
1. Lambert 光照模型
1.1 基本原理
Lambert 光照模型是最基础的漫反射模型,基于 Lambert 余弦定律,模拟光线在粗糙表面的均匀散射效果。其核心公式为:
I
=
I
0
⋅
cos
(
θ
)
I = I_0 \cdot \cos(\theta)
I=I0⋅cos(θ)
其中:
- I I I 是反射光强度。
- I 0 I_0 I0 是入射光强度。
- θ \theta θ 是光线入射方向与表面法线方向的夹角。
1.2 Unity Shader 实现
以下是 Lambert 光照模型的实现代码:
Shader "LightModel/LambertShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex); // 顶点变换
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 法线变换
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 纹理坐标变换
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 光源方向
float3 normal = normalize(i.worldNormal); // 法线方向
float diff = max(dot(normal, lightDir), 0); // 漫反射强度
fixed4 col = tex2D(_MainTex, i.uv) * _Color * diff; // 最终颜色
return col;
}
ENDCG
}
}
FallBack "Diffuse"
}
Lambert模型实现效果如图:
2. Phong 光照模型
2.1 基本原理
Phong 光照模型在 Lambert 模型的基础上增加了 高光反射,模拟光线在光滑表面的镜面反射效果。其核心公式为:
I
=
I
diffuse
+
I
specular
I = I_{\text{diffuse}} + I_{\text{specular}}
I=Idiffuse+Ispecular
其中:
- I diffuse I_{\text{diffuse}} Idiffuse 是漫反射强度(Lambert 模型)。
-
I
specular
I_{\text{specular}}
Ispecular 是高光反射强度,计算公式为:
I specular = I 0 ⋅ ( R ⋅ V ) n I_{\text{specular}} = I_0 \cdot (\mathbf{R} \cdot \mathbf{V})^n Ispecular=I0⋅(R⋅V)n - R \mathbf{R} R 是反射方向。
- V \mathbf{V} V 是视线方向。
- n n n 是高光指数,控制高光的集中程度。
2.2 Unity Shader 实现
以下是 Phong 光照模型的实现代码:
Shader "LightModel/PhongShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
_SpecularColor("Specular Color", Color) = (1,1,1,1) // 高光颜色
_Gloss("Gloss", Range(1, 256)) = 32 // 高光指数(控制高光集中程度
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2; // 世界空间顶点位置
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
float4 _SpecularColor;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 法线变换
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 顶点变换到世界空间
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 光源方向
float3 normal = normalize(i.worldNormal); // 法线方向
float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); // 视线方向
float3 reflectDir = reflect(-lightDir, normal); // 反射方向
float diff = max(dot(normal, lightDir), 0); // 漫反射强度
float spec = pow(max(dot(reflectDir, viewDir), 0), _Gloss); // 高光强度
fixed4 texColor = tex2D(_MainTex, i.uv); // 纹理颜色
fixed4 col = texColor * _Color * diff + spec; // 最终颜色
return col;
}
ENDCG
}
}
}
Phong模型实现效果如图:
3. Blinn-Phong 光照模型
3.1 基本原理
Blinn-Phong 模型是 Phong 模型的改进版,通过引入 半角向量 简化了高光反射的计算。其核心公式为:
I
=
I
diffuse
+
I
specular
I = I_{\text{diffuse}} + I_{\text{specular}}
I=Idiffuse+Ispecular
其中:
-
I
specular
I_{\text{specular}}
Ispecular 的计算公式为:
I specular = I 0 ⋅ ( N ⋅ H ) n I_{\text{specular}} = I_0 \cdot (\mathbf{N} \cdot \mathbf{H})^n Ispecular=I0⋅(N⋅H)n -
H
\mathbf{H}
H 是半角向量,计算公式为:
H = L + V ∣ L + V ∣ \mathbf{H} = \frac{\mathbf{L} + \mathbf{V}}{|\mathbf{L} + \mathbf{V}|} H=∣L+V∣L+V
# 3.2 Unity Shader 实现
以下是 Blinn-Phong 光照模型的实现代码:
Shader "LightModel/BlinnPhongShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
_SpecularColor("Specular Color", Color) = (1,1,1,1) // 高光颜色
_Gloss("Gloss", Range(1, 256)) = 32 // 高光指数(控制高光集中程度
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2; // 世界空间顶点位置
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
float4 _SpecularColor;
float _Gloss;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 法线变换
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 顶点变换到世界空间
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 光源方向
float3 normal = normalize(i.worldNormal); // 法线方向
float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos); // 视线方向
float3 halfDir = normalize(lightDir + viewDir); // 半角向量
float diff = max(dot(normal, lightDir), 0); // 漫反射强度
float spec = pow(max(dot(normal, halfDir), 0), _Gloss); // 高光强度
fixed4 texColor = tex2D(_MainTex, i.uv); // 纹理颜色
fixed4 col = texColor * _Color * diff + spec; // 最终颜色
return col;
}
ENDCG
}
}
}
Blinn-Phong模型实现效果如图:
4. 光照模型的对比与选择
4.1 对比
- Lambert 模型:简单高效,适合粗糙表面,但缺乏高光反射。
- Phong 模型:增加了高光反射,适合光滑表面,但计算量较大。
- Blinn-Phong 模型:高光计算更高效,适合大多数场景。
4.2 选择建议
- 如果需要快速实现基础光照效果,选择 Lambert 模型。
- 如果需要表现光滑表面的高光效果,选择 Phong 模型 或 Blinn-Phong 模型。
- 在性能要求较高的场景中,优先选择 Blinn-Phong 模型。