GPU Gems 基於物理模型的水面模擬 學習筆記 (一)

三年前我參與開發一款手機3D空戰遊戲,裡面需要用到大面積擬真的水面效果。當時Unity官方是提供了一個水面特效的demo,但是由於手機設備的性能限制而無法使用。所以我只好開始自己製作適合手機設備的水面效果,之後也一直對水面效果很有興趣。

說到水面效果,就不能不提GPU Gems。 GPU Gems 第一部雖然已經年代久遠,但裡面有些內容現在來看也不算過時。其中第一章就是講如何實現一個基於物理模型的水面效果。

我看的是Nvidia官網中提供的免費版本:

GPU Gems

這一章除了圖形編程以外,還涉及到高等數學和線性代數的知識,可以幫我們回顧大學學過的內容,所以還是比較值得深入學習的。本文的內容主要是圍繞第一章的前半部分。

1.1 Goals and Scope 目標和適用範圍

目標:參照現實中的物理模型,用正弦波來得到近似的水面效果。

適用範圍:從大面積的水面(如海洋)到小池塘。

1.2 The Sum of Sines Approximation 正弦近似值求和

水面的生成分兩部分,一部分是水波網格的形狀,另一部分是水波的凹凸貼圖(bump map)。網格用來控制水波總體的形狀,凹凸貼圖用來展示水面的細節。水波形狀和凹凸貼圖中波紋形狀的生成在本質上都是一樣的,即正弦函數的求和。

我們可以把一個水面當成很多個正弦波的疊加,每個正弦波的方向、移動速度和振幅都不一樣,再累加到一起,就近似真實的水波了。

假設坐標系的x和y軸平行於地面,z軸垂直向上,那麼一個網格的頂點位置就是輸入水平坐標(x,y),通過正弦函數計算得到高度z。可以把z值理解為正弦函數的波峰。那麼(x,y,z)就是該頂點的坐標。

1.2.1 Selecting the Waves 波的選擇

用一組參數來定義波:

· 波長L:波峰到波峰的長度 頻率w = 2π/L

· 振幅A:波峰到水平面的高度

· 速度S:波峰每秒移動速度。相位常量φ=2π*S/L

· 方向D:波峰移動方向的水平向量(一個二維向量)

等式1:

這是一個波的函數。其中i指的是此波為第i個波。

等式2:

所有波累加以後的函數。就是把第1個到第i個波的函數加到一起

這兩個公式里sin函數中有一個向量

,按理來說這裡應該傳一個值進去。這裡我的理解

指的是這個向量的模。

1.2.2 Normals and Tangents 法向量和切向量

這裡主要講求水面的法向量、切向量和次法向量。有了這三個向量,就可以得到水面網格里某個頂點的切線空間坐標系。同時法向量也可以用來生成凹凸貼圖(bump map)。從凹凸貼圖中取出向量值用於計算某個像素點在世界空間中的法向量,這個計算也是需要轉換到頂點的切線空間坐標系中。

關於凹凸貼圖

我們知道在實時光照中,物體的著色要根據物體表面的法向量,眼睛和光源的位置計算而得出顏色。

在逐像素光照中,我們不僅要知道頂點的法向量(通過建模的時候加進去),還要知道網格上每個像素點的法向量。每個像素點的法向量是通過鄰近的網格頂點的法向量插值而算出來的。凹凸貼圖的作用就是在不增加頂點數量的情況下,給網格表面增加細節,即給某個像素點賦一個新的法向量。 所以凹凸貼圖中保存的實際上是一個個的向量。我們知道單位向量的一個分量取值範圍是-1到1,而顏色貼圖中rgb中某個顏色的取值範圍為0到1,所以只需要把單位向量的值加1再除以2,就可以保存在一張貼圖中。這樣的圖叫做Normal Map Textures(法向量貼圖紋理)。

關於凹凸貼圖的更多內容可以參考這裡:Bump Mapping

繼續前面的內容,這裡我們可以得到水面上一個點的方程:

等式3:

可以通過對這個函數求偏導數來得到法向量、切向量和次法向量的函數。

(以下內容高數好的人請略過)

Our binormal B and tangent T vectors are the partial derivatives in the x and y directions, respectively

次法向量B的函數和切向量T的函數分別通過對x和y軸求偏導數而得到

這裡應該從曲面方程開始說起。

等式3的函數相當於是一個二元函數,因為時間t肯定每次都是一個定值,暫時不去管它,那麼等式3可以看作函數z=f(x,y),H就是水面的高度。或者改寫成F(x,y,z)=0這種形式,這就是一個三維空間的曲面方程。我們的水面就是一個符合此方程的曲面。

另外,還要了解曲線方程。曲線可以看作兩個曲面相交

在高等數學多元函數微分學的幾何應用中,介紹了空間曲線的切線與法平面,曲面的切平面與法線。

於是可以得到法向量 等式6b

根據空間曲線的切線推導可以得到,曲線的切向量為

即三個參數方程各自求導。 那麼應用在曲面上,其實這個切向量就是經過曲面一點,和這一點的法平面平行的任何一個向量。

書中的切向量是對y軸求導,次法向量是對x求導。

把曲線的參數方程寫為

f(x,y)就等價於H(x,y,t),可以得到

對y軸求偏導數,則x為定值,定值的導數是0,所以得到切向量函數

同理得到

次法向量函數 等式4b

我這裡是先計演算法向量,再計算切向量和次法向量,跟GPU Gems上的順序不同。書上是計算出切向量和次法向量,然後叉乘來獲得法向量。

這裡又提到,由於真實中的水面應該波峰更尖,波谷更平,於是把公式的正弦部分加一,除以二然後再取k次冪來達到這個效果。

1.2.3 Geometric Waves 幾何體(網格)波紋

Directional or Circular 線型或圈型

有兩種波的形式,一種是線型的,一種是圈型的。

線型更適合大面積的水域,如一望無際的海洋。圈型適合小池塘或瀑布落水的位置。

線型的就是一個波的方向向量D始終固定,圈型就是方向向量D要根據每個頂點的位置到圈的中心算出來。

圈型波特定點的方向向量公式:

Ci為圈的中心的水平坐標

Gerstner Waves

介紹水波的一種物理模型Gerstner Waves。它的特點是波峰附近的頂點在水平位置上要更靠近波峰,這樣可以讓波峰看起來更陡峭。

通過改寫之前的坐標點公式,把頂點位置的x和y都加一個值使頂點更靠近波峰。

其中Qi的值用來控制波峰的陡峭程度,Qi的範圍最好在0到1,太大會造成波峰不自然

用新的公式求偏導數得到

Wavelength and Speed 波長和速度

這裡給出了一個模擬現實情況的波長與頻率的關係公式

g就是重力加速度

Amplitude 振幅

Direction 方向

1.2.4 Texture Waves 紋理波

主要講如何生成凹凸貼圖,凹凸貼圖的要求等。

在手游中,擬真的水面一般只用到了凹凸貼圖,水面的網格是一個平面,面數也不高。如果要讓網格也有水波形狀,就需要大量的三角面,對手機性能的消耗太大。

在實際運用中,可以把十幾個不同頻率、波長和振幅的波保存在幾張256x256的凹凸貼圖裡,然後在shader中用多個通道讀取這些凹凸貼圖,並讓這些貼圖紋理朝不同方向移動,疊加到一起就會產生動態的法線。根據我自己的經驗,2個通道的效果就已經不錯了。

Wavelength and Speed 波長和速度

因為紋理貼圖需要可以平鋪(tiling),所以在紋理上正弦波至少要重複一次,因此波長不能超過貼圖的寬度。同時波長不能太短,因為接近4個texel(紋素),波就會變為鋸齒狀。這裡推薦波長範圍4~32個紋素

Amplitude and Precision 振幅和精度

把振幅對波長的比作為一個常數 KAmpOverLen。

同時這裡引入了lookup texture(查找圖)的概念。在pixel shader(像素著色)中,不能出現太過複雜的運算,因為渲染的物體每幀展示在屏幕上的像素點有幾個,這種運算每幀就要運行幾次。可以把複雜的公式根據變數的範圍直接算出結果然後存在一張紋理圖上,以後就可以不用公式計算,直接把變數當作uv從紋理圖上取結果。當然這種方法也有局限性,就是輸入的值範圍必須是0到1,因為uv的範圍就是0到1

這裡是把cos(2πu) (註:原文括弧里是2u,如果u的範圍0到1,那麼2u是覆蓋不了cos函數的一個周期的。並且我查了中文版,這裡也是2πu)放到查找圖裡,u的範圍是0到1。把cos函數放到查找圖的而不是把整個公式保存在法線紋理貼圖中,是因為紋理貼到目標網格上的時候可能會有旋轉,把cos方法拿出去旋轉會更方便一些。

參考之前的等式

因為現在水波是產生在u-v space(紋理空間),那麼x,y其實就相當於紋理的u和v 。得到公式:

我們可以不用關心水波的高度Wi,因為反正法線紋理貼圖裡只能保存法線。只要把這個偏導數算出來就行了。

在vertex shader(頂點函數)中計算

我個人的理解,這個值需要處理。因為lookup texture中參數u的範圍為0到1,所以這個值需要先減去n倍2π,再除以2π,使結果在0到1的範圍內。n是一個整數。

然後把處理後的值在pixel shader中作為lookup texture的u傳進去,可以獲得cos函數的結果。

等式14的其他部分

就作為常量傳入pixel shader中乘以剛才得出的cos的值。

考慮到要用前面說的波峰更陡峭,波谷更平的效果,應該是基於等式8b的公式來計算

所以lookup texture的生成公式應該為

關於精度的問題,如果波峰太過陡峭,則法向量的x,y值會遠大於z,書中傾向於讓z大一點,x,y小一些,這樣效果看起來更好。

根據前面得出的法向量公式我們知道,法向量的x和y分量是所有波函數的偏導數求和

求和之前,w = 2π/L

這裡要保證法向量的x和y分量都在-1到1的範圍里,而正弦和餘弦的範圍是在-1到1的範圍中,那麼就需要讓正弦或餘弦外面的那部分比較小才行。

又kAmpOverLen = A/L,

書中的做法是在求和之前,把numWaves x kAmpOverLen x 2π提出來(numWaves是波的數量是一個常數,kAmpOverLen也是常數),得到求和之前的公式

用這個公式來做累加存到法線紋理貼圖中,用的時候再乘以 numWaves x kAmpOverLen x 2π,這樣可以保證法線紋理貼圖的精度。

Direction and Tiling 方向和平鋪

圈型的水波演算法不能做凹凸貼圖,因為沒法平鋪。

總結:這篇文章是記錄我對原作前半部分的理解。主要包括:

1.水面的生成思想,用正弦波求和來計算網格形狀和生成凹凸貼圖

2.水波函數的推演,法向量,切向量和次法向量的計算,高數的相關知識

3.凹凸貼圖、查找圖等技術的介紹


推薦閱讀:

Unity特效(1) 夢幻旋屏
手游逆向分析<二>: Unity內還原《鎮魔曲》角色渲染效果
Unity命令行模式,也能「日誌實時輸出」
利用GPU實現無盡草地的實時渲染

TAG:Unity游戏引擎 | 游戏开发 | 计算机图形学 |