US3S1L4-1——Phong式高光反射光照模型的逐顶点光照

知识回顾

公式:

高光反射光照颜色=光源的颜色×材质高光反射颜色×max(0, 标准化后观察方向向量标准化后的反射方向)光泽度高光反射光照颜色 = 光源的颜色 \times 材质高光反射颜色 \times \max(0,\ \overrightarrow{标准化后观察方向向量} \cdot \overrightarrow{标准化后的反射方向})^{光泽度}

  1. 标准化后观察方向向量标准化后的反射方向\overrightarrow{标准化后观察方向向量} \cdot \overrightarrow{标准化后的反射方向} 得到的结果就是 cosθ\cos\theta
  2. 光泽度是幂运算,相当于:(max(0,cosθ))n(max(0,\cos\theta))^n

信息获取:

  1. 观察者的位置(摄像机的位置):_WorldSpaceCameraPos
  2. 相对于法向量的反射向量 方法:reflect(入射向量, 顶点法向量)​ 返回反射向量
  3. 指数幂 方法:pow(底数, 指数)​ 返回计算结果

其他关键信息获取方式和兰伯特光照模型差不多,参考:如何在Shader中获取公式中的关键信息

利用Phong式高光反射模型实现光照效果(逐顶点光照)

关键步骤

  1. 属性声明(材质高光反射颜色、光泽度)

    1
    2
    3
    4
    5
    Properties
    {
    _SpecularColor("SpecularColor", Color) = (1, 1, 1, 1) //材质高光反射颜色
    _SpecularNum("SpecularNum", Range(0, 20)) = 0.5 //光泽度
    }
  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 svPos : SV_POSITION; //裁剪空间下的顶点坐标
    fixed3 color : COLOR; //颜色信息,这里只传递颜色的rgb,而不传递透明度,故使用fixed3
    };
  5. 基本公式实现逻辑

    1. 计算标准化的观察方向向量(使用世界坐标系下的摄像机坐标减去顶点坐标获取,可见:向量减法)
    2. 计算标准化的反射光线向量(使用反射函数 reflect()​ 计算,需要入射光和顶点法线,入射光需要对标准化的光源向量取反得到)
    3. 将光照颜色,高光材质颜色,标准化的观察方向向量,反射光线向量和光泽度代入到公式内
    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
    fixed4 _SpecularColor;  //属性设置的材质高光颜色
    float _SpecularNum; //属性设置的光泽度

    v2f vert (appdata_base v)
    {
    v2f v2fData;
    v2fData.svPos = UnityObjectToClipPos(v.vertex); //首先将模型空间下的顶点转换到裁剪空间下
    //计算标准化的观察方向向量
    float3 worldPos = mul(UNITY_MATRIX_M, v.vertex); //使用UNITY_MATRIX_M矩阵,将模型空间下的顶点转换到世界空间下
    float3 viewDir = _WorldSpaceCameraPos.xyz - worldPos; //将摄像机坐标和顶点坐标通过相减,得到观察方向向量
    viewDir = normalize(viewDir); //将观察空间向量标准化
    //计算标准化的反射光线向量
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //得到世界空间下的指向光源方向的标准化向量
    float3 normal = UnityObjectToWorldNormal(v.normal); //得到世界空间下的法线向量
    float3 reflectDir = reflect(-lightDir, normal); //计算反射光线向量,由于光源向量指向光源,函数需要入射向量,因此对光源向量取反
    //Phong高光反射模型的实现,这里的颜色计算只取rgb,不考虑透明度的情况
    fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, reflectDir)), _SpecularNum);
    v2fData.color = color;

    return v2fData;
    }

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

所有Phong式高光反射模型的逐顶点光照的显示效果如下(左图光泽度为0.5,右图光泽度为2)

imageimage

可见,随着光泽度的增加,反射光斑会越来越小

完整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 "TeachShader/Lesson35"
{
Properties
{
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1) //材质高光反射颜色
_SpecularNum("SpecularNum", Range(0, 20)) = 0.5 //光泽度
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

struct v2f
{
float4 svPos : SV_POSITION; //裁剪空间下的顶点坐标
fixed3 color : COLOR; //颜色信息
};

fixed4 _SpecularColor; //属性设置的材质高光颜色
float _SpecularNum; //属性设置的光泽度

v2f vert (appdata_base v)
{
v2f v2fData;
v2fData.svPos = UnityObjectToClipPos(v.vertex); //首先将模型空间下的顶点转换到裁剪空间下
//计算标准化的观察方向向量
float3 worldPos = mul(UNITY_MATRIX_M, v.vertex); //使用UNITY_MATRIX_M矩阵,将模型空间下的顶点转换到世界空间下
float3 viewDir = _WorldSpaceCameraPos.xyz - worldPos; //将摄像机坐标和顶点坐标通过相减,得到观察方向向量
viewDir = normalize(viewDir); //将观察空间向量标准化

float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); //得到世界空间下的指向光源方向的标准化向量
float3 normal = UnityObjectToWorldNormal(v.normal); //得到世界空间下的法线向量
float3 reflectDir = reflect(-lightDir, normal); //计算反射光线向量,由于光源向量指向光源,而函数需要入射向量,因此需要对光源向量取反
//Phong高光反射模型的实现,这里的颜色计算只取rgb,不考虑透明度的情况
fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, reflectDir)), _SpecularNum);
v2fData.color = color;

return v2fData;
}

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

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

image