HPP_Graphics_2.5 Bump Mapping
a). Bump Map的分类
b). Normal Map
- 法线贴图:存有物体局部(切线空间)表面法线信息的一张贴图
- 储存空间:
- (一般采用)切线空间
- 模型空间
b.1). Tangent Space
切线空间中,x轴为切线,y轴为副切线,z轴为表面初始法线
- 其中,切线的方向对应UV空间的 $u$ 轴,副切线对应 $v$ 轴(详见Link1、Link3)
使用切线空间的优点:
- 自由度高,可复用
- 可进行uv动画
- 可压缩
c). Unity中的法线压缩格式
d). Parallax Mapping(时差映射)
d.1). 普通实现方式
- 这里能用切线空间的视线方向 $viewDirTS$ 来计算 UV 的偏移的原因正是因为切线空间的$x, y$轴和UV空间的 $u, v$ 轴对应;
d.2). Steep Parallax Mapping(陡峭视差映射)
- 采用光线步进;
- 但如效果仍有不足,可采用浮雕贴图(Relief Mapping)
e). Relief Mapping(浮雕贴图)
- 实现方法:
- 最后一步步进后,在最后一步的范围内使用二分查找,直到找到近似P点的点;
- 优化方法:
- 二分法性能开销过大;
- 最后一个步进时得到高度 $h1$ 和 $h2$ ,$H_p = (h1+h2)/2$ (见上图)
作业 视差贴图、浮雕贴图的实现
资料补充:视差贴图(Parallax Mapping)与陡峭视差贴图(Steep Palallax Mapping, 已收藏在收藏夹)
Shader见 S_Parallax_Relief.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226Shader "BRJH/MetallicWorkflow"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_DiffuseTint ("Diffuse Tint", color) = (1, 1, 1, 1)
_Metallic ("Metallic", Range(0, 1)) = 0.5
_Gloss ("Gloss", Range(1, 255)) = 20
_NormalMap ("Normal", 2D) = "bump" {}
_NormalScale ("Normal Scale", Range(0, 5)) = 1
_HeightMap ("Height Map", 2D) = "black" {}
_HeightScale ("Height Scale", Range(0, 0.3)) = 0.2
_CubeMap ("Cube Map", Cube) = "_skybox" {}
_EnvIntensity ("Envirment Intensity", Range(0, 5)) = 1
[KeywordEnum(Default, POM)] _PARALLAX_MAPPING("Parallax Mapping Type", Float) = 0
[KeywordEnum(T, F, C)] _USING_IBL("If use IBL", Float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque"
"LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#pragma multi_compile _USING_IBL_T _USING_IBL_F
#pragma multi_compile _PARALLAX_MAPPING_Default _PARALLAX_MAPPING_POM
#include "UnityCG.cginc"
#include "UnityPBSLighting.cginc"
#include "Lighting.cginc"
#define MAX_LAYER_NUM 20
#define MIN_LAYER_NUM 20
sampler2D _MainTex;
fixed4 _MainTex_ST;
sampler2D _NormalMap;
fixed4 _NormalMap_ST;
sampler2D _HeightMap;
samplerCUBE _CubeMap;
fixed4 _DiffuseTint;
half _Metallic;
fixed _Gloss;
fixed _NormalScale;
fixed _HeightScale;
fixed _EnvIntensity;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 TangentLightDir : TEXCOORD0;
float3 TangentViewDir : TEXCOORD1;
float2 uv : TEXCOORD2;
#if defined(_USING_IBL_T)
float4 TtoW0 : TEXCOORD4;
float4 TtoW1 : TEXCOORD5;
float4 TtoW2 : TEXCOORD6;
#endif
};
float3 ACESToneMapping(float3 color, float adapted_lum) {
const float A = 2.51f;
const float B = 0.03f;
const float C = 2.43f;
const float D = 0.59f;
const float E = 0.14f;
color *= adapted_lum;
return (color * (A * color + B)) / (color * (C * color + D) + E);
}
// 浮雕映射
float2 ReliefMapping(float2 uv, half3 TangentViewDir) {
float NumLayer = 40;//lerp(MIN_LAYER_NUM, MAX_LAYER_NUM, abs(dot(Normal, TangentViewDir)));
float2 dtex = _HeightScale * TangentViewDir.xy / TangentViewDir.z / NumLayer;
float2 addUV = float2(0, 0);
float2 currentUV = uv + addUV;
float currentLayerHeight = 0;
float currentHeightMapValue = tex2D(_HeightMap, currentUV + addUV).r;
for (int n = 0; n < NumLayer; n++) {
currentLayerHeight += 1.0/NumLayer;
currentUV += dtex;
currentHeightMapValue = tex2D(_HeightMap, currentUV).r;
if (currentHeightMapValue < currentLayerHeight) {
break;
}
}
float2 T0 = currentUV, T1 = currentUV - dtex;
for (int j = 0; j<20 ;j++) {
float2 P0 = (T0+T1)/2;
float P0_HeightMapValue = tex2D(_HeightMap, P0).r;
float P0_LayerHeight = length(P0) / length(dtex*NumLayer);
if (P0_HeightMapValue < P0_LayerHeight) {
T1 = P0;
}
else {
T0 = P0;
}
}
return (T0 + T1) / 2 - uv;
}
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
TANGENT_SPACE_ROTATION;
o.TangentLightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.TangentViewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
#if defined(_USING_IBL_T)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 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);
#endif
return o;
}
fixed4 frag(v2f i) : SV_TARGET {
//Base
fixed3 TangentLightDir = normalize(i.TangentLightDir);
fixed3 TangentViewDir = normalize(i.TangentViewDir);
fixed3 HalfDir = normalize(TangentLightDir + TangentViewDir);
float Height = tex2D(_HeightMap, i.uv).x;
fixed4 packedNormal = (1-tex2D(_NormalMap, i.uv));
fixed3 Normal = UnpackNormal(packedNormal);
#if defined(_PARALLAX_MAPPING_Default) // 视察贴图
float2 offsetUV = TangentViewDir.xy / TangentViewDir.z * Height * _HeightScale;
i.uv += offsetUV;
#elif defined(_PARALLAX_MAPPING_POM) // 浮雕贴图
// float NumLayer = 20;//lerp(MIN_LAYER_NUM, MAX_LAYER_NUM, abs(dot(Normal, TangentViewDir)));
// float2 dtex = _HeightScale * TangentViewDir.xy / TangentViewDir.z / NumLayer;
// float currentLayerHeight = 0;
// for (int n = 0; n < NumLayer; n++) {
// currentLayerHeight += 1.0/NumLayer;
// i.uv += dtex;
// if (Height > currentLayerHeight) {
// Height = tex2D(_HeightMap, i.uv).r;
// }
// else {
// Height = (Height + tex2D(_HeightMap, i.uv).r)/2;
// break;
// }
// }
i.uv += ReliefMapping(i.uv, TangentViewDir);
Height = tex2D(_HeightMap, i.uv).x;
#endif
fixed3 albedo = tex2D(_MainTex, i.uv) * _DiffuseTint.rgb;
fixed3 SpecularTint;
fixed OneMinusReflectivity;
albedo = DiffuseAndSpecularFromMetallic(albedo, _Metallic, SpecularTint, OneMinusReflectivity);
packedNormal = tex2D(_NormalMap, i.uv);
Normal = UnpackNormal(packedNormal);
// 通过高度计算IBL方向
#if defined(_USING_IBL_T)
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
float3 worldNormal = normalize(float3(i.TtoW0.z, i.TtoW1.z, i.TtoW2.z));
float3 worldPosUndata = worldPos - worldNormal*Height*_HeightScale;
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPosUndata));
float NormalRef = normalize(cross(ddy(worldPosUndata), ddx(worldPosUndata))); // 通过高度贴图计算法线方向;
fixed3 worldRef = reflect(-worldViewDir, NormalRef);
// fixed3 WorldRef = normalize(fixed3(dot(i.TtoW0.xyz, TangentRef),
// dot(i.TtoW1.xyz, TangentRef),
// dot(i.TtoW2.xyz, TangentRef)));
fixed3 Reflection = ACESToneMapping(texCUBElod(_CubeMap, float4(worldRef, (255-_Gloss)*8/255)).rgb, 1) * SpecularTint * _EnvIntensity;
#endif
Normal.xy *= _NormalScale;
Normal.z = sqrt(1.0 - saturate(dot(Normal.xy, Normal.xy)));
fixed3 Ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 Diffuse = lerp(Ambient, _LightColor0.rgb * albedo, saturate(dot(Normal, TangentLightDir)));
fixed3 Specular = _LightColor0.rgb * SpecularTint * pow(saturate(dot(HalfDir, Normal)), _Gloss);
#if defined(_USING_IBL_T)
//Reflection.rgb = lerp(diffuse*Specular, )
fixed4 color = fixed4(Ambient+Diffuse+Specular+Reflection, 1);
#elif defined(_USING_IBL_F)
fixed4 color = fixed4(Ambient+Diffuse+Specular, 1);
#endif
return color;
}
ENDCG
}
}
}
- 在使用浮雕/视差贴图时,使用IBL;
- 需要用Offset后的高度图和世界空间坐标得到更新后的世界坐标,再通过ddx,ddy叉乘获得世界法线,进一步进行CubeMap的采样;