シェーダー入門編
この記事は Godot 3から Godot 4 へ内容の書き換え中です。 Godot4では存在しない変数、関数が含まれている場合があります。もしその場合はリポジトリのIssuesまでご報告ください。
課題
シェーダーのコーディングをやってみたい。
解決策
シェーダーとは、コンピュータのGPU(グラフィックスカード)上で動作する専用プログラムのことです。GPUは特定種類の演算処理を極めて効率的に実行できるよう最適化されています。このシェーダーコードをオブジェクトに適用することで、画面上でのレンダリング方法に影響を与えることができます。
シェーダープログラムの出力結果は、オブジェクトを構成するピクセル群の色情報です。シェーダーは2D環境(canvas_item 用シェーダー)および3D環境(spatial シェーダー)で利用できます。
シェーダーに関して初心者が特に理解しにくいのは、これが並列処理で行われるという点です。シェーダーは画面全体のすべてのピクセルに対して同時に実行されます。この仕組みにより処理速度が大幅に向上しますが、一方でシェーダー内でアクセス可能な情報にも制限が生じます。
オブジェクトにシェーダーを追加するには、まず対象のオブジェクトの[マテリアル]プロパティを見つけ、「新規シェーダーマテリアル」を選択します。新しく作成したマテリアルをクリックして開くと、「新規シェーダー」を選択する画面が表示されます。さらにクリックすると、画面下部にシェーダー編集画面が開きます。
シェーダーの最初の行にはそのタイプを指定が必要です。接続されているノードが2Dノードの場合は。
shader_type canvas_item;
もしくは3Dノードの場合:
shader_type spatial;
これらの初期例では2Dに限定して進めてください。まずSpriteノードを追加し、上記の手順に従ってシェーダーを適用してください。テクスチャにはGodotのデフォルトアイコンを使用することもできます。
ここで解説するシェーダーには主に2種類あります。 頂点シェーダー と フラグメントシェーダー です。
フラグメントシェーダー
フラグメントシェーダーはピクセルの色を計算します。具体例を見てみてください。
void fragment() {
COLOR = vec4(1.0, 0.0, 0.0, 1.0);
}
全てのピクセルが赤色になります。COLORはフラグメントシェーダーの出力値であり、これをすべてのピクセルに同時に適用します。しかし、何らかのバリエーションを持たせたい場合はどうしますか?
UV座標
シェーダーでは、ピクセル座標は UV 表記で指定されます。これらは正規化された値で、範囲は (0, 0)(左上)から (1, 1)(右下)までです。
シェーダーではベクトル型 (vec4) を使用してRGBAカラーを表現します。個々の成分には color.r のようにアクセスできます。色をベクトルとして扱うことで、ベクトル演算に基づく様々な興味深いエフェクトを実現できます。
void fragment() {
COLOR = vec4(UV.x, 0.0, 0.0, 1.0);
}
現在の赤色チャンネルは、左側から右側にかけて0から1.0まで変化し、これはUV座標とともに変動します。
別の例:
void fragment() {
COLOR = vec4(UV.x, 1.0 - UV.y, 0.5, 1.0);
テクスチャ
ピクセルカラーを直接設定するため、Godotアイコンのデータは破棄されています。テクスチャデータにはTEXTURE入力とtexture()関数を使用してアクセスできます。
void fragment() {
COLOR = texture(TEXTURE, UV);
}
これで元の画像に戻りました。各ピクセルの色は、それぞれのUV座標に対応するテクスチャの色値に設定されています。
また、COLOR出力の特定のチャンネルのみを変更することもできます。
void fragment() {
COLOR = texture(TEXTURE, UV);
COLOR.a = 1.0 - UV.x;
}
この操作によりアルファチャンネルの値が低下し、フェードアウト効果が得られます。
時間で変動させる
もう一つの便利な組み込みシェーダープロパティはTIMEで、現在の経過時間を表す増加し続ける値を提供します。さらに、範囲が-1から1であるsin()関数も使用すると、以下のような効果が得られます。
void fragment() {
COLOR = texture(TEXTURE, UV);
COLOR.a = abs(sin(TIME * 0.5));
}
またはこちら:
void fragment() {
COLOR = texture(TEXTURE, UV);
COLOR.a = max(0.0, UV.x - abs(sin(TIME)));
}
頂点シェーダー
頂点シェーダーはオブジェクトの頂点を操作し、変形や拡大縮小できます。フラグメントシェーダーが各ピクセルごとに処理されるのと同様に、頂点シェーダーもオブジェクトの各__頂点__に対して実行されます。canvas_item シェーダーの場合、通常はテクスチャの4隅の頂点を指します。spatial シェーダーの場合は、メッシュの各頂点に対する処理となります。
例えば、以下の例でどうなるか観察してみます。
void vertex() {
VERTEX.x += UV.x * 10.0;
}
このシェーダーでは、左側の2つの頂点 (0, 0) と (0, 1) は変更されず、右側の頂点がそれぞれ (10, 0) と / 10, 1) に変わります。
頂点位置を時間経過に応じて変化させることで、さまざまな興味深い効果を生み出せます。
void vertex() {
VERTEX.y += sin(UV.x * TIME) * 10.0;
}
ユニフォーム
シェーダーに値を渡すには、uniform キーワードで宣言された変数が必要です。この設定すると、インスペクターにはexport変数と似た形でその変数が表示されます。ただし注意点として、ユニフォーム変数の値はシェーダー内で変更することはできません!
均一値(uniforms)はシェーダー全体でグローバルに使用可能であり、任意の関数からアクセスできます。
ヒント
インスペクターで値を設定する際に補助として使用できるオプションの ヒント も利用できます。
uniform float radius : hint_range(0, 1);
各種データ型に対応するヒントが用意されています。完全なリストについては シェーダー言語リファレンス を参照してください。
まとめ
これはシェーダーで実現できる機能のほんの一例に過ぎません。このセクションの他のレシピも参照して、プロジェクトで使えるテクニックを増やしていきます。