【Unity Shader學習筆記】Unity光照基礎-高光反射
1、原理
1.1、Phong模型
計算高光反射需要表面法線、視角方向、光源方向、反射方向等。
在這四個向量中,我們實際上只需要知道其中3個向量即可,而第4個向量(反射方向r)可以通過其他資訊計算得到:
這樣,我們就可以利用Phong模型來計算高光反射的部分:
m(gloss)是材質的光澤度,也被反稱為反光度。它用於控制高光區域的“亮點”有多寬,m(gloss)越大,亮點就越小。
m(spscular)是材質的高光反射顏色,它用於控制該材質對於高光反射的強度和顏色。
c(light)則是光源的顏色和強度。
使用max防止結果為負。
1.2、Blinn模型
它的基本思想是,避免計算反射方向r。為此,Blinn模型引入了一個新的向量h,如下:
然後,使用n和h之間的夾角進行計算,而非v和r之間的夾角,如下圖所示:
總結一下,Blinn模型的公式如下:
在硬體實現時,如果攝像機和光源距離模型足夠遠的話,Blinn模型會快於Phong模型。
當V或者I不是定值時,Phong模型可能反而更快一些。
2、程式碼實現
2.1、逐頂點光照
先計算漫反射部分,再計算高光反射部分。最後把漫反射、高光反射與環境光加到一起形成最後顏色。
因為需要計算漫反射與高光反射,Properties中含有三個變數:
Properties { _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) _Specular("Specular", Color) = (1, 1, 1, 1) _Gloss("Gloss", Range(8.0, 256)) = 20 }
在Pass程式碼塊中寫Tags、#include、定義結構體:
Pass{ Tags{"LightMode" = "ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" // 變數 fixed4 _Diffuse; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; fixed3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; fixed3 color : COLOR; };
主要計算集中在頂點著色器。
頂點著色器中,首先把座標從模型空間變換到裁剪空間。
隨後獲得環境光向量、worldNormal法線向量、worldLightDir光線向量。
計算漫反射項diffuse。
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//獲得環境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//世界座標下法線方向
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//世界座標下光線方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//計算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
使用reflect()函式並歸一化,求得反射方向。
視角方向的計算,需要先_WorldSpaceCameraPos.xyz求得攝像機位置向量,再與mul(unity_ObjectToWorld, v.vertex).xyz頂點位置向量相減,即可得到視角方向的向量。使用normalize歸一化。
//計算反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//計算視角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
最後便可以計算高光反射。環境光、漫反射、高光反射三者相加即可得到最後的顏色。
pow(x, y)即x的y次方。
//計算高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
片元著色器很簡單
fixed4 frag(v2f i) : SV_Target
{
return fixed4(i.color, 1.0);
}
最終程式碼如下:
Shader "Practice/Specular Vertex-Level"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular("Specular", Color) = (1, 1, 1, 1)
_Gloss("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
// 變數
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
fixed3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//獲得環境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//世界座標下法線方向
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//世界座標下光線方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//計算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//計算反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//計算視角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
//計算高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "SPECULAR"
}
逐頂點的高光反射會出現視覺問題,因為高光反射部分的計算是非線性的,而頂點著色器中計算光照再進行插值的過程是線性的。
效果如下,可以看到過度不太平滑。
2.2、逐畫素光照
逐畫素光照只要在頂點著色器中把變數的座標轉換做好,計算部分集中放在片元著色器即可。
具體程式碼如下:
Shader "Unity Shaders Book/Chapter 6/Specular Pixel-Level"
{
Properties{
//宣告屬性,使用之前需要在Pass中定義
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
//用於控制材質的高光反射顏色
_Specular("Specular", Color) = (1, 1, 1, 1)
//用於控制高光區域的大小
_Gloss("Gloss", Range(8.0, 256)) = 20
}
SubShader{
Pass {
//LightMode用於定義該Pass在Unity的光照流水線上的角色。
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
//告訴Unity,我們定義的頂點與片元著色器函式叫什麼名字
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//定義與屬性型別相匹配的變數
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
//定義頂點著色器的輸入與輸出結構體
struct a2v {
float4 vertex : POSITION;
fixed3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
//頂點著色器計算高光反射
v2f vert(a2v v) {
v2f o;
//Transform the vertex from object space to world space
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target{
//Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//Get the reflect direction in world space
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}