Lambert光照模型

一、Lambert光照模型理论

在现实生活中,一个物体的颜色是由射进眼镜里的光线决定的。

当光线射到物体表面时,一部分被物体表面吸收,另一部分被反射。对于透明物体而言,还有一部分光会透过物体,产生透射光。

被物体吸收的光会产生热量,所以只有反射光和透射光才能进入人眼,从而产生视觉效果。

物体表面的光照颜色由入射光、物体材质以及材质和光的交互规律觉定。

Lambert光照模型可以比较真实地还原粗糙物体表面与光的交互行为,且计算效率非常高,被广泛使用。

当光线照射到物体表面粗糙的物体,例如:石灰墙壁、纸张等,光线会向各个方向等强度的反射,即光的漫反射现象(Diffuse)。

漫反射满足Lambert定律:反射光线强度与表面法线和光源方向之间的夹角呈正比。产生漫反射的物体表面称为理想漫反射体。

漫反射现象

Lambert光照模型计算公式

Cdiffuse=(ClightMdiffuse)saturate(nl)C_{diffuse}=(C_{light} \cdot M_{diffuse})saturate(n \cdot l)

其中:

·CdiffuseC_{diffuse}:物体的漫反射颜色

·ClightC_{light}:入射光线的颜色

·MdiffuseM_{diffuse}:物体材质的漫反射颜色

·nn:物体表面法线

·ll:物体指向灯光的方向

根据点积数学运算可知:表面法线与灯光方向之间的夹角越小,点积结果越大,漫反射越强。即当灯光方向与表面法线夹角为 0°,漫反射强度达到最大值;相反,当灯光方向与表面法线夹角为90°,漫反射强度达到最小值。

同时为了避免负值的出现,使用CG数学函数saturate()将点积结果截取到[0,1]的区间范围内。

二、在Shader中获取灯光变量

灯光参数如何传递给Shader取决于当前Unity项目使用的渲染路径(Rendering Path),以及Shader中用于灯光模式的Pass Tag。

依次点击Edit->Project Settings->Graphics

Graphics

从图中可知,Unity默认将Rendering Path设置成了Forward。

前向渲染中可以使用的灯光属性变量
变量 类型 说明
_LightColor0 fixed4 灯光的颜色乘上亮度,在UnityLIghtingCommon.cginc中被声明
_WorldSpaceLightPos0 float4 平行光属性:float4(世界空间灯光方向,0),其他灯光属性:float4(世界空间灯光位置,1)
_LightMatrix0 float4x4 世界到灯光的变换矩阵,用于采样灯光cookie和衰减贴图,在AutoLight.cginc中被声明
unity_4LightPosX0,
unity_4LightPosY0,
unity_4LightPosZ0
float4 前四个非重要点光在世界空间的位置,只能用于ForwardBasepass
unity_4LightAtten0 float4 前四个非重要点光的衰减系数,只能用于ForwardBasepass
unity_LightColor half4[4] 前四个非重要点光的颜色,只能用于ForwardBasepass
unity_WorldToShadow float4x4[4] 世界岛阴影的变换矩阵,一个用于聚光灯矩阵,最多4个用于串联平行光的矩阵

三、基于Lambert光照模型的Shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Shader "Custom/Lambert"
{
Properties
{
_MainColor("Main Color",Color)=(1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
//声明包含灯光变量的文件
#include "UnityLightingCommon.cginc"

struct v2f
{
float4 pos:SV_POSITION;
fixed4 dif:COLOR0;
};

fixed4 _MainColor;

v2f vert(appdata_base v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);

//法线向量
float3 n=UnityObjectToWorldNormal(v.normal);
n=normalize(n);

//灯光方向向量
fixed3 l=normalize(_WorldSpaceLightPos0.xyz);

//按照公式计算漫反射
fixed ndot1=dot(n,l);
o.dif=_LightColor0*_MainColor*saturate(ndot1);

return o;
}

fixed4 frag(v2f i):SV_Target
{
return i.dif;
}

ENDCG
}
}
}

代码讲解

(1)灯光颜色变量:在Shader中使用了灯光颜色变量_LightColor0,所以需要声明UnityLightingCommon.cginc

(2)法线向量:使用内置的appdata_base结构体输入到顶点着色器。因为计算光照时要保证物体的法线向量和灯光的方向向量在同一空间,而输入到着色器顶点的法线向量是在模型空间中,灯光的方向向量在世界空间中,所以需要将法线向量变换到世界空间,并进行标准化处理得到 nn

1
2
3
//法线向量
float3 n=UnityObjectToWorldNormal(v.normal);
n=normalize(n);

(3)灯光向量:世界空间平行光方向_WorldSpaceLightPos0标准化后得到 ll

1
2
//灯光方向向量
fixed3 l=normalize(_WorldSpaceLightPos0.xyz);

渲染效果图:

Lambert渲染效果

四、Half-Lambert光照模型

Lambert光照模型有一个明显的缺点,就是物体背光面完全是黑的,看不到任何表面细节。

于是则有了一种基于Lambert进行算法优化的Half-Lambert光照模型。

Half-Lambert光照模型的计算公式

Cdiffuse=(ClightMdiffuse)[0.5(nl)+0.5]C_{diffuse}=(C_{light} \cdot M_{diffuse})[0.5(n \cdot l)+0.5]

表面法线和光照方向向量的点积并不是直接截取到区间[0,1],而是先乘0.5,将数值区间缩小到[-0.5,0.5],然后加上0.5,将区间移动到[0,1],这样光线强度就会逐渐从最亮的迎光面逐渐过渡到最暗的背光面。

只需要修改vert函数部分代码即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
v2f vert(appdata_base v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);

//法线向量
float3 n=UnityObjectToWorldNormal(v.normal);
n=normalize(n);

//灯光方向向量
fixed3 l=normalize(_WorldSpaceLightPos0.xyz);

//按照公式计算漫反射
fixed ndot1=dot(n,l);
o.dif=_LightColor0*_MainColor*(ndot1*0.5+0.5);

return o;
}

二者渲染图对比:

正面对比

背面阴影对比

相比之下,Half-Lambert背部多了许多细节,不是全黑的了,并且过渡更加柔和自然。