要約
- GRADIUS(グラディウス)、DARIUS(ダライアス)、XEXEX(ゼクセクス)風ラスタースクロールの紹介です
- XEXEX風シェーダーの実装例が探しても見当たらないので自作しました
環境:Godot4.2+ C#(.Net Framework6.0)
はじめに
1枚の背景絵を元にした動きのある背景をシェーダーで作ります。
背景スクロールの実装には色々な方法がありますが、1組の背景絵とシェーダーだけで実現できるのがコンパクトなので気に入ってます。
実装はGodot Shaderを使っていますがシンプルな方式なので、他のシェーダー実装にも容易に移植できるでしょう。
横スクロール
シンプルな横スクロールです。 時間経過に伴い描画する座標を徐々に右(UV座標系のX+)にずらしていきます。そのままだと元絵に無い部分が歯抜けとなってしまうので、左にはみ出た部分を右側に都度補完していきます。
godot shaderのキーワード説明もかねて紹介しています。
shader_type canvas_item;
uniform float speed = 1;
void fragment() {
vec2 vec = UV;
float t = mod(TIME * speed, 1);
vec.x = mod(vec.x + t, 1);//時間経過に伴い右にずらし、左からはみ出た分を右に補完する
COLOR = texture(TEXTURE, vec);
}
shader_type canvas_itemはキャンバス用のシェーダーの宣言です。
uniform float speed はインスペクタやスクリプトからシェーダーにパラメータを与える為の書式です。機能的には外部から引数を与るものです。
void fragment()はfragmentシェーダーの宣言部です。頂点シェーダーは使いません。
大文字のUV、TIME、TEXTUREはシステム変数です。関数内から参照できます。
float tは時間情報と剰余を用いてfloat t に0~1の範囲の値を得ます。
vec.xもUV座標系のx座標であるため0~1の値をとります。tを加えて1の剰余を得る事で横スクロールになります。
グラディウス風
スクロール速度の異なる複数の光点を並列にスクロールします。
shader_type canvas_item;
uniform float speed = 0.01;
uniform float ysize = 288;
void fragment() {
vec2 vec = UV;
float t = mod(TIME * speed, 1);
int m = int(vec.y * ysize) % 5;
vec.x = mod(vec.x + t * float(m + 1), 1.0);
COLOR = texture(TEXTURE, vec);
}
ysizeは縦方向のピクセル数です。(この例では512*288の画像を使っています。)
同じ剰余でも浮動小数点数のmod()ではなく整数型の%を使っています。元絵のピクセル1行毎に変換をかけたいため、小数点以下を切り捨てる目的でint型を使っています。そのためint()やfloat()といったキャスト関数があちこちに入って少々見づらくなってます。
int mには1行毎に0,1,2,3,4,0,1,....のように行毎の移動量が入ります。元絵の右側に1本の直線を引いてあるので、このシェーダーの動きを理解するのにちょうど良いでしょう。
ダライアス風
皆(?)の憧れ、ゆらゆら揺れるサインカーブスクロールです。
shader_type canvas_item;
uniform float speed = 0.5;
uniform float depth = 0.1;
void fragment() {
vec2 vec = UV;
float t = mod(TIME * speed, 1);
float radian = (vec.y - t) * PI * 2.0;
vec.x = vec.x + sin(radian) * depth;
COLOR = texture(TEXTURE, vec);
}
float radian はy座標の値(0~1)と時間経過に伴う変数t(0~1)から-1~1の範囲の数値に2πをかえて-2π~2πの値をとります。
xの値をsin(radian)で補正します。depthはユラユラの振れ幅です。
ゼクセクス風
正直これを再現できたことが嬉しくてこの記事を書いてます笑
shader_type canvas_item;
uniform float speed = 0.5;
uniform float xo = 0.33;//X原点
uniform float yo = 0.5;//Y原点
void fragment() {
vec2 vec = UV;
float t = mod(TIME * speed, 1);
float y = (vec.y - yo) * 2.0; //y軸の0~1を、-1~1の値に変換
vec.x = (vec.x - xo) * (1.0 / abs(y)); //(1)消失点方向に縮小をかける
vec.x = mod(vec.x + t, 1); //(2)左へスクロール
vec.y = mod(vec.y + t, 1); //(3)上へスクロール
COLOR = texture(TEXTURE, vec);
if((-0.05<y)&&(y<0.05)) //(4)水平線付近のモアレを非表示
COLOR = vec4(0, 0, 0, 0);
}
xo,yoが消失点になります。
(1) 1/ abs(y) は画面横方向の縮小係数です。この式で元の画像を消失点方向へ絞るように変形します。
(2)横方向スクロールです。X方向に関する変形ですが(1)と順番を入れ替えてしまうと意図した変形にならないので、この順序で変形してください。
(3)縦方向スクロールです。
(4)水平線付近のモアレがCG酔いを誘発するので別の色で塗りつぶします。
余談
(3)は消失点に向かって集まり再度拡散するように動きます。このままでもオリジナル通りなので必要充分ですが、水平線から拡散するようにしても良いでしょう。
vec.y = mod(vec.y -t*sign(y), 1);//(3')水平線から上下に拡散する