US3S2L3——纹理结合光照模型

单张纹理结合 BlinnPhong 光照模型

如果要让有纹理的模型受到光照影响,就需要让纹理颜色结合光照模型计算出来的颜色,计算出模型显示在屏幕上的最终颜色

体现在 Shader 计算中,有以下的三点注意点:

  1. 纹理颜色 需要和 漫反射颜色 进行乘法叠加,它们两共同影响最终的颜色,也就是需要计算反射颜色

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    sampler2D _MainTex;        // 贴图纹理对应的映射成员
    fixed4 _MainColor; // 漫反射颜色

    struct v2f
    {
    float4 pos: SV_POSITION; //裁剪空间下的顶点坐标
    float2 uv: TEXCOORD0; //纹理UV坐标
    float3 wNormal: NORMAL; //世界空间下的法线
    float3 wPos: TEXCOORD1; //世界空间下的顶点坐标
    };

    fixed4 frag (v2f i) : SV_Target
    {
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _MainColor.rgb; // 反射率,即纹理颜色和漫反射材质颜色乘法叠加共同决定的颜色
    }
  2. 兰伯特光照模型计算时,漫反射材质颜色使用第一步计算出来的颜色计算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    sampler2D _MainTex;        // 贴图纹理对应的映射成员
    fixed4 _MainColor; // 漫反射颜色

    struct v2f
    {
    float4 pos: SV_POSITION; //裁剪空间下的顶点坐标
    float2 uv: TEXCOORD0; //纹理UV坐标
    float3 wNormal: NORMAL; //世界空间下的法线
    float3 wPos: TEXCOORD1; //世界空间下的顶点坐标
    };

    fixed4 frag (v2f i) : SV_Target
    {
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _MainColor.rgb; // 反射率,即纹理颜色和漫反射材质颜色乘法叠加共同决定的颜色
    // 漫反射颜色
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 指向光源的方向
    fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(i.wNormal, lightDir));
    }
  3. 最终使用的环境光叠加时,环境光变量 UNITY_LIGHTMODEL_AMBIENT​ 需要和第一步计算出来的颜色进行乘法叠加,为了避免最终的渲染效果偏灰

    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
    sampler2D _MainTex;        // 贴图纹理对应的映射成员
    // 漫反射颜色、高光反射颜色、光泽度
    fixed4 _MainColor;
    fixed4 _SpecularColor;
    float _SpecularNum;

    struct v2f
    {
    float4 pos: SV_POSITION; //裁剪空间下的顶点坐标
    float2 uv: TEXCOORD0; //纹理UV坐标
    float3 wNormal: NORMAL; //世界空间下的法线
    float3 wPos: TEXCOORD1; //世界空间下的顶点坐标
    };

    fixed4 frag (v2f i) : SV_Target
    {
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _MainColor.rgb; // 反射率,即纹理颜色和漫反射材质颜色乘法叠加共同决定的颜色

    // 漫反射颜色
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 指向光源的方向
    fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(i.wNormal, lightDir));

    // 高光反射颜色
    float3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos)); // 视角方向
    float3 halfA = normalize(viewDir + lightDir); // 半角向量
    fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(i.wNormal, halfA)), _SpecularNum);

    // 最终颜色,环境光需要和乘法叠加反射颜色
    fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

    return fixed4(color.rgb, 1);
    }

    效果对比(左图为不叠加 albedo,右图为叠加 albedo):

    imageimage

其他的计算步骤同 BlinnPhong 的逐片元光照 实现

完整 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Shader "TeachShader/Lesson50"
{
Properties
{
_MainTex("MainTex", 2D) = ""{} // 纹理贴图
_MainColor("MainColor", Color) = (1, 1, 1, 1) // 漫反射颜色
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1) // 高光反射颜色
_SpecularNum("SpecularNum", Range(0, 20)) = 15 // 光泽度
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }

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

// 贴图纹理对应的映射成员
sampler2D _MainTex;
float4 _MainTex_ST;
// 漫反射颜色、高光反射颜色、光泽度
fixed4 _MainColor;
fixed4 _SpecularColor;
float _SpecularNum;

struct v2f
{
float4 pos: SV_POSITION; // 裁剪空间下的顶点坐标
float2 uv: TEXCOORD0; // 纹理UV坐标
float3 wNormal: NORMAL; // 世界空间下的法线
float3 wPos: TEXCOORD1; // 世界空间下的顶点坐标
};

v2f vert (appdata_base v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex); // 将模型空间下的法线转换到世界空间下
data.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 计算UV
data.wNormal = UnityObjectToWorldNormal(v.normal); // 法线转换到世界空间
data.wPos = mul(unity_ObjectToWorld, v.vertex); // 顶点转换到世界空间

return data;
}

fixed4 frag (v2f i) : SV_Target
{
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _MainColor.rgb; // 反射率,即纹理颜色和漫反射材质颜色乘法叠加共同决定的颜色

// 漫反射颜色
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 指向光源的方向
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(i.wNormal, lightDir));

// 高光反射颜色
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos)); // 视角方向
float3 halfA = normalize(viewDir + lightDir); // 半角向量
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(i.wNormal, halfA)), _SpecularNum);

// 最终颜色 = 环境光 * 反射颜色 + 漫反射颜色 + 高光反射颜色
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

return fixed4(color.rgb, 1);
}
ENDCG
}
}
}

显示效果(左边是不计算光照效果的 Shader,右边是使用 BlinnPhong 逐片元光照的 Shader):

image

可见,结合了光照模型计算结果的模型能够收到光照的影响,出现背光变暗的效果