寫與燈光交互的Shader是很復雜的,因為有不同的燈光類型(light Type),不同的陰影選項(Shadow Options),不同的渲染路徑(renderingPath:forward,deferred rendering),所以Shader應該以某種方式來處理所有的復雜情況。
Unity中的==Surface Shaders==是一種比使用底層的頂點/像素著色器程序(vertex/pixel shader programs)寫起來更為簡單通用的方式。 surfaceShaders 也是用Cg/hlsl來寫。
How It Works
首先你需要定義一個"Surface"函數(shù)來獲取任何uv信息或者你需要的數(shù)據作為輸入,最后會填充在輸出結構體"SurfaceOutput"里,SurfaceOutput是對表面屬性的基本描述(例如Albedo,Normal,Emission,Specular等等),你可以將這些代碼寫在cg/hlsl里。
然后Surface Shaders計算編譯需要哪些輸入數(shù)據,以及哪些輸出被填充等等,然后生成原生的vertex/pixed shaders,同時渲染通道會去處理forward和deferred渲染。
標準的表面著色器輸出結構:
struct SurfaceOutput
{
fixed3 Albedo; // diffuse color /反射顏色
fixed3 Normal; // tangent space normal, if written(法向量)
fixed3 Emission;//自發(fā)光顏色
half Specular; // specular power in 0..1 range//鏡面反射度
fixed Gloss; // specular intensity(光澤度)
fixed Alpha; // alpha for transparencies(透明度)
};
在Unity5中,SurfaceShader也可以使用光照模型的基本物理屬性。內置的"Standard"和"StandardSpecular"光照模型分別使用下面的輸出結構體:
(Standard和StandardSpecular光照模型對應普通沒有物理的的Lambert何BlinnPhong光照模型)
struct SurfaceOutputStandard
{
fixed3 Albedo; // base (diffuse or specular) color
fixed3 Normal; // tangent space normal, if written
half3 Emission;
half Metallic; // 0=non-metal, 1=metal,金屬度
half Smoothness; // 0=rough, 1=smooth,光滑度
half Occlusion; // occlusion (default 1),遮擋率
fixed Alpha; // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{
fixed3 Albedo; // diffuse color
fixed3 Specular; // specular color
fixed3 Normal; // tangent space normal, if written
half3 Emission;
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
};
對比SurfaceOutput和SurfaceOutputStandard,會發(fā)現(xiàn)前者的結構中Specular和Gloss被替換成在后者的結構中的Metallic,Smoothness,Occlusion三個屬性。這三個屬性就是Unity5里新加的物理屬性。同樣比較SurfaceOutputStandard和SurfaceOutputStanaedSpecular結構體,發(fā)現(xiàn)Metallic屬性被Specular屬性替換掉。
所以Unity5.x基于光照模型添加的基本物理屬性應該是在Specular鏡面發(fā)射上做了一些處理,添加了遮擋和粗糙度的屬性以及混合Specular而形成的Metallic屬性,使物體表面具有更加接近真實的物理光澤效果,從而減輕開發(fā)者自己書寫相似效果的工作量。
##Surface Shader compile directives****(表面著色器編譯指令)
表面著色器和其它Shader一樣被放在CGPROGRAM……ENDCG塊中執(zhí)行,不同的是:
- 代碼必須寫在子著色器塊(SubShader)而不是通道(Pass)中,表面著色器會自己把它編譯到多個通道里。
- 代碼必須使用#pragma surface ... 指令去指示它的表面著色器。
“#Pragma surface” 指令是:
#pragma surface surfaceFunction lightModel [optionalparams]
必要參數(shù)
- surfaceFunction - 含有表面著色器代碼的cg函數(shù)。這個函數(shù)必須是
void surf(Input IN,inout SurfaceOutput o)
的形式,Input是一個自己定義的結構體,Input結構體里應該包含一些紋理坐標和其它的surface函數(shù)需要的其它的可以被自動識別的變量(autonmatic variables)。 - lightmodel - 使用的光照模型。內置一些基于物理的
Standard
和StandardSpecular
光照模型,以及一些沒有基于物理的Lambert(Diffuse)和BlinnPhong(specular)光照模型,當然也可以自己寫光照模型(參見:自定義光照模型) - Standard光照模型使用SurfaceOutputStandard作為輸出結構體,對應Unity中的Standard(Metallic workflow)Shdaer。
- StandardSpecular光照模型使用SurfaceOutputStandardSpecular作為輸出結構體,對應Unity中的Standard(Specular workflow)Shader
- Lambert和BlinnPhong光照模型是不基于物理的(大部分在Unity4.x中),但是在低端設備上使用會更加快,性能更好。
****可選參數(shù)****
Transparency和Alpha testing是由alpha和alphatest指令控制。不透明度主要分為兩種:傳統(tǒng)的alpha混合(一般用于淡出物體)或更加看似物理的“premultiplied blending”(允許半透明表面保留適當?shù)溺R面反射效果)。使半透明能夠使用生成的表面著色器代碼包含blending指令,而能夠讓alpha基于已有的變量剔除被生成的pixed shader丟棄的片段。
- ****alpha**** 或者 ****alpha:auto****: 將會為簡單的光照函數(shù)挑出淡出透明度(就像alpha:fade),并且為基于物理的光照函數(shù)預乘透明度(就像 alpha:premul)。
- ****alpha:fade****:使用傳統(tǒng)的淡出透明。
- ****alpha:premul****:使用預乘alpha透明度。
- ****alphatest : variableName****:使用alpha裁剪透明度。剔除值是一個浮點型變量variableName,你可能也會想使用addshadow指令去生成適當?shù)年幱敖邮芡ǖ溃╯hadow caster pass)。
- ****keepalpha****:默認不透明(Opaque)選項,不管輸出結構的Alpha或者光照函數(shù)的返回值都將表面著色器將Alpha通道值設置為1.0。使用這個選項允許保持光照函數(shù)的返回alpha值,即使使用的是不透明表面著色器。
- ****decal:add****: 額外的貼花著色器(例如,terrian AddPass),這意味著物體在其它表面之上使用additive blending。
- ****decal:blend****:半透明貼花著色器,這意味物體可以在其它物體表面使用alpha blending。
自定義函數(shù)可以被用來改變或計算來自頂點的數(shù)據,或者改變最后計算得出的片段顏色。
- ****vertex:VertexFunction****,用戶自定義的頂點修改函數(shù)。這個函數(shù)會在生成的頂點著色器開始被調用,并且可以修改或計算每個頂點的數(shù)據。
- ****finalcolor:ColorFunction****,自定義的最終顏色修改函數(shù)。
Shadows and Tessellation(陰影和鑲嵌花紋),可以控制如何處理陰影和鑲嵌花紋的額外指令。
- ****addshadow****,生成一個陰影投射通道,通常被用在自定義頂點修改函數(shù)中,因為陰影投射也獲得程序頂點動畫。通常shaders不需要任何特殊的shadows處理,因為可以使用它們FallBack里的陰影投射通道。
- ****fullforwardshadows****,支持正向渲染路徑(forward render path)的所有的燈光陰影。默認的著色器僅支持來自directional light的正向渲染(為了節(jié)省內部著色器變量數(shù)量),如果需要point light和spotlight在正向渲染中的陰影,可以直接使用這個。
- ****tessellate:TessFunction****,使用DX11 GPU Tessellation,函數(shù)計算鑲嵌紋理因子。
Code Generation(代碼生成) 選項,通過默認生成的表面著色器代碼試圖去處理各種可能的lighting/shadowing/lightmap情況,然而有些情況下你不需要他們中的一些內容,這里你就可以調整代碼生成去跳過它們。這樣的結果會使你的shader更小并且被更快的加載。
- ****exclude_path:deferred,forward,prepass****.不要為已經給的渲染路徑生成pass,
- ****noshadow****,不支持陰影接收。
- ****noambient****,不適用于環(huán)境光或者light probes。
- ****novertexlights****,不適用于正向渲染(forward rendering)的任何light probes或者逐頂點光照。
- ****nolightmap****,不支持光照貼圖。
- ****nodynlightmap****,不支持動態(tài)的全局光照。
- ****nodirlightmap**** , shader不支持定向光照貼圖。
- ****nofog****,不支持所有的內置霧效。
- ****nometa****,不生產”meta“通道,常被用于lightmapping和動態(tài)全局照明去提取表面信息。
- ****noforwardadd****,不適用正向渲染額外的通道。這個使shaders支持一個全局光,而使用其它燈光逐頂點計算。可以使shader更小更有效率。
其它
- ****softvegetation****,使表面著色器僅僅在軟植被上的時候被渲染。
- ****interpolateview****,計算在頂點著色器上視覺方向并且進行差值來代替在像素著色器中計算。可以使像素著色器更快,但是會使用多張貼圖進行差值。
- ****halfasview****,通過half-direction向量進入光照函數(shù)來代替視角方向,half-direction會被逐頂點計算并歸一化。這會更快,但并不總是正確。
- ****approxview****,Unity5中移除了,使用interpolateview代替。
- ****dualforward****,在正向渲染路徑中使用貼花光照貼圖。
表面著色器輸入結構
在輸入結構體Input
中通常會有一些shader使用需要的紋理坐標。紋理坐標必須以uv
命名開頭,后面街上紋理名稱(或者以uv2開頭,使用第二個紋理坐標)。
其它可以被放到Input結構中的值有:
- ****float3 viewDir****,將含有視角方向,用于計算視差效果,邊緣光照等。
- ****float4 ****加上****COLOR****語意綁定********,將會包含差值的逐頂點顏色。
- float4 screenPos,將包含屏幕空間位置,用于反射后屏幕空間效果。
- ****float3 worldPos****,包含世界空間位置信息。
- ****float3 worldRef1****,如果表面著色器沒有寫給o.Normal,將包含世界反射向量。
- ****float3 worldNormal****,如果表面著色器沒有寫給o.Normal,將包含世界法向量。
- ****float3 worldRef1;INTERNAL_DATA****,如果表面著色器沒有寫給o.Normal,將包含世界反射向量。使用WorldReflectionVector (IN, o.Normal)基于逐頂點法線貼圖獲取反射向量。
- ****float3 worldNomal;INTERANT_DATA****,如果表面著色器沒有寫給o.Normal,使用WorldReflectionVector (IN, o.Normal)基于逐頂點法線貼圖獲取法線向量。
現(xiàn)在有部分表面著色器編譯管線不支持DX11上特殊的HLSL語法,所以如果你使用HLSL一些特性,比如StructuredBuffers,RWTextures和其它的DX9沒有的特性,可以使用宏包裹住。