Shader學習筆記(三):Shader中的光照
這篇文章討論shader中用到的光照。
理解光照
Unity中的光照技術包括:
- 實時光照(realtime lighting)
- 烘焙後的光照貼圖(baked lightmaps)
- 預計算的實時全域性光照(precomputed realtime global illumination)
實時光照
Unity最基礎的光照方式,能夠隨光線和物體移動實時變化。但是隻能處理直射光,無法處理反射光,所以只是一種區域性光照。
烘焙後的光照貼圖
Unity可以將靜態物體的光照資訊(包括直射光和反射光)預先烘焙到一張貼圖(lightmap)上,供執行時使用,從而避免動態計算。
lightmap的原理是:預先計算整個光照路徑(只除去物體表面到攝像機的一段)。
它的優點是省去了執行時計算的代價,缺點是執行時無法改變。
另外一種烘焙方式叫光照探針(light probe)。與lightmap的不同點在於:lightmap烘焙的是射到物體表面的光;而light probe烘焙的是空間中的傳播光,可用於靜態物體到動態物體的反射。
Unity將光照資料儲存在名為Lighting Data Asset的檔案中。它是Unity 5.3新增的檔案,替代原來的Snapshot。用於儲存GI資料,以及render、lightmap、light probe等的引用。
預計算的實時全域性光照
預計算靜態物體之間所有可能的反射並編碼,在執行時再生成間接光照。執行時可以隨光源位置、方向、顏色的改變而改變。Unity 5.0支援的新功能,需要設定開啟。
優點是既能隨光源動態改變,也能降低執行時消耗;缺點是隻能針對靜態物體。
各種光照技術比較
三種技術典型的應用場景包括:
- 實時光照:移動的角色。
- 烘焙後的光照貼圖:場景中靜態的物體。
- 預計算的實時全域性光照:一天之內的陽光,隨時間變化而角度、強度各不相同。
在移動裝置上適合用baked lightmaps,在PC上適合用precomputed realtime GI。
光源型別
名字 | 英文名 | 解釋 |
---|---|---|
點光源 | point light | 從一個點向四面八方發射,類似電燈泡 |
錐形光 | spot light | 從一個點以錐形發出,類似手電筒 |
有向光 | directional light | 以固定方向平行發出,類似太陽 |
區域光 | area light | 光線限制在一個矩形區域內,固定從一側以任意角度發出 |
環境光 | ambient light | 光在場景中無處不在,不從特定物體發出 |
理解陰影
unity中的陰影分為實時陰影和陰影貼圖。
- 實時陰影:實時光產生的陰影。要使用這種陰影,需要在產生的物體上開啟投射(cast),在投影到的物體上開啟接收(receive)。
- 陰影貼圖:以貼圖方式實現的陰影。這種方式可以降低執行時消耗,但是陰影的大小、形狀都是固定的。
shader中的光照
光照是如何影響我們的視覺的呢?事實上,我們之所以能看到物體,除去那些能自發光的物體外(如燈泡),都是因為光線射到物體表面再反射到我們眼睛中。一個物體之所以呈現不同顏色,那是因為它對光的不同顏色分量的反射率不一樣。例如,紅色的物體對於紅光幾乎全部反射,而對於綠光和藍光幾乎全部吸收。所以,物體最終呈現在我們眼中的顏色,取決於反射光和自發光的疊加。
不同的光照模型對於反射光的處理大不一樣。Unity中內建了兩種光照模型:Lambert光照是漫反射,光線射過去向四面八方反射,適合於普通較粗糙的材質;BlingPhong光照是鏡面反射,適合光滑如鏡的材質。這兩種光照就可以涵蓋絕大多數情況。除此之外,我們也可以根據需求自己定製光照模型。
Unity中可以使用Surface Shader或者Vertex/Fragment Shader來處理光照。對於前者,需要自定義光照處理函式;對於後者,則需要在vert函式中做varying變數的賦值,而在frag函式中處理光照。對於Surface Shader和光照函式在渲染管線中的位置,可以看圖(來自知乎):
可以看到,surf函式和光照都位於Fragment Shader中,兩者是緊挨著的關係。
例項解析
以下均為Unity Manual中的例子。
Surface Shader中的光照
- DiffuseTexture:與Lambert光照等效的實現。自定義光照型別SimpleLambert,對應的函式在surf之後執行。計算頂點顏色涉及到的引數:片元的法向量、光入射角度、光的rgba和衰減、片元的Albedo。
Shader "Example/Diffuse Texture" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf SimpleLambert
half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) {
half NdotL = dot (s.Normal, lightDir);
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
c.a = s.Alpha;
return c;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
- SimpleSpecular:與BlingPhong光照等效的實現。計算頂點顏色涉及到的引數:視角、片元的法向量、光入射角度、光的rgba和衰減、片元的Albedo。
...ShaderLab code...
CGPROGRAM
#pragma surface surf SimpleSpecular
half4 LightingSimpleSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) {
half3 h = normalize (lightDir + viewDir);
half diff = max (0, dot (s.Normal, lightDir));
float nh = max (0, dot (s.Normal, h));
float spec = pow (nh, 48.0);
half4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * atten;
c.a = s.Alpha;
return c;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
...ShaderLab code...
Vertex/Fragment Shader中的光照
要使用光照,首先需要在pass tag中設定LightMode,從而定義各種rendering path。常見的選擇有:
關鍵字 | 意義 | 解釋 |
---|---|---|
ForwardBase | forward rendering | 使用關鍵光照資訊立即渲染 |
Deferred | deferred shading | 使用所有光照資訊延遲渲染 |
ShadowCaster | shadow caster | 投射陰影所必需,通常與ForwardBase或Deferred配合使用 |
- SimpleDiffuse:與Lambert光照等效。需要在vert和frag中做處理。
Shader "Lit/Simple Diffuse"
{
Properties
{
[NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
// indicate that our pass is the "base" pass in forward
// rendering pipeline. It gets ambient and main directional
// light data set up; light direction in _WorldSpaceLightPos0
// and color in _LightColor0
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" // for UnityObjectToWorldNormal
#include "UnityLightingCommon.cginc" // for _LightColor0
struct v2f
{
float2 uv : TEXCOORD0;
fixed4 diff : COLOR0; // diffuse lighting color
float4 vertex : SV_POSITION;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
// get vertex normal in world space
half3 worldNormal = UnityObjectToWorldNormal(v.normal);
// dot product between normal and light direction for
// standard diffuse (Lambert) lighting
half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
// factor in the light color
o.diff = nl * _LightColor0;
return o;
}
sampler2D _MainTex;
fixed4 frag (v2f i) : SV_Target
{
// sample texture
fixed4 col = tex2D(_MainTex, i.uv);
// multiply by lighting
col *= i.diff;
return col;
}
ENDCG
}
}
}
- ShadowCasting:實現陰影投射。定義了兩個Pass,LightMode分別是ForwardBase和ShadowCaster。
Shader "Lit/Shadow Casting"
{
SubShader
{
// very simple lighting pass, that only does non-textured ambient
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
fixed4 diff : COLOR0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
half3 worldNormal = UnityObjectToWorldNormal(v.normal);
// only evaluate ambient
o.diff.rgb = ShadeSH9(half4(worldNormal,1));
o.diff.a = 1;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return i.diff;
}
ENDCG
}
// shadow caster rendering pass, implemented manually
// using macros from UnityCG.cginc
Pass
{
Tags {"LightMode"="ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag(v2f i) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
}