nVIDIA SDK White Paper ----Vertex Texture Fetch Water

nVIDIA SDK White Paper
Vertex Texture Fetch Water
作者: Jeremy Zelsnack
本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。也欢迎大家和我多多交流。
翻译:clayman
Blog:http://blog.csdn.net/soilwork
nVIDIA SDK White Paper ----Vertex Texture Fetch Water_第1张图片
 
水面模拟
水面模拟的目标是在合理的计算量下,创造视觉上真是可信的水面。其中可能的方法之一是基于流体力学中的 Navier-Stokes 方程求解。这种方法可以创建极度真是的水面,不幸的是,计算量太过沉重。一种较为简单,同时资源消耗较少的方法是在一张均匀网格上解 2D 波方程。牺牲一点点真实性换取实现的简洁和速度。本文描述了 2D 波方程技术。
  

      
上面的就是 2D 波方程。这个方程表示点将在 Y 方向上上下起伏。 C 表示波传播的速度。直观的说,这个方程表示点上下起伏的加速度,和表面起伏改变的快慢是成正比的。
看到这里,你可能会问,“如何来求解这样一个方程呢?” GPU 并不知道偏导数是什么;只知道空间位置而已。如果我们把方程左边对 t 进行两次积分,就得到了 y 值。如果继续把计算出的 y 值与点 x z 的值组合到一起,就得到了 3D 坐标,这就是 GPU 所能理解的了。

nVIDIA SDK White Paper ----Vertex Texture Fetch Water_第2张图片

接下来的难题就是如何对方程的右边进行积分。有许多种积分的技术可以使用,每种都有其优点和缺点。对我的程序来说,需要的是稳定性和速度。稳定性是相当重要的,因为公正的说,这个方程太过于呆板。方程右边的 C2 将使左边的值迅速增大。因此,不能使用类似欧拉的简单积分方法――它们不太稳定。 Runge Kutta 方法也是不行的,因为它需要计算中间值,同时,还对储存速率有很高要求。这对于我们的程序来说太复杂,太耗费资源。 Verlet 积分计算量很轻,同时也很稳定,对储存速率也没有要求。不幸的是我忘记了 Verlet 方程( -_-# ~~ ),并且在 30 秒内都没有找到任何关于 Verlet 方程的参考资料( = = )。因此,我重新发明了一种类似于 Vertet 的方法。
如果我们知道了点在 t=0 时刻的位置 P t0 ),以及 t=1 时刻的位置 P(t1) ,加速度 a(t1) ,那么使用方程 3 ,只需要一些高中的物理知识,就可以求解 t=2 时刻的位置 P(t2) 。这里的推导假设采样步长是常数,因此, t1-t0 = t2 – t1 = t(n+1) – tn 。当然,也可以步长值也可以为非常量,但这就需要更多的数学计算。

nVIDIA SDK White Paper ----Vertex Texture Fetch Water_第3张图片

         方程 3 和真正的 Verlet 积分(方程 4 )相当类似。唯一的不同,就是在加速度系数上有区别。

         好了,方程 3 描述了一种计算积分的方法。但是有一个问题,它并不像看起来那么稳定,而是抖动的( jittery )。我们需要做一些额外的处理,来消耗由于不精确的积分而带来的额外能量。可以添加一个常量因子来减小通过推测而得来的速率。

nVIDIA SDK White Paper ----Vertex Texture Fetch Water_第4张图片

         好了,现在我们有了积分的技术,几乎可以对 2D 波方程的右边进行积分了。现在来看看如何计算方程右边的偏导数。方法之一,就是找一个和我们所要模拟的顶点相称的表面。有了这个表面,我们就能分析推测偏导数的计算公式。
         那么什么样的表面才和要模拟的网格类似呢?我们需要一个快速,简单同时能在一定程度上保持精确的模型。 3 次样条曲线集合正好能满足我们的要求。可以对问题进行简化,变为对两个 2D 三次样条曲线的问题。这样就推导出了公式 6 。对一个三次方程来说,需要指定 4 个常数。其中, 3 个显而易见的常数就是当前以及邻近顶点的坐标。第四个常量则是当前顶点位置下的导数。图一描述了这种情况。假设切线和邻近的顶点是平行的,同时,网格也是均匀的,则可以得到一个 2 阶偏导数方程(公式 7 )。使用偏导数,右边的 2D 波公式就变为了方程 8

nVIDIA SDK White Paper ----Vertex Texture Fetch Water_第5张图片
nVIDIA SDK White Paper ----Vertex Texture Fetch Water_第6张图片

         好了,现在我们简单分析了计算方程右边的算法,以及积分方法,可以实际计算网格中顶点的 y 值了。由于我们假设网格中的所有顶点之间的距离都为 1 个单位(在世界坐标中不一定合适),因此需要调整波的速度,让他看起来正常。
         好了,现在可以使用简单的迭带来求解波形方程,我们让 GPU 来完成这个任务吧。我们把均匀网格的高度保存为一张纹理。同时,还需要追踪上一张高度图的值。这样就可以计算下一张高度图的值了。下面是使用 HLSL 求解波形方程的代码:

 
         顶点纹理将保存为 16bit 的浮点纹理。使用 8bit 的整数是不可行的, 16bit 的整数虽然可以满足精度的要求,但无论如何浮点数都是最好以及最方便的选择。示例的程序中,我们把高度和法线混合起来,保存为一张 D3DFMT_A16B16G16R16 的纹理。这样做,可以减少 vertex shader 中纹理拾取的次数。
         好了,现在有一张用来模拟水面的高度图了,如何来使用它呢? Shader Model 3.0 支持在 vertex shader 中进行纹理拾取(在 VS 3.0 中)。为了渲染水面,我们把包含了有效顶点位置的网格 mesh 传入到 vertex shader 中。顶点纹理拾取能有效的帮助我们把纹理转换为几何体。更多信息请参考( ftp://download.nvidia.com/developer/Papers/2004/Vertex_Textures/Vertex_Textures.pdf
         需要注意的是, GeForce 6 系列的硬件在 vertex shader 中并不支持 D3DFMT_A16B16G16R16 的格式。这就需要把 fp16 的波形方程结果转换为 fp32 格式。你可能会问,问什么开始不直接把波形方程的结果保存为 fp32 格式呢?答案是 fp2 的纹理渲染起来很慢,此外, fp32 的渲染目标不支持混合或者纹理过滤。由于 fp32 缺乏混合机制,将导致水面控制更加复杂。而缺乏过滤,则会降低渲染质量。

nVIDIA SDK White Paper ----Vertex Texture Fetch Water_第7张图片

         好了,有了模拟水面的方法,接下来看看如何控制它。幸运的是,使用 Verlet 积分对此进行控制。 Verlet 积分本身就表示了高度偏移的速率。在 GeForce 6 系列的硬件上,高度偏移值将直接渲染为 fp16 格式的高度纹理。这是最自然也是最高效的方法。但是在 Geforce 6 系列之前的硬件上,这却是不可行的,应为缺乏对 fp16 alpha 混合的支持。没有混合,就不得不把高度偏移值渲染为额外的非 fp16 渲染目标中,之后,再把这张偏移值纹理渲染到高度图之上。这是很麻烦的方法,同时精度也不高。
         再进一步来看看这个方法的性能如何,水面模拟到底有多快。虽然看起来有很多操作。幸运的是,这对 GPU 来说并算不了什么。你基本上只是在一个简单的 pixel shader 中渲染了一张小渲染目标(比如 128x128 )。凭感觉来说,这对现代 GPU 算不了什么。作为一个稍微“科学”的基准来看,在 GeForce 6800GT 的显卡上,关闭了水面模拟之后,帧速率从 263fps 变为了 268fps 。这表示我们的模拟只需要 0.07 毫秒,这并不算长。与实际的模型相比,顶点纹理拾取的性能才是瓶颈。
~~~~~~~~~~~~~~~~~~~·未完待续·~~~~~~~~~~~~~~~~~~~~~~~~
这篇文章是SDK里Vertex Texture Fetch Water的白皮书,今天头晕晕的,好多地方翻的不恰当,要看的同志还是对照原文一起来-_-#。 又快天亮了,偶睡觉去鸟~~~~

你可能感兴趣的:(算法,Blog,FP,shader,数学计算,网格)