前言
近些年,電子競技行業發展迅速,游戲的品質也是逐年提升。從用戶體驗的角度來講,畫質渲染及畫質特效方面非常重要。任何渲染特效都主要是光影的變幻,從而達到美輪美奐的效果。漫反射是最簡單的光照效果,所有的光照特效都是以漫反射為基礎的。本篇就來詳細介紹,漫反射著色器如何實現。
- 首先我們來看一下什么漫反射
-
漫反射,是投射在粗糙表面上的光向各個方向反射的現象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向著四面八方反射,所以入射線雖然互相平行,由于各點的法線方向不一致,造成反射光線向不同的方向無規則地反射,這種反射稱之為“漫反射”或“漫射”。這種反射的光稱為漫射光。很多物體,如植物、墻壁、衣服等,其表面粗看起來似乎是平滑,但用放大鏡仔細觀察,就會看到其表面是凹凸不平的,所以本來是平行的太陽光被這些表面反射后,彌漫地射向不同方向。
漫反射 -
以簡單人物為例,在Unity中的漫反射是這樣的
人物漫反射
-
- 關于漫反射的光照模型,常見的是蘭伯特(Lambert)和半蘭伯特(Half-Lambert)
Lambert的光照公式是(C-light · M-diffuse)max(0,n·I),
即光照顏色 * 漫反射顏色 * max(0,法向量*光照方向)
-
下面以球形2D圖像解析一下原理
-
第一步,我們確認公式中的兩個方向向量,注意光照方向是指向光的方向,即入射光向量的逆向量,紅色光照方向,藍色頂點法向量。
光照方向及法向量方向 -
第二步,計算不同頂點兩向量的夾角,我們可以得到面向光的部分角度在0-90度之間,背向光的角度在90-180度之間。
計算不同頂點兩向量的夾角 - 第三步,Lambert漫反射需要的面向光的部分渲染,背向光的地方不渲染,且渲染部分需要做曲線性的遞減。公式中兩個向量做點乘計算,即n · I,點乘公式是|n|*|I|*Conθ,其中θ是兩個向量的夾角,下面先看一下余弦圖。
余弦函數曲線 -
第四步,我們的夾角范圍是0-180度,因此我們裁掉后半部分
0°-180°余弦圖 -
第五步,我們需要0度時顏色最顯眼,到90度逐漸遞減,余弦前半段得到的結果與我們的預期相符,但90度到180度區間內,漫反射不做任何渲染,即純黑色代表陰暗面效果,因此得出下圖。
90°--180°渲染比例為0 -
最終我們在兩向量夾角為0°到90°范圍內按比例渲染,就形成了最終的漫反射效果。
最終比例
-
-
使用Lambert進行漫反射渲染,在Shader中有兩種寫法,一種是逐頂點著色,另一種是逐像素著色。從代碼中我們可以看出,逐頂點是在頂點著色器中進行漫反射計算,而逐像素則在片元著色器中進行漫反射計算,下面來看逐頂點代碼。
//蘭伯特Lambert漫反射(逐頂點) Shader "AlbertShader/VertexDiffuse" { Properties { //漫反射顏色 _DiffuseColor("Color",Color)=(1,1,1,1) } SubShader { Pass { //正向渲染 Tags{ "LightMode"="ForwardBase" } CGPROGRAM//------------------CG語言開始------------------- //聲明頂點函數 #pragma vertex vert //聲明片段函數 #pragma fragment frag //引入光照函數庫 #include "Lighting.cginc" //定義外部屬性-漫反射顏色 float4 _DiffuseColor; //頂點輸入結構體 struct appdata { //頂點坐標 float4 vertex : POSITION; //頂點法線 float3 normal : NORMAL; }; //頂點輸出結構體 struct v2f { //像素坐標 float4 vertex : SV_POSITION; //臨時變量:顏色 fixed3 color : COLOR; }; //頂點函數實現 v2f vert (appdata v) { //定義頂點輸出結構體對象 v2f o; //頂點坐標轉換到屏幕像素坐標 o.vertex = UnityObjectToClipPos(v.vertex); //獲取環境光 float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //將頂點法線轉換到世界空間下,并做歸一化處理 float3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); //將光照方向轉換到世界空間下,并做歸一化處理 float3 worldLight = normalize(_WorldSpaceLightPos0.xyz); //帶入公式運算 fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLight)); //結合漫反射和環境光 o.color = ambient + diffuse; //返回結果 return o; } fixed4 frag (v2f i) : SV_Target { //將結果返回 return fixed4(i.color,1.0); } ENDCG//------------------CG語言結束------------------- } } }
-
下面來看逐像素代碼
//蘭伯特Lambert漫反射(逐像素) Shader "AlbertShader/PixelDiffuse" { Properties { //漫反射顏色 _DiffuseColor("Color",Color)=(1,1,1,1) } SubShader { Pass { //正向渲染 Tags{ "LightMode"="ForwardBase" } CGPROGRAM//------------------CG語言開始------------------- #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" float4 _DiffuseColor; struct appdata { float4 vertex : POSITION; //頂點法線 float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; //世界空間下的頂點法線 fixed3 worldNormal : TEXCOORD0; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); //通過矩陣運算,得到世界空間下的頂點法線 o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); return o; } fixed4 frag (v2f i) : SV_Target { float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); //使用蘭伯特光照模型公式 fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLightDir)); fixed3 color = diffuse + ambient; return fixed4(color,1.0); } ENDCG//------------------CG語言結束------------------- } } }
Lambert漫反射效果不錯,但背光的一面全是黑色,若鏡頭在后面照射,則渲染出來的就是一個黑色平面,視覺效果很差,因此,有時我們也常常使用半蘭伯特(Half-Lambert)的光照模型。
-
Half-Lambert的公式與Lambert相似,只是人為改動了公式中的渲染比例,半蘭伯特沒有太多的物理證明,可以理解為測試出來的一種實現方式。公式即(C-light · M-diffuse)(α(n·I)+β),通常情況下α和β的值為0.5。具體代碼如下:
//半蘭伯特HalfLambert漫反射(逐像素) Shader "Hidden/HalfPixelDiffuse" { Properties { _DiffuseColor("Color",Color)=(1,1,1,1) } SubShader { Pass { Tags{ "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" float4 _DiffuseColor; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; //臨時變量:世界法線 fixed3 worldNormal : TEXCOORD0; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); //世界法線 o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); return o; } fixed4 frag (v2f i) : SV_Target { //環境光 float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //世界法線 fixed3 worldNormal = normalize(i.worldNormal); //世界入射光 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); //半蘭伯特漫反射公式 fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * (0.5 * (dot(worldNormal,worldLightDir)) + 0.5); fixed3 color = diffuse + ambient; return fixed4(color,1.0); } ENDCG } } }
-
Half-Lambert顯的更透亮一點,背光面也不是全黑的,三種Shader的渲染效果對比如下:
逐頂點Lambert、逐像素Lambert、逐像素Half-Lambert
結束語
漫反射是光影效果的基礎,通過對漫反射底層實現原理,以及對漫反射公式的圖像剖析,大家有沒有更清晰的認識呢?Shader就是如此奇妙,這就是所謂的數學之美、編程之美。想成為邏輯開發和圖形學開發的雙料大師嗎?一起加油吧!??????