US3S1L2-2——兰伯特光照模型的逐片元光照

知识回顾

兰伯特光照模型公式:

Color漫反射光=Color光源×Color材质的漫反射×max(0,标准化后物体表面法线向量标准化后光源方向向量)Color_{漫反射光}=Color_{光源} \times Color_{材质的漫反射} \times \max(0,\overrightarrow{标准化后物体表面法线向量} \cdot \overrightarrow{标准化后光源方向向量})

其中,标准化后物体表面法线向量标准化后光源方向向量\overrightarrow{标准化后物体表面法线向量} \cdot \overrightarrow{标准化后光源方向向量} 等于物体表面法线与光源方向向量的夹角的 cos\cos

会使用的信息:

  1. 光源的颜色

    Lighting.cginc​ 内置文件中的 _LightColor0

  2. 光源的方向

    _WorldSpaceLightPos0​ 表示光源0在世界坐标系下的位置

  3. 向量归一化(标准化)方法 normalize()

  4. 取最大值方法 max()

  5. 点乘方法 dot

  6. 兰伯特光照模型环境光变量(用于模拟环境光对物体的影响,避免物体阴影部分完全黑暗)

    UNITY_LIGHTMODEL_AMBIENT.rgb

  7. 将法线从模型空间转换到世界空间 UnityObjectToWorldNormal

利用兰伯特光照模型实现光照效果(逐片元光照)

关键步骤:基本和逐顶点一致

区别:

  1. 在顶点着色器中计算顶点(转换到裁剪空间)和法线(转换到世界空间)
  2. 在片元着色器中计算兰伯特光照

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
Shader "TeachShader/Lesson30"
{
Properties
{
_MainColor("MainColor", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
#include "Lighting.cginc"

fixed4 _MainColor; //材质漫反射的颜色

struct v2f
{
float4 pos : SV_POSITION;
float3 normal : NORMAL;
};

//顶点着色器函数,主要就是要处理顶点、法线、切线等数据的坐标转换
v2f vert (appdata_base v)
{
v2f v2fData;
v2fData.pos = UnityObjectToClipPos(v.vertex); //将模型空间下的顶点转换到裁剪空间下
v2fData.normal = UnityObjectToWorldNormal(v.normal); //将模型空间下的法线转换到世界空间下
return v2fData;
}

fixed4 frag (v2f i) : SV_Target
{
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //将光源0的位置标准化,得到方向,用于计算夹角
//兰伯特光照模型的实现,这里的颜色计算只取rgb,不考虑透明度的情况
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(i.normal, lightDir));
color = UNITY_LIGHTMODEL_AMBIENT.rgb + color; //为了让阴影不全黑,需要加上兰伯特环境光颜色公共变量
return fixed4(color.rgb, 1); //因为传递过来的颜色变量不包括透明度,因此这里需要手动指定透明度
}
ENDCG
}
}
}

使用逐顶点光照和逐片元光照的Shader的材质显示效果如下(左边为逐顶点光照,右边为逐片元光照):

image

显然,使用逐片元光照的Shader的材质在阴影处的过渡效果,比逐顶点光照的效果更柔和一些