US3S1L2-1——兰伯特光照模型的逐顶点光照

知识回顾

兰伯特光照模型公式:

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. 材质漫反射颜色属性声明

    1
    2
    3
    4
    Properties
    {
    _MainColor("MainColor", Color) = (1, 1, 1, 1)
    }
  2. 渲染标签 Tags​ 设置:将 LightMode​ 光照模式 设置为 ForwardBase​ 向前渲染(通常用于不透明物体的基本渲染)

    1
    Tags { "LightMode"="ForwardBase" }
  3. 引用内置文件 UnityCG.cginc​ 和 Lighting.cginc​

    1
    2
    #include "UnityCG.cginc"
    #include "Lighting.cginc"
  4. 结构体声明

    对于输入到顶点着色器的参数,可以直接使用 UnityCG.cginc​ 的 appdata_base​

    对于从顶点着色器输出到片元着色器的参数,需要我们自己声明结构体 v2f

    1
    2
    3
    4
    5
    struct v2f
    {
    float4 pos : SV_POSITION;
    fixed3 color : COLOR; //这里只传递颜色的rgb,而不传递透明度,故使用fixed3
    };
  5. 基于公式实现逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    fixed4 _MainColor;  //材质的漫反射颜色

    v2f vert (appdata_base v)
    {
    v2f v2fData;
    v2fData.pos = UnityObjectToClipPos(v.vertex); //首先将模型空间下的顶点转换到裁剪空间下

    float3 normal = UnityObjectToWorldNormal(v.normal); //将模型空间下的法线转换到世界空间下
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //光源0在世界坐标系下的位置标准化,用于和法线计算夹角

    //兰伯特光照模型的实现,这里的颜色计算只取rgb,不考虑透明度的情况
    fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(normal, lightDir));
    v2fData.color = color; //将计算完毕的颜色赋值给结构体成员

    return v2fData; //将顶点着色器计算结果传递给片元着色器
    }

    fixed4 frag (v2f i) : SV_Target
    {
    return fixed4(i.color.rgb, 1); //因为传递过来的颜色变量不包括透明度,因此这里需要手动指定透明度
    }

使用这样的Shader的材质显示效果如下:

image

可以看到,被光源照射到的地方会显示颜色,而阴影部分是全黑的

注意:为了阴影出不全黑,需要加上兰伯特环境光颜色公共变量 UNITY_LIGHTMODEL_AMBIENT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v2f vert (appdata_base v)
{
v2f v2fData;
v2fData.pos = UnityObjectToClipPos(v.vertex); //首先将模型空间下的顶点转换到裁剪空间下

float3 normal = UnityObjectToWorldNormal(v.normal); //将模型空间下的法线转换到世界空间下
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //光源0在世界坐标系下的位置标准化,用于和法线计算夹角

//兰伯特光照模型的实现,这里的颜色计算只取rgb,不考虑透明度的情况
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(normal, lightDir));
v2fData.color = UNITY_LIGHTMODEL_AMBIENT.rgb + color; //为了让阴影不全黑,需要加上兰伯特环境光颜色公共变量

return v2fData; //将顶点着色器计算结果传递给片元着色器
}s

使用这样的Shader的材质显示效果如下:

image

可以看到,阴影部分不再是全黑色的

完整的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
Shader "TeachShader/Lesson29"
{
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;
fixed3 color : COLOR; //这里只传递颜色的rgb,而不传递透明度,故使用fixed3
};

v2f vert (appdata_base v)
{
v2f v2fData;
v2fData.pos = UnityObjectToClipPos(v.vertex); //首先将模型空间下的顶点转换到裁剪空间下

float3 normal = UnityObjectToWorldNormal(v.normal); //将模型空间下的法线转换到世界空间下
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //光源0在世界坐标系下的位置标准化,用于和法线计算夹角

//兰伯特光照模型的实现,这里的颜色计算只取rgb,不考虑透明度的情况
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(normal, lightDir));
v2fData.color = UNITY_LIGHTMODEL_AMBIENT.rgb + color; //为了让阴影不全黑,需要加上兰伯特环境光颜色公共变量

return v2fData; //将顶点着色器计算结果传递给片元着色器
}

fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color.rgb, 1); //因为传递过来的颜色变量不包括透明度,因此这里需要手动指定透明度
}
ENDCG
}
}
}