80年代ゲーセン筐体っぽいレトロ調の画面をUnityシェーダーで作る。
ええ、ども、さやちゃんぐbotです。
この記事はシェーダーアドベントカレンダー15日目の記事として書かれています。
14日目は7CITさんによる「フラグメントシェーダーでルーン文字のようなものをプロシージャルに生成する」でした。
(ここに後でリンクを貼ります)
昭和から平成初期にかけてのゲーセンには、当時の空気がありました。雑居ビルの薄暗い地下、学生服の高校生、空調のない空間にただよう煙草の煙、常連が日々の雑文やイラストを書き残していく雑記帳、そしてメンテが行き届いていないレバーやボタンと色焼けしてしまったブラウン管の画面。
そんなレトロなゲーセンは90年代なかばに大部分が姿を消し、明るい照明でUFOキャッチャーやプリクラだけがある姿に変貌してしまいました。
オーケー、青春の日々はもう取り戻せない。しかし、当時のような画面をシェーダーで作り出すことはできるかもしれない!
ということでやっていきましょう!!
シェーダーコードだけ見たいという方にはこちらを参照いただくと良いでしょう。
ここに一枚のテクスチャがあります。
これをUnityのフラグメントシェーダーで表示するのはシンプルです。0.0から1.0の範囲にテクスチャを読み込んであげるだけです。
return tex2D(_MainTex, i.uv);
バレルディストーション
CRTモニタと言えば樽の形でちょっと歪んだ表示です。ここでdoxas先生にGLSLスクールで教えてもらったことを思い出します。
シェーダーに追記してみましょう。
中心から外側に向かってちょっと歪ませるように調整してみます。
まずは樽形に座標を加工する関数を用意します。
float2 barrel(float2 uv)
{
float s1 = .99, s2 = .125;
float2 centre = 2.*uv - 1.;
float barrel = min(1. - length(centre)*s1, 1.0)*s2;
return uv - centre * barrel;
}
あとはフラグメントシェーダー側でこれにuvを渡して、戻ってきた値でテクスチャを読むだけです。
// barrel distortion
float2 p = barrel(i.uv);
fixed4 col = tex2D(_MainTex, p);
カラーグレーディング
なんかこう、昔のゲーセンには赤味が増したような画面がちょこちょこ見られた記憶があります。色を調整してみましょう。
iq神のシェーダーにカラーグレーディングが実装されています。これを使わせてもらいましょう!!
// color grading
col.rgb *= float3(1.25, 0.95, 0.7);
col.rgb = clamp(col.rgb, 0.0, 1.0);
col.rgb = col.rgb * col.rgb * (3.0 - 2.0 * col.rgb);
RGB成分のうち、強くしたいものに1より大きい数値を、弱くしたいものに1より小さな数値を掛け算してあげます。あとは0.0から1.0の範囲にクランプして、ついでに3次エルミートっぽく加工します。
さらに明るくしてしまいます。
col.rgb = 0.5 + 0.5 * col.rgb;
ああー…ゲーセンっぽい……。画面が白く焼けてる…。あるいは照度を特に意識しないで地下に取り付けられた照明の下にあるアーケード筐体の画面の見え方です。ああー…。
走査線
インターレースってやつですね。こう、画面を良く見ると網目っぽく出てるの。
Shadertoyにいいかんじのマリオシェーダーがあるので、これを使ってみましょう。
このシェーダーはスーパーマリオの1-1をプレイしてクリアするまでのようすが動く超絶コードの上、GLSLサウンドでBGMまで流れるとんでもないシロモノです。
ここから走査線とCRTモニタ風ヴィネットを使わせてもらいます。
float Scanline(float2 uv)
{
float scanline = clamp(0.95 + 0.05 * cos(3.14 * (uv.y + 0.008 * floor(_Time.y*15.) / 15.) * 240.0 * 1.0), 0.0, 1.0);
float grille = 0.85 + 0.15 * clamp(1.5 * cos(3.14 * uv.x * 640.0 * 1.0), 0.0, 1.0);
return scanline * grille * 1.2;
}
フラグメントシェーダー側でScanlineの値を色に掛け算します。
// scanline
col.rgb *= Scanline(i.uv);
拡大して見てみましょう。いいかんじにスジがでています。
CRTモニタ風ヴィネット
仕上げです。ヴィネットを入れてCRTモニタっぽくしましょう。
float2 CRT(float2 uv)
{
float2 nu = uv * 2. - 1.;
float2 offset = abs(nu.yx) / float2(6., 4.);
nu += nu * offset*offset;
return nu;
}
VGAっぽいアスペクトを作ります。640x480に近いサイズを想定した実装ですが、ここの6と4はテクスチャサイズに合わせて調整すると良いでしょう。
// crt monitor
float2 crt = CRT(i.uv);
crt = abs(crt);
crt = pow(crt, 15.);
col.rgb = lerp(col.rgb, (.0).xxx, crt.x + crt.y);
フラグメントシェーダー側で、画面の端側を黒くするようにそれっぽく線形補間します。
はい、ゲーセンのあまりメンテされていない画面っぽくなりました!!
いかがでしたでしょうか。
このシンプルなシェーダーの組み合わせで、ブラウン管的なレトロ調に変えるやり方がわかっちゃったと思います。
みなさまもこの80年代ルックをコンテンツに導入してみてはいかがでしょうか!
アドベントカレンダー明日16日目の記事はttk1さんによる「WebGLかThree.jsで何か書く。」です。
(ここに後でリンクを貼ります)
ではでは。
おまけ
時間で何かを動かす場合、コマ落ちさせるとさらにそれっぽくなったりするのではないでしょうか。_Time.yを使うかわりに以下のように加工すると、なんかカクカクしてファミコン的になるかもしれません。
floor(_Time.y*15.) / 15.
おまけその2
この作業はカラースペースにGamma指定でやっていたのですが、
「ひょっとして追加で余分なガンマ補正を入れちゃえば、白焼けしたより昭和のゲーセンっぽい画面になるのでは?」という発想で入れてみましたガンマ補正。
col.rgb = pow(col.rgb, (.4545).xxx);
ビンゴ!ビンゴです。こういうの、古いテーブル筐体型の画面でよく見かけた気がする…。ああー…ゲーセン、昔のゲーセンがUnityでよみがえったよ……。