Unity 水、流體、波紋基礎系列(三)——波浪(Waves)

目錄算法

1 正弦波微信

1.1 調整頂點數據結構

1.2 調整Yapp

1.3 振幅函數

1.4 波長flex

1.5 速度優化

1.6 法線向量動畫

1.7 Mesh分辨率url

1.8 陰影spa

2 格斯特納波(Gerstner)

2.1 來回移動

2.2 法線向量

2.3 防止循環

2.4 相速度

3 波方向

3.1 方向向量

3.2 法線向量

4 多重波

4.1 單參數向量

4.2 兩個波

4.3 循環動畫

4.4 循環波

4.5 三個波

收起


本文重點:


一、頂點動畫

二、建立格斯特納波浪(Gerstner )

三、控制波浪方向

四、合併多波浪

這是有關流體材質的第三篇教程。前兩篇的內容都是如何處理紋理動畫,這個章節咱們講如何經過頂點位置動畫來產生波浪。


本教程是CatLikeCoding系列的一部分,原文地址見文章底部。「原創」標識意爲原創翻譯而非原創教程。


教程使用Unity2017.4.4f1建立。

(讓咱們一塊兒來浪吧)


1 正弦波


設置紋理動畫能夠建立運動表面的錯覺,但網格表面自己保持靜止。這對於較小的波紋很好,但不能表明較大的波浪。在大片水域(如大湖海洋)上,風會產生大波浪,並能持續很長時間。爲了表示這些風浪,咱們將使用正弦波函數製做新的着色器,在垂直方向移動網格頂點。


1.1 調整頂點


建立一個名爲Waves的新表面着色器。讓片斷表面功能保持不變。添加另外一個函數vert來調整頂點數據。此函數具備單個頂點參數,用於輸入和輸出。咱們將使用Unity的默認頂點數據結構appdata_full。

若要指示表面着色器應使用vertex函數,請將vertex:vert添加到surface pragma指令。

建立一個使用此着色器的新Waves材質。我給它提供了與其餘兩種材質相同的albedo和smoothness。

(Wave材質)


由於咱們要置換頂點,因此此次不能使用四邊形。而是經過GameObject 3D Object Plane建立一個默認平面,並使用Waves材質。這使咱們可使用10×10的四邊形網格。

(Waves 平面, wireframe視角下)


1.2 調整Y


忽略Z維度,將每一個頂點的位置定義爲 P = [x,y],其中P是其最終位置,x是原始X座標,而y是原始Y座標,二者 都是在對象空間中。要建立波,咱們必須調整P 的Y份量。生成波的最簡單方法是使用基於x 的正弦波,所以 y = sinx。那麼最後,該點是P = [x,sinx]。

(單個波)


結果是沿X方向的正弦波,沿Z方向恆定。平面的四邊形具備單位大小,所以整個平面覆蓋以其本地原點爲中心的10×10區域。所以,咱們最終看到正弦波的10/2π≈1.59週期。


1.3 振幅


正弦波的默認振幅爲1,但咱們不能侷限於此。向着色器添加一個屬性,以便咱們可使用 Py = asinx代替,其中a是振幅。

(振幅設置爲2)


1.4 波長


這個例子中是sinx,正弦波的總長度爲 2 π ≈ 6.28 2π≈6.28。這是波長,咱們也能夠對其進行配置。


爲了輕鬆控制波長,咱們首先用2π乘以X而後除以所需的波長。因此咱們最後獲得 ( 2 π X /λ ) sin(2πx/λ),其中 λ (λ)是波長。


2π除以λ被稱爲波數k =2π/λ。咱們能夠將其用做shader屬性,所以不須要在shader中執行除法。這是一個有用的優化,可是在本教程中,咱們將堅持使用對開發者更加友好的波長。

(λ (從0到10線性)和 k)


在着色器中,咱們將顯式使用波數,所以最終獲得 Py = asin(kx)。

(波長爲10 振幅爲1)


1.5 速度


波浪須要移動,所以咱們必須定義速度。使用相位速度c最爲方便,該速度定義了整個波以每秒單位的速度移動。這是經過使用時間偏移量kct來完成的。爲了使波向正方向移動,咱們必須從kx中減去它,所以咱們得出Py = sin(kx-kct )= sin(k(x-ct))。

(速度設置爲5)


1.6 法線向量


咱們的曲面是彎曲且移動的,但燈光仍然是靜止的平面。那是由於咱們尚未改變頂點法線。讓咱們直接查看X維度T上的表面切向量,而不是直接計算法線向量。對於平坦表面 T = [x',0] = [1,0 ],它對應於原始平面的切線。可是對於咱們的波,咱們必須使用T = P'= [x',asin(k(x-ct))']。


正弦的導數是餘弦,因此 sin'x = cosx。可是在咱們的例子中,正弦的論點自己就是一個函數。咱們能夠說咱們有 Py = asinf,其中 f = k(x-ct)。


咱們必須使用鏈式規則,(Py)'= f'acosf。f'= k,所以咱們得出T = [1,kacosf]。這是有道理的,由於更改波長也會更改波的斜率。爲了在着色器中得到最終的切向量,咱們必須歸一化T。

法線向量是兩個切向量的叉積。因爲咱們的波在Z維度上是恆定的,所以雙法線始終是單位矢量而且能夠忽略,所以咱們獲得 N = [-kacosf,1]。咱們只須要在對它們進行歸一化以後就能夠獲取它們。

(正確的法向量)


1.7 Mesh分辨率


當使用10波長時,咱們的波看起來不錯,但對於小波長而言,效果卻不佳。例如,波長爲2時會產生鋸齒波。

(波長爲2 速度爲1)


波長爲1根本不產生波,而是整個平面均勻地上下波動。其餘小的波長會產生更加醜陋的波,甚至能夠向後移動。


此問題是由咱們的平面網格的有限分辨率引發的。因爲頂點之間相隔一個單位,所以沒法處理2個或更小的波長。一般,必須保持波長大於網格中三角形邊緣長度的兩倍。你不想將他們剪得太近,由於由兩個或三個四邊形組成的波浪看起來並很差。


可使用更大的波長,也能夠提升網格的分辨率。最簡單的方法是隻使用另外一個網格。這是一個替代平面網格,由100×100個四邊形組成,而不只僅是10×10。每一個四邊形仍爲1×1單位,所以您必須縮小波形屬性並將其乘以10才能得到與之前相同的結果。

(大的平面 波的設置所有x10,而且放大)


1.8 陰影


儘管咱們的表面看起來不錯,但還沒有與陰影正確交互。它仍然像平面同樣,能夠投射和接收陰影。

(不正常的陰影)


解決方案是將addshadow包括在Surface編譯指示中。這指示Unity爲咱們的着色器建立一個單獨的陰影投射器通道,該通道也使用咱們的頂點位移功能。

陰影如今是正確的,波浪也能夠正確地自陰影化。因爲咱們如今正在大的縮放下工做,所以可能必須先增長陰影距離,而後陰影纔會出現。

(正確的陰影,陰影距離爲300)


在本教程的其他部分中,我會禁用陰影。


2 格斯特納波(Gerstner)


正弦波很簡單,但它們與實際水波的形狀不匹配。大風引起的波浪其實是由斯托克斯(Stokes )波函數建模的,但它至關複雜。相反,Gernster波一般用於水面的實時動畫。


Gerstner波以發現它們的Franti?ek Josef Gerstner的名字命名。它們也被稱爲擺線波,以其形狀命名,或週期性的表面重力波,描述其物理性質。


2.1 來回移動


當波浪在水錶面上移動時,基本的觀察結果是,水自己並不會隨之移動。在正弦波的狀況下,每一個表面點都會上下移動,但不會水平移動。


可是實際的水不只僅只有表面。下面還有更多的水。當表面的水向下移動時,其下方的水會怎麼運動?當表面向上移動時,什麼填充了其下面的空間?事實證實,表面點不只向上和向下移動,並且也向前和向後移動。它們有一半的時間與波一塊兒移動,而另外一半則沿相反的方向移動。表面如下的水也是如此,但越深,運動就越少。


具體來講,每一個表面點都繞一個固定的錨點繞圓周運動。隨着波峯的接近,該點向其移動。波峯通過後,它會向後滑動,而後出現下一個波峯。結果是水在波峯中聚攏,並在波谷中散佈開來,咱們的頂點也會發生一樣的狀況。

(正弦波與格斯特納波)


實際上,表面點確實會漂移而且不是完美的圓,可是Gerstner Wave並非對此進行建模的。咱們把原始頂點位置用做錨點。


咱們能夠經過使用P = [acosf,asinf]將正弦波變成一個圓,但這會將整個平面摺疊成一個圓。相反,咱們必須將每一個點錨定在其原始X座標上,所以咱們須要 P = [x + acosf,asinf]。

(Gerstner波,振幅10,波長100,速度50)


結果是,與常規正弦波相比,其波峯和波谷更平坦


Gersner Wave不是應該該用SinX和 cosY?

這是定義它們的常規方法,可是正如咱們已經使用sin Y,那麼X就直接用Cos了。其餘方法相比惟一的不一樣是,波的週期偏移了四分之一。


2.2 法線向量


因爲咱們更改了表面函數,所以其導數也發生了變化。T的X份量曾經是x'= 1,但如今有點複雜了。餘弦的導數爲負正弦,所以咱們得出T = [1-kasinf,kacosf]。

(正確的法線向量)


2.3 防止循環


儘管產生的波浪看起來不錯,但並不是老是如此。例如,將波長減少到20卻將幅度保持在10會產生奇怪的結果。

(波循環,波長20)


由於振幅相對於波長而言很是大,因此表面點過調並在表面上方造成迴路。若是這是真實的水,那麼海浪會破裂並散開,因此咱們沒法用格斯特納海浪來表示。


經過觀察當 ka大於1時Tx能夠變爲負數,咱們能夠從數學上看到爲何發生這種狀況。這種狀況下,切向量最終指向後方而不是向前。當 ka爲1時,咱們獲得的切線向量指向正上方。


實際上,在波峯兩側之間的角度超過120°的狀況下,咱們就不會獲得完整的波浪了。Gerstner波沒有這個限制,可是咱們不想低於0°,由於那樣就會產生表面循環。


波長和波幅之間存在關係。咱們可使用 a = ekb/k,其中b與表面壓力有關。壓力越大,波浪越平坦。在零壓力的狀況下,咱們最終獲得a = 1k,這將產生0°的波峯,這是循環以前最尖的。咱們能夠改用a = s/k,其中s是陡度的度量,介於0和1之間,更易於使用。那麼咱們有P = [x + s/k cosf,s/k sinf],這簡化了咱們與T = [1-ssinf,scosf]的切線。

(陡度替代振幅。)


2.4 相速度


實際上,波沒有任意相位速度。它與波數有關

其中g是引力,在地球上約爲9.8。深水中的波浪確實如此。在淺水中,水深也起着必定的做用,但咱們在這裏不作介紹。儘管咱們可使用正確的材質屬性,但在着色器中進行計算更加方便。

如今咱們能夠消除速度屬性。

注意,這種關係意味着更長的波具備更高的相速度。一樣,重力越強,運動速度就越快。

(λ(線性,從0到100)和 C)


3 波方向


到目前爲止,咱們的波只在X維度上移動。如今,咱們將刪除此限制。這使得咱們的計算更加複雜,由於構造最終波及其切向量須要同時使用X和Z。


3.1 方向向量


爲了指示波的傳播方向,咱們將引入方向矢量 D = [Dx,Dz]。這純粹是方向的指示,因此它是單位長度的向量, || D || = 1。


如今,x 對波函數的貢獻由D 的X份量調製。所以咱們獲得f = k(DxX-ct)。可是z 如今也以相同的方式發揮做用,致使f =f = k(DxX + DzZ-ct)。換句話說,咱們使用DD與原始X和Z座標的點積。所以,咱們最終獲得f = k(D?[x,z] -ct)。


將方向屬性添加到咱們的着色器,並將其合併到咱們的函數中。它應該是一個單位長度的向量,可是爲了使其更易於使用,咱們將在着色器中對其進行標準化。請注意,全部矢量屬性均爲4D,所以只需忽略Z和W份量。

咱們還必須調整Px和 Pz的水平偏移,以使其與波方向對齊。所以,不只要將偏移量添加到x 上,咱們還必須將偏移量也添加到z 上,在兩種狀況下都由D 的適當份量進行調製。所以最終的計算成爲

(方向設置爲 [0,1] 和[1,1])


3.2 法線向量


再一次,咱們必須調整切線的計算,而不只僅是調整X尺寸。如今,咱們還必須計算Z維上的切線,即雙法線向B.


X方向上f 的偏導數爲fx'= kDx。在Tx和Ty的狀況下,這僅意味着咱們將Dx再乘一次。除此以外,咱們還必須加上Tz,由於它再也不爲零。最終切線爲:

雙重法線相同,除了 fz'= kDz,咱們乘以Dz,以及X和Z組件的角色互換。因此B=

如今咱們確實須要採起適當的叉積來找到法線向量。

(正確的法線向量)


注意 Tz = Bx。咱們不須要爲此進行優化,由於着色器編譯器會處理此問題,就像正弦和餘弦僅計算一次同樣。


4 多重波


實際上,不多會發現只有一個均勻的波在水面上傳播。取而代之的是,有許多波以大體相同的方向傳播。咱們也能夠經過累積多個波來改善效果的真實感。


合併多個波只是添加全部偏移便可。數學上,對於P的X份量,咱們獲得

這是和之前相同的公式,只是增長了總和。P 的其餘份量和切線也是如此。


4.1 單參數向量


每一個單獨的波都有其本身的屬性。爲了使此操做更易於管理,讓咱們將wave的全部屬性合併到一個着色器屬性中。咱們能夠將它們擬合爲單個4D向量,其中X和Y表示方向,Z表示陡度,W表示波長。使用此技巧爲咱們的第一個浪潮A浪定義一個屬性。

(波A的設置)


用新的波矢替換舊變量。

而後將波動代碼移至新的GerstnerWave函數。此功能將波浪設置做爲參數,後跟原始網格點。同時給它輸入切線和雙法線的輸入輸出參數,這樣咱們就能夠對其進行累加。它返回其點偏移量。


由於它會累積偏移量,因此請保留 X 和 Z部分超出結果。所以,也應從導數中省略它們,並消除1。最後,不會對每一個單獨的波進行歸一化。

波浪如今相對於平面。所以,咱們從原始網格點以及默認的切線和雙法線向量開始,而後調用GerstnerWave並將其結果添加到最終點。以後,經過叉積和歸一化建立法線向量。


4.2 兩個波


要添加對第二個wave的支持,咱們要作的就是添加另外一個wave屬性並再次調用GerstnerWave。我沒有在浪B的標籤上重複數據描述,由於它與浪A相同。

(兩個波浪)


4.3 循環動畫


如今咱們有了兩個波,你能夠觀察到波長較長的波的確比短波長的波快。可是相速度和波長之間的關係是非線性的,由於

當你要建立具備多個波形的循環動畫時,他們是相關的。對於兩個波,你必須找到兩個波長,它們產生的相速度爲 ac1 = bc2,其中a和b 是整數。你能夠經過對波長使用2的偶次冪來作到這一點。

好比,

而且

那麼

觀察到

是常數,所以咱們能夠將其定義爲q,並使用

。所以 c1 = 2c2,這意味着每次大波重複一次,小波重複兩次。循環持續時間等於大波的週期,即

秒。

(方向 [1,0],陡度?,波長64和16)

你也能夠重寫數學,以便直接控制相速度並從中得出波長。


4.4 循環波


另外一個重要的觀察結果是,咱們能夠再次獲得循環波。若是偏導數之和超過1,則會造成循環。爲了防止產生波動,你必須確保全部波動的陡度總和不超過1。

(具備兩個波的循環 陡度爲1)


你能夠經過規範化着色器的steepness 來實施此限制。這意味着,若是更改一個波的陡度,它將影響全部其餘波。或者,你能夠將全部陡度值除以波浪數,但這會限制每一個波浪的陡度。你也能夠在着色器中不設置任何限制,而是經過材質檢查器提供反饋和選項。對於本教程,咱們沒有設置任何限制。


4.5 三個波


最後,咱們增長了對另外一波的支持。添加的波越多,咱們的着色器就越複雜。你能夠根據波的數量進行着色器變化,但咱們將固定數量設爲三個。

(三個波)



歡迎掃描二維碼,查看更多精彩內容。點擊 閱讀原文 能夠跳轉原教程。










本文翻譯自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials








本文分享自微信公衆號 - 壹種念頭(OneDay1Idea)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索