Unity Shader 學習之旅
unity
shader
圖形圖像
紙上學來終覺淺,絕知此事要躬行
美麗的夢和美麗的詩同樣 都是可遇而不可求的——席慕蓉 html
1、渲染流水線
示例圖
Tips:什麼是 GPU 加速計算? 編程
VIDEO
1.1Draw Call
CPU過Draw Call來g告訴GPU開始一個渲染過程。一個Draw Call會指向本次調用須要渲染的圖元列表。 通俗的講咱們能夠把CPU理解成一羣專家,他們有着超強和快速的計算能力,能解決各類各樣的問題。GPU則是許許多多個流水線上的工人,儘管它們只能作簡易單一的任務,可是成千上萬個工人一塊兒開動能夠迅速處理大量的工做。可是專家和這些工人協同工做的過程當中須要交接任務,此時交接的過程就是Draw Call調用的過程。專家告訴工人們這些東西大家須要把這些零件加工成什麼樣,交接溝通是須要代價,這也就是爲何Draw Call過多會影響幀率。瑣碎的零部件一點點的交接給工人是很浪費資源的,工人成千上萬個對於100個零部件加工和一萬個零部件加工耗費的代價是同樣的,這樣專家也忙不過來,這時專家忙的焦頭爛額,工人還在等待下一批零部件,零部件組裝成成品的過程就出現了卡頓。因此Draw Call優化的過程也就是儘量一次由專家給工人一大批須要統一操做組裝的零件(合併網格),和減小這些零件的類型(避免過多材質,儘量多的公用材質)。數組
2、最簡單的頂點片元着色器
2.1.頂點片元着色器基本結構
Unity Shader基本結構:Shader ,Properties,SubShder,Fallback等。數據結構
2.1.1結構
Shader "ShaderName"{
Properties{
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
SubShader{
}
Fallback "VertexLit"
}
2.1.2一個簡單的示例
Shader "UnityShaderBoook/SimplerShader"{
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION):SV_POSITION{
return UnityObjectToClipPos(v);
}
fixed4 frag():SV_TARGET{
return fixed4(1.0 ,1.0 ,1.0 ,1.0 );
}
ENDCG
}
}
}
當咱們把使用該shader的材質賦給場景物體時,物體僅僅表現出單純的白色。咱們在shader中沒有賦予它更豐富的內容,如法線,紋理等,因此他不會表現出任何深度,陰影等,固然也不會受到光照的影響。app
注意: 編程語言
POSITION 和SV_POSITION 都是CG/HLSL 中的語義,他們是不可省略的,這些語義用來告訴系統輸入值和輸出值的含義。POSTION 告訴Unity這裏將模型的頂點數據傳入到v參數,而SV_POSITION 則告訴Unity頂點着色器輸出的是裁剪空間 中的頂點座標。
UnityObjectToClipPos(v) 是mul(UNITY_MATRIX_MVP,v)更新後的函數,他是Unity內置函數,用來幫咱們進行頂點座標從模型空間 轉換到裁剪空間 的轉換。
SV_TARGET 是HLSL 中的系統語義,它輸出的是一個float4類型的變量,它等同於告訴渲染器,把用戶的輸出顏色存儲到一個渲染目標中,有時也使用COLOR 代替,考慮到平臺的通用性最好使用SV_TARGET 。
模型空間 :模型空間咱們能夠理解爲模型自身的局部座標,一個模型上的頂點位置信息,都是依託自身的局部座標的,但當咱們把它放到場景中的時候,就須要咱們轉換這些信息來使用。
裁剪空間 :能夠簡單的理解爲Unity中攝像機視椎體包圍的空間,不在其空間內的信息將會被剔除,保留的信息後續會被投影到屏幕上被看到。
2.2.頂點着色器與片元着色器之間通訊
片元着色器是沒法直接獲取陳模型的頂點信息的,這時就須要咱們經過使用結構體做爲媒介,有頂點着色器向片元着色器傳遞一些數據,如:模型法線,紋理座標等。ide
2.2.1示例
Shader "UnityShaderBoook/vert2frag"{
Properties {
_Color ("Color Tint", Color) = (1 , 1 , 1 , 1 )
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform fixed4 _Color;
struct a2v{
float4 vertex:POSITION;
float4 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + fixed3(0.5 , 0.5 , 0.5 );
return o;
}
fixed4 frag(v2f i):SV_TARGET{
fixed3 c = i.color;
c *= _Color.rgb;
return fixed4(c, 1.0 );
}
ENDCG
}
}
}
注意: 頂點着色器是逐個頂點調用的,片元着色器是逐片元調用的。片元着色器中的輸入實際是吧頂點着色器的輸出插值後獲得的結果。函數
2.3.Unity內置文件及變量
2.3.1 名詞解釋
ShaderLab 是unity本身封裝調用CG/HLSL/GLSL的接口。能夠理解爲C#與C,一個方便咱們使用,一個更加接近底層。 語義 是CG/HLSL這些底層提供的用於限定參數含義的字符串。而unity爲了方便的對模型的數據進行傳輸,對一些語義進行了特別規定。如:頂點着色器中用TEXCOORD0來描述texcoord,unity會自動識別出TEXCOORD0的語義,並把模型的第一套紋理座標信息填充到texcoord中。學習
2.3.2 Unity支持的經常使用語義
從應用階段傳遞模型數據到頂點着色器 優化
語義
描述
POSITION
模型空間中的頂點位置,一般是float4類型
NORMAL
頂點法線,一般是float3類型
TANGENT
頂點切線,一般是float4類型
TEXCOORDn
如TEXCOORD0、TEXCOORD1該頂點的紋理座標,TEXCOORD0表示第一組紋理座標,依此類推,一般是float2或float4類型
COLOR
頂點顏色,一般是fixed4或float4類型
注意: 其中TEXCOORDn中n的數目是和Shader Model有關的,例如通常在Shader Model 2(即Unity默認編譯到的Shader Model版本)和Shader Model 3中,n等於8,而在Shader Model 4和Shader Mode 5中,n等於16。一般狀況下,一個模型的紋理座標組數通常不超過2,即咱們每每只使用TEXCOORD0和TEXCOORD1。在Unity內置的數據結構體appdata_full中,它最多使用了6個座標紋理組。 從頂點着色器傳遞數據給片元着色器
語義
描述
SV_POSITION
裁剪空間中的頂點座標,結構體中必須包含一個用該語義修飾的變量。等同於DirectX9中的POSITION,但最好使用SV_POSITION
COLOR0
一般用於輸出第一組頂點顏色,但不是必須的
COLOR1
一般用於輸出第二組頂點顏色,但不是必須的
TEXCOORD0~TEXCOORD7
一般用於輸出紋理座標,但不是必須的
注意 上面的語義中,除了SV_POSITION是有特別含義外,其餘語義對變量的含義沒有明確要求,也就是說,咱們能夠存儲任意值到這些語義描述變量中。一般,若是咱們須要把一些自定義的數據從頂點着色器傳遞給片元着色器,通常選用TEXCOORD0等。 從片元着色器輸出時Unity支持的語義
語義
描述
SV_Target
輸出值將會存儲到渲染目標(render target)中。等同於DirectX9中的COLOR語義,但最好使用SV_Target
注意 一個語義可使用的寄存器只能處理4個浮點值(float)。所以咱們想要定義矩陣類型,如float3×4,float4×4等變量是就須要使用更多的空間。一種方法是,把這些變量拆分紅多個變量,例如對於float4×4的矩陣類型,咱們能夠拆分紅四個float類型的變量,每一個變量存儲矩陣中的一行數據。
3、Unity中三種shader類型
3.1固定管線着色器
3.1.1簡介
固定功能着色器爲固定功能渲染管線的具體表現。實現較爲簡單,可是功能單一,效果較差。Unity5.2及之後備拋棄,全部的固定管線着色器都會別Unity編譯成對應的頂點片元着色器。
3.1.2效果
固定功能
3.1.3示例代碼
Shader "ShaderCookbook/固定功能着色器"
{
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("基本紋理",2 D)="white"{}
_SecTex("副紋理",2 D)="white"{}
}
SubShader
{
Tags{"Queue"="Transparent"}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
Material
{
Diffuse[_Color]
Ambient[_Color]
Shininess[_Shininess]
Specular[_SpecColor]
Emission[_Emission]
}
Lighting On
SeparateSpecular On
SetTexture[_MainTex]
{
Combine texture * primary DOUBLE,texture * primary
}
SetTexture[_SecTex]{
Combine texture * previous double ,texture * previous
}
}
}
}
3.2表面着色器
表面着色器介紹
3.2.1簡介
Unity包裝過一層的着色器類型,須要較少的代碼量就能達到很好的效果,但因爲Unity背後會作不少工做,渲染的代價比較大。
3.2.2效果
表面着色器
3.2.1示例代碼
Shader "ShaderCookbook/簡單表面着色器"
{
Properties
{
_MainTex ("【紋理】Texture", 2 D) = "white" {}
_BumpMap ("【凹凸紋理】Bumpmap", 2 D) = "bump" {}
_RimColor ("【邊緣顏色】Rim Color", Color) = (0.17 ,0.36 ,0.81 ,0.0 )
_RimPower ("【邊緣顏色強度】Rim Power", Range(0.6 ,9.0 )) = 1.0
}
SubShader
{
Tags { "RenderType" = "Opaque"}
Blend SrcAlpha OneMinusSrcAlpha
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);
}
ENDCG
}
Fallback "Diffuse"
}
3.3頂點片元着色器
3.3.1 簡介
更爲複雜,但也更爲靈活,能夠完成不少複雜的效果,可是須要咱們控制渲染的實現細節。
3.3.2效果
頂點片元着色器
3.3.3示例代碼:
shader "ShaderCookbook/簡單頂點片元着色器"{
Properties
{
_Color ("Color", Color) = (1.0 ,1.0 ,1.0 ,1.0 )
_MainTex("MainTex",2 D)="white"{}
_SpecColor ("Specular Color", Color) = (1.0 ,1.0 ,1.0 ,1.0 )
_Shininess ("Shininess", Float) = 10
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _SpecColor;
float _Shininess;
float4 _LightColor0;
struct vertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float2 uv :TEXCOORD0;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o;
o.uv= v.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;;
float3 normalDirection = normalize ( mul( float4(v.normal, 0.0 ), unity_WorldToObject ).xyz );
float3 viewDirection = normalize ( float3( float4( _WorldSpaceCameraPos.xyz, 1.0 ) - mul(unity_ObjectToWorld, v.vertex).xyz ) );
float3 lightDirection;
float atten = 1.0 ;
lightDirection = normalize (_WorldSpaceLightPos0.xyz);
float3 diffuseReflection = atten * _LightColor0.xyz * max ( 0.0 , dot ( normalDirection, lightDirection ) );
float3 specularReflection = atten * _LightColor0.xyz * _SpecColor.rgb * max ( 0.0 , dot ( normalDirection, lightDirection ) ) * pow ( max ( 0.0 , dot ( reflect ( -lightDirection, normalDirection ), viewDirection ) ), _Shininess );
float3 lightFinal = diffuseReflection + specularReflection + UNITY_LIGHTMODEL_AMBIENT;
o.col = float4(lightFinal * _Color.rgb, 1.0 );
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag(vertexOutput i) : COLOR
{
fixed4 tex = tex2D(_MainTex,i.uv);
i.col*=tex;
return i.col;
}
ENDCG
}
}
Fallback "Diffuse"
}
3.3.4 效果
在世界座標系中頂點座標超過限定值則不顯示,相似切面效果
切面
3.3.5 示例代碼
Shader "ShaderCookbook/VertexWorldPos" {
Properties {
_Color ("Color", Color) = (1 ,1 ,1 ,1 )
_MainTex ("Albedo (RGB)", 2 D) = "white" {}
_YLimit("YLimit", float )= 0.0
}
SubShader {
Tags { "RenderType"="AlphaTest" "IgnoreProjector"="True" "Queue"="Transparent"}
Pass{
Tags{"LightMode"="ForwardBase"}
ZWrite off
Blend SrcAlpha OneMinusSrcAlpha
Cull off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _YLimit;
struct a2v{
float4 vertex:POSITION;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD;
float3 worldPos:TEXCOORD1;
};
v2f vert(a2v i){
v2f v;
v.pos=UnityObjectToClipPos(i.vertex);
v.uv.xy=i.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
v.worldPos=mul(unity_ObjectToWorld,i.vertex);
return v;
}
fixed4 frag(v2f v):SV_TARGET{
fixed4 Col=_Color*tex2D(_MainTex,v.uv.xy);
if (v.worldPos.y>_YLimit)
discard ;
return Col;
}
ENDCG
}
}
FallBack "Diffuse"
}
3.3.6 效果
這裏咱們再次使用頂點片元着色器造一個黑洞同樣的效果。物體靠近黑洞時會被黑洞吸引拉扯,逐漸縮成一點,在另外一端則會逐漸變大出現。
BlankHole
3.3.7 示例代碼
Shader "ShaderCookbook/黑洞" {
Properties {
_Color ("Color", Color) = (1 ,1 ,1 ,1 )
_MainTex ("Albedo (RGB)", 2 D) = "white" {}
_YLimit("YLimit", float )= 0.0
_Length("Length",float )=1
}
SubShader {
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _YLimit;
float _Length;
struct a2v{
float4 vertex:POSITION;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD;
float3 worldPos:TEXCOORD1;
};
v2f vert(a2v i){
v2f v;
v.worldPos=mul(unity_ObjectToWorld,i.vertex);
if (distance (v.worldPos.y,_YLimit)<_Length)
{
float s=(distance (v.worldPos.y,_YLimit)/_Length);
i.vertex.xz*=s;
}
v.pos=UnityObjectToClipPos(i.vertex);
v.uv.xy=i.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
return v;
}
fixed4 frag(v2f v):SV_TARGET{
fixed4 Col=_Color*tex2D(_MainTex,v.uv.xy);
return Col;
}
ENDCG
}
}
FallBack "Diffuse"
}
開簾頓覺春風暖 滿紙淋漓白雲聲——楊玉香
X Shader實例
X.1.1 熱度圖
X.1.1.1 效果
運行結果:
漸變顏色效果
梯度顏色效果
熱度圖紋理:
漸變ramp圖片
梯度顏色效果
X.1.1.2 示例代碼
Shader "Ellioman/Heatmap"
{
Properties
{
_HeatTex("Texture", 2 D) = "white" {}
}
SubShader
{
Tags{ "Queue" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct vertInput
{
float4 pos : POSITION;
};
struct vertOutput
{
float4 pos : POSITION;
fixed3 worldPos : TEXCOORD1;
};
uniform int _Points_Length = 0 ;
uniform float3 _Points[100 ];
uniform float2 _Properties[100 ];
sampler2D _HeatTex;
vertOutput vert(vertInput input)
{
vertOutput o;
o.pos = UnityObjectToClipPos(input.pos);
o.worldPos = mul(unity_ObjectToWorld, input.pos).xyz;
return o;
}
half4 frag(vertOutput output) : COLOR
{
half h = 0 ;
for (int i = 0 ; i < _Points_Length; i++)
{
half di = distance (output.worldPos, _Points[i].xyz);
half ri = _Properties[i].x;
half hi = 1 - saturate(di / ri);
h += hi * _Properties[i].y;
}
h = saturate(h);
half4 col = tex2D(_HeatTex, fixed2(h, 0.5 ));
return col;
}
ENDCG
}
}
Fallback "Diffuse"
}
X.1.1.3 分析
首先,咱們須要將ramp圖片的wrapMode格式設爲Clamp。 Texture.wrapMode 循環模式:
TextureWrapMode.Clamp:設置紋理充滿拉伸使用
TextureWrapMode.Repeat:紋理重複平鋪使用
若是採用Repeat,那麼等於U>=1的狀況就會用紋理圖在右邊在平鋪一張圖。
咱們使用了相似使用ramp紋理製做toon卡通風格shader效果的方式,來用uv中的u指得到顏色的強度。 這裏在對blend混合命令作一個筆記: Blend SrcAlpha OneMinusSrcAlpha // 傳統透明度 Blend One OneMinusSrcAlpha // 預乘透明度 Blend One One // 疊加 Blend OneMinusDstColor One // 柔和疊加 Blend DstColor Zero // 相乘——正片疊底
那海和天空之間星星消失的地方 連時間也沒有確切的命運——楊煉
X.1.2 山崖
X.1.2.1 效果
這裏主要實現了山崖上草地覆蓋面積,高度還有山石高光,平滑度,法線強度的控制。
X1.2.2 示例代碼
Shader "Unlit/Cliff"
{
Properties
{
_MainTex ("Texture", 2 D) = "white" {}
_MainNormal("MainNormal",2 D)="white"{}
_GrassTex ("GrassTex", 2 D) = "white" {}
_GrassNormal ("GrassNormal", 2 D) = "white" {}
_Smooth("smooth ",Range(0 ,1 ))=1
_Gloss("Gloss",Range(0 ,128 ))=96
_Height("Height",float )=0.5
_Offset("Offset",float )=0.5
_BumpScale("bumpscale",float )=0.5
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 uv : TEXCOORD0;
float3 normal:NORMAL;
float4 tangent:TANGENT;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 lightDir:TEXCOORD1;
float3 worldPos:TEXCOORD2;
float3 tanView:TEXCOORD3;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _GrassTex;
float4 _GrassTex_ST;
sampler2D _MainNormal;
sampler2D _GrassNormal;
float _Offset;
float _BumpScale;
float _Height;
float _Gloss;
float _Smooth;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv.xy, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv.zw, _MainTex);
TANGENT_SPACE_ROTATION;
o.worldPos=UnityObjectToWorldDir(v.vertex);
o.lightDir=normalize (mul(rotation,ObjSpaceLightDir(v.vertex)).xyz);
o.tanView=normalize (mul(rotation,ObjSpaceViewDir(v.vertex)));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 halfDir=normalize (i.tanView+i.lightDir);
fixed4 col = tex2D(_MainTex, i.uv.xy);
fixed4 grass=tex2D(_GrassTex,i.uv.zw);
float3 colNormal=UnpackNormal(tex2D(_MainNormal,i.uv.xy))*_BumpScale;
float3 GrassNormal=UnpackNormal(tex2D(_GrassNormal,i.uv.zw))*_BumpScale;
colNormal.z=sqrt (1.0 -saturate(dot (colNormal.xy,colNormal.xy)));
GrassNormal.z=sqrt (1.0 -saturate(dot (GrassNormal.xy,GrassNormal.xy)));
float angleY=1 -saturate(dot (UnityObjectToWorldDir(colNormal),float3(0 ,1 ,0 ) )+_Offset);
angleY-=i.worldPos.y>_Height?0 :i.worldPos.y-_Height;
fixed4 finalColor=lerp(grass,col,angleY);
float3 diffuse=finalColor*max (0.0 ,dot (i.lightDir,colNormal))*_LightColor0.xyz;
float3 specular= _LightColor0.rgb * pow (saturate(dot (halfDir, colNormal)), _Gloss)*_Smooth;
return fixed4(diffuse+specular+UNITY_LIGHTMODEL_AMBIENT.xyz*finalColor,1 );
}
ENDCG
}
}
Fallback "Diffuse"
}