unity-shader-法线图使用

法线图 中的法线信息 在 切线空间 或 世界空间 中的使用,在同一空间下 与 光照、观察方向 计算出来的结果是一致的

法线图 主要是用于 低模 获得 高模 的效果

切线空间相关 : https://blog.csdn.net/yangxuan0261/article/details/79685426


效果

这里写图片描述


切线空间下使用

  1. 顶点着色器 中使用顶点中的 法线normal、切线tangent 信息,通过 叉积cross 算出 副切线bitangent(也有人命名为binormal) ,然后通过 这三个向量 构建一个 模型空间 到 切线空间 的变换矩阵,可以将 模型空间 的 向量 变换到 切线空间下,如:将 光照、观察 向量 从 世界空间 变换到 模型空间 再变换到 切线空间,然后传递给 片段着色器
  2. 片段着色器 中拿到 切线空间 下的 光照、观察 向量,就可以和 法线图解包(UnpackNormal 像素到法线值的转换)出的 法线值 做 点积dot 处理,算出凹凸、反色光等

世界空间下使用

  1. 顶点着色器 将顶点中的 法线normal、切线tangent 转换到 世界空间下, 通过 叉积cross 算出世界空间下的 副切线bitangent,然后通过 这三个向量 构建一个 切线空间 到 世界空间 的变换矩阵,可以将 切线空间 的 向量 变换到 世界空间下,
  2. 片段着色器 中,将法线图解包(UnpackNormal 像素到法线值的转换)出的 法线值,通过构建的变换矩阵 装换到世界空间下,和 世界空间 下的 光照、观察 向量 做 点积dot 处理,算出凹凸、反色光等

两种方式的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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "ITS/test/testFlashLight"
{
Properties
{
_NormalTex ("法线贴图", 2D) = "white" {}

_Specular ("高光颜色", Color) = (1, 1, 1, 1)
_Gloss ("高光系数", Range(8, 256)) = 20

_RimColor ("边缘颜色", Color) = (1, 0, 0, 1)
_RimPower ("边缘颜色强度", Range(0.1, 1)) = 1

_MaskTex ("光遮罩图", 2D) = "white" {}
_MoveDir ("边缘光移动方向", Range(-1, 1)) = 1
_MainTex ("Base 2d", 2D) = "white" {}
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }

Pass
{
CGPROGRAM
#pragma vertex vert2
#pragma fragment frag2
#include "UnityCG.cginc"
#include "Lighting.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};

struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir : TEXCOORD1;
float3 viewDir : TEXCOORD2;
};

sampler2D _NormalTex;

fixed4 _Specular;
float _Gloss;

fixed4 _RimColor;
half _RimPower;

sampler2D _MaskTex;
sampler2D _MainTex;
fixed4 _MainTex_ST;
fixed _MoveDir;

// 方式一,切线空间下计算
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);

TANGENT_SPACE_ROTATION;

//rotation 是由 顶点法线和切线 计算除 副切线 后,组成的 切线空间的矩阵
//转换 光线和观察方向 从 世界空间 到 模型空间(ObjSpaceLightDir) 再到 切线空间
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
//不在这里归一化是为了会不影响 插值结果,所以在 frag 中归一化
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);

//采样 法线贴图,并转换 像素值 为 法线值 (normal = pixel * 2 -1)
fixed4 packedNormal = tex2D(_NormalTex, i.uv);
fixed3 tangentNormal = UnpackNormal(packedNormal);

fixed4 tex = tex2D(_MainTex, i.uv);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex.rgb;

//漫反射
fixed3 diffuse = _LightColor0.rgb * tex.rgb * saturate(dot(tangentNormal, tangentLightDir));
//Blinn-Phong高光光照模型,相对于普通的Phong高光模型,会更加光
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss);

//边缘颜色,对于法线和观察方向,只要在同一坐标系下即可
fixed dotProduct = 1 - saturate(dot(tangentNormal, tangentViewDir));
fixed3 rim = _RimColor.rgb * pow(dotProduct, 1 / _RimPower);

fixed4 maskCol = tex2D(_MaskTex, i.uv + float2(0, _Time.y * _MoveDir));
return fixed4(ambient + diffuse + specular + rim * maskCol.rgb, 1);
// return fixed4(ambient + diffuse, 1);
}

// 方式二,世界空间下计算
struct appdata2
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};

struct v2f2
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;

float4 TtoW0 :TEXCOORD1;
float4 TtoW1 :TEXCOORD2;
float4 TtoW2 :TEXCOORD3;

float3 lightDir : TEXCOORD4;
float3 viewDir : TEXCOORD5;
};

v2f2 vert2 (appdata2 v)
{
v2f2 o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);

float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.lightDir = UnityWorldSpaceLightDir(worldPos);
o.viewDir = UnityWorldSpaceViewDir(worldPos);

// 构建 法线 从 切线空间 到 世界空间 的 变换矩阵(三个向量)
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); // 顺便把 世界坐标 也存在这里
return o;
}

fixed4 frag2 (v2f2 i) : SV_Target
{
//获得世界空间中的坐标
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
//计算光照和视角方向在世界坐标系中
fixed3 worldLightDir = normalize(i.lightDir);
fixed3 worldViewDir = normalize(i.viewDir);

fixed4 packedNormal = tex2D(_NormalTex, i.uv);
fixed4 tex = tex2D(_MainTex, i.uv);

// 法线图 解包 后 的 切线空间的法线向量
fixed3 tangentNormal = UnpackNormal(packedNormal);

// 转换 法线向量 从 切线空间 到 世界空间, 等价于 下面注释部分
fixed3 worldNormal = normalize(half3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal), dot(i.TtoW2.xyz, tangentNormal)));

/* // 构建 转换矩阵
float3x3 worldNormalMatrix = float3x3(i.TtoW0.xyz, i.TtoW1.xyz, i.TtoW2.xyz);
fixed3 worldNormal = normalize(mul(worldNormalMatrix, tangentNormal));
*/

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex.rgb;

//漫反射
fixed3 diffuse = _LightColor0.rgb * tex.rgb * saturate(dot(worldNormal, worldLightDir));

//Blinn-Phong高光光照模型,相对于普通的Phong高光模型,会更加光
fixed3 halfDir = normalize(worldLightDir + worldViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);

//边缘颜色,对于法线和观察方向,只要在同一坐标系下即可
fixed dotProduct = 1 - saturate(dot(worldNormal, worldViewDir));
fixed3 rim = _RimColor.rgb * pow(dotProduct, 1 / _RimPower);

fixed4 maskCol = tex2D(_MaskTex, i.uv + float2(0, _Time.y * _MoveDir));
return fixed4(ambient + diffuse + specular + rim * maskCol.rgb, 1);
// return fixed4(ambient + diffuse, 1);
}

ENDCG
}
}
}

参考资料