[轉]解讀Unity中的CG編寫Shader系列9——鏡面反射

  

討論完漫反射以後,接下來確定就是鏡面反射了
在開始鏡面反射shader的coding以前,要擴充一下前面提到的知識,加深理解鏡面反射與漫反射的區別。
注:這篇文章實現的鏡面反射是逐頂點着色(per-vertex lighting),最後效果圖能夠看到高亮區域並不光滑,更光滑的着色方式請看系列10逐像素着色(per-pixcel lighting),又稱馮氏着色
引用一下一位前人博文中的一些基礎概念,特別是關於馮氏反射模型的:
平行光(directional light)
一種是從特定方向射入並只會照亮面對入射方向的物體,咱們稱之爲平行光(directional light)。函數

環境光(ambient light)
另外一種光是來自全部方向而且會照亮全部物體,無論這些物體的朝向如何,咱們稱之爲環境光(ambient light)。固然在真實世界裏,這只是平行光照到其餘物體上,好比空氣、灰塵等等,而後反射出來的散射而已。可是在這裏,咱們須要把它單獨做爲一個光照模型列出來。
漫反射(Diffuse)
不管光的入射角度如何,都會向全部方向發生反射。反射光的亮度只和光線的入射角度有關,與觀察角度無關。光線越平行於物體表面,則反射光越弱,表面越暗;光線越垂直於表面,反射光越強,表面越亮。漫反射是咱們一般想到一個物體受到光照時須要首先想到的。
鏡面反射(Specular)
這就像鏡子同樣,反射光將按照和入射角相同的角度反射出來。這種狀況下,你看到的物體反射出來的光的亮度,取決於你的眼睛和光反射的方向是否在同一直線上;也就是說,反射光的亮度不只與光線的入射角有關,還與你的視線和物體表面之間的角度有關。鏡面反射一般會形成物體表面上的「閃爍」和「高光」現象,鏡面反射的強度也與物體的材質有關,無光澤的木材不多會有鏡面反射發生,而高光澤的金屬則會有大量鏡面反射。orm

P.S.:能夠看出除了環境光是咱們計算機圖形學中抽象出來的虛擬現象,其餘3種現象都是真實存在的
馮氏反射模型(Phong Rlection Model)
馮氏反射模型引伸了這個四步走的光照系統,首先全部的光線都有如下兩個屬性:對象

發生漫反射光的RBG值。
發生鏡面反射光的RGB值。blog

其次全部材質都有如下四個屬性input

反射的環境光RGB值
反射的漫反射光RGB值
反射的鏡面反射光RGB值
物體的反光度,它決定了鏡面反射的細節數學

每條光線咱們都須要知道兩個屬性,每一個物體表面上的點都須要4個屬性it

因此在咱們討論鏡面反射光的時候要明白鏡面反射光的渲染效果跟觀察者的觀察角度已經有了直接聯繫。
在Unity中,環境光能夠在菜單Edit > Render Settings中開啓,而untiy中的Cg函數已經提早內置了環境光uniform參數UNITY_LIGHTMODEL_AMBIENTio

鏡面反射與觀察視角的聯繫form

系列6中我已經說明了材料表面的平整程度決定了鏡面反射的明顯與否,現實生活中找不到絕對平的物體表面,因此咱們引入一個概念,每一種材料的表面的平整程度爲Nshininess, n越大越平整,越小越粗糙,理想狀態下n無窮大的時候是絕對的鏡面反射,也就是前面引用的文字中所說的你想看到光源,則必須從光線的反射角徹底重合去看。基礎

 

 

結合上圖,也就是說咱們的材料表面越平整,係數n趨近於無窮大的時候,想要看到光源,則必須從射線R所在的方向去看。
當材料表面的粗糙程度更大時,即便咱們在R附近,也能看到部分光源。
現實世界中的材料每每是這樣的,也就是從R附近的觀察這個物體時,都能看到光源的影像,至於這個附近的範圍有多大,取決於材料的平整程度。
反射向量R與法向量N和入射向量L的數學關係爲:

R=2N(N·L)-L (ps:N與L是點乘關係)

在Unity中,Cg內置了2個函數來計算這個關係:
float3 reflect(float3 I, float3 N)
float4 reflect(float4 I, float4 N)
其中I即爲入射向量

前面廢話了一大堆,這裏終於能夠說出我想說的了:
觀察向量V越接近R,則鏡面反射越明顯;
觀察向量V越遠離R,鏡面反射越不明顯;
緣由就在於世界上沒有絕對平整的材料。
因此咱們終於以數學公式給出馮氏反射模型:

 

 

其中I specular即爲鏡面反射的強度,I incoming爲入射光線的顏色向量 k specular爲材料的鏡面反射光顏色,一般是白色的

至於max(0,R·V)又於系列中的漫反射同理,當R與V的夾角超過90°的時候餘弦值爲負數,物理意義爲觀察者從物體的內表面去看外表面了,因此咱們仍是與0取最大值再去作n次方
可見這個數學公式中鏡面反射的強度與反射向量和觀察向量的夾角呈指數關係

編寫Shader

前面已經提到Cg中已經爲咱們提供了環境光參數,因此咱們只須要將這個參數與光源的顏色向量作矢量相乘獲得環境光的顏色向量:

float3 ambientLighting = float3(UNITY_LIGHTMODEL_AMBIENT) * float3(_Color);

漫反射光 diffuseReflection 上文中已經計算好。

接下來咱們要計算V,沒有V的話咱們的shader就沒法由觀察者的視角不一樣產生鏡面反射的效果變化:

觀察向量的單位向量計算過程爲 MainCamer在世界座標系中的3維座標(由內置uniform參數_WorldSpaceCameraPos)與 頂點的世界座標系中的座標作矢量相減,最後單位化。
step1:將頂點座標從對象座標系變換至世界座標系:

float 4 vertexPos=mul(_Object2World,input.vertex);

step2:將兩個座標矢量相減(ps,_Object2World是4X4矩陣,因此變換後的頂點座標是4維的,mianCamera的座標須要補充一維,減完後再去掉無心義的那一維)

float4 direction=float4(_WorldCameraPos,1.0)-vertexPos;

stpe3:減去無心義的第四維,而後單位長度化:

float3 viewDirection=normalize(float3(direction));

而後是入射向量L與法向量N:
//平行光源傳遞過來的_WorldSpaceLightPos0參數是直接就是方向,而點光源傳遞過來的是位置,關於點光源咱們後面再深刻

float3 lightDirection=normalize(float3(_WorldSpaceLightPos0));

//將法向量變換至對象座標系獲得N

float3 normalDirection=normalize(float3(mul(float4(input.normal, 0.0), _World2Object)));

至此咱們計算好了觀察向量V,入射向量L,法向量N,根據上面的公式I=I*k*max(0,R·V)^n,R由內置函數reflect(-L,N)計算出,咱們能夠計算鏡面反射強度了(指數n _Shininess咱們經過shader的property定義,以便在inspector中調節不一樣材料的光澤程度,同理還有鏡面反射光顏色_SpecColor):

float3 specularReflection=float3(_LightColor0)*float3(_SpecColor)*pow(max(0.0,dot(reflect(-lightDirection, normalDirection),viewDirection)),_Shininess);

有了環境光、漫反射光、鏡面反射光3個顏色向量以後,根據馮氏鏡面反射模型進行相加獲得接近真實的物體表面反射效果顏色:
//alpha仍是設置爲1不透明度
output.col = float4(ambientLighting + diffuseReflection+ specularReflection, 1.0);

最終代碼爲:

Shader "Custom/CustomSpecular" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
//材料表面的光澤程度,根據前文所述,此參數無窮大時,材料徹底不會產生鏡面反射
_Shininess ("Shininess", Float) = 10
}
SubShader {
Pass{
Tags { "LightMode" = "ForwardBase" }

CGPROGRAM

//定義頂點着色器與片斷着色器入口
#pragma vertex vert
#pragma fragment frag
//獲取property中定義的材料顏色
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;

// 光源的位置或者方向
//uniform float4 _WorldSpaceLightPos0;

// 光源的顏色 (from "Lighting.cginc")
uniform float4 _LightColor0;

//定義頂點着色器的輸入參數結構體
//咱們只須要每一個頂點的位置與對應的法向量
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//定義頂點着色的輸出結構體/片斷着色的輸入結構體
//已經計算好的顏色
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};

//頂點着色器
vertexOutput vert (vertexInput input) {
vertexOutput output;
//對象座標系到世界座標系的變換矩陣
//_Object2World與_World2Object均爲unity提供的內置uniform參數
float4x4 modelMatrix = _Object2World;
//世界座標系到對象座標系的變換矩陣
float4x4 modelMatrixInverse = _World2Object;

//法向量N變化至對象座標系
float3 normalDirection = normalize(float3(mul(float4(input.normal, 0.0), modelMatrixInverse)));

//平行光源的入射向量L直接由uniform_WorldSpaceLightPos0給出
float3 lightDirection =normalize(float3(_WorldSpaceLightPos0));

//觀察向量V由攝像機座標與頂點座標矢量相減
float3 viewDirection = normalize(float3(float4(_WorldSpaceCameraPos, 1.0)
- mul(modelMatrix, input.vertex)));

//鏡面反射光的計算
float3 specularReflection=float3(_LightColor0)*float3(_SpecColor)*pow(max(0.0,dot(reflect(-lightDirection, normalDirection),viewDirection)),_Shininess);

//前文計算好的漫反射光
float3 diffuseReflection=float3(_LightColor0) * float3(_Color)* max(0.0, dot(normalDirection, lightDirection));

//環境光直接獲取
float3 ambientLighting = float3(UNITY_LIGHTMODEL_AMBIENT) * float3(_Color);

//根據馮氏反射模型將上述3個RGB顏色向量相加,而後補充A:

output.col = float4(ambientLighting + diffuseReflection+ specularReflection, 1.0);
//國際慣例,頂點變化三步曲
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);

return output;
}

//片斷着色器,老規矩,把頂點着色器的輸出參數做爲片斷着色器的輸入參數
float4 frag(vertexOutput input): COLOR
{
return input.col;

}

ENDCG
}
}
FallBack "Diffuse"
}

用新的shader再建立一個材質球,再建立一個球體,與以前的2個球體進行比較。(本例的shader仍是隻有一個ForwardBase的單光源Shader,可是相信看到這裏您應該會把這個例子也作成多光源了)

 

 

 

 

 

 

可見咱們的新shader 會根據觀察者的視角表面的顏色隨之變化了

相關文章
相關標籤/搜索