Unity3D教程:实现水面渲染(二)
水面渲染在很多游戏项目中都会碰到,下面就分四篇文章去给大家介绍在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}; |
问题转化成了求binormal、normal、tangent这三个向量。一般来说网格模型自带了法线和切线,然后通过叉乘来得到副法线,但是对于水面来说,因为我们在顶点着色器中改变了顶点的位置,所以这个办法行不通。我们需要通过计算来获取他们。
还记得我们的Gerstner函数公式吗:
从上到下分别可以求出XZY的值。然后我们现在需要求副法线、法线和切线。那么其实对其分别求对于x、z、y的导数,得到的就是我们需要的。想象一下笛卡尔坐标系中的函数y=cos(x),我们对其进行求导后得到y’=-sin(x),从几何意义上来说y’是y的斜率,就是说函数在x方向上的导数就是其副法线,其他同理。
然后我们得到Gerstner函数对于x、y、z的导函数:
在获取到binormal、normal、tangent后,我们构造出了从世界坐标系转换到切线空间的矩阵(注:在这里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 } } |