筆記十二——動畫效果

學習教材:《UnityShader入門精要》——馮樂樂

部分計算圖例為《UnityShader入門精要》書中截圖

代碼和實例截圖均為個人實際操作得到


動畫效果

動畫效果需要引入時間變數,Unity內置的時間變數:

這些時間變數可以用來實現紋理動畫和頂點動畫

紋理動畫

  • 序列幀動畫

    序列幀動畫是通過依次播放一系列關鍵幀圖像,當播放速度達到一定數值時,看起來就像是一個連續的動畫。靈活性較強,通過一張包含關鍵幀的圖像可以得到比較細膩的動畫效果,缺點在於需要大量時間製作包含關鍵幀的圖像。

    關鍵幀實現的關鍵在於,每個時刻計算該時刻下應該播放的關鍵幀的位置

完整代碼

Shader "Custom/Chapter11_ImageSequenceAnimation" {Properties{ _Color("Main Clolr",Color)=(1,1,1,1) _MainTex("MainTex",2D)="white"{} _HorizantalAmount("HorizantalAmount",Float)=4 _VerticalAmount("VerticalAmount",Float)=4 _Speed("Speed",Range(1,100))=30}SubShader{ Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} //關鍵幀動畫中使用的關鍵幀圖像一般包含透明通道,因此當成半透明來處理 Pass{ Tags{"LightMode"="ForwardBase"} ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; float _HorizantalAmount; float _VerticalAmount; float _Speed; struct a2v{ float4 vertex:POSITION; float4 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float2 uv:TEXCOORD0; }; v2f vert(a2v v){ v2f o; o.pos=UnityObjectToClipPos(v.vertex); o.uv=TRANSFORM_TEX(v.texcoord,_MainTex); return o; } fixed4 frag(v2f i):SV_Target{ float time=floor(_Time.y*_Speed); //內置時間變數 _Time(t/20,t,2t,3t) float row=floor(time/_HorizantalAmount); float column=time-row*_VerticalAmount; //根據播放的速度計算對應的序列幀行和列 //紋理中包含許多關鍵幀圖像,將採樣坐標映射到每一個關鍵幀的坐標範圍內 //紋理中的採樣方向與關鍵幀的播放順序在Y方向是反的,因此Y坐標做減法 half2 uv=float2(i.uv.x/_HorizantalAmount,i.uv.y/_VerticalAmount); uv.x+=column/_HorizantalAmount; uv.y-=row/_VerticalAmount; fixed4 c=tex2D(_MainTex,uv); c.rgb*=_Color; return c; } ENDCG }}FallBack "Transparent/VertexLit"}

  • 滾動背景

    滾動背景一般使用多個層以不同的速度進行滾動,滾動的關鍵在於使滾動方向上的紋理坐標的增量與時間變數相乘,這樣隨著時間的變化,滾動方向上的採樣坐標不斷變換,畫面也能連續變化。

完整代碼:

Shader "Custom/Chapter11_ScrollingBackground" {Properties{ _MainTex("Base Layer",2D)="white"{} _DetialTex("Second Layer",2D)="white"{} _ScrollX("Base Layer Scroll Speed",Float)=1.0 _Scroll2X("Second Layer Scroll Speed",Float)=1.0 _Multiplier("Layer Multiplier",Float)=1}SubShader{ Pass{ Tags{"LightMode"="ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" sampler2D _MainTex; float4 _MainTex_ST; sampler2D _DetialTex; float4 _DetialTex_ST; float _ScrollX; float _Scroll2X; float _Multiplier; struct a2v{ float4 vertex:POSITION; float4 texcoord:TEXCOORD0; }; struct v2f { float4 pos:SV_POSITION; float4 uv:TEXCOORD0; }; v2f vert(a2v v){ v2f o; o.pos=UnityObjectToClipPos(v.vertex); o.uv.xy=TRANSFORM_TEX(v.texcoord,_MainTex)+frac(float2(_ScrollX,0)*_Time.y); o.uv.zw=TRANSFORM_TEX(v.texcoord,_DetialTex)+frac(float2(_Scroll2X,0)*_Time.y); //frac取小數函數,使取樣坐標在[0,1]範圍內,背景連續重複滾動 return o; } fixed4 frag(v2f i):SV_Target{ fixed4 baseLayer=tex2D(_MainTex,i.uv.xy); fixed4 secondLayer=tex2D(_DetialTex,i.uv.zw); fixed4 c=lerp(baseLayer,secondLayer,secondLayer.a); c.rgb*=_Multiplier; //_Multiplier用來控制整體亮度 return c; } ENDCG }}FallBack "VertexLit"}

實例效果:

  • 頂點動畫

    遊戲中通常使用頂點動畫模擬飄動旗幟、湍流小溪的效果,其主要方式是使模型頂點隨著時間變化而變化。

完整代碼:

Shader "Custom/Chapter11_Water" {Properties{ _Color("Main Color",Color)=(1,1,1,1) _MainTex("MainTex",2D)="white"{} _Magnitude("Magnitude",Float)=1 _Frequency("Frequency",Float)=1 _InvWaveLength("InvWaveLength",Float)=10 _Speed("Speed",Float)=0.5}SubShader{Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"} //為透明效果設置對應標籤,這裡「DisableBatching」的標籤是關閉批處理, //包含模型空間頂點動畫的Shader是需要特殊處理的Shader, //而批處理會合併所有相關的模型,這些模型各自的模型空間會丟失 //而頂點動畫需要在模型空間對頂點進行偏移 Pass{ Tags{"LightMode"="ForwardBase"} ZWrite Off Blend SrcAlpha OneMinusDstAlpha Cull Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "UnityCG.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; float _Magnitude; float _Frequency; float _InvWaveLength; float _Speed; struct a2v{ float4 vertex:POSITION; float4 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float2 uv:TEXCOORD0; }; v2f vert(a2v v){ v2f o; float4 offset; offset.yzw=float3(0.0,0.0,0.0); offset.x=sin(_Frequency*_Time.y+v.vertex.x*_InvWaveLength+v.vertex.y*_InvWaveLength+v.vertex.z*_InvWaveLength)*_Magnitude; //在頂點進行空間變換前,對x分量進行正弦操作 o.pos=UnityObjectToClipPos(v.vertex+offset); o.uv=TRANSFORM_TEX(v.texcoord,_MainTex); o.uv+=float2(0.0,_Time.y*_Speed); //進行紋理動畫 return o; } fixed4 frag(v2f i):SV_Target{ fixed4 c=tex2D(_MainTex,i.uv); c.rgb*=_Color.rgb; return c; } ENDCG }}FallBack "Transparent/VertexLit"}

  • 廣告牌技術

    廣告牌技術是另一種常見的頂點動畫。廣告牌會根據視角方向來旋轉一個被紋理著色的多邊形,使多邊形看起來始終朝著攝像機。

    廣告牌技術的本質是構建旋轉矩陣。計算過程中先根據初始計算得到目標的表面法線(例如視角方向)和指向上的方向,這兩者的方向往往不是垂直的,根據這兩者的方向做叉乘得到垂直於兩者的方向,再根據這個得到的方向,假定之前的兩個方向某一個不變,計算另一個垂直的方向,這樣得到三個正交的方向,計算過程類似於:

在廣告牌技術中需要指定一個錨點,這個錨點在旋轉過程中是固定不變的,以此來確定多邊形在空間中的位置。

完整代碼:

Shader "Custom/Chapter11_Billboarding" { Properties{ _Color("Color",Color)=(1,1,1,1) _MainTex("MainTex",2D)="white"{} _VerticalBillboarding("VerticalBillboarding",Range(0,1))=1 } SubShader{ Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RendererType"="Transparent" "DisableBatching"="True"} Pass{ Tags{"LightMode"="ForwardBase"} ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Cull Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; float _VerticalBillboarding; struct a2v{ float4 vertex:POSITION; float4 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float2 uv:TEXCOORD0; }; v2f vert(a2v v){ v2f o; float3 center=float3(0,0,0); //選擇模型空間的原點作為變換錨點 float3 viewer=mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)); //將模型空間的觀察方向作為法線方向 float3 normalDir=viewer-center; normalDir.y=normalDir.y*_VerticalBillboarding; normalDir=normalize(normalDir); //當_VerticalBillboarding的值為1時,法線方向固定為視角方向, //當_VerticalBillboarding的值為0時,法線在Y方向上沒有分量,那麼向上的方向固定為(0,1,0),這樣才能保證與法線方向垂直 float3 upDir=abs(normalDir.y)>0.999 ? float3(0,0,1) : float3(0,1,0); //這裡對向上方向是否與法向方向相平行,防止得到錯誤的叉乘結果 float3 rightDir=normalize(cross(normalDir,upDir)); upDir=normalize(cross(normalDir,rightDir)); float3 offset=v.vertex.xyz-center; float3 localPos=center+rightDir*offset.x+upDir*offset.y+normalDir*offset.z; o.pos=UnityObjectToClipPos(float4(localPos,1)); o.uv=TRANSFORM_TEX(v.texcoord,_MainTex); return o; } fixed4 frag(v2f i):SV_Target{ fixed4 c=tex2D(_MainTex,i.uv); c.rgb*=_Color.rgb; return c; } ENDCG } } FallBack "Transparent/VertexLit"}

注意事項:

  • 在頂點變換的Shader中需要做特殊處理,即關閉批處理,這樣能防止Unity自動對相關模型做合併處理,從而丟失模型空間。
  • 在對頂點進行變換後,如果想要得到正確的陰影,需要添加一個自定義的ShadowCaster Pass,否則得到的陰影會是變換前的陰影。自定義的Pass中,需要使用內置的宏。完整代碼為:

Shader "Custom/Chapter 11_Vertex Animation With Shadow" { Properties { _MainTex ("Main Tex", 2D) = "white" {} _Color ("Color Tint", Color) = (1, 1, 1, 1) _Magnitude ("Distortion Magnitude", Float) = 1 _Frequency ("Distortion Frequency", Float) = 1 _InvWaveLength ("Distortion Inverse Wave Length", Float) = 10 _Speed ("Speed", Float) = 0.5 } SubShader { Tags {"DisableBatching"="True"} Pass { Tags { "LightMode"="ForwardBase" } Cull Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; float _Magnitude; float _Frequency; float _InvWaveLength; float _Speed; struct a2v { float4 vertex : POSITION; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(a2v v) { v2f o; float4 offset; offset.yzw = float3(0.0, 0.0, 0.0); offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude; o.pos = mul(UNITY_MATRIX_MVP, v.vertex + offset); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.uv += float2(0.0, _Time.y * _Speed); return o; } fixed4 frag(v2f i) : SV_Target { fixed4 c = tex2D(_MainTex, i.uv); c.rgb *= _Color.rgb; return c; } ENDCG } Pass { Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #include "UnityCG.cginc" float _Magnitude; float _Frequency; float _InvWaveLength; float _Speed; struct v2f { V2F_SHADOW_CASTER; }; v2f vert(appdata_base v) { v2f o; float4 offset; offset.yzw = float3(0.0, 0.0, 0.0); offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude; v.vertex = v.vertex + offset; TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) return o; } fixed4 frag(v2f i) : SV_Target { SHADOW_CASTER_FRAGMENT(i) } ENDCG } } FallBack "VertexLit" }


相關參考

《UnityShader入門精要》 馮樂樂

相關學習鏈接

關於ZTest和ZWrite :

cnblogs.com/ljx12138/p/

Unity著色器訓練營(1):

入門篇 http://forum.china.unity3d.com/thread-27522-1-1.html

小小的頂點變換能實現大大的效果

(出處: Unity官方中文論壇)

分享Shader實現思路和源代碼的專欄:

zhuanlan.zhihu.com/myas

zhuanlan.zhihu.com/Meow


推薦閱讀:

蘇打世界和拍拍卡洛琳這種扁平化風格遊戲動畫用什麼軟體做的?
為什麼Unity3D能夠產生如此多的插件(中間件)?
會Unity 3D的技術人才現在是否緊俏?做此類培訓的市場前景如何?
如何看待King即將發布的遊戲引擎Defold?
從0開始用Unity3D做遊戲原型,該學JS還是C#?

TAG:Unity游戏引擎 | shader |