LoginSignup
6

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-02-23

概要

この記事では、Godot Engineでの衝撃波シェーダーの作り方を紹介します。
shot.gif

といっても、以下の動画の内容で実装できるので、シェーダーコードはほぼそのままです

より詳しい解説を聞きたい場合には動画が参考になると思います(とてもわかりやすい説明です)

衝撃波シェーダーの実装

素材画像

衝撃波の動作確認用の画像です。"bg.png" として保存します。
bg.png

画像の登録

画像をプロジェクトに追加して、ドラッグ&ドロップで配置します。
Godot_Engine_-_TestShockWave____.png

シェーダーの作成

bgノード (Sprite) のインスペクタから、CanvasItem > Material > Material > [空] をクリックして、「新規 Shader Material」を選びます。
Godot_Engine_-_TestShockWave_-_Main_tscn.png
作成した Shader Materialをクリックして、Shader > [空] をクリック、「新規 Shader」を選びます。
Godot_Engine_-_TestShockWave_-_Main_tscn____.png
作成した Shaderをクリックすると、シェーダーエディタが表示されます。
Godot_Engine_-_TestShockWave_-_Main_tscn____.png

シェーダーコードの記述

シェーダーコードは以下のように記述します。

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_SIZESCREEN_PIXEL_SIZE に修正
  • UVSCREEN_UV に修正
  • 中心座標 center のX方向の比率をあわせる処理は不要

テクスチャの比率ではなく画面比率(アスペクト比)で計算して、スクリーンバッファのUVを使うことになります。

また、中心座標 center の比率を左側 0.0 右側 1.0 に合わせたかったので、
Godot_Engine_-_TestShockWave_-_Main_tscn____.png

    // X方向の比率を変換する (x=0.0〜1.0 をテクスチャの比率にあわせる)
    vec2 center2 = vec2(0.5 + (center.x-0.5) / ratio, center.y);

という逆変換の処理を入れていますが、これもおそらく不要となります。

動作確認

CanvasItem > Shader Param からパラメータの変化に対する動作を確認します。
shot.gif

画面中心から衝撃波が広がっていくのが確認できます。

クリックした位置から衝撃波が出現するようにする

最後に、スクリプトからこのシェーダーを呼び出すスクリプトを書きます。
bgノードに以下のスクリプトをアタッチします。

bg.gd
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   

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

プロジェクトファイル

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

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6