GPU Gems 基於物理模型的水面效果 學習筆記 (二)

上一篇內容:GPU Gems 基於物理模型的水面效果學習筆記 (一)

原文地址:GPU Gems

本文繼續介紹GPU Gems水面模擬後半部分的內容,並補充一個鏡面反射的實現方法。

1.3 Authoring 創作

關於水面網格的離線生成。

1.3.1 Using Depth 使用深度

因為頂點的高度是通過輸入x,y算出來的,所以z值沒有用到。把水底的高度存入z值,然後把所有頂點的z值存入一張表(water-table),運行時用水面高度減水底高度,算出水的深度,可以方便以後控制水面的不同深度的表現。例如控制透明度,讓水淺的地方更透明。也可以控制波的振幅衰減,讓淺灘的波浪振幅減小,逐漸平息消失。

1.3.2 Overrides 重載

給頂點的RGB顏色賦予另外的意義。用R控制透明度,為0時使水面完全透明。G控制表面的反射強度,G為0時不反射,這樣使表面會看起來很不光滑。B控制根據視角的不透明度的衰減,影響Fresnel term。

Fresnel term來自菲涅耳效應。

關於菲涅耳效應可以參考Cg Tutorial

7.4.1 The Fresnel Effect

In general, when light reaches an interface between two materials, some light reflects off the surface at the interface, and some refracts through the surface. This phenomenon is known as the Fresnel effect (pronounced 「freh-『nell」). The Fresnel equations describe how much light is reflected and how much is refracted. If you have ever wondered why you can see fish in a pond only when you』re looking practically straight down, it』s because of the Fresnel effect. At shallow angles, there is a lot of reflection and almost no refraction, so it is hard to see through the water』s surface.

簡單的說就是這樣一種現象:當你看遠處的水面時,視線和水面夾角很小,水的反射會比折射更強烈,這時水看起來是不透明的,而當你垂直往水裡看的時候,折射比反射更強烈,水看起來比較透明。

1.3.3 Edge-Length Filtering 邊長過濾

邊長指的是水波網格三角面的邊長。

邊長不能比波長大太多,否則無法表現出波的形狀。

可以根據頂點附近的邊長值,過濾掉波長太大的波。這樣水面的網格密度可以自己定製,例如在靠近海岸的區域用密集的網格。並且,輸入的波的波長也可以不受限制,反正波長太大的波會自動被過濾掉。

1.3.4 Texture Coordinates 紋理坐標

一般情況不需要給頂點指定紋理坐標。可以通過頂點的世界空間坐標位置計算凹凸貼圖的uv從而得到法向量。只需要一個縮放和偏移的演算法,讓頂點的xy值變化到uv就行了。

但是如果水面是河流那種流動的水,就需要給頂點指定紋理坐標。因為河流可能會拐彎,所以要把凹凸貼圖的法向量從紋理空間先轉換到頂點的切線空間,再轉換到世界空間。

1.4 Runtime Processing 運行時處理

這裡討論獲得一種映射方式(矩陣變換),把凹凸貼圖轉換到世界空間和把視線向量通過水麵反射到cubemap環境貼圖上。

1.4.1 Bump-Environment Mapping Parameters 凹凸貼圖環境映射參數

Tangent-Space Basis Vectors 切線空間基礎向量

根據上一篇的內容,可以得到水面上一個點的切向量,法向量和次法向量。這三個向量組成的坐標系就是這個點的切線空間。

根據矩陣和線性變換的相關知識,可以把切向量,法向量和次法向量構造出一個3x3的矩陣作為切線空間到世界空間的變換矩陣。

如果水面的uv是通過頂點位置算出來的,可以不用考慮凹凸貼圖中法向量的旋轉。如果水面是河流那樣的形狀,即頂點被指定了紋理坐標,就要把法向量的旋轉也考慮進去。

如果水面上一個點正好是河流的拐彎處,這個點的切向量方嚮應該和凹凸貼圖中的v方向平行,相當於是把凹凸貼圖水平旋轉。於是可以得出從紋理空間到水的表面空間的變換矩陣

其中

是可以離線計算的,因為這是個固定的值,在運行時不會改變。

記得在上一篇生成凹凸貼圖的時候,存入的值是經過處理增加了精度的。當時有個常量被提出來,現在需要加進去。另外可能要根據相機到像素點的距離,適當的縮放精度,使遠處的像素精度更小,避免出現鋸齒。 寫成矩陣就是

最終結果就是

Eye Vector 視向量

通過環境貼圖來實現水面的反射效果。

這種方法只適用於反射的物體離相機很遠的情況,如果要反射的物體太近,就不準了。

首先要生成一張環境貼圖,我的理解這張環境貼圖不一定僅僅是天空盒,只要離相機比較遠的物體都可以放進去。

這部分原文詳細講了水面某一點要反射的環境貼圖上的點是怎麼算出來的,推導公式說的比較清楚,可以看一下原文。

我個人更傾向於用另一種方法來實現反射效果。具體內容在本文最後。

1.4.2 Vertex and Pixel Processing 頂點和像素的處理

先從water-table中把之前存的水底高度Z取出來,在vertex shader中計算水深。同時也根據水深算出水波振幅的衰減係數。

取出頂點臨近的最小邊長(生成網格的時候要離線保存起來),過濾掉波長太短的波,並對波的高度進行衰減。然後是處理凹凸貼圖,前面已經說過。

pixel shader中進行光照計算,根據上一篇文章里說到的頂點RGB中存的值對像素顏色做一些處理。

然後是反射的顏色疊加。

補充:鏡面反射

我個人更傾向於用兩個相機來實現鏡面反射,這裡補充說一下。

大概的思路是使用兩個相機,一個主相機,一個反射相機。主相機在水面上方,負責正常的渲染,反射相機在水面下方,通過水平面和主相機位置對稱。這樣反射相機看到的內容就好像是從主相機位置看水面時水面中反射的圖像。最後把反射相機渲染的圖投影到水面上,反射效果就實現了。

這裡先要處理一個問題,就是反射相機的裁剪面問題。反射相機只應該渲染水面上方的物體,那麼可以讓反射相機的近裁剪面和水面重合。

用這張圖來舉例:

假設C是水面,剪頭方向是水上,水面與視錐體相交,我們希望反射相機的近裁剪面移到C上,這樣反射相機就只會看到水面上的物體。

通過投影矩陣投影矩陣 The Projection Matrix,被渲染的物體從攝像機空間被變換到裁剪空間。

在Unity中,其實就是Camera.projectionMatrix變數。

通過修改反射相機的projectionMatrix,可以讓改變近裁剪面後的視空間仍然可以正確的變換到裁剪空間。

投影矩陣的推導可以參考這篇文章,推導工程講的比較詳細了:

Oblique View Frustum Depth Projection and Clipping

後來又有人優化了這個方法:

Oblique Near-Plane Clipping with Orthographic Camera

後者直接寫出了優化後的投影矩陣演算法,這裡我也不是太理解,如果有知道的朋友麻煩告訴我。

簡單的梳理一下步驟:

-生成反射相機

-根據水平面的坐標和法向量構建反射平面的向量

-使用反射矩陣(有現成的)計算反射相機的坐標位置和視變換矩陣

-計算反射平面在攝像機空間的向量

-使用前面的提到的演算法得出新的投影矩陣

-讓反射相機渲染目標模型的背面。因為反射相機從水底往上看,可能會看到模型的裡面

-獲取反射相機的targetTexture,傳到水面的shader中,投影到水面上

具體的代碼可以詳細閱讀Unity3D wiki

MirrorReflection4

題圖是我幾年前寫的給移動設備使用的水面,只用到鏡面反射和法線貼圖。

推薦閱讀:

從零開始手敲次世代引擎(十九)
《Inside》開發秘辛:沒有腳本、沒有設計文檔,同一個場景做了5年迭代
如何成為一名技術美術
從零開始手敲次世代遊戲引擎(三十四)

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