暴米花 发表于 2006-12-7 10:53:00

Using Vertex Texture Displacement for Realistic Water Render

<table cellspacing="1" cellpadding="4" width="100%" border="0" style="TABLE-LAYOUT: fixed; WORD-WRAP: break-word;"><tbody><tr><td valign="top">&nbsp;<b>Using Vertex Texture Displacement for Realistic Water Rendering(上)</b><br/><br/>  这篇是《GPU&nbsp;Gems&nbsp;2》中的第18章,讨论了如何使用顶点纹理置换来渲染水面。个人觉得这篇文章讲的不太细致,而且很多问题,比如如何表现水面的深浅、纹理映射,都没有讲,对于法线生成的方法也只是大概提了一下。当然,总的来说,还是值得一看的,特别是文中使用了一种特别的LOD处理,比较有趣。 <p></p><p>Using&nbsp;Vertex&nbsp;Texture&nbsp;Displacement&nbsp;for&nbsp;Realistic&nbsp;Water&nbsp;Rendering</p><p>此教程版权归我所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。<br/>由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。也欢迎大家和我多多交流。<br/>翻译:clayman<br/>Blog:<a href="http://blog.csdn.net/soilwork" target="_blank"><font color="#9c0000">http://blog.csdn.net/soilwork</font></a><br/>clayman_joe@yahoo.com.cn</p><p>  水面在计算机图形学,特别是游戏中,是一种常见的效果。它是增加场景真实性的重要元素之一。但要模拟真实的水面,却是一个难题,因为水面的运动和光学效果都相当复杂。这篇文章描述了游戏Pacific&nbsp;Fighters中,用来渲染水面的技术。<br/>  支持DirectX&nbsp;Shader&nbsp;Model&nbsp;3.0的硬件提供了许多相当有用的特性,可以用来帮助渲染水面。下面,我们就将讨论如何使用顶点纹理(Vertex&nbsp;Texture)来渲染真实的水面。此外,我们还使用了braching技术来提高渲染效率。</p><p><img src="http://blog.csdn.net/images/blog_csdn_net/soilwork/sampler%20result.jpg" border="0" alt=""/><br/>最终的结果,左图使用了displacement&nbsp;mapping,右图则没有</p><p><br/>1.1水体模型<br/>  对水面动画和渲染来说,已经发展出多种方法。其中,最出名也是效果最真实的,就是基于流体动力学和快速傅立叶的方法(FFTs)(比如Tessendorf&nbsp;2001中描述的)。这些方法可以提供非常真实的渲染效果,但不幸的是他们需要相当大的计算量,因此,不适合于交互式的实时渲染。<br/>  此外,现在大多数游戏使用的都是相当简单的模型,大部分方法仅仅是通过改变水面法线创建水面效果。使用这些方法渲染水面,虽然相当高效,但真实度很低,而且并没有真正在水面产生任何波纹。<br/>因此,我需要一种综合了两种方法优点的技术来渲染水面。</p><p>1.2&nbsp;实现<br/>  我们的实现中,水面渲染将依赖于法线图(normal&nbsp;map)来进行光照计算。因为法线图可以忠实的重现高频率波形下的所有细节。此外,使用低频率、高振幅的波对水面网格进行扰动。</p><p>1.2.1&nbsp;水面模型<br/>  我们的水面模型基于多张,在空间和时间上都进行了分割(tiled)的高度图(height&nbsp;map)的重叠。每张纹理都代表了频谱(spectrum)中的一个“谐波(harmonic)”或者“倍频程(octave)”,和那些使用FFT的方法一样,将把这些纹理叠加到一起。这些纹理就是高度图,每个像素元代表了对应位置的水平高度。</p><p>  对于艺术家来说,创建高度图是很简单的:创建它们就和绘制一张简单的灰度图一样。使用高度图,艺术家只需分别绘制单个波纹的形状,就可以可以很容易的控制水面动画。把高度图当作顶点纹理来使用也是很方便的:使用它来置换(displace)顶点的垂直位置是很有效的。</p><p>&nbsp;<img src="http://blog.csdn.net/images/blog_csdn_net/soilwork/height%20map.jpg" border="0" alt=""/><br/>一张用来置换水面位置的高度图</p><p>  使用不同的空间和时间缩放比例,混合多张高度图,我们可以获得相当复杂的水面动画效果:<br/>&nbsp;<img src="http://blog.csdn.net/images/blog_csdn_net/soilwork/gs.jpg" border="0" alt=""/></p><p>  为了实现真实可信的水面,应该合理的选择参数A、B和i的值,以最小化波纹的重复效果。对于Pacific&nbsp;Fighters来说,我们混合了4张高度图来进行光照计算,其中比例尺较大的两张还将用作置换贴图。这样,我们可以模拟比例尺从10cm到40km大小的水面。</p><p>1.2.2&nbsp;实现细节<br/>  可以把所有需要进行的运算分为两类:几何体置换计算和光照计算。因为水面镶嵌(tessellated)良好,因此,可以把光照计算放到片断(fragment)程序中,而把置换映射放到顶点程序里。当然,把光照计算放到顶点处理阶段也是可以的,特别是对于远处的顶点来说。</p><p>  在写这篇文章时,唯一支持顶点纹理的硬件就是GeForce&nbsp;6系列以及最新的Quadro&nbsp;FX&nbsp;GPU。在这些硬件上,使用顶点纹理有一些特别的限制,只能使用32bit的浮点纹理,只能使用nearest&nbsp;filtering。</p><p>1.2.3&nbsp;采样高度图<br/>  我们的实现中,对高度图中的每个顶点进行采样,在顶点程序中计算置换值。对于采样来说,使用了一个放射状的网格,它的中心位于摄像机所在位置。按照这个样子镶嵌的网格,可以让靠近观察者的地方提供更多细节。<br/><img src="http://blog.csdn.net/images/blog_csdn_net/soilwork/grid.jpg" border="0" alt=""/></p><p>下面的公式说明了如何计算放射网格中的顶点位置:<br/>&nbsp;<img src="http://blog.csdn.net/images/blog_csdn_net/soilwork/eqt.jpg" border="0" alt=""/></p><p>这里i&nbsp;=&nbsp;[&nbsp;0….N-1],&nbsp;j&nbsp;=&nbsp;[&nbsp;0…..M-1]。我们选择<br/>r0&nbsp;=&nbsp;a0&nbsp;=&nbsp;10cm<br/>rN-1&nbsp;=&nbsp;a0&nbsp;+&nbsp;a1(&nbsp;N&nbsp;–&nbsp;1)4&nbsp;=&nbsp;40km</p><p>  这种基于距离的镶嵌,提供了一种相当简洁的LOD方法。当然,这里也可以使用其他的地形渲染算法来实现LOD,比如ROAM或者SOAR,但这就需要占用一定的CPU资源进行计算,这完全违背了使用顶点纹理的初衷。</p><p>  下面的代码显示了如何在vertex&nbsp;shader中,使用放射形网格对一张高度图进行采样:<br/>float4&nbsp;main(float4&nbsp;position : POSITION,<br/>uniform&nbsp;sampler2D&nbsp;tex0,<br/>uniform&nbsp;float4x4&nbsp;ModelViewProj,<br/>uniform&nbsp;float4&nbsp;DMParameters, //&nbsp;displacement&nbsp;map&nbsp;parameters<br/>uniform&nbsp;float4&nbsp;VOFs)&nbsp;: POSITION<br/>{<br/>//read&nbsp;vertex&nbsp;packed&nbsp;as&nbsp;(cos(),sin(),j)<br/>float4&nbsp;INP&nbsp;=&nbsp;position;<br/>//transform&nbsp;to&nbsp;radial&nbsp;grid&nbsp;vertex<br/>INP.xy&nbsp;=&nbsp;INP.xy&nbsp;*&nbsp;(pow(INP.z,4)&nbsp;*&nbsp;VOFs.z);<br/>//find&nbsp;displacement&nbsp;map&nbsp;texture&nbsp;coordinates<br/>//VOFs.xy&nbsp;,DMParameters.x&nbsp;-&nbsp;height&nbsp;texture&nbsp;offset&nbsp;and&nbsp;scale<br/>float2&nbsp;t&nbsp;=&nbsp;(INP.sy&nbsp;+&nbsp;VOFs.xy)&nbsp;*&nbsp;DMParameters.x;<br/>//fetch&nbsp;displacement&nbsp;value&nbsp;form&nbsp;texture&nbsp;(lod&nbsp;0)<br/>float&nbsp;vDisp&nbsp;=&nbsp;tex2d(tex0,&nbsp;t).x;<br/>//scale&nbsp;fetched&nbsp;value&nbsp;form&nbsp;0...1<br/>//DMParameters.y&nbsp;-&nbsp;water&nbsp;level<br/>//DMParameters.z&nbsp;-&nbsp;wavy&nbsp;amplitude<br/>INP.z&nbsp;=&nbsp;DMParameters.y&nbsp;+&nbsp;(vDisp&nbsp;-&nbsp;0.5)&nbsp;*&nbsp;DMParameters.z;<br/>//displace&nbsp;current&nbsp;position&nbsp;with&nbsp;water&nbsp;height&nbsp;and&nbsp;project&nbsp;it<br/>return&nbsp;mul(ModelViewProj,INP);<br/>}</p><p><br/>1.2.4&nbsp;质量改进和优化<br/>  Packing&nbsp;Heights&nbsp;for&nbsp;Bilinear&nbsp;Filtering<br/>  顶点纹理拾取(fetch)是相当耗费资源的操作。在GeForce&nbsp;6系列的硬件上,可以在顶点程序中引入一个顶点纹理拾取器。我们必须最小化在顶点程序中进行纹理拾取的次数。此外,对纹理中的值进行过滤(filtering)也是必须的,否则视觉效果将会大打折扣。</p><p>  一般来说,最常见的过滤方法就是双线性过滤和三线性过滤。双线性过滤将对最靠近纹理选取坐标位置处的四个像素元(texels)进行均值计算。三线性过滤则需要对邻近mip层次中的双线性过滤结果进行均值计算,同时根据不同的LOD层次选择每个层次的混合权重。</p><p>  由于当前的图形卡无法对顶点纹理中的值进行任何形式的过滤,我们不得不在shader中直接使用数学运算指令来模拟过滤。对于不好的实现来说,即使是最简单的双线性过滤也需要通过通过四次纹理查找来计算一个过滤值,而三线过滤的查找次数更是翻了两倍。</p><p>  为了减少过滤时的纹理拾取次数,我们用一种特殊的方法来创建纹理,让一个像素元就包含了一次双线性纹理查找所需要的所有数据。这是一个相当可行的方法,因为高度图本质上就是一张单通道(one-component)的纹理,我们可以把4张高度图打包为一张每个像素元四通道的纹理:<br/>&nbsp;<br/>  这里i&nbsp;=&nbsp;[&nbsp;0….N-1],&nbsp;j&nbsp;=&nbsp;[&nbsp;0…..M-1]。H表示高度图的值,F是过滤函数,A则是打包好的输出纹理。</p><p>下面的代码实现了对顶点纹理进行双线性查找<br/>float&nbsp;tex2D_bilinear4x(uniform&nbsp;sampler2D&nbsp;tex,&nbsp;<br/>float4&nbsp;t,<br/>float2&nbsp;Scales)<br/>{<br/>float&nbsp;size&nbsp;=&nbsp;Scales.x;<br/>float&nbsp;scale&nbsp;=&nbsp;Scales.y;<br/>float4&nbsp;tAB0&nbsp;=&nbsp;tex2Dbisa(tex,&nbsp;t);<br/>float2&nbsp;f&nbsp;=&nbsp;frac(t.xy&nbsp;*&nbsp;size)&nbsp;;<br/>float3&nbsp;tAB&nbsp;=&nbsp;lerp(tAB0.xy,&nbsp;tAB0.yw,&nbsp;f.x);<br/>return&nbsp;lerp(tAB.x,&nbsp;tAB.y,&nbsp;f.y);<br/>}</p><p>  我们可以把这个方法扩展为三线性过滤。因为三线性过滤需要局部LOD的值,因此,我们把顶点到摄像机的距离作为LOD的近似值。下面的代码实现了对打包之后的顶点纹理进行三线性查找:<br/>float&nbsp;tex2D_trilinear(uniform&nbsp;sampler2D&nbsp;tex<br/>float4&nbsp;t,<br/>float4&nbsp;Scales)<br/>{<br/>float&nbsp;fr&nbsp;=&nbsp;frac(t.z);<br/>t.z&nbsp;-=&nbsp;fr;&nbsp; //&nbsp;floor(r.zw)<br/>float&nbsp;Res;<br/>if(fr&nbsp;&lt;&nbsp;0.30)<br/>Res&nbsp;=&nbsp;tex2D_bilinear4x&nbsp;(&nbsp;tex,&nbsp;t.xyzz,&nbsp;Scales);<br/>else&nbsp;if&nbsp;(fr&nbsp;&gt;&nbsp;0.70)<br/>Res&nbsp;=&nbsp;tex2Dbilinear4x(tex,&nbsp;t.xyzz&nbsp;+&nbsp;float4(0,0,1,1),&nbsp;Scales&nbsp;*&nbsp;float2(0.5,2);<br/>else<br/>{<br/>Res&nbsp;=&nbsp;tex2D_bilinear4x(tex,&nbsp;t.xyzz,&nbsp;Scales);<br/>float&nbsp;Res1&nbsp;=&nbsp;tex2D_bilinear4x(tex,&nbsp;t.xyzz&nbsp;+&nbsp;float4(0,0,1,1),&nbsp;Scales&nbsp;*&nbsp;float2(0.5,2));<br/>fr&nbsp;=&nbsp;saturate&nbsp;(&nbsp;(&nbsp;fr&nbsp;–&nbsp;0.30)&nbsp;*&nbsp;(&nbsp;1&nbsp;/&nbsp;(&nbsp;0.70&nbsp;–&nbsp;0.30)));<br/>Res&nbsp;=&nbsp;Res1&nbsp;*&nbsp;fr&nbsp;+&nbsp;Res&nbsp;*&nbsp;(-fr)&nbsp;;<br/>}<br/>return&nbsp;Res&nbsp;;<br/>}</p><p>  注意,这里我们进一步对三线性纹理选取进行了优化,只对影响较大的两个mip层次区域进行纹理查找。对其他区域来说,直接把他们的LOD值设置为最相邻mip层次的值,从而节约纹理带宽。<br/>(未完待续ing~~~)</p></td></tr></tbody></table>
页: [1]
查看完整版本: Using Vertex Texture Displacement for Realistic Water Render