この記事では、Godot Engineでの衝撃波シェーダーの作り方を紹介します。
といっても、以下の動画の内容で実装できるので、シェーダーコードはほぼそのままです
より詳しい解説を聞きたい場合には動画が参考になると思います(とてもわかりやすい説明です)
目次
衝撃波シェーダーの実装
衝撃波の動作確認用の画像です。”bg.png” として保存します。
画像の登録
画像をプロジェクトに追加して、ドラッグ&ドロップで配置します。
シェーダーの作成
bgノード (Sprite
) のインスペクタから、CanvasItem > Material > Material > [空]
をクリックして、「新規 Shader Material」を選びます。
作成した Shader Materialをクリックして、Shader > [空]
をクリック、「新規 Shader」を選びます。
作成した Shaderをクリックすると、シェーダーエディタが表示されます。
シェーダーコードの記述
シェーダーコードは以下のように記述します。
shader_type canvas_item;
uniform vec2 center = vec2(0.5, 0.5); // 衝撃波の中心
uniform float force : hint_range(-1.0, 1.0) = 0.1; // 歪みの強さ
uniform float size : hint_range( 0.0, 2.0) = 0.0; // 円の半径
uniform float thickness : hint_range(0.01, 1.0) = 0.1; // ドーナツの厚み
void fragment() {
// テクスチャ比を計算
float ratio = TEXTURE_PIXEL_SIZE.x / TEXTURE_PIXEL_SIZE.y;
// X方向の比率を変換する (x=0.0〜1.0 をテクスチャの比率にあわせる)
vec2 center2 = vec2(0.5 + (center.x-0.5) / ratio, center.y);
// テクスチャ比率に依存しない真円にする
vec2 scaleUV = (UV - vec2(0.5, 0.0) ) / vec2(ratio, 1.0) + vec2(0.5, 0.0);
// エルミート補間でドーナツ型のマスクサイズを決める
float mask = (1.0 - smoothstep(size-0.1, size, length(scaleUV - center2))) *
smoothstep(size-thickness-0.1, size-thickness, length(scaleUV - center2));
// 中心からの歪みを計算
vec2 disp = normalize(scaleUV - center2) * force * mask;
COLOR = texture(TEXTURE, UV - disp);
// 確認用.
//COLOR.rgb = vec3(mask);
}
現在のテキスチャに適用するために元のコードを修正しています。
(おそらく運用を考えると Viewport Texture に適用していくのがいいのでは……と考えこのようにしました)
もし動画のように画面全体に適用する場合は、以下の修正をします。
TEXTURE_PIXEL_SIZE
をSCREEN_PIXEL_SIZE
に修正UV
をSCREEN_UV
に修正- 中心座標
center
のX方向の比率をあわせる処理は不要
テクスチャの比率ではなく画面比率(アスペクト比)で計算して、スクリーンバッファのUVを使うことになります。
また、中心座標 center
の比率を左側 0.0 右側 1.0 に合わせたかったので、
// X方向の比率を変換する (x=0.0〜1.0 をテクスチャの比率にあわせる)
vec2 center2 = vec2(0.5 + (center.x-0.5) / ratio, center.y);
という逆変換の処理を入れていますが、これもおそらく不要となります。
CanvasItem > Shader Param
からパラメータの変化に対する動作を確認します。
画面中心から衝撃波が広がっていくのが確認できます。
最後に、スクリプトからこのシェーダーを呼び出すスクリプトを書きます。
bgノードに以下のスクリプトをアタッチします。
extends Sprite
# 衝撃波が有効かどうか
var shockwave_enabled := false
# 衝撃波の中心座標
var shockwave_center := Vector2()
# 衝撃波の半径
var shockwave_radius := 0.0
# 衝撃波の太さ
var shockwave_thickness := 0.1
# 衝撃波の強さ
var shockwave_force := 0.1
func start_shockwave(pos:Vector2):
# 衝撃波演出開始
shockwave_enabled = true
shockwave_radius = 0
shockwave_thickness = 0.1
shockwave_force = 0.2
# テクスチャサイズで割合を求める
var tex_size := texture.get_size()
shockwave_center.x = pos.x / tex_size.x
shockwave_center.y = pos.y / tex_size.y
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton:
if event.is_pressed():
# クリックで衝撃波演出開始
start_shockwave(get_viewport().get_mouse_position())
func _process(delta: float) -> void:
if shockwave_enabled:
# 半径サイズを更新
shockwave_radius += delta
shockwave_force *= 0.93 # 徐々に弱くする
# 各種シェーダーパラメータを設定
material.set_shader_param("center", shockwave_center)
material.set_shader_param("force", shockwave_force)
material.set_shader_param("size", shockwave_radius)
material.set_shader_param("thickness", shockwave_thickness)
if shockwave_force < 0.001:
# 衝撃波終了
material.set_shader_param("force", 0.0)
shockwave_enabled = false
実行してクリックした位置に衝撃波が出ることを確認します。
プロジェクトファイル
確認用にプロジェクトファイルをアップロードしておきました