【淺墨Unity3D Shader程式設計】之五 聖誕夜篇: Unity中Shader的三種形態對比&混合操作合輯
阿新 • • 發佈:2018-12-31
本系列文章由出品,轉載請註明出處。
雪花飄落:
可愛的聖誕雪人:
精心裝扮的聖誕樹:
月是故鄉明:
霧氣瀰漫:
OK,圖先就上這麼多。文章末尾有更多的執行截圖,並提供了源工程的下載。可執行的exe下載在這裡:
【可執行的exe遊戲場景請點選這裡下載試玩】
好的,我們正式開始。
一、再談Unity中Shader的三種形態
因為Unity中基礎的固定功能Shader的知識點基本上講完,下期開始就要準備講表面著色器(Surface Shader)了,所以在文章開頭,讓我們複習和更深入瞭解一下Unity中Shader的三種形態。
在Unity中,Shader便可以分成如下三種基本型別:
1.固定功能著色器(FixedFunction Shader)
2.表面著色器(SurfaceShader)
3.頂點著色器&片段著色器(Vertex Shader & Fragment Shader)
顧名思義,其中的固定功能著色器便是我們所說的固定功能渲染管線(fixed-functionrenderingpipelines)的具體表現,而表面著色器、頂點著色器以及片段著色器便屬於可程式設計渲染管線。下面分別對其進行簡單的介紹。
1.1Unity中的Shader形態之一:固定功能Shader
這裡的固定功能著色器可以說是Unity為Shader的書寫自帶的一層殼,Unity已經在內部為我們做了大量的工作,我們只要稍微記住一些關鍵字、一些規範就可以實現出很多不錯的效果。固定功能著色器是我們初學Unity Shader的最近幾篇文章中的主要學習物件。而後面的表面著色器、頂點著色器以及片段著色器就是在固定功能著色器的基礎上嵌套了CG語言的程式碼而成的更加複雜的著色器。我們來看看他們的一些基本概念。
固定管線是為了相容老式顯示卡。都為頂點光照,就是我們前四篇文章加上這篇文章中講到的內容。
其特徵是裡面的核心是下面Material材質屬性塊、沒有CGPROGRAM和ENDCG塊,以及各種頂點著色和片段著色的巨集命令。
一個光照材質完備版的固定功能Shader示例如下:
[cpp]view plaincopyprint?
Shader "淺墨Shader程式設計/Volume5/固定功能的Shader示例"
{
//-------------------------------【屬性】-----------------------------------------
Properties
{
_Color ("主顏色", Color) = (1,1,1,0)
_SpecColor ("高光顏色", Color) = (1,1,1,1)
_Emission ("自發光顏色", Color) = (0,0,0,0)
_Shininess ("光澤度", Range (0.01, 1)) = 0.7
_MainTex ("基本紋理", 2D) = "white" {}
}
//--------------------------------【子著色器】--------------------------------
SubShader
{
//----------------通道---------------
Pass
{
//-----------材質------------
Material
{
//可調節的漫反射光和環境光反射顏色
Diffuse [_Color]
Ambient [_Color]
//光澤度
Shininess [_Shininess]
//高光顏色
Specular [_SpecColor]
//自發光顏色
Emission [_Emission]
}
//開啟光照
Lighting On
//開啟獨立鏡面反射
SeparateSpecular On
//設定紋理並進行紋理混合
SetTexture [_MainTex]
{
Combine texture * primary DOUBLE, texture * primary
}
}
}
} 我們將此Shader編譯後賦給材質,得到如下效果:
實際場景中的測試效果:
1.2 Unity中的Shader形態之二:表面著色器SurfaceShader
這部分算是Unity微創新自創的一套著色器標準。
表面著色器(Surface Shader)這個概念更多的只是在Unity中聽說,可以說是Unity自己發揚光大的一項使Shader的書寫門檻降低和更易用的技術。我們會在接下來的學習中逐漸意識到Unity是如何為我們把Shader的複雜性包裝起來,使其書寫的過程更便捷和易用
的。一些特性如下:
• SurfaceShader可以認為是一個光照Shader的語法塊、一個光照VS/FS的生成器。減少了開發者寫重複程式碼的需要。
• 特徵是在SubShader裡出現CGPROGRAM和ENDCG塊。(而不是出現在Pass裡。因為SurfaceShader自己會編譯成多個Pass。)
• 編譯指令是:
#pragma surface surfaceFunction lightModel[optionalparams]
o surfaceFunction:surfaceShader函式,形如void surf (Input IN, inoutSurfaceOutput o)
o lightModel:使用的光照模式。包括Lambert(漫反射)和BlinnPhong(鏡面反射)。
也可以自己定義光照函式。比如編譯指令為#pragma surface surf MyCalc
在Shader裡定義half4 LightingMyCalc (SurfaceOutputs, 引數略)函式進行處理(函式名在簽名加上了“Lighting”)。
• 我們自己定義輸入資料結構(比如上面的Input)、編寫自己的Surface函式處理輸入、最終輸出修改過後的SurfaceOutput。而SurfaceOutput的定義為:
[cpp]view plaincopyprint?
struct SurfaceOutput
{
half3 Albedo; // 紋理顏色值(r, g, b)
half3 Normal; // 法向量(x, y, z)
half3 Emission; // 自發光顏色值(r, g, b)
half Specular; // 鏡面反射度
half Gloss; // 光澤度
half Alpha; // Alpha不透明度
};
上面是一些特性總結,讓我們看一個具體Shader示例:
[cpp]view plaincopyprint?
Shader "淺墨Shader程式設計/Volume5/表面Shader示例 "
{
//-------------------------------【屬性】-----------------------------------------
Properties
{
_MainTex ("【紋理】Texture", 2D) = "white" {}
_BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}
_RimColor ("【邊緣顏色】Rim Color", Color) = (0.17,0.36,0.81,0.0)
_RimPower ("【邊緣顏色強度】Rim Power", Range(0.6,9.0)) = 1.0
}
//----------------------------【開始一個子著色器】---------------------------
SubShader
{
//渲染型別為Opaque,不透明
Tags { "RenderType" = "Opaque" }
//-------------------開始CG著色器程式語言段-----------------
CGPROGRAM
//使用蘭伯特光照模式
#pragma surface surf Lambert
//輸入結構
struct Input
{
float2 uv_MainTex;//紋理貼圖
float2 uv_BumpMap;//法線貼圖
float3 viewDir;//觀察方向
};
//變數宣告
sampler2D _MainTex;//主紋理
sampler2D _BumpMap;//凹凸紋理
float4 _RimColor;//邊緣顏色
float _RimPower;//邊緣顏色強度
//表面著色函式的編寫
void surf (Input IN, inout SurfaceOutput o)
{
//表面反射顏色為紋理顏色
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
//表面法線為凹凸紋理的顏色
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
//邊緣顏色
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
//邊緣顏色強度
o.Emission = _RimColor.rgb * pow (rim, _RimPower);
}
//-------------------結束CG著色器程式語言段------------------
ENDCG
}
//“備胎”為普通漫反射
Fallback "Diffuse"
}
我們將此Shader編譯後賦給材質,得到如下效果:
調各種顏色玩一玩:
而實際場景中的測試效果(對應於一開始的金色):
1.3 Unity中的Shader形態之三:可程式設計Shader
可程式設計Shader其實就是頂點著色器和片段著色器,這一部分和DirectX系的HLSL和CG著色器語言聯絡緊密。其實就是Unity給HLSL和CG報了一個ShaderLab的殼。
研究過Direct3D和OpenGL著色器程式設計的童鞋們一定對頂點著色器和片段著色器不陌生。我們來簡單介紹一下他們的用途。
頂點著色器:產生紋理座標,顏色,點大小,霧座標,然後把它們傳遞給裁剪階段。
片段著色器:進行紋理查詢,決定什麼時候執行紋理查詢,是否進行紋理查詢,及把什麼作為紋理座標。
特徵是在Pass裡出現CGPROGRAM和ENDCG塊
編譯指令#pragma。詳見官網Cg snippets。其中重要的包括:
關於引用庫。通過形如#include "UnityCG.cginc"引入指定的庫。常用的就是UnityCG.cginc了。其他庫詳見官網Built-in
shader include files。
ShaderLab內建值。Unity給Shader程式提供了便捷的、常用的值,比如下面例子中的UNITY_MATRIX_MVP就代表了這個時刻的MVP矩陣。詳見官網ShaderLab built-in values。
Shader輸入輸出引數語義(Semantics)。在管線流程中每個階段之間(比如Vertex Shader階段和FragmentShader階段之間)的輸入輸出引數,通過語義字串,來指定引數的含義。常用的語義包括:COLOR、SV_Position、TEXCOORD[n]。完整的引數語義可見HLSL Semantic(由於是HLSL的連線,所以可能不完全在Unity裡可以使用)。
特別地,因為Vertex Shader的的輸入往往是管線的最開始,Unity為此內建了常用的資料結構:
QQ交流群:330595914
本文算是固定功能Shader的最後一篇,下一次更新應該就會開始講解表面Shader,而講解完表面Shader,後續文章最終會講解到頂點著色器和片段著色器(也就是可程式設計Shader)。
文章第一部分複習和進一步瞭解了Unity中Shader的三種形態,然後講解了固定功能Shader中混合操作的方方面面,然後以6個Shader的書寫作為實戰內容,最後建立了一個溫馨美好的聖誕夜場景進行了Shader的測試。
依舊是國際慣例,先上本文配套程式的截圖吧。
聖誕節就快到了,而下次更新就已經過了聖誕節,於是這次更新淺墨就提前把這個場景放出來吧,預祝大家聖誕節快樂~
雪花飄落:
可愛的聖誕雪人:
精心裝扮的聖誕樹:
月是故鄉明:
霧氣瀰漫:
OK,圖先就上這麼多。文章末尾有更多的執行截圖,並提供了源工程的下載。可執行的exe下載在這裡:
【可執行的exe遊戲場景請點選這裡下載試玩】
好的,我們正式開始。
一、再談Unity中Shader的三種形態
因為Unity中基礎的固定功能Shader的知識點基本上講完,下期開始就要準備講表面著色器(Surface Shader)了,所以在文章開頭,讓我們複習和更深入瞭解一下Unity中Shader的三種形態。
在Unity中,Shader便可以分成如下三種基本型別:
1.固定功能著色器(FixedFunction Shader)
2.表面著色器(SurfaceShader)
3.頂點著色器&片段著色器(Vertex Shader & Fragment Shader)
顧名思義,其中的固定功能著色器便是我們所說的固定功能渲染管線(fixed-functionrenderingpipelines)的具體表現,而表面著色器、頂點著色器以及片段著色器便屬於可程式設計渲染管線。下面分別對其進行簡單的介紹。
1.1Unity中的Shader形態之一:固定功能Shader
這裡的固定功能著色器可以說是Unity為Shader的書寫自帶的一層殼,Unity已經在內部為我們做了大量的工作,我們只要稍微記住一些關鍵字、一些規範就可以實現出很多不錯的效果。固定功能著色器是我們初學Unity Shader的最近幾篇文章中的主要學習物件。而後面的表面著色器、頂點著色器以及片段著色器就是在固定功能著色器的基礎上嵌套了CG語言的程式碼而成的更加複雜的著色器。我們來看看他們的一些基本概念。
固定管線是為了相容老式顯示卡。都為頂點光照,就是我們前四篇文章加上這篇文章中講到的內容。
其特徵是裡面的核心是下面Material材質屬性塊、沒有CGPROGRAM和ENDCG塊,以及各種頂點著色和片段著色的巨集命令。
一個光照材質完備版的固定功能Shader示例如下:
[cpp]view plaincopyprint?
Shader "淺墨Shader程式設計/Volume5/固定功能的Shader示例"
{
//-------------------------------【屬性】-----------------------------------------
Properties
{
_Color ("主顏色", Color) = (1,1,1,0)
_SpecColor ("高光顏色", Color) = (1,1,1,1)
_Emission ("自發光顏色", Color) = (0,0,0,0)
_Shininess ("光澤度", Range (0.01, 1)) = 0.7
_MainTex ("基本紋理", 2D) = "white" {}
}
//--------------------------------【子著色器】--------------------------------
SubShader
{
//----------------通道---------------
Pass
{
//-----------材質------------
Material
{
//可調節的漫反射光和環境光反射顏色
Diffuse [_Color]
Ambient [_Color]
//光澤度
Shininess [_Shininess]
//高光顏色
Specular [_SpecColor]
//自發光顏色
Emission [_Emission]
}
//開啟光照
Lighting On
//開啟獨立鏡面反射
SeparateSpecular On
//設定紋理並進行紋理混合
SetTexture [_MainTex]
{
Combine texture * primary DOUBLE, texture * primary
}
}
}
} 我們將此Shader編譯後賦給材質,得到如下效果:
實際場景中的測試效果:
1.2 Unity中的Shader形態之二:表面著色器SurfaceShader
這部分算是Unity微創新自創的一套著色器標準。
表面著色器(Surface Shader)這個概念更多的只是在Unity中聽說,可以說是Unity自己發揚光大的一項使Shader的書寫門檻降低和更易用的技術。我們會在接下來的學習中逐漸意識到Unity是如何為我們把Shader的複雜性包裝起來,使其書寫的過程更便捷和易用
的。一些特性如下:
• SurfaceShader可以認為是一個光照Shader的語法塊、一個光照VS/FS的生成器。減少了開發者寫重複程式碼的需要。
• 特徵是在SubShader裡出現CGPROGRAM和ENDCG塊。(而不是出現在Pass裡。因為SurfaceShader自己會編譯成多個Pass。)
• 編譯指令是:
#pragma surface surfaceFunction lightModel[optionalparams]
o surfaceFunction:surfaceShader函式,形如void surf (Input IN, inoutSurfaceOutput o)
o lightModel:使用的光照模式。包括Lambert(漫反射)和BlinnPhong(鏡面反射)。
也可以自己定義光照函式。比如編譯指令為#pragma surface surf MyCalc
在Shader裡定義half4 LightingMyCalc (SurfaceOutputs, 引數略)函式進行處理(函式名在簽名加上了“Lighting”)。
• 我們自己定義輸入資料結構(比如上面的Input)、編寫自己的Surface函式處理輸入、最終輸出修改過後的SurfaceOutput。而SurfaceOutput的定義為:
[cpp]view plaincopyprint?
struct SurfaceOutput
{
half3 Albedo; // 紋理顏色值(r, g, b)
half3 Normal; // 法向量(x, y, z)
half3 Emission; // 自發光顏色值(r, g, b)
half Specular; // 鏡面反射度
half Gloss; // 光澤度
half Alpha; // Alpha不透明度
};
上面是一些特性總結,讓我們看一個具體Shader示例:
[cpp]view plaincopyprint?
Shader "淺墨Shader程式設計/Volume5/表面Shader示例 "
{
//-------------------------------【屬性】-----------------------------------------
Properties
{
_MainTex ("【紋理】Texture", 2D) = "white" {}
_BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}
_RimColor ("【邊緣顏色】Rim Color", Color) = (0.17,0.36,0.81,0.0)
_RimPower ("【邊緣顏色強度】Rim Power", Range(0.6,9.0)) = 1.0
}
//----------------------------【開始一個子著色器】---------------------------
SubShader
{
//渲染型別為Opaque,不透明
Tags { "RenderType" = "Opaque" }
//-------------------開始CG著色器程式語言段-----------------
CGPROGRAM
//使用蘭伯特光照模式
#pragma surface surf Lambert
//輸入結構
struct Input
{
float2 uv_MainTex;//紋理貼圖
float2 uv_BumpMap;//法線貼圖
float3 viewDir;//觀察方向
};
//變數宣告
sampler2D _MainTex;//主紋理
sampler2D _BumpMap;//凹凸紋理
float4 _RimColor;//邊緣顏色
float _RimPower;//邊緣顏色強度
//表面著色函式的編寫
void surf (Input IN, inout SurfaceOutput o)
{
//表面反射顏色為紋理顏色
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
//表面法線為凹凸紋理的顏色
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
//邊緣顏色
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
//邊緣顏色強度
o.Emission = _RimColor.rgb * pow (rim, _RimPower);
}
//-------------------結束CG著色器程式語言段------------------
ENDCG
}
//“備胎”為普通漫反射
Fallback "Diffuse"
}
我們將此Shader編譯後賦給材質,得到如下效果:
調各種顏色玩一玩:
而實際場景中的測試效果(對應於一開始的金色):
1.3 Unity中的Shader形態之三:可程式設計Shader
可程式設計Shader其實就是頂點著色器和片段著色器,這一部分和DirectX系的HLSL和CG著色器語言聯絡緊密。其實就是Unity給HLSL和CG報了一個ShaderLab的殼。
研究過Direct3D和OpenGL著色器程式設計的童鞋們一定對頂點著色器和片段著色器不陌生。我們來簡單介紹一下他們的用途。
頂點著色器:產生紋理座標,顏色,點大小,霧座標,然後把它們傳遞給裁剪階段。
片段著色器:進行紋理查詢,決定什麼時候執行紋理查詢,是否進行紋理查詢,及把什麼作為紋理座標。
可程式設計Shader的特點為:
功能最強大、最自由的形態。特徵是在Pass裡出現CGPROGRAM和ENDCG塊
編譯指令#pragma。詳見官網Cg snippets。其中重要的包括:
編譯指令 |
示例/含義 |
#pragma vertex name |
替換name,來指定Vertex Shader函式、Fragment Shader函式。 |
#pragma target name |
替換name(為2.0、3.0等)。設定編譯目標shader model的版本。 |
#pragma only_renderers name name ... |
#pragma only_renderers gles gles3, |
ShaderLab內建值。Unity給Shader程式提供了便捷的、常用的值,比如下面例子中的UNITY_MATRIX_MVP就代表了這個時刻的MVP矩陣。詳見官網ShaderLab built-in values。
Shader輸入輸出引數語義(Semantics)。在管線流程中每個階段之間(比如Vertex Shader階段和FragmentShader階段之間)的輸入輸出引數,通過語義字串,來指定引數的含義。常用的語義包括:COLOR、SV_Position、TEXCOORD[n]。完整的引數語義可見HLSL Semantic(由於是HLSL的連線,所以可能不完全在Unity裡可以使用)。
特別地,因為Vertex Shader的的輸入往往是管線的最開始,Unity為此內建了常用的資料結構:
資料結構 |
含義 |
appdata_base |
頂點著色器輸入位置、法線以及一個紋理座標。 |
appdata_tan |