1. 程式人生 > >[DirectX11]Gerstner波 實現簡單的水面模擬

[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點乘結果為一個標量)。

(x0,z0)又代表什麼呢?這個座標表示的是某一個頂點原本在x-z平面的位置。這裡需要解釋一下,總所周知,我

們模擬水面,經常是先在x-z平面上構建一個平面網格,然後修改每個頂點的y值,如下圖:

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程式碼片段如下:

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 );   }
計演算法線的HLSL片段:
[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; } 
DirectX11部分的詳細程式碼就不放出了,沒有什麼難度,對DirectX11不是很熟悉的朋友,可以參考此書。Dx11的

經典入門書籍,有DirectX龍書之稱。

三、小結

多個適當的Gerstner波疊加,能夠製造出較為真實的水面,但是如果需要電影級的水面波紋效果,Gerstner波可

能力不從心了。所以這個時候,就是FFT(快速傅立葉變化)發揮作用的,可能下篇文章會介紹利用FFT實現水面波紋的模擬(本人數學基礎不是很好。需要補補課。。)

最後附幾張截圖:

\

\

\

簡陋見解,不足之處,歡迎交流、拍磚。

參考資料:

《Simulating Ocean Water》  Jerry Tessendorf 

JohnHany部落格文:水面的簡單渲染 – Gerstner波

================================The End==============================