Unity3D教程:实现水面渲染(二)

发表于2016-08-14
评论0 1.69w浏览

水面渲染在很多游戏项目中都会碰到,下面就分四篇文章去给大家介绍在Unity3D中水面渲染的实现方法,现在介绍的水面渲染(二)


一、前言

    本文旨在与大家一起探讨学习新知识,如有疏漏或者谬误,请大家不吝指出。

二、概述

在上一篇文章中,我们讲了水面反射与波的生成。这篇文章中,我们通过法线贴图来给波增加细节。先来看下加了法线贴图与未加法线贴图的对比:

可以看到,效果的差别很大。使用法线贴图来增加物体表面的凹凸细节,这个方法比较常用。如果不使用法线贴图,而单单依靠Gerstner波,那么要增加细节必须要叠加更多的波长更短的波,这会显著增加性能开销。

 

三、实现

法线贴图的原理想必大家都不陌生,这里大概提一下思路.

1、通过Properties属性块来定义一张2D纹理变量,在fragment像素着色器中通过tex2D采样函数来获取相对应的rgb值。注意这时获取到的值并不是正确的法线。通过Unity3D自带的函数UnpackNormal来计算得到正确的法线值。具体细节代码如下:

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
inline fixed3 UnpackNormalDXT5nm (fixed4packednormal)
 
{
 
         fixed3normal;
 
         normal.xy= packednormal.wy * 2 - 1;
 
         normal.z= sqrt(1 - saturate(dot(normal.xy, normal.xy)));
 
         returnnormal;
 
}
 
  
 
inline fixed3 UnpackNormal(fixed4packednormal)
 
{
 
#if defined(UNITY_NO_DXT5nm)
 
         returnpackednormal.xyz * 2 - 1;
 
#else
 
         returnUnpackNormalDXT5nm(packednormal);
 
#endif
 
}

仔细看看其实也比较简单。因为归一化的法线值,它的范围是[-1, 1],而颜色值其实只能存储[0, 1]的值,所以法线需要通过简单的编码来映射,映射公式是x=(x+1)*0.5。那么我们在像素着色器中获取到的颜色值就需要解码,以映射回正确的法线值。另外一点,DirectX为了减少法线贴图文件的大小,不存储Z值,而是通过sqrt(x*x+ y*y)来计算得到,所以需要另外解码。

2、这时获取到的法线值还不可以直接使用,因为我们要保证所有参与计算的变量要在同一个坐标系中。这时的法线是指在切线空间中的法线。在这里我们需要在世界坐标系下进行一系列的计算,所以我们还需要将切线空间的法线转换到世界坐标系中。

切线空间由三个向量定义构成:副法线(binormal)、法线(normal)、切线(tangent);得知了这三个向量,就可以构造出从世界坐标系转换到切线空间的3x3变换矩阵:

1
Float3x3 M ={binormal, normal, tangent};

问题转化成了求binormalnormaltangent这三个向量。一般来说网格模型自带了法线和切线,然后通过叉乘来得到副法线,但是对于水面来说,因为我们在顶点着色器中改变了顶点的位置,所以这个办法行不通。我们需要通过计算来获取他们。

还记得我们的Gerstner函数公式吗:

从上到下分别可以求出XZY的值。然后我们现在需要求副法线、法线和切线。那么其实对其分别求对于xzy的导数,得到的就是我们需要的。想象一下笛卡尔坐标系中的函数y=cos(x),我们对其进行求导后得到y’=-sin(x),从几何意义上来说y’y的斜率,就是说函数在x方向上的导数就是其副法线,其他同理。

然后我们得到Gerstner函数对于xyz的导函数:


在获取到binormalnormaltangent后,我们构造出了从世界坐标系转换到切线空间的矩阵(注:在这里Gerstner函数是对世界坐标系中的点进行运算,所以构造出来的是世界到切线的矩阵。一般地,使用网格模型自带的binormal、normal、tangent构造出来的是局部坐标系到切线空间的矩阵):

1
Float3x3 M ={binormal, normal, tangent};

当然,其实我们需要的是从切线空间到世界坐标系的变换矩阵,所以我们需要对矩阵M进行求逆运算。很幸运,M矩阵是一个正交矩阵(旋转矩阵是正交的),所以其逆矩阵等于转置矩阵。

1
2
3
M =transpose(M);
 
worldNormal =mul(M,  bumpNormal);

通过上述步骤,我们终于得到了世界坐标下的法线向量。

四、源代码

    下面给出实现水面渲染(一)和(二)的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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
Properties
 
{
 
    _Skybox("Skybox",Cube)=""{}
 
    _BumpTex("Bump Texture", 2D) = "white"{}
 
    _BumpStrength("Bump strength", Range(0.0, 10.0)) = 1.0
 
    _BumpDirection("Bump direction(2 wave)", Vector)=(1,1,1,-1)
 
    _BumpTiling("Bump tiling", Vector)=(0.0625,0.0625,0.0625,0.0625)
 
    _ReflOffset("reflectionoffset", Range(0, 1))=0.8
 
}
 
SubShader
 
{
 
    Tags{ "RenderType"="Opaque""LightMode"="ForwardBase"}
 
    LOD100
 
  
 
    Pass
 
    {
 
        CGPROGRAM
 
        #pragmavertex vert
 
        #pragmafragment frag
 
        #pragmamulti_compile_fwdbase
 
                           
 
        #include"UnityCG.cginc"
 
  
 
        structappdata
 
        {
 
            float4vertex : POSITION;
 
            float2uv : TEXCOORD0;
 
        };
 
  
 
        structv2f
 
        {
 
            float3screenPos:TEXCOORD0;
 
            float4bumpCoords:TEXCOORD1;
 
            float3worldPos:TEXCOORD2;
 
            float4vertex : SV_POSITION;
 
        };
 
                           
 
        //gerstnerwave params.defined in GerstnerWave.cs
 
        float4_QA;
 
        float4_A;
 
        float4_S;
 
        float4_Dx;
 
        float4_Dz;
 
        float4_W;
 
        //
 
        sampler2D_ReflTexture;
 
        samplerCUBE_Skybox;
 
        //bumptex
 
        sampler2D_BumpTex;
 
        float_BumpStrength;
 
        float4_BumpDirection;
 
        float4_BumpTiling;
 
                           
 
        float_ReflOffset;
 
 
        float3CalculateWavesDisplacement(float3 vert)
 
        {
 
            float3pos = float3(0,0,0);
 
            float4phase = _Dx*vert.x+_Dz*vert.z+_S*_Time.y;
 
            float4sinp=float4(0,0,0,0), cosp=float4(0,0,0,0);
 
            sincos(_W*phase,sinp, cosp);
 
  
 
            pos.x= dot(_QA*_Dx, cosp);
 
            pos.z= dot(_QA*_Dz, cosp);
 
            pos.y= dot(_A, sinp);
 
  
 
            returnpos;
 
        }
 
        float3CalculateWavesNormal(float3 vert)
 
        {
 
            float3nor = float3(0,0,0);
 
            float4phase = _Dx*vert.x+_Dz*vert.z+_S*_Time.y;
 
            float4sinp=float4(0,0,0,0), cosp=float4(0,0,0,0);
 
            sincos(_W*phase,sinp, cosp);
 
  
 
            nor.x= -dot(_W*_A*_Dx, cosp);
 
            nor.z= -dot(_W*_A*_Dz, cosp);
 
            nor.y= 1-dot(_QA*_W, sinp);
 
  
 
            nor= normalize(nor);
 
  
 
            returnnor;
 
        }
 
        float3CalculateWavesDisplacementNormal(float3 vert, out float3 nor)
 
        {
 
            float3pos = float3(0,0,0);
 
            float4phase = _Dx*vert.x+_Dz*vert.z+_S*_Time.y;
 
            float4sinp=float4(0,0,0,0), cosp=float4(0,0,0,0);
 
            sincos(_W*phase,sinp, cosp);
 
  
 
            pos.x= dot(_QA*_Dx, cosp);
 
            pos.z= dot(_QA*_Dz, cosp);
 
            pos.y= dot(_A, sinp);
 
  
 
            nor.x= -dot(_W*_A*_Dx, cosp);
 
            nor.z= -dot(_W*_A*_Dz, cosp);
 
            nor.y= 1-dot(_QA*_W, sinp);
 
  
 
            nor= normalize(nor);
 
  
 
            returnpos;
 
        }
 
        voidCalculateWavesBinormalTangent(float3 vert, out float3 binormal, out float3tangent)
 
        {
 
            float4phase = _Dx*vert.x+_Dz*vert.z+_S*_Time.y;
 
                                    
 
            float4sinp=float4(0,0,0,0), cosp=float4(0,0,0,0);
 
                                    
 
            sincos(_W*phase,sinp, cosp);
 
  
 
            binormal= float3(0,0,0);
 
            binormal.x= 1-dot(_QA, _Dx*sinp*_Dx*_W);
 
            binormal.z= -dot(_QA, _Dz*sinp*_Dz*_W);
 
            binormal.y= dot(_A, _Dx*cosp*_W);
 
                                    
 
            tangent= float3(0,0,0);
 
            tangent.x= -dot(_QA, _Dx*sinp*_Dz*_W);
 
            tangent.z= 1-dot(_QA, _Dz*sinp*_Dz*_W);
 
            tangent.y= dot(_A, _Dz*cosp*_W);
 
  
 
            binormal= normalize(binormal);
 
            tangent= normalize(tangent);
 
        }
 
                           
 
        float3PerPixelNormal(sampler2D bumpMap, float4 coords, float bumpStrength)
 
        {
 
            float2bump = (UnpackNormal(tex2D(bumpMap, coords.xy)) + UnpackNormal(tex2D(bumpMap,coords.zw))) * 0.5;
 
            bump+= (UnpackNormal(tex2D(bumpMap, coords.xy*2))*0.5 + UnpackNormal(tex2D(bumpMap,coords.zw*2))*0.5) * 0.5;
 
            bump+= (UnpackNormal(tex2D(bumpMap, coords.xy*8))*0.5 + UnpackNormal(tex2D(bumpMap,coords.zw*8))*0.5) * 0.5;
 
            float3worldNormal = float3(0,0,0);
 
            worldNormal.xz= bump.xy * bumpStrength;
 
            worldNormal.y= 1;
 
            returnworldNormal;
 
        }
 
  
        v2f vert (appdata v)
 
        {
 
             v2fo;
 
             float3worldPos = mul(_Object2World, v.vertex);
 
             float3disPos = CalculateWavesDisplacement(worldPos);
 
             worldPos= worldPos+disPos;
 
             v.vertex.xyz= mul(_World2Object, float4(worldPos, 1));
 
             o.vertex= mul(UNITY_MATRIX_MVP, v.vertex);
 
             o.screenPos= ComputeScreenPos(o.vertex).xyw;
 
             o.worldPos= worldPos;
 
             o.bumpCoords.xyzw= (worldPos.xzxz + _Time.yyyy * _BumpDirection.xyzw) * _BumpTiling.xyzw;
 
             returno;
 
         }
 
                           
 
         float4frag (v2f i) : SV_Target
 
         {
 
              float3viewVector = normalize(i.worldPos - _WorldSpaceCameraPos.xyz);
 
              //calculatenormal
 
              float3binormal = float3(0,0,0);
 
              float3tangent = float3(0,0,0);
 
              CalculateWavesBinormalTangent(i.worldPos,binormal, tangent);
 
              float3worldNormal = normalize(cross(tangent, binormal));
 
              float3x3M = {binormal, worldNormal, tangent};//from world coord to tangent coord
 
              M=transpose(M);
 
              float3bumpNormal = PerPixelNormal(_BumpTex, i.bumpCoords, _BumpStrength);
 
              //worldNormal= normalize( mul(M, normalize( bumpNormal)));
 
                                    
 
              //reflect
 
              float3reflUV = reflect( viewVector, worldNormal);
 
              float2offsets = worldNormal.xz*viewVector.y;
 
              float4reflectionColor = tex2D(_ReflTexture,i.screenPos.xy/i.screenPos.z+offsets*_ReflOffset);
 
                                    
 
              float3skyColor = texCUBE(_Skybox, reflUV);
 
              reflectionColor.xyz= lerp(skyColor, reflectionColor.xyz,reflectionColor.a);//==reflectionColor.xyz+(1-reflectionColor.a)*skyColor;
 
                                    
 
              returnreflectionColor;
 
          }
 
          ENDCG
 
      }
 
}


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引