一、Diffuse Reflection
漫反射(Diffuse Reflection),又稱Lambert反射,是投射在粗糙表面上的光向各個方向反射的現(xiàn)象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向著四面八方反射,所以入射線雖然互相平行,由于各點的法線方向不一致,造成反射光線向不同的方向無規(guī)則地反射。
在生活中,我們看到的月球的光近乎完全是漫反射。粉筆或者磨砂紙也是漫反射。事實上,任何表面的漫反射看起來都是類似于磨砂表面的效果。
而在完美的漫反射的情況下,所觀察到的反射光的強度取決于在表面法線矢量和入射光的光線之間的角度的余弦。如圖所示,經(jīng)過歸一化的物體表面法線向量N與物體表面正交,光源照射到物體表面的光線方向為L。
也就是說,我們可以使用表面的法線矢量N和入射光方向矢量L,計算漫反射。
想要計算出眼睛觀察到的漫反射光Idiffuse,需要計算歸一化的表面法向量N和歸一化的方向光源向量L之間夾角的余弦值,即點積運算N·L。因為任何兩個向量a和b的點積運算可以表示為:
而對于已經(jīng)經(jīng)過歸一化的向量,|a| 和|b|都是1。
如果點積運算N·L為負,那么光源方向就是在表面內(nèi)部照射過來的,這是錯誤的。這種情況下,我們就將反射光設為0即可。這可以通過代碼max(0, N·L)來實現(xiàn),這樣就確保了在點積結果為負時,得到的結果為0。此外,漫反射光還取決于入射光light Iincoming和材質(zhì)的漫反射系數(shù)Kdiffuse。而對于一個黑色的表面,材質(zhì)漫反射系數(shù) 為0,而對于白色的表面,材質(zhì)的漫反射強度Kdiffuse就為1.
根據(jù)如上的表述,漫反射強度的方程如下:
ps:此公式適用于任何單一的顏色分量(如紅、綠、藍),也適用于顏色分量混合顏色的入射光。
二、實現(xiàn)
核心代碼可以在頂點著色器中實現(xiàn),也可以在片段著色函數(shù)中實現(xiàn)。
需在世界空間中進行實現(xiàn),因為世界空間中Unity提供了光源方向。
關于如何獲取參數(shù),總結如下:
Kdiffuse通過屬性properties中指定后傳進來。
世界空間的光源方向由unity內(nèi)置變量_WorldSpaceLightPos0給出。
光源顏色Iincoming由unity內(nèi)置變量_LightColor0給出。
環(huán)境光顏色通過內(nèi)置變量UNITY_LIGHTMODEL_AMBIENT給出。
用Tags {"LightMode" = "ForwardBase"}確保上述內(nèi)置變量的值處于正確的狀態(tài)。
世界空間下的物體表面的法線向量,可以通過輸出參數(shù)語義NORMAL來獲得物體空間下的表面的法線向量,然后將此向量從物體空間轉到世界空間中獲得。
2.1 單色可調(diào)的漫反射光照Shader
源碼如下:
Shader "Shader/Diffuse(Lambert) Shader"
{
//------------------------------------【屬性值】------------------------------------
Properties
{
//顏色值
_Color("Main Color", Color) = (1, 1, 1, 1)
}
//------------------------------------【唯一的子著色器】------------------------------------
SubShader
{
//渲染類型設置:不透明
Tags{ "RenderType" = "Opaque" }
//設置光照模式:ForwardBase
Tags{ "LightingMode" = "ForwardBase" }
//細節(jié)層次設為:200
LOD 200
//--------------------------------唯一的通道-------------------------------
Pass
{
//===========開啟CG著色器語言編寫模塊===========
CGPROGRAM
//編譯指令:告知編譯器頂點和片段著色函數(shù)的名稱
#pragma vertex vert
#pragma fragment frag
//包含頭文件
#include "UnityCG.cginc"
//頂點著色器輸入結構
struct appdata
{
float4 vertex : POSITION;//頂點位置
float3 normal : NORMAL;//法線向量坐標
};
//頂點著色器輸出結構
struct v2f
{
float4 position : SV_POSITION;//像素位置
float3 normal : NORMAL;//法線向量坐標
};
//變量聲明
float4 _LightColor0;
float4 _Color;
//--------------------------------【頂點著色函數(shù)】-----------------------------
// 輸入:頂點輸入結構體
// 輸出:頂點輸出結構體
//---------------------------------------------------------------------------------
v2f vert(appdata input)
{
//【1】聲明一個輸出結構對象
v2f output;
//【2】填充此輸出結構
//輸出的頂點位置為模型視圖投影矩陣乘以頂點位置,也就是將三維空間中的坐標投影到了二維窗口
output.position = mul(UNITY_MATRIX_MVP, input.vertex);
//獲取頂點在世界空間中的法線向量坐標
output.normal = mul(float4(input.normal, 0.0), _World2Object).xyz;
//【3】返回此輸出結構對象
return output;
}
//--------------------------------【片段著色函數(shù)】-----------------------------
// 輸入:頂點輸出結構體
// 輸出:float4型的像素顏色值
//---------------------------------------------------------------------------------
fixed4 frag(v2f input) : COLOR
{
//【1】先準備好需要的參數(shù)
//獲取法線的方向
float3 normalDirection = normalize(input.normal);
//獲取入射光線的值與方向
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
//【2】計算出漫反射顏色值 Diffuse=LightColor * MainColor * max(0,dot(N,L))
float3 diffuse = _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));
//【3】合并漫反射顏色值與環(huán)境光顏色值
float4 DiffuseAmbient = float4(diffuse, 1.0) + UNITY_LIGHTMODEL_AMBIENT;
//【4】將漫反射-環(huán)境光顏色值乘上紋理顏色,并返回
return DiffuseAmbient;
}
//===========結束CG著色器語言編寫模塊===========
ENDCG
}
}
}
2.2 可調(diào)顏色和自定義紋理的漫反射光照Shader
在Properties屬性中加上一項紋理,然后在最終的漫反射顏色計算完成之后,乘上紋理即可。
源碼如下:
Shader "Shader/Diffuse(Lambert) Shader with Texture"
{
//------------------------------------【屬性值】------------------------------------
Properties
{
//主紋理
_MainTex("Texture", 2D) = "white"{}
//主顏色值
_Color("Main Color", Color) = (1, 1, 1, 1)
}
//------------------------------------【唯一的子著色器】------------------------------------
SubShader
{
//渲染類型設置:不透明
Tags{ "RenderType" = "Opaque" }
//設置光照模式:ForwardBase
Tags{ "LightingMode" = "ForwardBase" }
//細節(jié)層次設為:200
LOD 200
//--------------------------------唯一的通道-------------------------------
Pass
{
//===========開啟CG著色器語言編寫模塊===========
CGPROGRAM
//編譯指令:告知編譯器頂點和片段著色函數(shù)的名稱
#pragma vertex vert
#pragma fragment frag
//包含頭文件
#include "UnityCG.cginc"
//頂點著色器輸入結構
struct appdata
{
float4 vertex : POSITION;//頂點位置
float3 normal : NORMAL;//法線向量坐標
float2 texcoord : TEXCOORD0;//紋理坐標
};
//頂點著色器輸出結構
struct v2f
{
float4 positon : SV_POSITION;//像素位置
float3 normal : NORMAL;//法線向量坐標
float2 texcoord : TEXCOORD0;//紋理坐標
};
//變量聲明
float4 _LightColor0;
float4 _Color;
sampler2D _MainTex;
//--------------------------------【頂點著色函數(shù)】-----------------------------
// 輸入:頂點輸入結構體
// 輸出:頂點輸出結構體
//---------------------------------------------------------------------------------
v2f vert(appdata input)
{
//【1】聲明一個輸出結構對象
v2f output;
//【2】填充此輸出結構
//輸出的頂點位置為模型視圖投影矩陣乘以頂點位置,也就是將三維空間中的坐標投影到了二維窗口
output.positon = mul(UNITY_MATRIX_MVP, input.vertex);
//獲取頂點在世界空間中的法線向量坐標
output.normal = mul(float4(input.normal, 0.0), _World2Object).xyz;
//輸出的紋理坐標也就是輸入的紋理坐標
output.texcoord = input.texcoord;
//【3】返回此輸出結構對象
return output;
}
//--------------------------------【片段著色函數(shù)】-----------------------------
// 輸入:頂點輸出結構體
// 輸出:float4型的像素顏色值
//---------------------------------------------------------------------------------
fixed4 frag(v2f input) : COLOR
{
//【1】先準備好需要的參數(shù)
//獲取紋理顏色
float4 texColor = tex2D(_MainTex, input.texcoord);
//獲取法線的方向
float3 normalDirection = normalize(input.normal);
//獲取入射光線的值與方向
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
//【2】計算出漫反射顏色值 Diffuse=LightColor * MainColor * max(0,dot(N,L))
float3 diffuse = _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));
//【3】合并漫反射顏色值與環(huán)境光顏色值
float4 DiffuseAmbient = float4(diffuse, 1.0) + UNITY_LIGHTMODEL_AMBIENT;
//【4】將漫反射-環(huán)境光顏色值乘上紋理顏色,并返回
return DiffuseAmbient* texColor;
}
//===========結束CG著色器語言編寫模塊===========
ENDCG
}
}
}