Unity Shader 光照基础之 Half Lambert 光照模型
Half Lambert 模型(也叫作半兰伯特模型)在 Lambert 模型的基础之上做了一些优化。
在 Lambert 模型中,光照无法到达的区域,比如模型的背面,模型外观通常是全黑的,没有任何明暗变化,而 Half Lambert 模型就是改善这一状况。
回顾 Lambert 模型的计算公式如下:
$c_{diffuse} = (c_{light} \cdot m_{diffuse}) \cdot max (0,n \cdot I)$
当光源和法向量夹角的余弦值为负数的时候,所得到的结果始终都是 0 了,所以就会有图中看到的一片黑。
Half Labmert 模型的计算公式如下:
$c_{diffuse} = (c_{light} \cdot m_{diffuse}) \cdot ( a (n \cdot I) + b)$
同样会计算余弦值,但是会把余弦值先缩放 a 倍,再偏移 b 大小,不再用 max 函数避免为负值。
绝大多数情况下,a 和 b 的值都是 0.5,所以也是为什么叫 half 了。
$c_{diffuse} = (c_{light} \cdot m_{diffuse}) \cdot ( 0.5 (n \cdot I) + 0.5)$
余弦值的取值范围是 [-1,1] ,经过缩放和偏移后就是 [0,1] 了,这样一来原本余弦值为负数导致直接取 0 的点,变成了取值结果在 [0,0.5] 了,从而就可以体现出明暗变化,不同的点积结果会映射到不同的值上。
对于 unity shader 的计算,也不会有太大的改变,主要如下:
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed halfLambert = dot(worldNormal,worldLight) * 0.5 + 0.5;
原本用 saturate 函数对点积结果做取值范围约束,现在直接通过映射来保证范围了。
具体的 shader 代码如下,以逐顶点着色为例:
Shader "Custom/HalfLambertShader"
{
Properties
{
_Diffuse ("Diffuse",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
Tags{ "LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Diffuse;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed halfLambert = dot(worldNormal,worldLight) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
o.color = ambient + diffuse;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1.0);
}
ENDCG
}
}
}
渲染效果如下:
可以看到 Half Lambert 模型在背面有了明显的亮暗变化。
同时 Half Lambert 模型在正面也会更加明亮一点。
小结
Half Lambert 模型在 Lambert 基础之上做了优化,而这正是应了图像渲染里面的那就话:如果看起来是对的,那么它就是对的。