LoginSignup
4
2

More than 3 years have 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 int   samples  : hint_range(2, 64, 2) = 16;  // サンプル回数
uniform float strength : hint_range(0.0, 1.0) = 0.0; // ブラーの広がる強さ
uniform vec2  center = vec2(0.5, 0.5); // 中心座標

void fragment() {
    // 最終的な色
    vec4 color = vec4(0.0); // 黒で初期化
    // 中心を基準にする
    vec2 pos   = UV - center;
    // 中心からの距離
    float dist = length(pos);
    float factor = strength / float(samples) * dist;
    for(int i = 0; i < samples; i++) {
        float uvOffset = 1.0 - factor * float(i);
        color += texture(TEXTURE, pos * uvOffset + center);
    }
    // 平均を求める
    color /= float(samples);

    COLOR = color;

    // テスト用
    //COLOR.rgb = vec3(dist);
}

CanvasItem > Shader > Shader Param > Strength でブラーの強さ(大きさ)が指定できます。
shot.gif
放射ブラーのしくみとしては、 元の画像を拡大しつつ何回もピクセルの書き込みを行う処理となります。
【Godot】2Dモーションブラーの実装方法」 で紹介したモーションブラーが「ベクトル(移動量)」のブラーであるのに対して、放射ブラーはスケール(拡大縮小)のブラーと言えるのかもしれません。

なお、Samples パラメータがブラーのピクセル書き込み回数です。この値を大きくするほどブラーのぼかし品質が上がりますが処理負荷は増大します。だからといって回数を少なくすぎると境界が目立ちすぎるので、個人的にはSamplesは「8〜16回」くらいが良いのかなと思います。

クリックした位置に放射ブラーをかける

スクリプトから、このブラー処理を呼び出せるようにします。
bgノード(Sprite)にスクリプトをアタッチして、以下のように記述します。

bg.gd
extends Sprite

const TIME_BLUR := 2.0; # 2秒

# ブラーの有効時間
var time_blur := 0.0

func start_radial_blur(pos:Vector2) -> void:
    # テクスチャのサイズ
    var tex_size := texture.get_size()

    # シェーダーに渡すブラーの中心座標
    var blur_center := Vector2()
    blur_center.x = pos.x / tex_size.x
    blur_center.y = pos.y / tex_size.y

    # 中心座標を設定
    material.set_shader_param("center", blur_center)

    # 放射ブラー開始
    time_blur = TIME_BLUR

func _input(event: InputEvent) -> void:
    if event is InputEventMouseButton:
        if event.is_pressed():
            # クリックで放射ブラー開始
            start_radial_blur(get_viewport().get_mouse_position())

func _process(delta: float) -> void:

    material.set_shader_param("strength", 0)
    if time_blur > 0:
        # シェーダー更新
        time_blur = max(0, time_blur - delta)
        # sinカーブで 0.0〜1.0 の半円を移動する
        var force := abs(sin(PI * time_blur / TIME_BLUR))
        material.set_shader_param("strength", force)

実行すると、クリックした位置を中心に放射ブラーがかかるようになります。
shot.gif

放射ブラーが適用されない部分を作る

最後に、放射ブラーが適用されない部分を設定できるようにします。
シェーダーコードを以下のように修正します。

shader_type canvas_item;

uniform int   samples  : hint_range(2, 64, 2) = 16;  // サンプル回数
uniform float strength : hint_range(0.0, 1.0) = 0.0; // ブラーの広がる強さ
uniform float mask     : hint_range(0.0, 1.0) = 0.0; // ブラーが適用されないサイズ
uniform vec2  center = vec2(0.5, 0.5); // 中心座標

void fragment() {
    // 最終的な色
    vec4 color = vec4(0.0); // 黒で初期化
    // 中心を基準にする
    vec2 pos   = UV - center; 
    // 中心からの距離
    float dist = length(pos);
    float factor = strength / float(samples) * dist;

    // ブラーが適用されないされない範囲を計算。0.1の範囲をぼかす
    factor *= smoothstep(mask-0.1, mask, dist);

    for(int i = 0; i < samples; i++) {
        float uvOffset = 1.0 - factor * float(i);
        color += texture(TEXTURE, pos * uvOffset + center);
    }
    // 平均を求める
    color /= float(samples);

    COLOR = color;

    // テスト用
    //COLOR.rgb = vec3(dist);
}

maskというパラメータが新たに追加されています。
これは中心から放射ブラーを適用しないサイズとなります。
また、適用する部分と適用しない部分で不連続だと境界が目立ちすぎてしまうので、smoothstepで補間処理を入れてぼかすようにしています。

実行すると放射ブラーの除外範囲が指定できます。
shot.gif

なぜこのような記述をするのかについては、以前に「衝撃波シェーダー」を作る際に紹介した動画が参考になると思います。

追記 (2021.2.23): 回転放射ブラーを実装する

シェーダーで遊んでいたら、回転放射ブラーが実装できたので追記します。
shot.gif

shader_type canvas_item;

uniform int   samples  : hint_range(2, 64, 2) = 16;  // サンプル回数
uniform float strength : hint_range(0.0, 1.0) = 0.0; // ブラーの広がる強さ
uniform float mask     : hint_range(0.0, 1.0) = 0.0; // ブラーが適用されないサイズ
uniform float vortex   : hint_range(0.0, 20) = 0.0;  // 回転値(ラジアン)
uniform vec2  center = vec2(0.5, 0.5); // 中心座標

void fragment() {
    // 最終的な色
    vec4 color = vec4(0.0); // 黒で初期化
    // 中心を基準にする
    vec2 pos   = UV - center; 
    // 中心からの距離
    float dist = length(pos);
    // 中心からの角度
    float angle = atan(pos.y, pos.x);
    float factor = strength / float(samples) * dist;

    // ブラーが適用されないされない範囲を計算。0.1の範囲をぼかす
    factor *= smoothstep(mask-0.1, mask, dist);

    for(int i = 0; i < samples; i++) {
        float uvOffset = 1.0 - factor * float(i);
        // 回転をかける
        vec2 ofs = vec2(dist * cos(angle + vortex * uvOffset), dist * sin(angle + vortex * uvOffset));
        color += texture(TEXTURE, ofs * uvOffset + center);
    }
    // 平均を求める
    color /= float(samples);

    COLOR = color;

    // テスト用
    //COLOR.rgb = vec3(dist);
}

Vortex で回転値を指定します。

プロジェクトファイル

プロジェクトファイルは以下からダウンロードできます
http://syun777.sakura.ne.jp/tmp/godot/TestRadialBlur.zip

参考

4
2
0

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
4
2