Chango的數學Shader世界(五)水波模擬-那平靜又洶湧的海面:疊加,Gerstner波

目的:

根據前篇的水波Shader,優化成一個不透明的,適合廣闊海域的Shader,以解決前篇Shader的一些缺陷。

參考:

《GPU Gems》

引言:

對於我們之前的初步可用品Shader,可以從不同方面進行改造,適用於不同需求下的水面。接下來幾篇着重於造型上的改造,適用於不透明水面,如深邃廣闊的大海。

思路:

回想起前篇結尾我們談論到的當前Shader的不足之處:

1.只能模擬一個方向簡單的水波狀態,波間距一致,缺少自然感。

2.波形狀一致,單一,且與某些常見的海浪形狀不一致。

下面,針對這兩點分別改進。

1.通過疊加增加複雜感

沒錯,這個問題的解決方案很簡單,我們當前不就只有一個方向的波麼?多加兩個,調調參數就完事了。

記得高中、大學數學學過的導數法則:

(f+g)'=f'+g'

所以,添加多個波之後的方程也極其簡單。

波面方程:

Z(x,y)=\sum _{i}A_{i}* Sin(w_{i}(D_{i}x* x+D_{i}y*y)+p_{i})

法線方程:

(-Z_{x},-Z_{y},1)

-Z_{x}=-\sum _{i}A_{i}*cos(w_{i}*(D_{i}x*x+D_{i}y*y)+p_{i})*w_{i}*D_{i}x

-Z_{y}=-\sum _{i}A_{i}*cos(w_{i}*(D_{i}x*x+D_{i}y*y)+p_{i})*w_{i}*D_{i}y

PS:

如果你知道傅里葉變換(Fourier transform),那麼就能意識到只要多加幾個波,什麼樣複雜的水面都可以模擬出來。

因爲幾乎任何函數都能被拆解成傅里葉級數(Fourier series),包括不連續的函數。

 

級數越多,“分辨率”越高。這個特性是不是像極了我們之前提到過的泰勒公式(Taylor's formula)

複分析(Complex Analysis)中,它們兩是"兩位一體"。(參考《複分析可視化方法》)

2.添加更有浪尖的Gerstner波

2.1Gerstner波方程

根據前文所說,我們的Sin函數不太像某些真實的浪,往往擁有鋒利的浪尖。

圖形學前輩們在我們目前的函數上進行改進,發明了Gerstner波

形狀如下:

公式(顯然這是多個Gerstner波的疊加):

X=x+\sum (\frac{Q_{i}}{w_{i}} *D_{i}x*cos(w_{i}*(D_{i}\cdot k)+p_{i}))

Y=y+\sum (\frac{Q_{i}}{w_{i}} *D_{i}y*cos(w_{i}*(D_{i}\cdot k)+p_{i}))

Z=\sum _{i}A_{i}* Sin(w_{i}(D_{i}\cdot k)+p_{i})

別忘了其中k=(x,y),只是爲了簡化寫法。

注意:我在此對《GPU Gem》上的Gerstner波公式改了寫法。一是爲了銜接我們之前推導的公式,改變了寫法。

二是原文的參數冗餘,不利於理解。但確爲完全一致的公式。

可以看出,Z方向上的函數與我們的函數一致,只是x,y方向加了一些東西。

在此我設L(x)=X-x,理解Gerstner波就在於理解L(x)

下面是我的理解。

我們要幹什麼?我們要把波峯搞尖。

看到沒有?如果我把每個點往法線相反方向擠一擠,這個峯就尖了。

x軸的往法線x分量負方向擠一擠,y軸的往法線y分量負方向擠一擠。

當這個擠的量爲法線在x/y軸的分量,會導致法線朝上的被擠的少,法線水平得擠的多,就成了尖峯的樣子。

再加兩個參數控制一下擠的程度,就完事了。

這也就是爲什麼L(x),L(y)會這麼像我們原來的法線

L(x)=\sum (\frac{Q_{i}}{w_{i}} *D_{i}x*cos(w_{i}*(D_{i}\cdot k)+p_{i}))

-Z_{x}=-\sum _{i}A_{i}*w_{i}*D_{i}x*cos(w_{i}*(D_{i}\cdot k)+p_{i})

那爲啥前面的係數是這樣呢?原來前輩早替不懂原理的美術們想好了,這樣設計,Q只要保證在[0,1]就行。取0時就和我們原來一樣,取1時浪尖最尖。Q大於1浪尖就不對了,浪尖出現環狀:

爲啥Q大於1就會這樣呢?

《GPU Gems》只簡單提了一下在頻域內可發現並解決這個問題。關於頻域我準備在以後必要的時候再回過頭來討論。

我現在給出一個簡單的幾何證明。波函數被簡化。

擠之前的點(x,Z(x)=Asin(wx))

擠之後變成了點(x+L(x),Z(x))

直觀上來說,對於Sin函數,僅看處於[0,\frac{T}{4}]的點x,x+L(x)還應該<=T/4。

有不等式

x+\frac{Q}{w}cos(wx)\leq \frac{T}{4}

0\leq x\leq \frac{T}{4}

設u=wx,化簡得:

Q\leq \frac{\frac{\pi }{2}-u}{cos(u)}=G(u)

0\leq u\leq \frac{\pi }{2}

接下來得過程很像高三數學大題(江蘇)。

不論是畫出圖形,還是研究單調性。你會發現函數單調減。

照理來說,證明當u=pi/2時,G(u)=1,就行了。但cos(pi/2)=0,G(u)不存在。

只能求極限,u->pi/2,求lim G(u)。

直接上高數洛必達法則(L'Hopital's rule)

lim=\frac{-1}{-sin(u=\frac{\pi }{2})}=1

兩個趣點:

1.沒錯,咱江蘇數學就是有高數內容,咱就喜歡超綱,傻了8。可惜以後都是全國捲了...

2.別忘了洛必達法則是土豪洛必達買來的,沒錯,有錢就是可以爲所欲爲。

2.2Gerstner波的法線方程

記得之前博文關於法線推導推導一嗎?我本人不贊成這種方法。但結果這兒發現了它的好處。

對於不存在z=Z(x,y)形式,而且全方程又很難列出來,但知道3分量各自解析式的情況,推導一就可用。

不再細述過程,結果:

Nx=-\sum (D_{i}x*w_{i}*A_{i}*C())

Ny=-\sum (D_{i}y*w_{i}*A_{i}*C())

Nz=1-\sum (Q_{i}*w_{i}*A_{i}*S())

這樣關於實現Gerstner波的內容就完整了。

步驟:

1.對材質進行修改。

運用上我們的公式,將結果給予WPOffset,Normal。

我在之前的shader的基礎上又加了2個Sin波,1個Gerstner波。

//wave normal
float3 re;
float pi=3.14f;

float w=2*pi/T;
float k=dot(float2(OriPos.x,OriPos.y),dir);
float phase=2*pi*frac(time/speedCycle)*phaseDir;
//wave2
float w2=2*pi/T2;
float k2=dot(float2(OriPos.x,OriPos.y),dir2);
float phase2=2*pi*frac(time/speedCycle2)*phaseDir2;
//wave3
float w3=2*pi/T3;
float k3=dot(float2(OriPos.x,OriPos.y),dir3);
float phase3=2*pi*frac(time/speedCycle3)*phaseDir3;
//add wave123
re.x=-A*cos(w*k+phase)*w*dir.x-A2*cos(w2*k2+phase2)*w2*dir2.x-A3*cos(w3*k3+phase3)*w3*dir3.x;
re.y=-A*cos(w*k+phase)*w*dir.y-A2*cos(w2*k2+phase2)*w2*dir2.y-A3*cos(w3*k3+phase3)*w3*dir3.y;
re.z=1;
//wave4
float3 wave4N;
float w4=2*pi/T4;
float k4=dot(float2(OriPos.x,OriPos.y),dir4);
float phase4=2*pi*frac(time/speedCycle4)*phaseDir4;
float S=sin(w4*k4+phase4);
float C=cos(w4*k4+phase4);
wave4N.x=-dir4.x*w4*A4*C;
wave4N.y=-dir4.y*w4*A4*C;
wave4N.z=1-Q*w4*A4*S;
re+=wave4N;
return re;
//WPOffset
float3 re;
float pi=3.14f;
re.x=0.0f;
re.y=0.0f;
float phase=2*pi*frac(time/speedCycle)*phaseDir;
float phase2=2*pi*frac(time/speedCycle2)*phaseDir2;
float phase3=2*pi*frac(time/speedCycle3)*phaseDir3;
float wave1=A*sin(2*pi/T*dot(float2(OriPos.x,OriPos.y),dir)+phase);
float wave2=A2*sin(2*pi/T2*dot(float2(OriPos.x,OriPos.y),dir2)+phase2);
float wave3=A3*sin(2*pi/T3*dot(float2(OriPos.x,OriPos.y),dir3)+phase3);
re.z=wave1+wave2+wave3;
//////////////////
//wave4,Gerstner
float3 wave4;
float w4=2*pi/T4;
float k4=dot(float2(OriPos.x,OriPos.y),dir4);
float phase4=2*pi*frac(time/speedCycle4)*phaseDir4;
wave4.x=Q*1/w4*dir4.x*cos(w4*k4+phase4);
wave4.y=Q*1/w4*dir4.y*cos(w4*k4+phase4);
wave4.z=A4*sin(w4*k4+phase4);
re+=wave4;
return re;

2.將平面擴大100倍,調整參數

平面擴大成爲海面,此時一定注意波長,小於最短波長會出現法線不對的情況。

(但有趣的是,失真有時會帶來比較複雜的波面,參考之前博文的失真情況。如果不需要用到法線,還是很用的)

結果:

我還將BaseColor簡單乘上一個貼圖,顯得有趣些。

3Sin不加Gerstner:

仔細看會發現還是有重複的模式(Pattern)出現,在高處俯視更加明顯,畢竟只有3個sin波。但是你去看看一些遊戲中的海面,說不定更加明顯。而且我們對其表面材質和光照還未關注,只有造型,遊戲中的海面通過其他東西增加了你的真實感。

增加Gerstner,模擬一陣一陣衝岸的小尖浪,或者災難片中的百尺巨浪,之後要解決固定波長的問題。

因爲這種浪速度較快要持續出現,固定波長的話就顯得十分人工。

這條斜面大,浪脊分明的波就是Gerstner波:

結語:

本篇對單一波進行疊加,並引入Gerstner波,完成了簡單的海面Shader。

下一篇會解決Gerstner波,波長固定的問題,並開始研究水波模擬的表面材質,光照部分。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章