2020年1月30日 星期四

基於快速傅立葉轉換的水波生成(4)


  • 前言
終於到了最後一篇,為了寫出這長達四篇的文章,花了我將近1個月的時間...
學習怎麼使用blog,怎麼嵌入程式碼,怎麼把數學符號放上去,讓我數度感到挫折。
廢話就到此為止,開始說明本篇內容。
本篇仍參考 Keith Lantz 所寫的 Ocean simulation part two: using the fast Fourier transform
(以下簡稱 Keith Lantz)文章進行實作。

該文章演示如何利用CPU透過快速傅立葉轉換(以下簡稱FFT)產生水面波浪。
我的目標是修改內容,利用GPU來實現,並在手機上可以有60fps的效能。

  • 實作原理分析
回頭觀察水面波浪的方程式和離散傅立葉轉換


可以發現彼此架構一樣,所以可以用快速傅立葉轉換去進行運算。

在 Keith Lantz 裡面,有花了很大的篇幅去證明如何將波浪方程式分解成奇數項和偶數項,

在此我不詳述證明原理,列出幾個要點就好

1.Keith Lantz 將2維的水面波浪方程式展開成兩個1維的水面波浪方程式


也就是將 X 軸 和 Z 軸分開計算,
接下來只要將其中一軸分解出奇數項和偶數項,另外一軸同理可得。

2.為了方便計算,Keith Lantz 將波形長度和取樣點統一



之後分解成奇數項和偶數項



其中,要注意的是 -1^x 這個值。
由於他不再快速傅立葉轉換的公式裡面,因此經過快速傅立葉轉換後,要根據 X 和 Z 的索引值,決定乘上 1 或 -1。


  • 程式碼實作

在實作上,為了在手機上能有60fps的效能,我設定了一個前提是假設若風速不會太大,那海浪的變化也不會太大,所以更新也不用太頻繁。

因此,我將波浪的計算分成4個步驟

  1. 更新時域頻譜
  2. 對X軸座標做FFT運算
  3. 對Z軸座標做FFT運算
  4. 將計算出的波浪位移和法線值儲存在兩張貼圖上,並Tiling化(或稱seamless)
把這4個步驟分散在4個Frame做批次運算,避免在同一個Frame計算造成計算峰值過高,導致遊戲會有突然會有卡頓的現象發生。

在海浪完成更新前,海浪的波形仍然需要被渲染,因此需要將之前計算出來的波形位移和法線值儲存,作為下一次更新前的海浪渲染資料來源。

由於海浪波形是個週期波,所以可以利用Tiling的方式來去對大範圍的海面做波浪計算。
原本的演算法是只對取樣點 0 到 N-1 之間做計算,並將結果儲存到貼圖內;但貼圖內沒有 N - 1 到 N (也就是 0)之間的波浪資料。因此,若直接拿來做Tiling,會出現一個明顯的斷層


為了解決這個問題,勢必要將取樣點 N-1 到 N (也就是0)的資料一併寫入貼圖內。

假設 N = 128,實作的流程是:
首先,產生1個 129 x 129 個節點的矩形Mesh,然後將X、Z軸的取樣點索引寫入UV值內。
接著將步驟3完成的波浪位移和法向量資料,根據UV的取樣索引點寫入到兩張貼圖儲存。
為了有足夠的空間能夠儲存所有資料,貼圖大小必須上升一個冪次(也就是256 x 256)。


如此,就可以得到一個Tiling化(seamless)的波浪位移與法向量貼圖。



但這個做法存在著一個問題點,Unity Mesh的節點數量上限是 65535。
當取樣超過128時,Mesh 的節點數會超過 65535。
雖然可以用多個Mesh去解決這個問題,
但考量在手機上運作,128 x 128個取樣點已經相當足夠了。
因此我設定取樣點上限為128 x 128來避免這個問題。




  • 測試結果



我的測試環境是在 Sony Xperia L3 上運作,並使用 ApowerREC 錄製。
FPS測定是使用 SRDebugger 。

在同時錄製下,仍可維持 fps 60,算是達到我的目標。
但在計算白沫(whitecap)時,我使用 ddx 和 ddy 來計算 jacobian determinant,
似乎在手機環境下,ddx 和 ddy 回傳的數值和 PC 環境比較起來明顯的粗糙。
所以白沫部分會顯得像是馬賽克一樣。

手機上的白沫計算可能還是採用 GPU Gems 2 提到的利用高度門檻
來進行渲染的方式會比較適合。


  • 總結
網路上關於使用FFT做波浪生成的相關文章其實並不少,但大多是國外(簡體也有)。
因此作為自己的第一篇文章給有志之士批評指教。

在這裡介紹兩個不錯的Unity專案,同樣是使用FFT生成海浪來做為參考與學習
(我的專案也是參考這兩個專案做成的)

也是根據 Keith Lantz 文章的內容所實作的海浪生成,
和我的專案不同的地方是 Phillips-Ocean 是在 CPU 運算(使用 Thread),而我是在GPU運算。
對於不熟悉 Unity Shader 的工程師,是個很好的學習教材


使用 JONSWAP Spectrum 作為海浪頻譜,並示範如何混合兩個頻譜輸出,
海洋渲染實現了  Real-time Realistic Ocean Lighting using Seamless Transitions from Geometry to BRDF [1],提及的方式。

如果要在專案中做出海洋效果,直接參考這個專案比較好。


最後附上我的專案 FastFourierOcean


參考

1.Bruneton, E., Neyret, F., Holzschuch, N: Real-time Realistic Ocean Lighting using Seamless Transitions from Geometry to BRDF. Comput. Graph. Forum 29(2), 487-496 (2010),Blackwell Publishing Ltd.














沒有留言:

張貼留言