どーも、RiG++の3Dおじさんです。
前日の記事に引き続き、僕らが作ったCMYCatchというゲームでの話になります。
いやぁ僕は宣伝を忘れないスタッフの鏡ですね!ドヤァw
今回の話はタイトルの通り、TextMeshProでShaderを書くんですが...
ん?Shaderなんて聞いてないって?大丈夫です。(多分)
と、その前にどうしてそうなったかを書かせてください。
そんなの興味ないからShader早く見せろって人は読み飛ばしてくださいね。
なぜShaderなのか?
このゲームは企画からUI、ロゴ、タイトル画面やリザルト画面が出来たから誰かコーディングやってという形で、
始まったのですが、そのUIのグリッチがカッコイイんですね。
ただ、それはグリッチの素材を用意したものなので、
もちろん毎回全ての文字や文をグリッチさせたものを作っているわけです。
つまり、流用が出来ない+コストが増えるということになります。
今回のゲーム制作であればスコアにもグリッチを適用させたいねという話があったのですが、
0~9までのテキスト各々の素材や、桁数が増えたときにもいい感じに見えるようにしないといけないなど,
アーティスト側の負担が爆上がりすることが明確でした。
いつだってそういう負担を失くすことが出来るのは技術なわけですが、
今回その技術というのがShaderでした。
Shaderであれば割り当てるだけで全てのものにそのエフェクトが表れます。今回であればグリッチです。
コンピュータの描画処理はすべて何かしらのShaderが司っています。
言い換えれば、描画処理を記述することが「Shaderを描く」ということです。
実際は、描く機会はとても少なく、いつも内部でいい感じにやってくれているのでShaderを意識することは少ないんですけどね...。
と、まぁ長々と書いたのですが要するに、
アーティストの負担削減(現実的に無理)なものを技術でなんとかしようっていうことです。
実装してみよう
さて、実装に当たりなんとなくShaderってなんにでも存在するんだから,
別にUnityのデフォルトのTextいじったらいいのでは?と思う方もいるはずです。
これはスタートをどこからにするかという話になります。
UnityのデフォルトのTextだと定義されていない値が多すぎて自分で実装する部分がとても大変です。
TextMeshProであれば、元々様々なコンストラクタや値があるので、
実装が比較的楽になります。
(まぁ、Unity使ってる時点でスタートラインはだいぶゴールに寄ってると思いますが...)
と、色々前置きがありましたが、いよいよ実装です。
参考にさせてもらった記事↓↓
[Unityシェーダ入門]シェーダで作るノイズ5種盛り-おもちゃラボ
[Unity] カスタムシェーダーでTextMeshProに独創的な演出を加える
この二つの記事は必読です!
// PIXEL SHADER fixed4 PixShader(pixel_t input) : SV_Target { half2 uv = input.texcoord0.xy * 1; if (abs(sin(_Time.w)) > 0.97) { float nx = noise1(uv * 30); float ny = noise2(uv * 8); nx = (nx - 0.5) * 0.07; ny = (ny - 0.5) * 0.04; float movex = nx; float movey = ny; uv.x = uv.x + movex; uv.y = uv.y + movey; } half d1 = tex2D(_MainTex, uv).a * input.param.x; half d2 = tex2D(_MainTex, uv - float2(-0.03, 1.0 / 4.7)).a * input.param.x; half d = lerp(d1, d2, 0.5 * (1.0 + sin(0.5 * PI * _Time.y))); d = tex2D(_MainTex, uv).a * input.param.x; half4 c = input.faceColor * saturate(d - input.param.w); // Alternative implementation to UnityGet2DClipping with support for softness. #if UNITY_UI_CLIP_RECT half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(input.mask.xy)) * input.mask.zw); c *= m.x * m.y; #endif #if (UNDERLAY_ON | UNDERLAY_INNER) c *= input.texcoord1.z; #endif #if UNITY_UI_ALPHACLIP clip(c.a - 0.1); #endif return c; }
コードを全部載せると変更箇所が分かりにくくなるので、全ては載せていません。
UnityのAssetフォルダ >> TextMesh Pro >> Resources >> Shaders にある
TextMeshPro/Mobile/Distance Fieldというやつを基にしていますので、
そちらと見比べるとわかりやすいと思います。
TextMeshProはフォントの文字がまとめられたテクスチャ(テクスチャアトラス)を作成し、
それを文字の大きさに合わせた板ポリゴンに各文字を表示するという形でTextを表示しています。
少しShaderを触ったことがある人であれば、これはとても扱いやすそう!!
って思うはず!多分...。
「あぁなるほど要するにテクスチャなんでしょ?」
「完全に理解したわ(勝利確信)」って顔になりましたよね?
まぁすぐに何も分からなくなるわけですが...(現実)
~考え方~
- y軸方向にブロックノイズを生成
- x軸にもブロックノイズを生成
- ノイズは0~1の値なので、-0.5を加算、2を乗算し、-1~1の値を取るようにする
- uvとノイズを乗算する
- ノイズ生成で時間を引数にとり毎フレーム異なるノイズを生成するように変更
- 一定時間だけ動くように条件分岐
- 完成!!
注意点として、上記のコードではフォントのテクスチャアトラスのUV座標を動かしているので、
動かしたときに違う表示したい文字とは異なる文字が入り込んでしまう場合があります。
その時はInspectotから、TextMeshProのFont Assetをダブルクリックし、
Inspector >> Generation Settings >> Padding を編集しましょう。僕は20にしていました。
これでテクスチャアトラスにした際の文字と文字の隙間が大きくなったので、
UVを大きく動かさない限り、他の文字が干渉することはないでしょう。
干渉したらもっとPaddingを上げれば大丈夫ですが、その場合文字が小さくなり解像度を上げないと
文字の粗さが無視できなくなってきます。
解像度を上げればデータは重くなっていく(自明)ので、そこは意識しておくといいと思います。
(そんなにフォントの種類が多くないなら全く問題ないです。)
ホントは、文字に対して板ポリゴンの大きさも大きくしたいんですが、
一文字ずつ編集していく以外の方法をまだ知らないので、そこはやりませんでした。
どなたかいい方法を教えていただきたいです...(← 自分でやれ!)
そんなこんなで、いい感じに文字が動くようになりました!!
もうちょっとカッコよく出来そうです。
ただ今回はあんまりうるさくしたくなかったのでこれで完成です。そうです。完成です。
(別にめんどくさくなったわけじゃないですよ?)
あ、疑ってる顔をしていますね?
まあいいです。
最後に、これを機にShaderやりたいなっていう方はこちらをどうぞ!
wgld.org | GLSL | とても丁寧にGLSLの書き方について解説されています!
The Book of Shaders Shaderといえばコレ!英語ですが、コードは万国共通なので大丈夫...なはず!
明日はどんな記事が上がるのでしょう?
楽しみですね!!