UnityShader系列之2:入門篇

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

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

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

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