目的:
推導出一個水波Shader。本篇基於前篇Shader,進行部分優化,成爲初步可用品。(僅參考複製玻璃表面材質,之後博文再分析。本篇僅考慮造型函數上的優化)
參考:
《GPU Gems》
引言:
我們之前已經完成了簡單水波的造型和法線計算。這一節我們讓它動起來,並暴露出一些常用變量,比如水波方向,水波速度等。並要求當我們隨意移動和旋轉模型時,整體表現還能正常。最後簡單參考複製玻璃表面材質,但不作分析,給出一個初步可用的成品。
思路:
我們當前的波函數方程爲:
1.位置移動
當我們上下移動,不會有問題,因爲我們給的是“世界位置偏移”。
當我們左右移動,不會帶動波移動,反而像是更改了採樣器的位置。因爲我們的方程是世界座標系下的。
這一點對於實際使用來說也無所謂,因爲水面模型一般不動,而是Shader的波浪在動。
所以從實際出發,這部分並沒有什麼需要更改的。
2.水波移動
之前講過,更改相位p,其實就是更改水波左右位置。
結合Chango的數學Shader世界(二)最後的動圖,你可能會想,如果我能一直增大p,那就好了。
但是不論你是什麼類型的變量,一直增大總有爆炸的一天。回想起:
如果我需要讓水波看起來勻速地無限循環移動下去,只要讓p不斷取值[0,2pi),就可以了,當要取到2pi,又一下子取到0,這個週期是流暢銜接的。這時我們期望p的函數圖像如下:
其中,speedCycle是每移動一個週期/波長,所需要的時間,根據時間=路程/速度:
別忘了T就是我們波函數的週期,也就是波長。
那麼問題來了,這種不連續週期函數怎麼表示呢?
引入Shader中另一個重要常用函數frac(),取小數部分。
例如frac(0.4)=0.4,frac(2.5)=0.5,frac(3.6)=0.6。
那麼問題解決了,設time爲當前時間(秒,正數,持續增加),則我們的相位函數p爲
最後再加一個取值爲+1,-1的變量,更改波向左移還是右移:
3.模型旋轉,水波方向
當我們將模型旋轉,發現兩個問題:
1.水波方向不會旋轉,和位置移動一樣,只是更改了採樣器旋轉
2.法線不對了
問題1:
我們不考慮座標系轉換,而是考慮怎樣改變波函數,使得我們可以調整波方向。這樣波方向和模型旋轉獨立開來,靈活度還更大。從實用角度出發,只指定在xy平面,波的方向,稱爲D。
我們這裏假設D已經歸一化(Normalized)了,也就是方向不變,調整長度爲1.
那麼,怎樣計算任意一點(x,y),得到一個值k,使得sin(k)在D方向上的效果和當前在x方向上sin(x)的效果是一樣的呢?
也就是說,我想知道(x,y)在D軸上的投影。
顯然,點乘滿足了我們的需求。
如果你不太理解,記得初高中點乘公式:
其中是a,b向量的夾角。令a爲D,由於D長度爲1,從幾何上很容易看出,就是點在D上的投影長度。
所以我們將k代替x,放入原方程:
問題2:
問題2發生的原因是,TBN座標系跟着物體座標系一同旋轉了,與世界座標系一致了。而我們的法線計算是在世界座標系下的。
這回,我們不進行手動計算座標系轉換了(之後的博文肯定會有更迫切需要手動計算的時候)。如果要大概地提一下,那就是我們將原先計算到的N*Matrix_WorldToLocal就行,因爲我們的模型中TBN總和local一致,這樣就從世界座標系下的Normal轉爲TBN座標系下的Normal。
實際的做法是,引擎往往提供選項。點擊主材質節點,detail欄搜索Normal,取消勾選默認的切線空間法線(TangentSpaceNormal)。這樣這個Normal就直接是世界座標系的了。我們無需改動。
4.總結當前波方程,重新計算法線
雖然解決了座標系的問題,但我們的法線還是要重新計算,因爲波方程改了。
,其中:
我們回憶起z=Z(x,y)形式曲面法線公式:
再次利用鏈式法則(Chain Rule)計算得:
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進一步優化,使得其動起來,並暴露許多參數可用,成爲一個初步可用品。
之後的博文會優化波間距太過一致,波形太多單一的問題,再後面纔會詳解透明,折射