なおしのこれまで、これから

学んだこと・感じたこと・やりたいこと

Unityで映画Matrixの暗号パーティクルを作る

f:id:vxd-naoshi-19961205-maro:20210912014640g:plain

始めに

つい先日「マトリックス リザレクション」のPVが公開されました。 滅茶苦茶楽しみです。


www.youtube.com


で、マトリックスと言えばあの意味の分からない文字の羅列が印象的ですが、それってパーティクルで作れない?ってことでノリと勢いで作ってみました。



プロジェクト

Unity 2020.3.7f1を使っています。

github.com



作成までの手順

私は普段全くパーティクルを触っていないので、何となくで作っています。

文字を用意する

フォトショとかイラレなどで普通は作るかもしれませんが、Googleスライドに文字列を書いてそれをスクショして文字のテクスチャを作成しました。

f:id:vxd-naoshi-19961205-maro:20210911191555p:plain


こんな感じに。

f:id:vxd-naoshi-19961205-maro:20210911191635p:plain



シェーダーで一文字ずつ描画するようにする

とても久しぶりにシェーダーを書いたので凄く雑になります。

やってることはTilingの値を一文字の大きさとして、与えられたIndexから計算してその文字を表示しています。

TextMapShader

Shader "Unlit/TextMapShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        
        _Index ("Index", int) = 0
        
        _MaxIndexX ("Max Index X", int) = 0
        _MaxIndexY ("Max Index Y", int) = 0
        
        _DiscardThreshold ("Discard Threashold", Range(0, 1)) = 0.5
    }
    
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha 
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _DiscardThreshold;

            int _Index;

            uniform int _MaxIndexX;
            uniform int _MaxIndexY;
            
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float letterSizeX = _MainTex_ST.x;
                float letterSizeY = _MainTex_ST.y;
                
                uint maxIndex = _MaxIndexX * _MaxIndexY;
                
                uint index = floor(_Index) % maxIndex;
                
                uint indexX = index % _MaxIndexX;
                uint indexY = index / _MaxIndexX;

                i.uv.x += letterSizeX * indexX;
                i.uv.y -= letterSizeY * indexY;
                
                fixed4 col = tex2D(_MainTex, i.uv);
                if(col.b > _DiscardThreshold)
                    discard;
                
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}


上手くいけば、InspectorのIndexをいじることで文字を切り替えることができます。

f:id:vxd-naoshi-19961205-maro:20210912010546g:plain

パーティクルの基礎作成

まず親となるパーティクルを作成します。基本的な設定は次の通りです。

  • Shapeにチェックをつけて、ShapeBoxにしてScaleをいい感じに設定
  • SubEmittersにチェックをつけて、後に作成する子のパーティクルをBirthで設定
  • Rendererのチェックを外す

この設定でパーティクルを1つの方向に出し続けるものができます。(下のgifは見やすいようRendererをチェックつけた状態にしています)

f:id:vxd-naoshi-19961205-maro:20210912005141g:plain


次に新しいパーティクルを作成し、先ほどのパーティクルの子に設定します。

パーティクルの設定は次のようになります。

  • Shapeのチェックを外す
  • Start Speedの値を小さくする
  • Rendererでテキストを出すマテリアルを設定する

あとはいい感じに値を調整します。(Start Size, Start Lifetime, etc)

出来れば次のようになります。 何となく完成が見えてきました。

f:id:vxd-naoshi-19961205-maro:20210912011207g:plain



Custom Dataを使ってParticleからシェーダーに値を渡す

Particle SystemのCustom Dataにチェックをつけて、表示する文字のIndexと色をシェーダーに渡すようにします。

また、文字をランダムに表示させたいので乱数のSeedも作成します。

子のパーティクルのCustom Dataを次のように設定します。

  • Custom1で文字のIndexと乱数のSeedを設定
  • Custom2で文字の色の変化を設定(マトリックスの映画を見るに最初の一瞬は白、そして緑)

f:id:vxd-naoshi-19961205-maro:20210912011905p:plain


次に、RendererのCustom Vertex Streamsにチェックをつけて、値を頂点シェーダーで渡してもらうよう設定します。 横の()で書かれている通り、Custom1.xyの値がTEXCOOR0.zwに入って渡されます。

f:id:vxd-naoshi-19961205-maro:20210912012238p:plain


後は渡された値を使って、シェーダーでindexを変えたり色を設定したりします。加えて、時間経過で文字が変わるようにもしました。

TextMapShader

Shader "Unlit/TextMapShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        
        _MaxIndexX ("Max Index X", int) = 0
        _MaxIndexY ("Max Index Y", int) = 0
        
        _DiscardThreshold ("Discard Threashold", Range(0, 1)) = 0.5
    }
    
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha 
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
                float4 color : TEXCOORD1;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float index : TEXCOORD1;
                float seed : TEXCOORD2;
                float4 color : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _DiscardThreshold;

            uniform int _MaxIndexX;
            uniform int _MaxIndexY;
            
            float GetRandomNumber(float2 texCoord, int Seed)
            {
                return frac(sin(dot(texCoord.xy, float2(12.9898, 78.233)) + Seed) * 43758.5453);
            }
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.index = v.uv.z;
                o.seed = v.uv.w;
                o.color = v.color;
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float letterSizeX = _MainTex_ST.x;
                float letterSizeY = _MainTex_ST.y;
                
                uint maxIndex = _MaxIndexX * _MaxIndexY;
                
                uint time = floor(_Time.w * frac(i.seed));
                uint index = GetRandomNumber(float2(time, time + i.seed), i.seed) * 10000;
                index = index % maxIndex;
                
                uint indexX = index % _MaxIndexX;
                uint indexY = index / _MaxIndexX;

                i.uv.x += letterSizeX * indexX;
                i.uv.y -= letterSizeY * indexY;
                
                fixed4 col = tex2D(_MainTex, i.uv);
                if(col.b > _DiscardThreshold)
                    discard;
                
                UNITY_APPLY_FOG(i.fogCoord, col);
                return i.color;
            }
            ENDCG
        }
    }
}

大体上手くいけば、ほぼほぼマトリックスの暗号の動作をするようになります。

f:id:vxd-naoshi-19961205-maro:20210912013557g:plain


あとはRendererのFilpをいじってみたり、Size over Lifetimeにチェックを入れて少しずつ小さくするようにするといい感じになります。



PostProcessingでBloomをつける

パーティクルだけだとやはり味気ないので、PostProcessingを使って見ます。

まずはPostProcessingに入る前に、Main CameraのClear FlagsSolid Colorに変更にします。

あとはPostProcessingを入れて、Bloomを追加してテキトーに設定すればかなりマトリックスのオープニングっぽくなります。 (ここら辺はあまり詳しくないので、調べてみてください. . .)

f:id:vxd-naoshi-19961205-maro:20210912014640g:plain



最後に

いつも見た目にこだわったものなどを作ったりはしないので、Particle SystemだったりPostProcessingを触るいい機会となりました。

また、やってみて意外にもっぽいものができたので楽しかったです。

最後に蛇足になりますが、マトリックスに出てくる暗号のやつは実はお寿司のレシピをスキャンしてできたものらしいです。

参考

Particle Systemでシェーダーに値を渡せないか調べてた際に参考になった記事です。

styly.cc

aizu-vr.hatenablog.com

neareal.net