【Godot】Fragment Shaderの基礎と最初の一歩 - 色変更とエフェクトの実現

作成: 2025-12-08最終更新: 2025-12-16

Godot EngineにおけるFragment Shaderの基本的な役割から、UV座標、時間、ノイズを使った動的エフェクトまで、具体的なコード例とパフォーマンスの最適化手法を解説します。

導入:なぜシェーダーを学ぶべきか

Godot Engineでゲーム開発を進める上で、 シェーダー は避けて通れない強力なツールです。シェーダーとは、グラフィックス処理ユニット(GPU)上で実行される小さなプログラムのことで、オブジェクトの見た目、色、光の反射、そして画面全体のエフェクトをピクセル単位で制御します。

なぜシェーダーが重要なのでしょうか?それは、 視覚的な魅力を飛躍的に向上させる と同時に、 高いパフォーマンス を実現できるからです。CPUで複雑な計算を行う代わりに、GPUの並列処理能力を最大限に活用することで、炎、水、カスタムライティング、ユニークな画面トランジションなど、GDScriptだけでは実現が難しい表現を軽快に実現できます。

本記事では、シェーダーの中でも特に視覚的な表現を担う Fragment Shader(フラグメントシェーダー) に焦点を当て、その基礎知識から、色変更やエフェクトを作成する最初の一歩を、具体的なコード例とともに解説します。


Godotシェーダーの基本構造

Godot Engineのシェーダーは、GLSL ES 3.0に似た独自のシェーディング言語を使用します。シェーダーファイル(.gdshader)を作成する際、まずそのシェーダーが何に適用されるかを定義する必要があります。

shader_type canvas_item; // 2Dオブジェクトに適用
// shader_type spatial; // 3Dオブジェクトに適用

canvas_itemは2DスプライトやUI要素に、spatialは3Dメッシュに適用されます。

Godotシェーダーは、主に以下の3つの関数(エントリーポイント)で構成されています。

関数名役割実行タイミング
vertexオブジェクトの頂点(位置)を操作します。変形、波打ち、回転などに使用されます。頂点ごとに1回
fragmentピクセル(フラグメント)の色を計算します。 テクスチャのサンプリング、色調整、エフェクトの核となります。ピクセルごとに1回
lightオブジェクトに当たる光の影響を計算します。カスタムライティングの実現に使用されます。光源とピクセルの組み合わせごとに1回

本記事の主役であるfragment関数は、画面上の すべてのピクセル に対して実行され、そのピクセルの最終的な色を決定する役割を担います。


実践1: Fragment Shaderによる基本的な色操作

Fragment Shaderの最も基本的な役割は、ピクセルの色を決定することです。ここでは、COLOR出力変数とtexture()関数を使った基本的な色の操作方法を学びます。

コード例1:単色での塗りつぶし

オブジェクトを特定の色で完全に塗りつぶす、最もシンプルなシェーダーです。

shader_type canvas_item;

void fragment() {
    // COLOR変数に直接、新しい色をvec4(R, G, B, A)形式で代入します。
    // 各成分は0.0から1.0の範囲です。
    COLOR = vec4(0.8, 0.2, 0.3, 1.0); // ワインレッド
}

コード例2:テクスチャのネガポジ反転

元のテクスチャの色を活かしつつ、それを加工してみましょう。texture()関数で現在のピクセルの色を取得し、反転させます。

shader_type canvas_item;

void fragment() {
    // 1. 現在のUV座標におけるテクスチャの色を取得します。
    vec4 original_color = texture(TEXTURE, UV);

    // 2. RGB成分を1.0から引くことで色を反転させます。
    vec3 inverted_rgb = vec3(1.0) - original_color.rgb;

    // 3. 最終的な出力色として、反転したRGBと元のアルファ値を設定します。
    COLOR = vec4(inverted_rgb, original_color.a);
}

コード例3:セピア調フィルター

より実践的な例として、画像に古典的なセピア調のフィルターをかけてみましょう。

shader_type canvas_item;

void fragment() {
    vec4 original_color = texture(TEXTURE, UV);
    vec3 c = original_color.rgb;

    // グレースケールに変換(輝度計算)
    float gray = dot(c, vec3(0.299, 0.587, 0.114));

    // セピア色のトーンを適用
    vec3 sepia_color = vec3(
        gray * 1.07, // 赤みを強く
        gray * 0.74, // 緑を抑えめに
        gray * 0.43  // 青をさらに抑える
    );

    COLOR = vec4(sepia_color, original_color.a);
}

実践2: UV座標と時間を使った動的エフェクト

Fragment Shaderの真価は、UV(テクスチャ座標)やTIME(時間)といった組み込み変数を使って、静的な画像を動的なエフェクトに昇華させられる点にあります。

コード例4:UV座標を使った円形マスク

UV座標を加工して、スポットライトのような円形マスクを作成します。

shader_type canvas_item;

void fragment() {
    // 1. UV座標の中心を(0,0)に補正します。
    vec2 centered_uv = UV - vec2(0.5);

    // 2. 中心からの距離を計算します。
    float dist = length(centered_uv);

    // 3. smoothstepを使い、境界線を滑らかに描画します。
    float mask = 1.0 - smoothstep(0.3, 0.4, dist);

    vec4 original_color = texture(TEXTURE, UV);

    // 4. 元のアルファ値にマスクを乗算して適用します。
    COLOR = vec4(original_color.rgb, original_color.a * mask);
}

コード例5:時間を使ったスクロールアニメーション

TIME変数をUV座標に加算することで、テクスチャを自動でスクロールさせることができます。流れる水面や雲の表現に不可欠です。

shader_type canvas_item;

uniform float scroll_speed = 0.1;

void fragment() {
    // 1. UV座標のx成分に時間を加算して、UVをずらします。
    vec2 scrolled_uv = UV + vec2(TIME * scroll_speed, 0.0);

    // 2. fract()関数でUV座標を0.0-1.0の範囲にループさせます。
    scrolled_uv = fract(scrolled_uv);

    // 3. 新しいUV座標でテクスチャをサンプリングします。
    COLOR = texture(TEXTURE, scrolled_uv);
}

fract()関数を使うことで、テクスチャが無限にスクロールし続けるようになります。

コード例6:ノイズを使ったディゾルブエフェクト

ノイズテクスチャを利用すると、より有機的で複雑なエフェクトが作成可能です。

shader_type canvas_item;

// ノイズテクスチャを外部から設定
uniform sampler2D noise_texture;
// ディゾルブの進行度をGDScriptから制御
uniform float dissolve_threshold : hint_range(0.0, 1.0) = 0.5;

void fragment() {
    // ノイズテクスチャから値を取得
    float noise_value = texture(noise_texture, UV).r;

    // ノイズの値がしきい値より低い場合、ピクセルを破棄(透明に)する
    if (noise_value < dissolve_threshold) {
        discard; // discardはピクセルを描画しない命令
    }

    COLOR = texture(TEXTURE, UV);
}

dissolve_thresholdをGDScriptから0.0から1.0まで変化させることで、滑らかなディゾルブアニメーションが実現できます。


パフォーマンスと最適化

シェーダーは強力ですが、無計画な実装はパフォーマンスのボトルネックになり得ます。特にFragment Shaderはピクセル単位で実行されるため、わずかな非効率が大きな負荷につながります。

  • if文を避ける: GPUの並列処理の性質上、if文による分岐はコストが高い処理です。可能な限りstep(), smoothstep(), mix()といった分岐を伴わない関数でロジックを代替しましょう。
  • 重い計算はVertex Shaderで: ピクセルごとに変化しない計算(例:TIMEを使ったsin/cosの計算など)は、Vertex Shaderで行い、その結果をvarying変数でFragment Shaderに渡すことで、計算量を大幅に削減できます。
  • テクスチャサンプリングを減らす: texture()関数の呼び出しは比較的重い処理です。同じテクスチャを複数回サンプリングする場合は、結果を変数に保存して再利用しましょう。

よくある間違いとベストプラクティス

Fragment Shaderを扱う上で陥りがちなミスと、それを避けるためのベストプラクティスをまとめました。

よくある間違いベストプラクティス
if文の多用step(), smoothstep(), mix()関数を活用して、条件分岐を算術演算に置き換える。
UV座標のハードコーディングUVは常にvec2(0.0, 1.0)の範囲にあるとは限りません。fract()mod()を使ってUVを正規化する習慣をつける。
discardの過度な使用discardは便利な一方、一部のGPUでは深度テストの最適化を阻害することがあります。アルファ値を0にする方法も検討する。
テクスチャの過剰なサンプリング同じテクスチャを何度もtexture()でサンプリングすると負荷が増えます。結果を変数に保存して再利用する。
GDScriptでできることをシェーダーでやるシェーダーはピクセル単位の操作に特化しています。オブジェクト全体の単純な色変更などは、Sprite2Dmodulateプロパティを使った方がシンプルで高速な場合があります。

まとめ:Fragment Shaderの次なる一歩

本記事では、Godot EngineにおけるFragment Shaderの基本的な役割、構造、そして色変更や動的なエフェクトを実現する方法を学びました。

Fragment Shaderは、ピクセルごとの色を計算する強力なツールであり、 UV 座標や TIME といった組み込み変数を活用することで、無限の視覚エフェクトを生み出すことができます。

本記事の要点:

  • シェーダーはGPUで実行され、高いパフォーマンスで視覚表現を制御します。
  • fragment 関数はピクセルごとの色を決定し、 COLOR 変数に出力します。
  • UV 座標はテクスチャ上の位置を示し、グラデーションや座標ベースのエフェクトに不可欠です。
  • TIME ユニフォームを使うことで、効率的な時間ベースのアニメーションが可能です。

この基礎をマスターしたら、次は外部から値を渡す ユニフォーム(Uniforms) を学び、GDScriptからシェーダーのパラメーターを制御する方法や、 vertex シェーダーを使ったオブジェクトの変形に挑戦してみましょう。