次第に姿を現す蜃気楼シェーダなど [ゲーム開発ログ 2020-09-14]

ゲーム開発ログを残すことにした
個人でゲームを作ろうと思い、日々少しずつゲームに必要な部品を作ったり絵作りの実験をしたりしている。 コツコツやってはいるのだが、ゲーム開発というものは時間がかかる。 仕事で作るゲームでも時間がかかるので、趣味の開発なら尚更だ。
もうちょっと アウトプットしてる実感 を得たくなったので、 これからはその細かい進捗を週単位くらいでサイトに記録することにした。 こういうのは習慣化が大事だ。
後から当時の思考や技術的なトピックスを振り返るのにもちょうどいいし、 知人に YTRK (やってる感) をアピールするのにも使える。
かつて、僕の師 (と僕が勝手に思ってるエンジニアの先輩) は、
「創作活動は URL を持つところまでやれ」 という言葉をくれた。
それを実行しよう。
色々な世界観をつくる
自作の 3D マップをキャラが歩くようなゲームがつくりたい。
僕は 3D マップをキャラが歩くようなゲームが好きだ。

この鳥居のマップは結構気に入っている。 別の記事 でも書いたが、自作シェーダによってテクスチャやポイントライトを使わずにこの色味を出しているというのがウリ。
横スクロール系
2D っぽい横スクロール系の画面も試してみた:

LIMBO とか INSIDE みたいなやつ。 動画だと以下のような感じになる。
手前を草木が通るとそれっぽく見える。
シーンは普通に 3D で作っている。画角狭めのカメラで遠くから撮ると先の動画のような画面が得られる。

これもポイントライトなどの光源は使わず、シェーダのパラメータによるグラデーションの色付けだけで画面を作っている。 (ポストエフェクトの Bloom は使っている)
蜃気楼シェーダ

遠くにあるうちはもやもやしていて、近づくと次第に姿を現すようなシェーダを書いてみた。 結構面白い見た目になった(※ 動画は音あり):
処理はシェーダで完結している。 Unity URP のシェーダでは、以下でカメラからの距離が取れるのでそれを利用する:
half distanceToCamera = TransformWorldToView(vertexInput.positionWS).z * -1;
頂点シェーダで遠くにあるほどウネウネさせて、
UNITY_BRANCH
if (_MirageOn > 0 && _MirageFactor1 > 0)
{
half noiseFactor = max(distanceToCamera - _MirageFactor1, 0);
noiseFactor = noiseFactor * noiseFactor * 0.01;
output.positionCS.x += sin(output.positionCS.y + _Time.y * 3) * noiseFactor;
output.positionCS.z += cos(output.positionCS.x + _Time.y * 2) * noiseFactor * 0.2;
}
フラグメントシェーダで遠くにあるほど描画をくり抜く(clip する)ような処理を書いている:
UNITY_BRANCH
if (_MirageOn > 0 && _MirageFactor2 > 0)
{
half3 noiseFactor = input.posWS;
noiseFactor.x += sin(input.posWS.y + _Time.y * 1.5);
noiseFactor.z += cos(input.posWS.x + _Time.y * 1);
half d = distanceToCamera / _MirageFactor2;
half dissolveFactor = d * noise(noiseFactor) + (d * 0.1);
clip(0.5 - dissolveFactor);
half dissolveEdge = saturate(1 - saturate(1 - dissolveFactor - 0.5) * 15);
color.rgb += half3(1, 1, 1) * dissolveEdge * 0.5;
}
ノイズ関数はネットで拾った以下のコードを使用:
float rand(float3 seed)
{
return frac(sin(dot(seed.xyz, float3(12.9898, 78.233, 56.787))) * 43758.5453);
}
float noise(float3 pos)
{
float3 ip = floor(pos);
float3 fp = smoothstep(0, 1, frac(pos));
float4 a = float4(
rand(ip + float3(0, 0, 0)),
rand(ip + float3(1, 0, 0)),
rand(ip + float3(0, 1, 0)),
rand(ip + float3(1, 1, 0))
);
float4 b = float4(
rand(ip + float3(0, 0, 1)),
rand(ip + float3(1, 0, 1)),
rand(ip + float3(0, 1, 1)),
rand(ip + float3(1, 1, 1))
);
a = lerp(a, b, fp.z);
a.xy = lerp(a.xy, a.zw, fp.y);
return lerp(a.x, a.y, fp.x);
}
悪夢っぽい感じの世界
蜃気楼シェーダはこんな表現にも使える:
遠くのものは消えるので、カメラの描画範囲の設定で近くのものだけ描画するようにできる。 オブジェクトが広範囲に多く存在しているシーンでも描画範囲を近くだけにすれば大きくパフォーマンスが稼げる。