US3S2L12——高光纹理综合实现

高光纹理综合实现

接下来将高光纹理的映射实现融合到之前实现的法线纹理 Shader 内,
这里复用之前实现的 切线空间下的法线纹理 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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
Shader "TeachShader/Lesson51"
{
Properties
{
_MainColor("MainColor", Color) = (1, 1, 1, 1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0, 1)) = 1
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_SpecularNum("SpecularNum", Range(0, 20)) = 18
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

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

struct v2f
{
float4 pos: SV_POSITION;
//float2 uvTex: TEXCOORD0;
//float2 uvBump: TEXCOORD1; //可以使用两个float2来分别存储主要纹理的uv和法线纹理的uv
float4 uv: TEXCOORD0; //可以使用一个float4来同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float3 lightDir: TEXCOORD1; //相对于切线空间下的光的方向
float3 viewDir: TEXCOORD2; //相对于切线空间下的视角方向
};

float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度

v2f vert (appdata_full v)
{
v2f data;

data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

// 计算副切线
float3 binormal = cross(normalize(v.tangent), normalize(v.normal)) * v.tangent.w;
// 得到模型空间到切线空间的转换矩阵
float3x3 rotation = float3x3(
v.tangent.xyz, //切线
binormal, //副切线
v.normal //法线
);

data.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); // 切线空间下的光的方向
data.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)); // 切线空间下的视角方向

return data;
}

fixed4 frag (v2f i) : SV_Target
{
float4 packedNormal = tex2D(_BumpMap, i.uv.zw); // 获取法线纹理的颜色数据
float3 tangentNormal = UnpackNormal(packedNormal); // 将颜色数据逆运算并解压缩,得到切线空间下法线数据
// 将法线数据的xy乘以凹凸系数,根据xy修正z,避免凹凸系数影响光照亮度
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

// 计算带纹理颜色的BlinnPhong光照计算,这里使用已经计算好的切线数据
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _MainColor.rgb; // 反射率

// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向,注意!这里的 tangentNormal 无需归一化
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(tangentNormal, normalize(i.lightDir)));
// 高光反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir)); // 半角向量
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfA)), _SpecularNum);
// 最终颜色计算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

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

将高光遮罩纹理实现融入其中

  1. 从纹理中取出对应的遮罩掩码值(颜色的RGB值都可以使用)
  2. 用该掩码值和遮罩系数(我们自己定义的)相乘得到遮罩值
  3. 用该遮罩值和高光反射计算出来的颜色相乘

属性需要添加高光遮罩纹理 _SpecularMask​ 和遮罩系数 _SpecularScale​:

1
2
3
4
5
6
7
8
9
10
11
Properties
{
_MainColor("MainColor", Color) = (1, 1, 1, 1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0, 1)) = 1
_SpecularMask("SpecularMask", 2D) = ""{}
_SpecularScale("_SpecularScale", Float) = 1
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_SpecularNum("SpecularNum", Range(0, 20)) = 18
}

同时,片元着色器的高光反射计算部分也要修改为:

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
struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; //使用一个float4来同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float3 lightDir: TEXCOORD1; //相对于切线空间下的光的方向
float3 viewDir: TEXCOORD2; //相对于切线空间下的视角方向
};

float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
sampler2D _SpecularMask; //高光纹理
float4 _SpecularMask_ST; //高光纹理的缩放和平移
float _SpecularScale; //遮罩系数
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度

fixed4 frag (v2f i) : SV_Target
{
float4 packedNormal = tex2D(_BumpMap, i.uv.zw); // 获取法线纹理的颜色数据
float3 tangentNormal = UnpackNormal(packedNormal); // 将颜色数据逆运算并解压缩,得到切线空间下法线数据
// 将法线数据的xy乘以凹凸系数,根据xy修正z,避免凹凸系数影响光照亮度
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

// 计算带纹理颜色的BlinnPhong光照计算,这里使用已经计算好的切线数据
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _MainColor.rgb; // 反射率

// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向,注意!这里的 tangentNormal 无需归一化
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(tangentNormal, normalize(i.lightDir)));
// 高光反射光照计算:这里会使用高光纹理和遮罩系数来计算高光遮罩值,通过这个值来影响高光表现
fixed maskNum = tex2D(_SpecularMask, i.uv.xy).r; // 从高光纹理的R通道内取出掩码值,uv可以直接套用主纹理使用的uv
fixed specularMaskNum = maskNum * _SpecularScale; // 用掩码值和遮罩系数相乘得到遮罩值
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir)); // 半角向量
fixed3 specularColor =
_LightColor0.rgb * _SpecularColor.rgb *
pow(max(0, dot(tangentNormal, halfA)), _SpecularNum) *
specularMaskNum; // 在原来的高光颜色上乘以遮罩值,得到最终高光颜色
// 最终颜色计算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

return fixed4(color.rgb, 1);
}

完整 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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
Shader "TeachShader/Lesson55"
{
Properties
{
_MainColor("MainColor", Color) = (1, 1, 1, 1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0, 1)) = 1
_SpecularMask("SpecularMask", 2D) = ""{}
_SpecularScale("_SpecularScale", Float) = 1
_SpecularColor("SpecularColor", Color) = (1, 1, 1, 1)
_SpecularNum("SpecularNum", Range(0, 20)) = 18
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

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

struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; //使用一个float4来同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float3 lightDir: TEXCOORD1; //相对于切线空间下的光的方向
float3 viewDir: TEXCOORD2; //相对于切线空间下的视角方向
};

float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
sampler2D _SpecularMask; //高光纹理
float4 _SpecularMask_ST; //高光纹理的缩放和平移
float _SpecularScale; //遮罩系数
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度

v2f vert (appdata_full v)
{
v2f data;

data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

// 计算副切线
float3 binormal = cross(normalize(v.tangent), normalize(v.normal)) * v.tangent.w;
// 得到模型空间到切线空间的转换矩阵
float3x3 rotation = float3x3(
v.tangent.xyz, //切线
binormal, //副切线
v.normal //法线
);

data.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); // 切线空间下的光的方向
data.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)); // 切线空间下的视角方向

return data;
}

fixed4 frag (v2f i) : SV_Target
{
float4 packedNormal = tex2D(_BumpMap, i.uv.zw); // 获取法线纹理的颜色数据
float3 tangentNormal = UnpackNormal(packedNormal); // 将颜色数据逆运算并解压缩,得到切线空间下法线数据
// 将法线数据的xy乘以凹凸系数,根据xy修正z,避免凹凸系数影响光照亮度
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

// 计算带纹理颜色的BlinnPhong光照计算,这里使用已经计算好的切线数据
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _MainColor.rgb; // 反射率

// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向,注意!这里的 tangentNormal 无需归一化
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(tangentNormal, normalize(i.lightDir)));
// 高光反射光照计算:这里会使用高光纹理和遮罩系数来计算高光遮罩值,通过这个值来影响高光表现
fixed maskNum = tex2D(_SpecularMask, i.uv.xy).r; // 从高光纹理的R通道内取出掩码值,uv可以直接套用主纹理使用的uv
fixed specularMaskNum = maskNum * _SpecularScale; // 用掩码值和遮罩系数相乘得到遮罩值
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir)); // 半角向量
fixed3 specularColor =
_LightColor0.rgb * _SpecularColor.rgb *
pow(max(0, dot(tangentNormal, halfA)), _SpecularNum) *
specularMaskNum; // 在原来的高光颜色上乘以遮罩值,得到最终高光颜色
// 最终颜色计算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

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

显示效果(左为不使用高光遮罩纹理,右为使用高光遮罩纹理):

image

可见,不使用高光遮罩纹理的模型出现了很亮的亮班,
而使用高光遮罩纹理的模型高光就没有那么明显,对于砖石材质来说更加自然了

遮罩纹理中的RGBA值

对于高光遮罩纹理中的RGBA值,从使用率上来讲是非常浪费的,因为我们只使用其中一个值就可以得到我们想要的数据
因此对于遮罩纹理来说,我们可以合理的利用其中的每一个值来存储我们想要的数据

随着以后的学习,我们可以在遮罩纹理当中存储更多信息,比如:

  • R值代表高光遮罩数据
  • G值代表透明遮罩数据
  • B值代表特效遮罩数据

等等,甚至可以用 n 张遮挡纹理存储 4*n 个会参与 每个片元渲染计算的值