1. 程式人生 > >用unity shaderlab 實現「影之詩」中的閃卡特效(一)

用unity shaderlab 實現「影之詩」中的閃卡特效(一)

引言

大家好我是9級鐵甲蛹,我從今天開始寫部落格了。雖然之前總想過要寫寫技術部落格,但是覺得自己之前的學習經驗對大家可能沒什麼幫助,而且網上許多資料非常詳細。現在決定寫一來是因為最近做了些有意思的東西,恰巧網上沒什麼具體的內容。二來是因為假期到了沒之前那麼忙,可以靜下心來總結一下之前的學習。希望大家有什麼問題多多交流。

「影之詩」中的閃卡特效

去年cygames在unite2017 Tokyo上分享了他們製作「影之詩」的經歷,其中專門講了他們是如何製作卡牌特效的。對相關的具體內容感興趣的朋友可以看看遊戲葡萄旅法師營地的文章。
其實本質上就是一個有多種動畫效果的shader,只是他們用一張圖的rgb三個通道,分別作為不同效果的遮罩層,並且詳細指定了各通道實現的效果型別。這樣既能通過對遮罩層的詳細繪製來控制效果的細節,又能通過shaderlab的便利在unity編輯器裡通過調節不同引數實現不同效果。

  

「影之詩」中閃卡的效果大致分為三種(畢竟他們只用一張圖的三個通道做遮罩層)。

  1. 圖中物體各種動感的效果,比如圖中人物的頭髮、衣物的飄動效果。這些部分都是圖中原有的部分,它們基本在一定的範圍內運動。
  2. 在原圖上新增的有動態效果的部分,比如左圖中龍口中的火焰,圖中上半部分的氣流,以及右圖中的飄動的霧氣,這些是原圖中沒有,是在原圖基礎上混合進去的部分,大多伴有簡單的運動規律,如平移、旋轉等。
  3. 是圖片中的部分割槽域存在的顏色變化,比如右圖中的弓上的流光。

接下來我就這三種類型的效果詳細展示各效果的實現思路,效果和程式碼。

卡牌上的波動效果

報道的譯文中提到他們可以選擇“滾動”、“迴旋”、“極座標”等移動方式,剛看到這三詞時我也沒想到到底它們指那些變化方式,尤其是“滾動”,後來看到OpenCanvas7裡濾鏡變形裡就三個選項“旋渦”“波浪”“極座標”大概就猜到是指什麼了。對應PS中的就是波浪、旋轉扭曲、和極座標這些濾鏡的效果。實際上“滾動”跟PS裡的“波浪”不太一樣,不過演算法只差一點。講的時候我會將“波浪”和“滾動”兩種效果對比展示一下。

波浪、滾動

無論是波浪還是滾動,這一系列的效果都可以看做是通過周期函式實現圖片上畫素顯示顏色的週期變化。具體實現都是通過給每一個片元的uv增加一個不同的偏移量來實現的。
這個偏移量是有周期性的,同時由於效果的動態性,它是跟時間變數相關。要實現不同位置的片元可以計算出不同的偏移量,我們還要將原有的UV納入考量,可以用原有的UV來作為周期函式的初相。
決定是波浪效果還是滾動效果的是作為初相的UV是直接作用到自己的方向上,還是和將UV的順序顛倒分別作用到y和x方向上。
程式碼如下:

fixed3 mask = tex2D(_Mask,i.uv).rgb;
float2 phase = (i.uv+_Speed*float2(_Time.y,_Time.y))*pi*2
;//原UV作為初相,為了便於描述資料和效果,再乘上2π float2 offset; //使用不同引數控制不同方向上的振幅、頻率。 #if _TYPE_ROLL offset.x = _Parameters.x*sin(_Parameters.y*phase.x); offset.y = _Parameters.z*sin(_Parameters.w*phase.y); #elif _TYPE_WAVE offset.y = _Parameters.x*sin(_Parameters.y*phase.x); offset.x = _Parameters.z*sin(_Parameters.w*phase.y); #endif fixed4 col = tex2D(_MainTex, i.uv+0.001*offset*mask.b);

以下圖片都使用了(4,4,4,4)作為_Parameters,_Speed為0.25。可以看到兩種不同的計算方式的差別,波浪方式就像它的名字一樣可以在圖片上看到圖片的波動,而滾動則像是圖片區域性拉大又拉小的過程。
兩種不同位移方式的對比

如果把一個方向上的振幅調為0,那麼滾動效果的圖片就像一張隨風飄揚的旗子。這樣看應該就明白我為什麼猜測後一種的效果就是cygames所使用的“滾動”,從一個方向上看它就像一塊布後面有一排圓柱體從它身上滾過去一樣。
一個方向的振幅為0時兩種不同位移方式的對比

然後就是應用上我們的遮罩層了,程式碼裡我用了圖片的b通道儲存這個效果的資訊,所以遮罩層的藍色顏色越大圖片就抖動的程度就越大。
這裡講一個小技巧,雖然遮罩圖讓人來繪製以控制細節效果更好,但我們只是簡單實現一下效果可以對原圖進行一些簡單處理來得到遮罩圖。我們可以先在PS中扣出想要動的區域,然後將其轉為黑白圖,如果黑白圖中更亮的部位是動的程度更高的部分那就不再改變圖片,直接用它來作遮罩圖。如果更亮的部分是動的更小的區域,那就用它的灰度翻轉圖來作遮罩層。
這裡寫圖片描述

應用遮罩層後的效果
應用遮罩層後兩種不同位移方式的對比
可以看到雖然應用在整張圖時滾動的效果很迷,但是如果圈定了應用範圍和強度,滾動的效果有很強的表現力,可以更好的體現縱深方向的變化。
最後附上完整原始碼

Shader "Custom/SVCardEffect"
{
    Properties
    {

        _MainTex ("Texture", 2D) = "white" {}
        [NoScaleOffset]
        _Mask("Mask", 2D) = "white" {}
        _Speed("Speed", Range(0,10)) = 1
        _Parameters("Parameters", vector) = (1,1,1,1)
        [KeywordEnum(Roll,Wave)] _Type("Type",float) = 0

    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _TYPE_ROLL _TYPE_WAVE
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            #define pi 3.14159265358979 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _Mask;
            float4 _Mask_ST;
            float4 _Parameters;
            float _Speed;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {

                fixed3 mask = tex2D(_Mask,i.uv).rgb;


                float2 phase = (i.uv+_Speed*float2(_Time.y,_Time.y))*pi*2;//原UV作為初相,為了便於描述資料和效果,再乘上2π
                float2 offset;
                //使用不同引數控制不同方向上的振幅、頻率。
                #if _TYPE_ROLL
                    offset.x = _Parameters.x*sin(_Parameters.y*phase.x);
                    offset.y = _Parameters.z*sin(_Parameters.w*phase.y);
                #elif _TYPE_WAVE
                    offset.y = _Parameters.x*sin(_Parameters.y*phase.x);
                    offset.x = _Parameters.z*sin(_Parameters.w*phase.y);
                #endif
                fixed4 col = tex2D(_MainTex, i.uv+0.001*offset*mask.b);

                return col;
            }
            ENDCG
        }
    }
}