在shader中,光照是一個非常基礎並且重要的知識點,下面我們就來學習一下shader中的基礎光照。
在現實中的光照是極其複雜的,而且會受到諸多因素的影響,這是我們有限的計算能力所無法模擬的。因此我們在渲染中基本上都是使用一個簡化的模型來模擬真實的光照環境。來達到我們想要的效果,看起來差不多就行。
計算機圖形學第一定律:如果它看起來是對的,那麼它就是對的。
——————《3D數學基礎:圖形與遊戲開發》
標準光照模型
光照模型的主要結構由4個分量組成:
- 自發光(emissive): 一個表面本身發射的光。
- 環境光(ambient): 模擬其他所有的間接光照。
- 漫反射(diffuse): 用於描述當光線從光源照射到模型表面時,該表面會向每個方向散射的輻射量。
- 鏡面高光(specular): 用於描述當光線從光源照射到模型表面時,該表面會完全鏡面反射方向散射的輻射量。
1.自發光(emissive)
它的計算很簡單,就是直接使用了該材質的自發光顏色
通常在實時渲染中,自發光的表面往往並不會照亮周圍的表面,也就是說,這個物體並不會被當成一個光源。
在片元着色器中直接返回一個顏色值就ok
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
如圖:
2.環境光(ambient)
光通常都不是來自同一個光源,而是來自於我們周圍分散的很多光源,由其他物體反射而來的光,爲了模擬這種間接光照,我們使用了一個常亮:環境光。
在Unity中可以通過 window => Rendering => Lighting Setting 修改環境光顏色。
shader中可以通過內置變量 UNITY_LIGHTMODEL_AMBIENT 獲取環境光。
fixed4 frag (v2f i) : SV_Target
{
//環境光
fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT.rgba;
return _Color+ambient;
}
我們可以看到該物體會隨着環境光的改變而改變。
3.漫反射(diffuse)
漫反射(diffuse) 是光線照射在物體粗糙的表面會無序地向四周反射的現象。是投射在粗糙表面上的光向各個方向反射的現象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向着四面八方反射,所以入射線雖然互相平行,由於各點的法線方向不一致,造成反射光線向不同的方向無規則地反射,
我們可以通過下圖觀察到,反射光的強度取決於在表面法向量和入射光的光線之間的角度的餘弦值。
所以我們首先需要計算出法向量N和入射光方向L的角度的餘弦值。
我們可以通過他們的點乘來計算,公式如下:
把向量歸一化處理後,|L| 和 |N| 都是1,所有可以簡化爲:
所以得出最終計算 漫反射的公式:
- :入射光顏色
- :材質顏色
- max函數:防止點乘結果爲負數
- :入射方向
- :當前點的法向量
演示:
Unity Shader代碼如下:
PS:這裏是在片元着色器裏計算的,也可以在頂點着色器裏計算。
如果想要效果好就在片元着色器裏計算。
如果想要性能好就在頂點着色器裏計算。
Shader "lcl/baseLighting/002_Diffuse_fargment" {
//在片元着色器計算漫反射
SubShader {
Pass{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
struct a2v {
float4 vertex : POSITION;
float3 normal: NORMAL;
};
struct v2f{
float4 position:SV_POSITION;
float3 worldNormalDir:COLOR0;
};
v2f vert(a2v v){
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
f.worldNormalDir = mul(v.normal,(float3x3) unity_WorldToObject);
return f;
};
fixed4 frag(v2f f):SV_TARGET{
//環境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
//法向量
fixed3 normalDir = normalize(f.worldNormalDir);
//光照方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//漫反射計算
fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0);
fixed3 resultColor = diffuse+ambient;
return fixed4(resultColor,1);
};
ENDCG
}
}
FallBack "VertexLit"
}
以上用到的光照模型是 蘭伯特光照模型(Lambert)
其實如果我們仔細觀察會發現背光的地方非常暗,完全就看不見該球體的形狀了,這樣的話效果就不太理想了,如圖:
有時候我們需要在背光的地方也能有一點光線,此時我們就可以用 半蘭伯特光照模型 了,該技術是Valve公式在開發遊戲《半條命》時提出的,由於該技術是在原蘭伯特光照模型的基礎上修改的,所以被稱爲半蘭伯特光照模型
半蘭伯特光照模型:
其實就是把 的結果範圍從 [-1,1] 映射到 [0,1]的範圍內。這樣的話在背光面也會有明暗變化,不會完全黑掉。
我們通過下圖可以清楚的看到他們的取值區間:
效果演示:
關鍵代碼如下:
fixed4 frag(v2f f):SV_TARGET{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 normalDir = normalize(f.worldNormalDir);
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//半蘭伯特漫反射 值範圍0-1
fixed3 halfLambert = dot(normalDir,lightDir)*0.5+0.5;
fixed3 diffuse = _LightColor0.rgb * halfLambert;
fixed3 resultColor = diffuse+ambient;
return fixed4(resultColor,1);
};
結束!下節繼續探索高光反射(Specular), Phone和BlinnPnone光照模型