[DirectX11]Gerstner波 實現簡單的水面模擬
上一篇文章中,介紹了一個簡單數值方法來模擬圓形擴散波的效果,但是這種方法對於自然中像海浪一樣的波
就無能為力了。所以,這篇文章介紹用Gerstner波來模擬水面波紋效果。
一、Gerstner波介紹
Gerstner波是一種動態模擬海面幅度的方法,已有200多年的歷史,後被用於計算機圖形學。
首先,我們介紹一下幾個常用的描述波函式的物理量,便於理解馬上要介紹的Gerstner波方程。
A:amplitude 振幅,波相較於平衡位置的最大偏移量。
ω:角速度。
λ:波長
k:波數 (2π/λ)
K:波向量(wavevector)其大小為波數,方向為波傳播的方向,在3D波方程中,是一個2D向量
現在給出Gerstner波的方程,以引數方程的形式給出:
x=x0-(K/k)*Asin(K*x0-ωt)
y=Acos(K*x0-ωt)
肯定有朋友會問,明明是3D的波方程,為何只有兩個變數xy。大家請注意,第一個式子中,加粗的部分,上面
已經介紹過,K是一個2D向量,方向表示在x-z平面波傳播的方向,所以第一個加粗的部分(包括x,x0)都不是一個標量,而是一個二元量,表示的是在x-z平面上的具體位置,x=(x,z),x0=(x0,z0)。這樣第一個式子得出的結果也自然是一個2D向量(K,x0點乘結果為一個標量)。
們模擬水面,經常是先在x-z平面上構建一個平面網格,然後修改每個頂點的y值,如下圖:(x0,z0)又代表什麼呢?這個座標表示的是某一個頂點原本在x-z平面的位置。這裡需要解釋一下,總所周知,我
Gerstner波會根據固定的(x0,z0)位置,計算出新的在x-z平面的位置(也就是第一個式子的結果,x)。Gerstner波
的特徵是 波峰較窄,波谷較寬,這符合自然中水波的特徵。而從第二個公式我們可以看出,y是一個餘弦函式,波峰波谷是一樣的,所以波峰窄,波谷寬的特徵便是通過對(x0,z0)進行一定量偏移造成的,如下圖:
多個Gerstner波疊加
x=x0-∑(K/k)*Asin(K*x0-ωt)
y=∑Acos(K*x0-ωt)
多個波疊加也是非常好理解的,第一個公式疊加的是偏移量,所以x0
不需要進行疊加。注意
在選擇Gerstner波的時候,是k,A是有一定限制的:
kA<1時,kA越大,波峰越窄。
kA>1時,會在波峰處產生環。如下圖:
圖片來自:《Simulating Ocean Water》 Jerry Tessendorf
二、Gerstner波的DirectX11實現
和上篇文章一樣,我們同樣適用DirectX11提供的Compute Shader進行平行計算。我們首先適用一個Buffer存放
網格資料,然後用一個RWStructuredBuffer存放計算後的資料。具體的HLSL程式碼片段如下:
計演算法線的HLSL片段:StructuredBuffer<VertexS> gOriVertices; RWStructuredBuffer<VertexS> gOutput; [numthreads(N, N, 1)] void csPos(int3 dispatchID:SV_DispatchThreadID) { int x = dispatchID.x; int z = dispatchID.y; float x0 = gOriVertices[z*gWidth + x].pos.x; float z0 = gOriVertices[z*gWidth + x].pos.z; float2 offset = float2(0.0f, 0.0f); float height = 0; float k = 0; float p; [unroll] for (uint i = 0; i < gWaveNum; ++i) { float2 waveVector = float2(gWaveVectors[i * 2 + 0], gWaveVectors[i * 2 + 1]); p = dot(waveVector, float2(x0, z0)) - gOmegas[i] * gTime; offset += normalize(waveVector)*gAmplitudes[i] * sin(p); height += gAmplitudes[i] * cos(p); } gOutput[z*gWidth + x].pos = float3(x0-offset.x, height, z0-offset.y ); }
DirectX11部分的詳細程式碼就不放出了,沒有什麼難度,對DirectX11不是很熟悉的朋友,可以參考此書。Dx11的[numthreads(N, N, 1)] void csVector(int3 dispatchID:SV_DispatchThreadID) { int x = dispatchID.x; int z = dispatchID.y; if (x == 0 || x == (gWidth - 1) || z == 0 || z == (gDepth - 1)) { return; } float3 left = gOutput[z*gWidth + x - 1].pos; float3 right = gOutput[z*gWidth + x + 1].pos; float3 top = gOutput[(z - 1)*gWidth + x].pos; float3 bottom = gOutput[(z + 1)*gWidth + x].pos; float3 tangent = normalize(right - left); float3 binormal = normalize(bottom - top); gOutput[z*gWidth + x].normal = normalize(cross(tangent, binormal)); gOutput[z*gWidth + x].tangent = tangent; }
經典入門書籍,有DirectX龍書之稱。
三、小結
多個適當的Gerstner波疊加,能夠製造出較為真實的水面,但是如果需要電影級的水面波紋效果,Gerstner波可
能力不從心了。所以這個時候,就是FFT(快速傅立葉變化)發揮作用的,可能下篇文章會介紹利用FFT實現水面波紋的模擬(本人數學基礎不是很好。需要補補課。。)
最後附幾張截圖:
簡陋見解,不足之處,歡迎交流、拍磚。
參考資料:
《Simulating Ocean Water》 Jerry Tessendorf
JohnHany部落格文:水面的簡單渲染 – Gerstner波
================================The End==============================