如果你還未了解過Shader lab 建議先了解一下Shader Lab 相關內容:跳轉接口。
編寫與光照相互做的着色器是很複雜的,有不同的燈光類型,不同的陰影選項,不同的渲染路徑,着色需要以某種方式處理所有的複雜性。
Unity中的Surface Shader是一種代碼生成方法,它使編寫着色比使用頂點/片段着色器要容易的多,Surface Shader只是生成所有需要手工編寫的重複性的代碼,仍然需要使用HLSL來編寫着色器。
Surface Shader 運作原理:
定義一個“surface function”(表面處理函數),他會將我們需要的數據放入Input 結構中,然後在方法內填充輸出結構"SurfaceOutput"結構。然後Surface Shader會計算出所需的輸入、填充輸出等操作,並生成實際的頂點/片段着色,以及創建處理Foward Rending和Deferred shadering渲染路徑的渲染通道(Pass)。
例如:
//...
#pragma surface surf Lambert
struct Input
{
//...
}
void surf(Input i,inout SurfaceOutput o)
{
o.Albedo =1;
//...
}
//...
上面就用#pragma surface 定義了一個surf表面函數,並且使用Lambert光照模型,定義了Input結構體,並在surf 方法中對輸出結構體SurfaceOutput進行了填充
Surface Shader 編譯指令:
Surface Shader 需要在CGPROGRAM和ENDCG塊內編寫,並且需要注意以下內容:
1.必須放在SubShder塊內(注意不是Pass內,表面着色器本身將編譯爲多個通道)
2.使用#pragma surface ...指令來指示當前shader爲Surface Shader。
#pragma surface指令格式:
#pragma surface surfaceFunction lightModel [optionalparams]
指令類型 | 指令 | 描述 |
---|---|---|
必須參數 | surfaceFunction | 是一個CG函數,幷包含表面着色器代碼,格式:void surf(Input IN,inout SurfaceOutput o),input爲自定義的結構,應包含表面函數所需要的任何紋理座標和額外的自動變量 |
— | lightModel | 要使用的照明模型,內置的有基於物理的Standard和StandardSpecular,以及簡單的非基於物理的Lambert、BlinnPhong以及自定義光照模型 |
— | ||
— | StandardSpecular:使用SurfaceOutputStandardSpecular輸出結構,並匹配Unity中的Standard(Specular setup)着色器 | |
— | Lamber和BlinnPhong照明模型不是基於物理的,但使用它們的着色器可以更快地在低端硬件上渲染 | |
可選參數 | 透明或透明度測試 | 透明度,透明度通常可以有兩種:alpha混合(用於淡出物體)和物理上更合理地"預乘混合"(premultiplied blending,運行半透明表面保留適當地鏡面反射)。 |
— | alpha或alpha:auto | 將爲簡單地照明方法選擇淡入透明(與alpha:fade相同),爲基於物理的照明方法選擇欲乘透明度(與alpha:premul相同)。 |
— | alpha:blend | 啓用alpha混合。 |
— | alpha:fade | 實現傳統地淡入淡出 |
— | alpha:premul | 啓用預乘alpha透明度 |
— | alphatest | 透明度測試,啓用alpha剪切透明度。截止使用variableName的浮點數變量,還可以使用addshadow指令來生成正確的陰影。 |
— | keepalpha | 默認情況下,不透明表面着色器將1.0寫入alpha通道,無論無論輸出結構的alpha值是什麼,或者照明功能返回什麼。使用此選項可以保持照明功能的alpha值,即使對於不透明的表面着色器也是如此。 |
— | decal:add | 添加貼花着色器(例如地形addpass)。適用於位於其他表面頂部的物體,並使用添加進行混合。 |
— | decal:blend | 半透明貼花着色器,適用於位於其他表面頂部的對象,並使用alpha混合。 |
— | 自定義修改器函數 | 可用於更改或計算傳入的頂點數據,或更改最終計算的片段顏色 |
— | vertex:VertexFunction | 自定義頂點修改功能,在生成的頂點着色器的開始處調用此函數,並且可以修改或計算每頂點數據 |
— | finalcolor:ColorFunction | 自定義最終顏色修改功能 |
— | finalgbuffer:ColorFunction | 用於更改gbuffer內容的自定以延遲路徑 |
— | finalprepass:ColorFunction | 自定以預通道基本路徑 |
— | 陰影和曲面細分 | 可以提供其他指令來控制和曲面細分的處理方式 |
— | addshadow | 生成陰影投射渲染通道(Pass),通常用於自定義頂點修改,以便陰影投射也可以獲得任何程序頂點動畫。通常着色器不需要任何特殊的陰影處理,因爲它們可以使用指定fallback來投射陰影 |
— | fullforwardshadow | 支持Forward Rendering路徑中的所有光影類型。默認情況下,着色器僅支持Forward Rendering中一個平行光的陰影(節省內部着色器變量計數)。如果在Forward Rendering中需要點光源或聚光燈陰影,可以使用此指令 |
— | tessellate:TessFunction | 使用DX11 GPU細分。該函數計算曲面細分因子。 |
— | 代碼生成選項 | 默認情況下,生成的表面着色器代碼會嘗試處理所有可能的光照/陰影/光照貼圖,但是在某些情況下,是不需要其中的某些,並且可以調整生成的代碼以跳過它們,以便生成更小的着色器提升加載速度。 |
— | exclude_path:deferred,exclude_path:forward,exclude_path:prepass | 不產生對於給定的渲染路徑渲染通道(Pass)(延遲着色,正向渲染和傳統延遲照明) |
— | noshadow | 禁用此着色器中的所有陰影接收支持。 |
— | noambient | 請勿使用任何環境照明或光探頭。 |
— | novertexlights | 請勿在向前渲染中應用任何光探測器或每頂點光源。 |
— | nolightmap | 禁用此着色器中的所有光照貼圖支持。 |
— | nodynlightmap | 在此着色器中禁用運行時動態全局照明支持。 |
— | nodirlightmap | 禁用此着色器中的方向光照貼圖支持。 |
— | nofog | 禁用所有內置的霧支持。 |
— | nometa | 不生成"meta“通道(由光照貼圖和動態全局照明用於提取表面信息)。 |
— | moforwardadd | 禁用Forward add 渲染通道,這使得着色支持一個全方向光,所有其他的光源按照頂點/SH的方式計算,可以使着色器更小 |
— | 其他 | |
— | softvegetation | 僅在啓用”Soft Vegetation“時才渲染表面着色器 |
— | interpolateview | 在頂點着色器中計算視圖方向並進行插值,而不是在像素着色器中計算。這樣可以使像素着色器更快,但需要消耗一個紋理插值器 |
— | halfasview | 將半向方向矢量傳遞到光照函數而不是試圖方向,每個頂點將計算並歸一化半方向。這樣會更快,但不完全正確 |
— | dualforward | 在forward rendering中使用雙光照貼圖 |
InputStruct(輸入結構):
輸入結構通常具有着色器所需要的任何紋理座標。紋理座標必須命名爲"uv",後跟紋理名稱(或”uv2“開頭,使用第二個紋理座標)(例如:uv_MainTex 或 uv2_MainTex);
其他可以放入輸入結構的值有:
指令 | 描述 |
---|---|
float3 viewDir | 包含視角方向,可用於計算邊緣光照等效果 |
float4 變量名:Color | 包含差值後逐頂點顏色 |
float4 screenPos | 包含了屏幕空間的座標,可用於反射或屏幕特效。注意,這不適合GrabPass;需要使用ComputeGrabScreenPos函數自己計算定義UV。 |
float3 worldPos | 包含世界空間位置。 |
float3 worldRefl | 如果沒有修改o.Normal,則包含世界空間下的反射向量。 |
float3 worldNormal | 如果修改o.Normal,則包含世界空間下的法線向量。 |
float3 worldRefl;INTERNAL_DATA | 如果修改了o.Normal,需要使用該變量告訴Unity要基於修改後的法線計算世界空間下的反射向量。可以使用WorldReflectionVector(IN,o.Normal)來得到世界空間下的反射方向。 |
float3 worldNormal;INTERNAL_DATA | 如果修改了o.normal,需要使用該變量告訴Unity要基於修改後的法線計算世界空間下的法線方向。可以使用WorldNormalVector(IN,o.Normal)來得到世界空間下的法線方向 |
SurfaceOutput結構:
SurfaceOutput基本上描述了平面的屬性(如albedo、normal、emission、specularity等)
標準輸出結構如下:
struct SurfaceOutput
{
fixed3 Albedo; //漫反射
fixed3 Normal; //正切空間法線
fixed3 Emission; //自發光
half Specular; //高光率 0-1
fixed Gloss; // 高光強度
fixed Alpha; // 透明度
};
在Unity中還可以使用基於物理的照明模型,內置Standard和StandardSpecular模型分別使用下面這些輸出結構:
struct SurfaceOutputStandard
{
fixed3 Albedo; //漫反射顏色
fixed3 Normal; // 切線空間法線
half3 Emission; //自發光顏色
half Metallic; // 金屬 0 - 1
half Smoothness; // 平滑度0-1
half Occlusion; // occlusion (default 1)
fixed Alpha; // 透明度
};
struct SurfaceOutputStandardSpecular
{
fixed3 Albedo; // 漫反射顏色
fixed3 Specular; // 高光顏色
fixed3 Normal; // 切線空間法線
half3 Emission; //自發光顏色
half Smoothness; //平滑度0-1
half Occlusion; // occlusion (default 1)
fixed Alpha; // 透明度
};
實例:
1.簡單的着色器:
Shader "Example/DiffuseSimple"
{
SubShader
{
Tags{"RenderType" = "Opaque"}
CGPROGRAM
#pragma surface surf Lambert
struct Input
{
fixed4 color : COLOR;
};
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = 1;
}
ENDCG
}
FallBack "Diffuse"
}
//在上述着色器中,聲明找色器爲表面找色器(#pragma surface) ,定義了表面函數(surf)以及指定基本光照爲Lambert。
//聲明輸入結構(Input) 包含頂點顏色。
//在表面方法(surf)中,設置輸出函數的漫反射顏色爲1(o.Albedo = 1;)
//如果此着色器在硬件上沒法使用的話會使用Diffuse着色器進行替換
2.Texture(紋理):
Shader "Example/Texture"
{
Properties
{
_MainTex("MainTex",2D) = ""{}
}
SubShader
{
Tags{"RenderType" = "Opaque"}
CGPROGRAM
#pragma surface surf Lambert
struct Input //獲取_MainTex的第一套紋理座標
{
float2 uv_MainTex;
};
//注意 在Properties中聲明的屬性,需要在CGPROGRAM塊內聲明一個同樣名稱且類型匹配的變量來進行關聯
sampler2D _MainTex;
void surf(Input IN, inout SurfaceOutput o)
{
//根據Input中紋理座標匹配_MainTex的紋理設置漫反射顏色
o.Albedo = tex2D(_MainTex, IN.uv_MainTex);
}
ENDCG
}
FallBack "Diffuse"
}
3.Normal(法線):
Shader "Example/DiffuseBump"
{
Properties
{
_MainTex("MainTex",2D) = ""{}
// 定義法線貼圖
_BumpTex("BumpTex",2D) = ""{}
}
SubShader
{
Tags{"RenderType"="Opaque"}
CGPROGRAM
#pragma surface surf Lambert
struct Input
{
float2 uv_MainTex;
//法線貼圖紋理座標
float2 uv_BumpTex;
};
sampler2D _MainTex;
sampler2D _BumpTex;
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex);
//設置法線
o.Normal = UnpackNormal(tex2D(_BumpTex, IN.uv_BumpTex));
}
ENDCG
}
FallBack "Diffuse"
}
4.Rim Light(邊緣照明):
Shader "Example/RimLight"
{
Properties
{
_MainTex("MainTex",2D) = ""{}
_BumpTex("BumpTex",2D) = ""{}
_RimColor("Rim Color",Color) = (1,1,1,1)
_RimPower("Rim Power",Range(0,8)) = 0
}
SubShader
{
Tags{"RenderType" = "Opaque"}
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
sampler2D _BumpTex;
float4 _RimColor;
float _RimPower;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpTex;
float3 viewDir;//視角方向
};
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex);
o.Normal = UnpackNormal(tex2D(_BumpTex, IN.uv_BumpTex));
half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
o.Emission = _RimColor.rgb*pow(rim, _RimPower);
}
ENDCG
}
}