转自:http://blog.csdn.net/soilwork/article/details/1548490
水面渲染小结
本文版权归我所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,欢迎大家和我交流。
作者:clayman
Blog:http://blog.csdn.net/soilwork
[email protected]
从几何模型上来看,水面其实和地面是一样的,可以看做普通的均匀网格,不同点在于地形中,顶点高度是固定的,而水面是动态的。此外,对于水面来说,还有许多特殊的光学效果。为了更好的描述渲染效果,下面借用了《Water Effect》一文中的部分图片。
一. 水面动画
当然,第一步就是让水面运动起来。对均匀网格来说,需要计算出每个顶点的位置和法线。实际上,如果仅模拟比较平静的水面,甚至只需计算法线就行了,这也是目前游戏中常见的做法。
仅使用法线来模拟水面的好处是可以极大的简化几何模型,最简单的情况下,一个网格就能代表整个水面,你甚至不必担心如何处理LOD计算。它的实现思想就是bump mapping,区别在于这里我们使用动态的normal map。可以实时计算出每条法线的位置,也可以从预先处理好的normal map中获得这些信息。显然,适用后者计算量可以减少很多。在Farcry中,水面实际上只使用了一张normal map,在vertex shader中,对纹理坐标进行不同的缩放和偏移,从而获得动态水面。对这种方法来说,normal map质量的好坏对最终效果有很大影响。下面的代码显示了通过混合三层不同的波来模拟水面的情况:
bumpCoord1 = texCoord + time*0.02;
bumpCoord2 = texCoord * 2.0f + time * 0.02;
bumpCoord3 = texCoord / 2.0f + time * 0.01;
half3 vBumpTexA = tex2D(tex0, bumpCoord1).xyz;
half3 vBumpTexB = tex2D(tex0, bumpCoord2).xyz;
half3 vBumpTexC = tex2D(tex0, bumpCoord3).xyz;
half3 vBumpTex = normalize(2.0 * (vBumpTexA.xyz + vBumpTexB.xyz+vBumpTexC.xyz) - 3.0);
上述方法最大的缺点就是对于起伏较大的水面来说,就无能为力了,由于仅仅使用法线来表示水面起伏,因此,当近距离观察时,可以看到水面实际是静止的。为了进一步增加真实性,让顶点真正运动起来是必须的。如何来计算水面运动呢?简单来说,可以把水面运动看作一系列正弦或余弦波的叠加。根据不同的时间和顶点坐标位置,计算出当前顶点的高度。然而,实践中我们却更偏向于适用Gerstner Wave。以下是3D Gerstner Wave的3D波动方程,注意这里的x是一个矢量x(x,z),代表顶点的坐标位置,w为角速度,k为波矢量,k为波数,x0为顶点的初始位置。
从数学角度来看,Gerstner Wave并不比普通的正余弦波动方程复杂,但他的波形却更像水波。对于正余弦波来说,波峰和波谷波的弧度都是均匀的,而实际上水波的波峰要尖一些,波峰则要圆滑。注意上述方程中对给定顶点x0来说,不同时刻,水平位置也是不同的,因此可以正确模拟出这种效果。多个Gerstner Wave相互叠加,就能模拟出相当不错的水面。有了水面高度,进一步对方程求导,就能计算出法线。
有时,你也许需要渲染电影级真实度的水面,那么这种简单波形叠加的方法也许就有些力不从心。可以适用FFT来模拟更真实的水面,它使用了统计波的模型(statistical wave model),通过快速傅立叶(FFT)变换求值。基本的思想是创建一个类似海面的,具有相同频谱的波形域,然后通过傅立叶变换转换到空间域。因此,水体表面实际上就是由许多由风产生的正弦波线性叠加而成。以下是所使用的波形方程:
公式中,h表示t时刻在网格位置 处的水面高度。 表示波矢(wave vector), 的计算公式如下
空间谱(spatial spectrum) 通过过滤复数形式的白噪声(complex white noise)获得:
对FFT生成水面的细节超出了本文讨论范围,但总的来说和Gerstner Wave是类似的,只不过方程更复杂而已。
还值得一提的是用shader mode3中的vertex texture技术,可以把以上两种计算高度的方法放到程序的预处理阶段,运行时直接使用计算好的水面高度图,这样可以大大提高效率。
(混合多个Gerstner wave得到的水面)
二 水面颜色
计算水面颜色的方法很多,这里介绍一种简单高效的方法。假设水的颜色只与水面高度和观察着角度有关,那么可以这样计算颜色:
half facing = saturate(dot( eyeVect,normal));
half3 waterColor = lerp(shallowWaterColor,deepWaterColor,facing);
下面是添加了水面颜色的渲染效果:
三. Bump Mapping
仅仅靠的第一部分所说的方法来计算水面顶点位置和法线还不够,它们受到网格间距精度限制,只能模拟波长较长的波。这样的水面太过于平和,而显得不够真实。因此,需要添加Bump Mapping来增加细节和高频波浪。这里可以使用的第一部分仅仅使用normal map模拟水纹的方式,来获得动态bump mapping效果。注意转换到tangent space下进行光照计算。
四. 反射和折射
反射和折射的思想都一样,以水面为裁减平面,分别把场景渲染为折射和反射帖图,然后投影到水面上。在渲染反射帖图时,需要以水平面为参考面,把摄像机镜像翻转到水面之下。投影纹理时,这里需要用到顶点位置作为参数,来动态调整纹理坐标,反映出水波的流动效果。下图是添加了反射帖图的水面:
五. 菲捏尔效果(Fresnel term)
对水面来说,当观察者和水面的角度越小时,反射效果越明显,角度越大时,折射效果越明显,称为菲捏尔效果。所以需要更具观察者的角度来计算反射,折射帖图,以及水面颜色的混合方式。物理上正确的菲捏尔向计算比较复杂,通常使用近似的计算方法就可以了:
fastFresnel = r + ( 1-r ) * pow ( 1.0 – dot ( viewDir, normal ), 5.0);
这里,空气到水面的反射系数约为0.02037。
最终水面颜色为
finalColor = waterColor + reflectionColor * fastFresnel + reftractionColor * ( 1 – fastFresnel );
六.参考
《Deep-Water Animation and Rendering》
《Simulating Ocean Water》
《Animation and Display of Water》
《Effective Water Simulation form Physical Models》
《Using Vertex Texture Displacement for Realistic Water Rendering》
《Animation and Rendering of Underwater God Rays》
《An Efficient Method for Rendering Underwater Optical Effects Using Graphics Hardware》
《Interactive Animation of Ocean Waves》
《Generic Refraction Simulation》
《Water Effect》
版权声明:本文为博主原创文章,未经博主允许不得转载。