Unityで作るDissolveShader

目次



自己紹介

初めましてクライアントエンジニアのK.Kです。

主にクライアントの作業をしている者ですが、 より見た目を綺麗にしたい、より製品のクオリティを自分の力で良くしたいなどの理由から1年ちょい前からシェーダーの勉強をしています。

まあシェーダーで何が出来るかイメージつかないかもなので、私が勉強がてらに作成したものを紹介します。

地面を塗ることができるシェーダー

f:id:rf-blog-sagyo:20201016160337g:plain

キャラクターを炭で書いたように表現するシェーダー

f:id:rf-blog-sagyo:20201016160825p:plain

塗るシェーダーと炭シェーダーを掛け合わせたもの

f:id:rf-blog-sagyo:20201016163500g:plain

そして今回ちょっとおしゃれな遷移を作りたかった!

f:id:rf-blog-sagyo:20201015200802g:plain

シェーダーとは

シェーダーとは簡単に考えるとCPUがGPUに表示方法を教えるための物です。 ではなぜCPUがGPUに表示方法を教える必要があるのでしょうか?

1920x1080のモニターで一度考えてみましょう。 1920x1080この数字はピクセル数を表しています、なので画面を一度描画するためには2,073,600ピクセルを処理しなければいけません。 CPUは難しい計算を淡々とこなしていくことが得意です。 ですが200万近くもしくはそれ以上の単純処理を一度にお願いしてしまうと突然処理が間に合わなくなります。 そこでGPUの登場です。 GPUは複数の単純処理を一度に行うことが得意で、200万近くの処理を行うにはもってこいです。 そのためにシェーダーが存在しています。

Unityでシンプルなシェーダーを見てみる

UnityでのShaderはShaderLabを使用しています。 ShaderLabは普通 Cg/HLSL プログラミング言語で書かれています。 Unityは自動的にDirect3D 9、OpenGLDirect3D 11、OpenGL ES 等にコンパイルしてくれるため、シェーダーはすべてのプラットフォームで動作します。 そこで画像をそのまま表示する一番シンプルなシェーダコードがこちらです。

Shader "Simple"
{
    Properties
    {
    }
    SubShader
    {
        Tags {"Queue"="Transparent" "RenderType"="Transparent"}
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;

            fixed4 frag (v2f_img i) : COLOR
            {
                fixed4 col = tex2D(_MainTex,i.uv);
                return col;
            }
            ENDCG
        }
    }
}

いきなり見ても分かりづらいのでかみ砕いてこのシェーダーコードを簡単に説明していきます。

Shader

Shader "Simple"
{
    // シェーダーの中身
}

こちらがシェーダーの名前を指定する箇所です。

Properties

Properties
{
    // 追加パラメータ
    _NoiseTex ("Noise",2D) = "white"{}
}

シェーダー外部から入れるパラメータです。 ここで画像や値を入れることができます。また外部のコードからアクセスする事が出来る箇所です。 f:id:rf-blog-sagyo:20201015195935p:plain

構造が シェーダーで使う変数名("外部から見たときの名前",変数の種類) = デフォルト値になります。

ですのでこちらは_NoiseTex ("Noise",2D) = "white"{} シェーダー内では_NoiseTexという変数名で扱う Unity内ではNoiseという名前になる パラメータ自体は2D画像の物になる 最後のデフォルト値は"white"{}とあります。 こちらは画像が設定されていない場合白い画像が使われます。

SubShader

SubShader
{
    // 共通な設定やTagなどの指定
    Pass
    {
        // passのコード
    }
}

SubShaderとはPassで共通使用する設定、Tagなどを指定できる場所です。

#pragma

#pragma vertex vert
#pragma fragment frag

#pragmaとありますがこちらは何を利用するかを指定する場所です。
#pragma vertex name はnameに値する関数で頂点プログラムを含むことです。
#pragma fragment name はnameに値する関数でフラグメントプログラムを含むことです。

FragmentShader

fixed4 frag (v2f_img i) : SV_Target
{
    return fixed4(1,1,1,1);
}

fragはフラグメントシェーダーのコード部分です。 フラグメントシェーダーは描画される各ポリゴンに対して行う処理です。 フラグメントシェーダー色々ありますが画像編集ツールでよくある効果やマスクをイメージしてもらえると良いかもしれません。 f:id:rf-blog-sagyo:20201015212532p:plain

DissolveShader

今回はここからフラグメントシェーダーのみを利用して画像が消える演出、DissolveShaderを作っていきます。 f:id:rf-blog-sagyo:20201015200109g:plain

DissolveShaderは色々な物に使われており、使い方次第で敵が倒され、黒くなりつつ消えていく演出もできます。f:id:rf-blog-sagyo:20201015200144g:plain

まずはノイズのテクスチャーのパラメータとエフェクトがかかる強さを表すRateを用意します。

Properties
{
    _NoiseTex ("Noise",2D) = "white"{}      // 追加
    _Rate ("DissolveRate",Range(0,1)) = 0   // 追加
}
SubShader
{
//...
    Pass
    {
        //...
        
        sampler2D _MainTex;
        sampler2D _NoiseTex;    // 追加
        FLOAT _Rate;            // 追加
    
        //...
    }
}

DissolveShaderはノイズの画像を利用してDissolveをかけるRateに応じて画像のα値を抜いていくものになります。 使用するノイズの画像ですが基本的には白黒の画像を使用します。
f:id:rf-blog-sagyo:20201015212747j:plain

さてこちらのグラデーションの画像を見て一般的にはどこから消えるべきでしょうか? こちらの画像をフラグメントシェーダー内で見る場合はピクセル単位の色を見ることになります。

シェーダの色はfixed4(r,g,b,a)で0から1の間で値を持っているのですが、 左上のピクセルの場合色がfixed4(1,1,1,1)、右下のピクセルの場合色がfixed4(0,0,0,1)になります。

マスクなどでもそうですが1が表示すべき、0が隠すべきといった考え方になります。 よって、黒い部分から消えていくのです。

この二つのパラメータを準備した後にノイズテクスチャーの色の値を取る関数を用意します。

FLOAT NoiseValue(sampler2D tex, half2 uv)
{
    fixed4 col = tex2D(tex, uv);
    return (col.r + col.g + col.b) * 0.3333; 
}

sampler2Dは画像でuvはピクセルの座標になります。 その後にrbgの色値を平均させた値を返します。

ノイズの値を取る関数を用意したのでフラグメントシェーダーに反映させ、以下のようにします。

fixed4 frag (v2f_img i) : COLOR
{
    fixed4 col = tex2D(_MainTex, i.uv);
    
    FLOAT noise = NoiseValue(_NoiseTex, i.uv);
    col.rgb = lerp(col.rgb ,1 ,step(col.a ,0));
    
    return col;
}

こちらが実行結果です。 f:id:rf-blog-sagyo:20201015212933g:plain

Rateのパラメータを変えても変わりません。

これはシェーダーに正しい設定が出来てないからです。

SubShader
{
    Tags {"Queue"="Transparent" "RenderType"="Transparent"}
    Blend SrcAlpha OneMinusSrcAlpha  // 追加
    ZWrite Off  // 追加
    Pass
    {
        //...
    }
}

Blend設定に SrcAlphaとOnMinusSrcAlphaを追加 Blend設定を簡単に考えると描画されている物の色の混ざり具合の設定です。 こちらではα値を見て不透明にする設定の追加です。

ZWrite Off でポリゴンの裏側の描画をし、無くしています。

こちらが実行結果 f:id:rf-blog-sagyo:20201016133944g:plain

おまけ

”DissolveShaderは色々な物に使われており、使い方次第で敵が倒され、黒くなりつつ消えていく演出もできます。”

α抜きを行う直前に指定した色に変更すれば結構おしゃれになります。

そのためには指定したい色、色のしきい値のパラメータを追加し反映します。

Properties
{
    _NoiseTex ("Noise",2D) = "white"{}
    _Rate ("DissolveRate",Range(-1,1)) = 0
    _Color ("AcidColor",Color) = (1,1,1,1)              // 追加
    _Threshold ("DissolveThreshold",Range(0,1)) = 0.5   // 追加
}
SubShader
{
//...
    Pass
    {
         //...
         
         sampler2D _MainTex;
         sampler2D _NoiseTex;
         FLOAT _Rate;
         fixed4 _Color;         // 追加
         FLOAT _Threshold;      // 追加
         
         //...

         fixed4 frag (v2f_img i) : COLOR
         {
             fixed4 col = tex2D(_MainTex, i.uv);
             
             FLOAT noise = NoiseValue(_NoiseTex, i.uv);
             col.rgb = lerp(col.rgb ,1 ,step(col.a ,0));
             col = lerp(col, _Color, step(noise, _Rate + _Threshold));  // 追加
             col.a = lerp(1,0,step(noise, _Rate));
             
             return col;
         }
         
         //...
    }
}

これが実行結果になります。

f:id:rf-blog-sagyo:20201015200802g:plain

さいごに

シェーダーは何処から始めればいいか、何を勉強すればよいかと敷居が高い分野ですが、 サンプルコードなどはたくさんネットに散らばっています。 色々なサンプルを作っていく際に確実に身につくものはあります。

様々なシェーダーを作っていけば、これが出来るのでないか、あれが出来るのでないかと想像や可能性を実感できるので興味があるならば諦めずに挑戦し続けてほしいです。
難しいけどシェーダーは楽しいからいっぱい書こう!!

参考

docs.unity3d.com

docs.unity3d.com

qiita.com

© UTJ/UCL