Chango的數學Shader世界(四)水波模擬,優化,初步可用品

目的:

推導出一個水波Shader。本篇基於前篇Shader,進行部分優化,成爲初步可用品。(僅參考複製玻璃表面材質,之後博文再分析。本篇僅考慮造型函數上的優化)

參考:

《GPU Gems》

引言:

我們之前已經完成了簡單水波的造型和法線計算。這一節我們讓它動起來,並暴露出一些常用變量,比如水波方向,水波速度等。並要求當我們隨意移動和旋轉模型時,整體表現還能正常。最後簡單參考複製玻璃表面材質,但不作分析,給出一個初步可用的成品。

思路:

我們當前的波函數方程爲:

Z(x,y)=A\cdot Sin(\frac{2\pi }{T}x+p)

1.位置移動

當我們上下移動,不會有問題,因爲我們給的是“世界位置偏移”。

當我們左右移動,不會帶動波移動,反而像是更改了採樣器的位置。因爲我們的方程是世界座標系下的。

這一點對於實際使用來說也無所謂,因爲水面模型一般不動,而是Shader的波浪在動。

所以從實際出發,這部分並沒有什麼需要更改的。

2.水波移動

之前講過,更改相位p,其實就是更改水波左右位置。

結合Chango的數學Shader世界(二)最後的動圖,你可能會想,如果我能一直增大p,那就好了。

但是不論你是什麼類型的變量,一直增大總有爆炸的一天。回想起:

Sin(x+2\pi )=Sin(x)

如果我需要讓水波看起來勻速地無限循環移動下去,只要讓p不斷取值[0,2pi),就可以了,當要取到2pi,又一下子取到0,這個週期是流暢銜接的。這時我們期望p的函數圖像如下:

其中,speedCycle是每移動一個週期/波長,所需要的時間,根據時間=路程/速度:

speedCycle=\frac{T}{speed}

別忘了T就是我們波函數的週期,也就是波長。

那麼問題來了,這種不連續週期函數怎麼表示呢?

引入Shader中另一個重要常用函數frac(),取小數部分。

例如frac(0.4)=0.4,frac(2.5)=0.5,frac(3.6)=0.6。

那麼問題解決了,設time爲當前時間(秒,正數,持續增加),則我們的相位函數p爲

p=2\pi \cdot frac(\frac{time}{speedCycle})

最後再加一個取值爲+1,-1的變量,更改波向左移還是右移:

p=2\pi \cdot frac(\frac{time}{speedCycle})\cdot phaseDir

3.模型旋轉,水波方向

當我們將模型旋轉,發現兩個問題:

1.水波方向不會旋轉,和位置移動一樣,只是更改了採樣器旋轉

2.法線不對了

問題1:

我們不考慮座標系轉換,而是考慮怎樣改變波函數,使得我們可以調整波方向。這樣波方向和模型旋轉獨立開來,靈活度還更大。從實用角度出發,只指定在xy平面,波的方向,稱爲D。

D=(Dx,Dy)

我們這裏假設D已經歸一化(Normalized)了,也就是方向不變,調整長度爲1.

那麼,怎樣計算任意一點(x,y),得到一個值k,使得sin(k)在D方向上的效果和當前在x方向上sin(x)的效果是一樣的呢?

也就是說,我想知道(x,y)在D軸上的投影。

顯然,點乘滿足了我們的需求。

k=D\cdot (x,y)=Dx*x+Dy*y

如果你不太理解,記得初高中點乘公式:

a\cdot b=\left | a\right |\left | b\right |cos(\theta )

其中\theta是a,b向量的夾角。令a爲D,由於D長度爲1,從幾何上很容易看出,\left | b\right |cos(\theta )就是點在D上的投影長度。

所以我們將k代替x,放入原方程:

Z(x,y)=A\cdot Sin(\frac{2\pi }{T}(Dx* x+Dy*y)+p)

問題2:

問題2發生的原因是,TBN座標系跟着物體座標系一同旋轉了,與世界座標系一致了。而我們的法線計算是在世界座標系下的。

    這回,我們不進行手動計算座標系轉換了(之後的博文肯定會有更迫切需要手動計算的時候)。如果要大概地提一下,那就是我們將原先計算到的N*Matrix_WorldToLocal就行,因爲我們的模型中TBN總和local一致,這樣就從世界座標系下的Normal轉爲TBN座標系下的Normal。

    實際的做法是,引擎往往提供選項。點擊主材質節點,detail欄搜索Normal,取消勾選默認的切線空間法線(TangentSpaceNormal)。這樣這個Normal就直接是世界座標系的了。我們無需改動。

4.總結當前波方程,重新計算法線

雖然解決了座標系的問題,但我們的法線還是要重新計算,因爲波方程改了。

 

Z(x,y)=A* Sin(w(Dx* x+Dy*y)+p),其中:

p=2\pi \cdot frac(\frac{time}{speedCycle})\cdot phaseDir

w=\frac{2\pi }{T}

我們回憶起z=Z(x,y)形式曲面法線公式:

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

再次利用鏈式法則(Chain Rule)計算得:

-Z_{x}=-A*cos(w*(Dx*x+Dy*y)+p)*w*Dx

-Z_{y}=-A*cos(w*(Dx*x+Dy*y)+p)*w*Dy

5.複製玻璃材質的折射,形成初步可用品

這裏的初步可用品,指的是對於不懂此Shader的程序/美術來說,達到了基本的品質需求(像個水波),可以無障礙地旋轉,移動,並調整參數。

顯然目前它還不像個水波。我們將材質的半透模式(Blend Mode)改爲半透明(Translucent)半透方式的具體細節之後的博文會細講,這裏只要知道我們這樣改後,材質能夠半透,更像透明水面。

然後複製玻璃材質的折射結果,就能大體像個透明的水面。

PS:

你可能會發現,對半透明材質來說,沒有什麼光影效果,那我們計算法線有何用?

法線與折射密切相關,如果法線不對了,那麼水底的折射也不對。光線的部分之後的博文會討論。

步驟:

1.在材質中對波函數方程進行修改

//WPOffset
float3 re;
float pi=3.14f;
re.x=0.0f;
re.y=0.0f;
float phase=2*pi*frac(time/speedCycle)*phaseDir;
re.z=A*sin(2*pi/T*dot(float2(OriPos.x,OriPos.y),dir)+phase);
return re;

2.在材質中對法線進行修改

//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;
re.x=-A*cos(w*k+phase)*w*dir.x;
re.y=-A*cos(w*k+phase)*w*dir.y;
re.z=1;
return re;

最後別忘了取消勾選切線空間法線

3.更改半透模式爲半透明

並將初學者內容中的玻璃材質的折射(Refraction)部分複製過來,連上。

這裏面內容之後的博文再講。

結果:

到此爲止,我們有了一個初步可用的水波Shader。

可以用程序控制波寬,波高,速度,方向,以模擬不同天氣狀況下的水面情況。這種情況下,就不能用法線貼圖做到了。

可以放大平面,注意控制波長大於最小波長,因爲放大後的頂點間距放大,最小波長也變長了。

分析:

1.當前這個Shader,只能模擬一個方向簡單的水波狀態,波間距一致,缺少自然感。這樣的Shader一般用於遠方海面。

我們可以改進,使得波間距看起來不那麼“人工”

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

我們可以改進,使得波形狀更加多變,接近現實。

3.我們對摺射還未進一步研究。

結語:

本篇對之前的水波Shader進一步優化,使得其動起來,並暴露許多參數可用,成爲一個初步可用品。

之後的博文會優化波間距太過一致,波形太多單一的問題,再後面纔會詳解透明,折射

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