目的:
解析,改進,批評一個國外免費透明水面Shader,進一步瞭解Shader背後的物理原理。
參考:
分析:
我將原水面Shader一再簡化,從中抽取最主要的部分,忽略細枝末節,並改掉缺陷,指出不足。
簡化版:
1.造型
其中,頂點偏移,也就是波的造型,是用貼圖製造的,一目瞭然:
就是一張高度圖經過4方向擾動,乘以波高度。然後它再乘上頂點法線在世界座標系上的Z軸高度,這個莫名奇妙的操作會使得Normal朝上的頂點的波高更加高,而偏的更加低。我認爲編者腦子裏也不清楚,所以我修改了,直接乘以(0,0,1)。如果你的高度圖只有b通道,這個(0,0,1)都不用乘。
2.Fresnel_Function
再觀原材質圖,你一定會注意到我們的主角,一個叫Fresnel_Function的節點。
UE4這樣起名字,也真是大言不慚,一會我們會說到。
這個節點,涉及到一個現象,菲涅爾效應。國外一般不稱爲效應,搜索Fresnel equations。
現象倒是很簡單:
對着玻璃看,越斜着看,它反射越多,越不透明,越像鏡子;
正着看,反射越少,就越透明。
我們在生活中常常見到過這種現象:
(我懷疑夏天遠方道路反光,也是這個原因)
總之,這是個在各個材質上都很普遍發生的現象,包括水面。
如果說我們之前的不透明水面本質就是鏡子,那我們這次的透明水面本質就是玻璃。
真正的Fresnel方程比較複雜,可脫胎於麥克斯韋電磁學方程。
對於透明物體:
然而UE4的Fresnel_Function節點的方程並不根據實際。它利用視線與平面越垂直,反射越少這一點,進行了粗略模擬。這個方程也是圖形學中常見的:
其中V向量是像素點到相機位置的向量,也就是光照模型中常見的View向量,與法線N起點一致。power用來控制效果,顯然由於1-V·N在[0,1]間,power越大,那麼越垂直於N的V的F才越大。在下圖中,也就是當power越來越大,黑色會擴大,而白環會減少,慢慢收縮到最邊緣那些法線十分垂直於視線的頂點上(白色爲1,黑色爲0):
顯然這個函數可以用來做邊緣光等其他特效。
那麼,我們回過頭來看透明水波材質。它的Fresnel節點的power賦予了5,十分接近邊緣:
注意:
以下簡稱Fresnel_Fuction(...)爲Fresnel(power)。
以下稱V垂直於N的情況爲“垂視”
3.金屬度(Metallic)
原材質給金屬度設置的是0。可以理解,因爲金屬材質不發生菲涅爾效應,而且金屬度越高,透明金屬材質越丟失BaseColor。但金屬度太低,水的透明和反光也就低了一個層次。看下金屬度爲1的水面,按需求和風格調整即可:
(油膩的反光又出現了)
4.反光度(Specular)
反光直接給1,沒什麼好說的。給0就失去了波光粼粼的效果。
5.粗糙度(Roughness)
回看透明水波材質。它的粗糙度被設置爲:
也就是越垂視的地方粗糙度越爲0,完全光滑;其他地方爲0.1,也很光滑,但較邊緣差了點。
對於對面,從直覺上來說是這樣,而且Roughness影響反射光計算,越光滑反射越多,符合菲涅爾效應。
實際效果上對比不明顯。
6.不透明度(Opacity)
越垂視,越不透明。也符合菲涅爾效應。
7.折射率(Refraction)
在介質密度不同的表明,光發生折射。從低密度到高密度,折射角小於入射角。
回憶初高中物理,折射率定律。國外更一般叫成斯涅爾定律(Snell's Law):
由於真空密度最低,或者說真空光速最快,所以所有的材質折射率都大於1。
從真空射入某材質:
透明水波Shader認爲越垂視,折射率越高,這是對的。首先直覺上,越彎曲的玻璃,折射越厲害。
其次,你可以認爲空氣與水折射率不變,當入射角變大,那麼折射率一定要變大。
或者你可以認爲折射率=c/v,在垂視的地方,光路更長,相當於v更小,所以折射率更大了。
8.風格化法線
你可能會發現我們這個水波像是三角透明玻璃組成的,十分風格化。放大之後是這樣的:
原因是這個透明水波Shader的法線。
圖中用了一個函數稱爲Shine_func,打開後發現實現十分簡單,效果也和迪廳反光球一樣:
關鍵是DDX和DDY是什麼?
這個蛋疼的起名讓我一開始以爲是二階導數,但明顯不對。經過搜索我發現它就是屏幕空間x方向偏導數和y方向偏導數。
問題在於爲什麼結果它是這樣的,而不是我們之前那麼順滑的連續函數。
以DDX爲例,就是檢查當前像素點(Pixel)在屏幕水平方向上離得最近的左右兩個頂點(Vertex),計算從左頂點到右頂點的空間向量,再歸一化。
我將這個結果賦予BaseColor,這樣看的準一點。
當屏幕向右是世界系y方向時,其值偏向爲(0,0,1)。因爲屏幕上的頂點從左到右,頂點向y軸方向變化(注意左下角世界座標系的方向):
當我大致面朝(1,1,0)方向:
注意到方塊紅面由於在往屏幕右方向走時,頂點世界位置x增大(導數大於0),y不變(導數爲0),而z變小(導數爲負,負數clamp後爲0),其ddx爲(1,0,0)
方塊上面由於在往屏幕右方向走時,頂點的x,y方向都有增加且一樣,ddx爲(1,1,0)
當我的屏幕右方向更接近x軸時,方塊黃面開始變紅:
儘管我們的相機位置一直在變,同一個頂點的DDX和DDY也在變,但其都保持在切平面上,叉乘出來也一定是法線方向。
這裏可以看出UE4材質系統的2個缺點:
1.明明與真正的WP無關,但卻必須引用這個節點,給讀者以誤導
2.程序執行順序對用戶不清楚。這裏的WP明顯是已經經過WPOffset以後的WP了。WPOffset中的WP節點和這裏的WP節點明明一樣,出來的值卻不一樣,若不是我們知曉渲染管線流程,就會被搞得懷疑人生。
9.問題一:法線
上面看出來了,叉乘出來的法線應該屬於世界座標系,但我們的材質默認是切線空間法線。原材質連這個都沒注意到,所以我說編者腦子不清楚。
你可能記得,材質有個選項可以修改。但因爲一個蛋疼的原因,我們不得不手動計算,之後會講到。我們需要手動將世界座標系的法線轉到切線座標系。
(上圖從左到右分別是原法線,編者原材質水波法線,和我修改後的法線。我猜由於透明水波既沒什麼陰影,水波的折射是否正確也很難有人看出來,所以這個問題沒人注意。)
所幸UE4有節點輕鬆解決座標系轉換:
10.問題二:折射
看到沒有,球下方邊緣有很奇怪的折射。
本來是水面折射,怎麼會和球有關?既然有“屏幕空間反射”,那麼這個折射是不是也是基於屏幕空間算的,然後出的問題呢?
在材質detail中搜索Refraction,我們發現引擎提供2種算法,默認算法是這樣的:
簡單地來說它警告你不要用於像水面一樣的大面積折射面。關於爲什麼會有這個bug它也沒說細說,只說這個是物理模擬。
我選擇第二種:
它說這個折射是個假模擬,值給1就是沒折射,2就是offset爲1的折射。這個offset是啥意思它也沒說清楚。還告訴我們必須用切線空間法線,這就是爲什麼我們之前要手動轉換。
蛋疼嗎?蛋疼。UE4辣雞嗎?辣雞。
我更改"折射"爲:
因爲這個假折射率要比較大,折射效果才明顯。沒辦法,誰叫真折射有Bug呢?咱目前這種情況,就是我寫本系列教程的緣由,瞭解原理,運用原理,避免這種情況。因爲弱勢,所以被牽着鼻子走,不知道在幹什麼。很不幸,我因爲辣雞,又碰上了這種情況,成爲了一個"能用就行"的乞討者。
以後有機會嘗試改寫引擎代碼,改寫這個折射。
結語:
本篇解析,改進,批評一個國外免費透明水面Shader,對簡單的物理和材質系統進行初步瞭解。
由於能力的不足,在折射問題上最終又成爲了一個被攔在原理和實現之外的人。之後提升自己的能力,改變這一狀況。
水波模擬系列到此告一段落。