Using Vertex Texture Displacement for Realistic Water Rendering(上)

最近在研究水面渲染,眼看马上要毕业交论文了,还什么都没有弄,大意了~~~。Tessendorf  2001发表的那篇论文真是影响深远,无论FFT还是顶点纹理的实现方法,本质上都是波纹叠加。简单对比了两种实现的demo,顶点纹理的实现CPU占用率确实很少,用任务管理器看大概就30%左右,而FFT的demo只要一开就是100%。不过顶点纹理只有GeForce 6以上的显卡能跑,而且ATI的卡似乎不支持顶点纹理,应该算是它最大的局限性。另外,对LOD的实现来说,也是方法多多,看了几篇论文,实现都不一样,还在研究中-_-#。 
        这段时间慢慢把看的文章都翻译整理出来,下面这篇是《GPU Gems 2》中的第18章,讨论了如何使用顶点纹理置换来渲染水面。个人觉得这篇文章讲的不太细致,而且很多问题,比如如何表现水面的深浅、纹理映射,都没有讲,对于法线生成的方法也只是大概提了一下。当然,总的来说,还是值得一看的,特别是文中使用了一种特别的LOD处理,比较有趣。

Using Vertex Texture Displacement for Realistic Water Rendering(上)

 

此教程版权归我所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。也欢迎大家和我多多交流。
翻译:clayman
Blog:http://blog.csdn.net/soilwork

         水面在计算机图形学,特别是游戏中,是一种常见的效果。它是增加场景真实性的重要元素之一。但要模拟真实的水面,却是一个难题,因为水面的运动和光学效果都相当复杂。这篇文章描述了游戏Pacific Fighters中,用来渲染水面的技术。

         支持DirectX Shader Model 3.0的硬件提供了许多相当有用的特性,可以用来帮助渲染水面。下面,我们就将讨论如何使用顶点纹理(Vertex Texture)来渲染真实的水面。此外,我们还使用了braching技术来提高渲染效率。

最终的结果,左图使用了displacement mapping,右图则没有

 

1.1水体模型

对水面动画和渲染来说,已经发展出多种方法。其中,最出名也是效果最真实的,就是基于流体动力学和快速傅立叶的方法(FFTs)(比如Tessendorf 2001中描述的)。这些方法可以提供非常真实的渲染效果,但不幸的是他们需要相当大的计算量,因此,不适合于交互式的实时渲染。

此外,现在大多数游戏使用的都是相当简单的模型,大部分方法仅仅是通过改变水面法线创建水面效果。使用这些方法渲染水面,虽然相当高效,但真实度很低,而且并没有真正在水面产生任何波纹。

因此,我需要一种综合了两种方法优点的技术来渲染水面。

 

1.2 实现

         我们的实现中,水面渲染将依赖于法线图(normal map)来进行光照计算。因为法线图可以忠实的重现高频率波形下的所有细节。此外,使用低频率、高振幅的波对水面网格进行扰动。

 

1.2.1 水面模型

         我们的水面模型基于多张,在空间和时间上都进行了分割(tiled)的高度图(height map)的重叠。每张纹理都代表了频谱(spectrum)中的一个“谐波(harmonic)”或者“倍频程(octave)”,和那些使用FFT的方法一样,将把这些纹理叠加到一起。这些纹理就是高度图,每个像素元代表了对应位置的水平高度。

         对于艺术家来说,创建高度图是很简单的:创建它们就和绘制一张简单的灰度图一样。使用高度图,艺术家只需分别绘制单个波纹的形状,就可以可以很容易的控制水面动画。把高度图当作顶点纹理来使用也是很方便的:使用它来置换(displace)顶点的垂直位置是很有效的。

一张用来置换水面位置的高度图

         使用不同的空间和时间缩放比例,混合多张高度图,我们可以获得相当复杂的水面动画效果:

         为了实现真实可信的水面,应该合理的选择参数A、B和i的值,以最小化波纹的重复效果。对于Pacific Fighters来说,我们混合了4张高度图来进行光照计算,其中比例尺较大的两张还将用作置换贴图。这样,我们可以模拟比例尺从10cm到40km大小的水面。

 

1.2.2 实现细节

         可以把所有需要进行的运算分为两类:几何体置换计算和光照计算。因为水面镶嵌(tessellated)良好,因此,可以把光照计算放到片断(fragment)程序中,而把置换映射放到顶点程序里。当然,把光照计算放到顶点处理阶段也是可以的,特别是对于远处的顶点来说。

         在写这篇文章时,唯一支持顶点纹理的硬件就是GeForce 6系列以及最新的Quadro FX GPU。在这些硬件上,使用顶点纹理有一些特别的限制,只能使用32bit的浮点纹理,只能使用nearest filtering。

 

1.2.3 采样高度图

         我们的实现中,对高度图中的每个顶点进行采样,在顶点程序中计算置换值。对于采样来说,使用了一个放射状的网格,它的中心位于摄像机所在位置。按照这个样子镶嵌的网格,可以让靠近观察者的地方提供更多细节。

下面的公式说明了如何计算放射网格中的顶点位置:

这里i = [ 0….N-1], j = [ 0…..M-1]。我们选择

r0 = a= 10cm

rN-1 = a0 + a1( N – 1)4 = 40km

         这种基于距离的镶嵌,提供了一种相当简洁的LOD方法。当然,这里也可以使用其他的地形渲染算法来实现LOD,比如ROAM或者SOAR,但这就需要占用一定的CPU资源进行计算,这完全违背了使用顶点纹理的初衷。

         下面的代码显示了如何在vertex shader中,使用放射形网格对一张高度图进行采样:

float4 main(float4 position :    POSITION,

     uniform sampler2D tex0,

     uniform float4x4 ModelViewProj,

     uniform float4 DMParameters,     // displacement map parameters

     uniform float4 VOFs) : POSITION

{

     //read vertex packed as (cos(),sin(),j)

     float4 INP = position;

     //transform to radial grid vertex

     INP.xy = INP.xy * (pow(INP.z,4) * VOFs.z);

     //find displacement map texture coordinates

     //VOFs.xy ,DMParameters.x - height texture offset and scale

     float2 t = (INP.sy + VOFs.xy) * DMParameters.x;

     //fetch displacement value form texture (lod 0)

     float vDisp = tex2d(tex0, t).x;

     //scale fetched value form 0...1

     //DMParameters.y - water level

     //DMParameters.z - wavy amplitude

     INP.z = DMParameters.y + (vDisp - 0.5) * DMParameters.z;

     //displace current position with water height and project it

     return mul(ModelViewProj,INP);

}

        

1.2.4 质量改进和优化

       Packing Heights for Bilinear Filtering

         顶点纹理拾取(fetch)是相当耗费资源的操作。在GeForce 6系列的硬件上,可以在顶点程序中引入一个顶点纹理拾取器。我们必须最小化在顶点程序中进行纹理拾取的次数。此外,对纹理中的值进行过滤(filtering)也是必须的,否则视觉效果将会大打折扣。

         一般来说,最常见的过滤方法就是双线性过滤和三线性过滤。双线性过滤将对最靠近纹理选取坐标位置处的四个像素元(texels)进行均值计算。三线性过滤则需要对邻近mip层次中的双线性过滤结果进行均值计算,同时根据不同的LOD层次选择每个层次的混合权重。

         由于当前的图形卡无法对顶点纹理中的值进行任何形式的过滤,我们不得不在shader中直接使用数学运算指令来模拟过滤。对于不好的实现来说,即使是最简单的双线性过滤也需要通过通过四次纹理查找来计算一个过滤值,而三线过滤的查找次数更是翻了两倍。

         为了减少过滤时的纹理拾取次数,我们用一种特殊的方法来创建纹理,让一个像素元就包含了一次双线性纹理查找所需要的所有数据。这是一个相当可行的方法,因为高度图本质上就是一张单通道(one-component)的纹理,我们可以把4张高度图打包为一张每个像素元四通道的纹理:

         这里i = [ 0….N-1], j = [ 0…..M-1]。H表示高度图的值,F是过滤函数,A则是打包好的输出纹理。

         下面的代码实现了对顶点纹理进行双线性查找

float tex2D_bilinear4x(uniform sampler2D tex,

         float4 t,

         float2 Scales)

{

         float size = Scales.x;

         float scale = Scales.y;

         float4 tAB0 = tex2Dbisa(tex, t);

         float2 f = frac(t.xy * size) ;

         float3 tAB = lerp(tAB0.xy, tAB0.yw, f.x);

         return lerp(tAB.x, tAB.y, f.y);

}

         我们可以把这个方法扩展为三线性过滤。因为三线性过滤需要局部LOD的值,因此,我们把顶点到摄像机的距离作为LOD的近似值。下面的代码实现了对打包之后的顶点纹理进行三线性查找:

float tex2D_trilinear(uniform sampler2D tex

         float4 t,

         float4 Scales)

{

         float fr = frac(t.z);

         t.z -= fr;    // floor(r.zw)

         float Res;

         if(fr < 0.30)

                   Res = tex2D_bilinear4x ( tex, t.xyzz, Scales);

         else if (fr > 0.70)

                   Res = tex2Dbilinear4x(tex, t.xyzz + float4(0,0,1,1), Scales * float2(0.5,2);

         else

         {

                   Res = tex2D_bilinear4x(tex, t.xyzz, Scales);

                   float Res1 = tex2D_bilinear4x(tex, t.xyzz + float4(0,0,1,1), Scales * float2(0.5,2));

                   fr = saturate ( ( fr – 0.30) * ( 1 / ( 0.70 – 0.30)));

                   Res = Res1 * fr + Res * (-fr) ;

         }

         return Res ;

}

         注意,这里我们进一步对三线性纹理选取进行了优化,只对影响较大的两个mip层次区域进行纹理查找。对其他区域来说,直接把他们的LOD值设置为最相邻mip层次的值,从而节约纹理带宽。(未完待续ing~~~)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值