始めに
つい先日「マトリックス リザレクション」のPVが公開されました。 滅茶苦茶楽しみです。
で、マトリックスと言えばあの意味の分からない文字の羅列が印象的ですが、それってパーティクルで作れない?ってことでノリと勢いで作ってみました。
プロジェクト
Unity 2020.3.7f1を使っています。
作成までの手順
私は普段全くパーティクルを触っていないので、何となくで作っています。
文字を用意する
フォトショとかイラレなどで普通は作るかもしれませんが、Googleスライドに文字列を書いてそれをスクショして文字のテクスチャを作成しました。
こんな感じに。
シェーダーで一文字ずつ描画するようにする
とても久しぶりにシェーダーを書いたので凄く雑になります。
やってることは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をいじることで文字を切り替えることができます。
パーティクルの基礎作成
まず親となるパーティクルを作成します。基本的な設定は次の通りです。
- Shapeにチェックをつけて、
Shape
をBox
にしてScaleをいい感じに設定 - SubEmittersにチェックをつけて、後に作成する子のパーティクルを
Birth
で設定 - Rendererのチェックを外す
この設定でパーティクルを1つの方向に出し続けるものができます。(下のgifは見やすいようRendererをチェックつけた状態にしています)
次に新しいパーティクルを作成し、先ほどのパーティクルの子に設定します。
パーティクルの設定は次のようになります。
- Shapeのチェックを外す
Start Speed
の値を小さくする- Rendererでテキストを出すマテリアルを設定する
あとはいい感じに値を調整します。(Start Size
, Start Lifetime
, etc)
出来れば次のようになります。 何となく完成が見えてきました。
Custom Dataを使ってParticleからシェーダーに値を渡す
Particle SystemのCustom Data
にチェックをつけて、表示する文字のIndexと色をシェーダーに渡すようにします。
また、文字をランダムに表示させたいので乱数のSeedも作成します。
子のパーティクルのCustom Dataを次のように設定します。
- Custom1で文字のIndexと乱数のSeedを設定
- Custom2で文字の色の変化を設定(マトリックスの映画を見るに最初の一瞬は白、そして緑)
次に、RendererのCustom Vertex Streams
にチェックをつけて、値を頂点シェーダーで渡してもらうよう設定します。 横の()で書かれている通り、Custom1.xy
の値がTEXCOOR0.zw
に入って渡されます。
後は渡された値を使って、シェーダーで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
}
}
}
大体上手くいけば、ほぼほぼマトリックスの暗号の動作をするようになります。
あとはRendererのFilp
をいじってみたり、Size over Lifetimeにチェックを入れて少しずつ小さくするようにするといい感じになります。
PostProcessingでBloomをつける
パーティクルだけだとやはり味気ないので、PostProcessingを使って見ます。
まずはPostProcessingに入る前に、Main CameraのClear Flags
をSolid Color
に変更にします。
あとはPostProcessingを入れて、Bloomを追加してテキトーに設定すればかなりマトリックスのオープニングっぽくなります。 (ここら辺はあまり詳しくないので、調べてみてください. . .)
最後に
いつも見た目にこだわったものなどを作ったりはしないので、Particle SystemだったりPostProcessingを触るいい機会となりました。
また、やってみて意外にもっぽいものができたので楽しかったです。
最後に蛇足になりますが、マトリックスに出てくる暗号のやつは実はお寿司のレシピをスキャンしてできたものらしいです。
参考
Particle Systemでシェーダーに値を渡せないか調べてた際に参考になった記事です。