【Godot】衝撃波シェーダーの作り方

この記事では、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   

実行してクリックした位置に衝撃波が出ることを確認します。

プロジェクトファイル

確認用にプロジェクトファイルをアップロードしておきました

参考