Shader - 編寫Surface Shader

如果你還未了解過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
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章