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 的计算,也不会有太大的改变,主要如下:

1fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
2fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);                
3fixed halfLambert = dot(worldNormal,worldLight) * 0.5 + 0.5;
CPP

原本用 saturate 函数对点积结果做取值范围约束,现在直接通过映射来保证范围了。

具体的 shader 代码如下,以逐顶点着色为例:

 1Shader "Custom/HalfLambertShader"
 2{
 3   Properties
 4    {
 5        _Diffuse ("Diffuse",Color) = (1,1,1,1)
 6    }
 7    SubShader
 8    {
 9 
10        Pass
11        {
12            Tags{ "LightMode" = "ForwardBase"}
13            CGPROGRAM
14            #pragma vertex vert
15            #pragma fragment frag
16            #include "UnityCG.cginc"
17            #include "Lighting.cginc"
18            fixed4 _Diffuse;
19            struct appdata
20            {
21                float4 vertex : POSITION;
22                float3 normal : NORMAL;
23            };
24            struct v2f
25            {
26                float4 pos : SV_POSITION;
27                fixed3 color : COLOR;
28            };
29            v2f vert (appdata v)
30            {
31                v2f o;
32                o.pos = UnityObjectToClipPos(v.vertex);
33                
34                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
35                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
36                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
37                
38                fixed halfLambert = dot(worldNormal,worldLight) * 0.5 + 0.5;
39                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
40                o.color = ambient + diffuse;
41                return o;
42            }
43            fixed4 frag (v2f i) : SV_Target
44            {
45                return fixed4(i.color,1.0);
46            }
47            ENDCG
48        }
49    }
50}
CPP

渲染效果如下:

可以看到 Half Lambert 模型在背面有了明显的亮暗变化。

同时 Half Lambert 模型在正面也会更加明亮一点。

小结

Half Lambert 模型在 Lambert 基础之上做了优化,而这正是应了图像渲染里面的那就话:如果看起来是对的,那么它就是对的

欢迎关注微信公众号:音视频开发进阶

Licensed under CC BY-NC-SA 4.0
粤ICP备20067247号
使用 Hugo 构建    主题 StackedJimmy 设计,Jacob 修改