UnityShader系列之2:入門篇

作者:朔宇
大家好。
吃飽喝足了之后才想起咱們還有這個系列需要填。

咳咳。之前的文章中,我們了解了渲染流水線及UnityShader的基本原理。這里放個上一期的傳送門:UnityShader系列之1:基礎篇
今天我們來具體了解UnityShader及ShaderLab。
ShaderLab
學習UnityShader首先要了解ShaderLab。
在Unity中,所有的UnityShader都是使用ShaderLab來編寫的,ShaderLab是Unity提供,用于編寫UnityShader的一種說明性語言,其用來描述一個UnityShader文件的結構。我們先來看看ShaderLab的寫法:
Shader "ShaerName"{//UnityShader名稱
???????? Properties{
?????????????????? //屬性
???????? }
???????? SubShader {
?????????????????? //顯卡A使用的子著色器
???????? }
???????? SubShader {
?????????????????? //顯卡B使用的子著色器
???????? }
???????? FallBack "VertexLit"
}
?
在這個結構中包含了許多渲染所需的數(shù)據,如使用“Properties”定義著色器所需的各種屬性,這些屬性會在材質面板中顯示。 Unity會根據使用的平臺,來把這些結構編譯成實際的代碼和Shader文件。 我們來詳細的分析上述偽代碼的語義含義和用途
1.第一行是定義UnityShader的名稱,定義了其名稱后我們就可以在使用此Shader的材質選擇中找到這個名稱。
2.Properties中包含了一系列的屬性,這些屬性也將會在材質面板中體現(xiàn),申明這些屬性可以更方便的在材質面板中調整材質的屬性。
3.SubShader,每一個UnityShader文件可以包含多個SubShader語義塊,但最少需要一個。當Unity需要加在此Shader時,Unity會掃描所有的SubShader,然后依次調用直到找到能夠在目標平臺上運行的SubShader。如果沒有,Unity就會使用Fallback語義制定的UnityShader。
UnityShader
接下來我們通過一個Unity默認的頂點/片元著色器代碼來進一步了解UnityShader。

在如下UnityShader中,我寫了關鍵位置的注釋,讀者可以對照閱讀從而進一步了解UnityShader
//Shader名稱
Shader "Hidden/NewImageEffectShader"
{
???????? //申明所需的屬性
???????? Properties
???????? {
??????? //屬性名為_MainTex,面板所顯示的名稱為Texture,2D只屬性的類型
??????? //"white" 屬性默認值
?????????????????? _MainTex ("Texture", 2D) = "white" {}
???????? }
???????? //一個Shader程序至少有一個SubShader,系統(tǒng)在渲染時會依次調用,直到找到匹配的SubShader,否則使用最后默認指定的Shader
???????? SubShader
???????? {
?????????????????? //Cull Off:關閉陰影剔除
?????????????????? //ZWrite :將像素的深度寫入深度緩存中??
??????? //Always:將當前深度值寫到顏色緩沖中
?????????????????? Cull Off ZWrite Off ZTest Always
?
?????????????????? //渲染通道
?????????????????? Pass
?????????????????? {
??????????????????????????? //Shader代碼段開始,著色器的代碼需要定義在CGPROGRAM-ENDCG之間
??????????????????????????? CGPROGRAM
??????????????????????????? //指定頂點著色器
??????????????????????????? #pragma vertex vert
??????????????????????????? //指定片元著色器
??????????????????????????? #pragma fragment frag
??????????????????????????? //引入Unity內置定義
??????????????????????????? #include "UnityCG.cginc"
?
??????????????????????????? //定義頂點著色器輸入結構體
??????????????????????????? struct appdata
??????????????????????????? {
???????????????????????????????????? //float4是思維向量,這里相當于告訴渲染引擎,這個屬性代表的含義
???????????????????????????????????? float4 vertex : POSITION;
???????????????????????????????????? //紋理
???????????????????????????????????? float2 uv : TEXCOORD0;
??????????????????????????? };
?
??????????????????????????? //與上邊類似,這里使用一個結構體來定義頂點著色器的輸出
??????????????????????????? struct v2f
??????????????????????????? {
???????????????????????????????????? float2 uv : TEXCOORD0;
???????????????????????????????????? float4 vertex : SV_POSITION;
??????????????????????????? };
?
??????????????????????????? //Vertex 頂點函數(shù)實現(xiàn)
??????????????????????????? v2f vert (appdata v)
??????????????????????????? {
???????????????????????????????????? v2f o;
???????????????????????????????????? //傳遞進來的頂點坐標是模型坐標系中的坐標值,需要經過矩陣轉換車成屏幕坐標
???????????????????????????????????? o.vertex = UnityObjectToClipPos(v.vertex);
???????????????????????????????????? o.uv = v.uv;
???????????????????????????????????? //將計算后的結果輸出給渲染引擎,底層會根據具體的語義去做對應的處理
???????????????????????????????????? return o;
??????????????????????????? }
???????????????????????????
??????????????????????????? sampler2D _MainTex;
?
??????????????????????????? //fragment 片元函數(shù)實現(xiàn)
??????????????????????????? fixed4 frag (v2f i) : SV_Target
??????????????????????????? {
???????????????????????????????????? fixed4 col = tex2D(_MainTex, i.uv);
???????????????????????????????????? col.rgb = 1 - col.rgb;
???????????????????????????????????? return col;
?????????????????? ???????? }
??????????????????????????? ENDCG
?????????????????? }
???????? }
}
?
我們通過一個實際的例子來看看UnityShader在實際項目中如何使用,這里我通過UnityShader來實現(xiàn)漫反射。
漫反射,是投射在粗糙表面上的光向各個 方向反射的現(xiàn)象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向著四面八方反射,所以入射線雖然互相平行 ,由于各點的法線方向不一致,造成反射光線向不同的方向無規(guī)則地反射,這種反射稱之為“漫反射”或“ 漫射 ”。這種反射的光稱為漫射光。
漫反射光照是用于對那些被物體表面隨機散射到各個方向的輻射度進行建模的。在漫反射中,視角的位置是不重要的因為漫反射是完全隨機的,因此可以認為在任何反射方向上的分布都是一樣的。但是入射光線的角度很重要。
漫反射符合蘭伯特定律(Lambert’s law):反射光線的強度與表面法線和光源方向之間夾角的余弦值成正比,因此,漫反射的計算如下:(C-light · M-diffuse)max(0,n·I)即光照顏色 漫反射顏色 max(0,法向量光照方向)
使用Lambert進行漫反射渲染,在Shader中有兩種寫法,一種是逐頂點著色,另一種是逐像素著色
使用蘭伯特定律進行漫反射渲染,在UnityShader中有兩種寫法,一種是逐頂點著色,另一種是逐像素著色。首先是逐頂點:
Shader "AlbertShader/VertexDiffuse"
{
??? Properties
??? {
??????? //漫反射顏色初始為白色
??????? _DiffuseColor("Color",Color)=(1,1,1,1)
??? }
??? SubShader
??? {
??????? Pass
??????? {
??????????? Tags{ "LightMode"="ForwardBase" }
??????????? CGPROGRAM
??????????? //聲明頂點著色器
??????????? #pragma vertex vert
??????????? //聲明片元著色器
??????????? #pragma fragment frag
??????????? //引入Unity內置光照函數(shù)庫
??????????? #include "Lighting.cginc"
??????????? //定義外部屬性-漫反射顏色
??????????? float4 _DiffuseColor;
???????? ???//定義頂點著色器輸入結構體
??????????? struct appdata
??????????? {
??????????????? //頂點坐標
??????????????? float4 vertex : POSITION;
??????????????? //頂點法線
??????????????? float3 normal : NORMAL;
??????????? };
??????????? //定義頂點著色器輸出結構體
??????????? struct v2f
??? ????????{
??????????????? //像素坐標
??????????????? float4 vertex : SV_POSITION;
??????????????? //臨時變量:顏色
??????????????? fixed3 color : COLOR;
??????????? };
??????????? //頂點函數(shù)實現(xiàn)
??????????? v2f vert (appdata v)
??????????? {
??????????????? //定義頂點輸出結構體對象
??????????????? v2f o;
??????????????? //頂點坐標轉換到屏幕像素坐標
??????????????? o.vertex = UnityObjectToClipPos(v.vertex);
??????????????? //獲取環(huán)境光
??????????????? float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
??????????????? //將頂點法線轉換到世界空間下,并做歸一化處理
??????????????? float3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
??????????????? //將光照方向轉換到世界空間下,并做歸一化處理
??????????????? float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
??????????????? //使用公式運算
??????????????? fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLight));
??????????????? //結合漫反射和環(huán)境光
??????????????? o.color = ambient + diffuse;
??????????????? //返回結果
??????????????? return o;
??????????? }
??????????? fixed4 frag (v2f i) : SV_Target
????? ??????{
??????????????? //輸入頂點顏色
??????????????? return fixed4(i.color,1.0);
??????????? }
??????????? ENDCG
??????? }
??? }
}
?
然后是逐像素:
Shader "AlbertShader/PixelDiffuse"
{
??? Properties
??? {
??????? _DiffuseColor("Color",Color)=(1,1,1,1)
??? }
??? SubShader
??? {
??????? Pass
??????? {
??????????? Tags{ "LightMode"="ForwardBase" }
??????????? CGPROGRAM
??????????? #pragma vertex vert
??????????? #pragma fragment frag
???????????
??????????? #include "Lighting.cginc"
?
??????????? float4 _DiffuseColor;
?????????? ?struct appdata
??????????? {
??????????????? float4 vertex : POSITION;
??????????????? float3 normal : NORMAL;
??????????? };
?
??????????? struct v2f
??????????? {
??????????????? float4 pos : SV_POSITION;
??????????????? fixed3 worldNormal : TEXCOORD0;
??????????? };
?
??????????? v2f vert (appdata v)
??????????? {
??????????????? v2f o;
??????????????? //把世界空間下的法線傳給片元著色器
??????????????? o.pos = UnityObjectToClipPos(v.vertex);
??????????????? o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
? ??????????????return o;
??????????? }
???????????
??????????? fixed4 frag (v2f i) : SV_Target
??????????? {
??????????????? float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
??????????????? fixed3 worldNormal = normalize(i.worldNormal);
??????????????? fixed3? worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
??????????????? fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLightDir));
??????????????? fixed3 color = diffuse + ambient;
??????????????? return fixed4(color,1.0);
??????????? }
??????????? ENDCG
??????? }
??? }
}
?
最后我們來看一下加入漫反射Shader后的效果

之后的文章,我們會給大家?guī)砀嗟腢nityShader在實際項目中的使用。
想系統(tǒng)學習游戲開發(fā)的童鞋,歡迎訪問?http://levelpp.com/? ? ? ? ? ? ? ???
游戲開發(fā)攪基QQ群:869551769? ? ? ? ? ? ? ???
微信公眾號:皮皮關