元旦快樂,送大家一條水晶龍

0x00 前言

hi,大家好。不知不覺時間就推進到了2018年,所以首先祝大家元旦快樂。由於前一段時間工作的內容發生了一些變化,因此有一段時間沒有更新博客了。不過時間來到了元旦,還是得寫點東西的。那麼本文就來聊一聊如何利用環境映射來實現一條水晶龍吧,內容主要包括如何實現反射、折射以及菲涅耳效果。

0x01 反射

首先,我仍然使用來自斯坦福的上鏡率超高的模型,這次是龍的模型。

將它導入到Unity的工程中。

同時,我們還需要一個CubeMap,用來提供環境信息。可以看到,使用了模型導入後Unity的默認material的龍,在藍天白雲的映襯下… 就像一條石膏龍。

好了,基本準備工作已經完成了。那麼作為水晶龍打造計劃的第一步,我們首先來實現水晶龍對環境的反射,讓龍模型根據反射的結果去這個CubeMap上採樣。

可能有些朋友會好奇,水晶龍的反射怎麼才能和CubeMap建立起聯繫呢?其實很簡單,我畫一張示意圖各位就明白了。

其實這個過程很簡單,就是求的反射方向然後去cubemap上採樣作為水晶龍身上該點的顏色即可。

當然,由於折射和反射是一個常用的需求,因此hlsl和glsl都有內置的方法用來計算反射方向和折射方向。但是下文我還是會給出相關的推導過程。

接下來,我們需要實現對反射方向的計算。同樣,我也來畫一張示意圖來推導反射的過程,大家就應該能明白了。

其中向量I代表入射光線,從眼睛到物體表面。向量N代表表面法向量,向量R則代表反射光線。

通過上面的推導,我們就可以在shader內實現自己的反射函數了。

//計算反射方向nfloat3 CaculateReflectDir(float3 I, float3 N)n{n float3 R = I - 2.f * N * dot(I, N); n return R;n}n

一旦獲取了反射方向之後,就可以在fragment shader內對CubeMap採樣了。

fixed4 frag(v2f input) : SV_Targetn{n float3 reflectedDir = CaculateReflectDir(input.viewDir, input.normalDir);nn fixed4 reflectCol = texCUBE(_Cube, reflectedDir);nn return reflectCol;n}n

結果我們的龍就從石膏龍變成了能夠反射周圍環境顏色的龍了。

0x02 折射

實現了反射的效果之後,我們還可以利用相似的思路來實現一些類似的效果,例如光的折射效果。

所謂的光的折射指的是當光通過不同密度的兩種材質之間的介面時——比如空氣和水——光的方向會發生改變。

當光波從一種介質傳播到另一種具有不同折射率的介質時,會發生折射現象,其入射角與折射角之間的關係,可以用斯涅爾定律(Snell』s Law)來描述。

下面我就利用斯涅爾定律來計算折射方向,進而正確的在CubeMap上為龍的折射效果進行採樣。同樣,我也會畫一張示意圖,並將推導過程寫在上面。

按照上文的公式推導,我們可以很簡單的翻譯成對應的shader方法。

//計算折射方向nfloat3 CaculateRefractDir(float3 I, float3 N, float ratio)n{n float cosTheta = dot(-I, N);n float cosTheta2 = sqrt(1.f - pow(ratio,2) * (1 - pow(cosTheta,2)));n float3 T = ratio * (I + N * cosTheta) - N * cosTheta2;n return T;n}n

設n1/n2的值為0.9,對龍進行一次折射(因為光從空氣進入模型會發生一次折射,從模型進入空氣仍然會有一次折射,為了簡單我們只考慮一次折射的情況)的結果如下圖:

fixed4 frag(v2f input) : SV_Targetn{n float3 reflectedDir = CaculateReflectDir(input.viewDir, input.normalDir);n fixed4 reflectCol = texCUBE(_Cube, reflectedDir);nn float3 refractedDir = CaculateRefractDir(normalize(input.viewDir), input.normalDir, .9f);n fixed4 refractCol = texCUBE(_Cube, refractedDir);n}n

0x03 菲涅爾效果

ok,在上文中我們分別討論了如何在shader內實現反射和折射效果。但是在現實生活中,反射和折射可能是同時發生的。

一個生活中簡單的小例子就是我們只有在幾乎垂直向下看的時候,才能看到池塘內水下的情況。而在一個較小的角度觀察池塘時,又會因為大部分會被反射而幾乎沒有什麼折射,因而很難透過水的表面看到池塘內的情況。

這就是因為當光從一種具有折射率為n1的介質向另一種具有折射率為n2的介質傳播時,在兩者的交界處(通常稱作界面)可能會同時發生光的反射和折射——這便是菲涅耳效果。

而菲涅爾方程則描述了不同光波分量被折射和反射的情況。也描述了波反射時的相變。但是,精確描述底層物理現象的菲涅耳公式是非常複雜的,因此在圖形學中往往會採用菲涅耳公式的近似,而非菲涅耳公式本身。

常見的菲涅耳公式的近似包括所謂的Schlick』s approximation,它的公式如下:

R(θ) = R0 + (1 - R0)(1 - cosθ)5

詳細的信息各位可以查看:

https://en.wikipedia.org/wiki/Schlick%27s_approximationen.wikipedia.org

還有一個近似則是我們要使用的經驗近似,相對來說經驗近似的效果更可控。

R = max(0, min(1, bias + scale * (1.0 + I ? N)power))

其中R代表了反射係數。向量I從眼睛指向物體表面,向量N是該點的法線,bias、scale、power則是用來調整菲涅耳效果表現的經驗參數。

而這個近似公式的基本概念就是當I和N幾乎重合的時候,反射係數應該接近0,大部分的光會被折射。當I和N分開的時候,反射係數逐漸增大,被反射的光變多。而這個係數也應該保持在0~1之間。因此,在shader中可以像下面這樣來實現:

//菲涅耳效果nfloat CaculateFresnelApproximation(float3 I, float3 N)n{n float fresnel = max(0, min(1, _FresnelBias + _FresnelScale * pow(min(0.0, 1.0 + dot(I, N)), _FresnelPower)));n return fresnel;n}n

最終,在fragment shader內利用菲涅耳近似公式將反射效果和折射效果結合在一起,就能夠實現一個更加真實的水晶龍效果了。

https://www.zhihu.com/video/931275018940203008

Demo地址:

https://github.com/chenjd/Unity-Miscellaneous-Shadersgithub.com

-EOF-

最後打個廣告,歡迎支持我的書《Unity 3D腳本編程》

歡迎大家關注我的公眾號慕容的遊戲編程:chenjd01

推薦閱讀:

從零開始手敲次世代遊戲引擎(六)
【大咖分享】熱血傳奇研發經驗分享by 鄭建鑫
從零開始手敲次世代遊戲引擎(五)
卡通渲染-向罪惡裝備xrd前進!

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