Godot Engineのカレンダー | Advent Calendar 2023 - Qiitaの11日目の記事です。
◆前の日は@NumAniCloudさんのC#プロジェクトを分割して開発しよう、の話です。
◆次の日はきゃべつさんのGodot(C#)シーン派生とクラス派生 #C# - Qiitaです。
現在、Godotへの移住を検討するために調査をしています。丁度、シェーダーの使い方を調べているところだったので、リアル「はじめてのGodotシェーダー」です。Godotの公式マニュアルには基礎から参考リンクまでばっちりまとまっています。
ただ動かすまでに少し手数がかかるので、Godotのインストールから簡単な2Dフラグメントシェーダーを作る流れをまとめました。GodotはノードベースのVisual Shaderも持っているのですが今回はコードで書きます。公式マニュアルの最初の2Dシェーダーの作り方を少しアレンジしたものと、The Book of Shadersに掲載されているサンプルコードをいくつか動かします。
Godotやシェーダーを使ったことがない人でも眺めてなんとなく雰囲気が伝わる記事を目指しました。
目次
- 目次
- バージョン
- 対象読者
- Godotとは
- Godotのインストール
- プロジェクト作成
- はじめてのシェーダープログラミング
- 画面の位置を色に反映させる
- マテリアルから値を渡す-Uniform入力-
- マテリアルから画像を設定
- The Book of Shadersを試す
- まとめ
- 参考・関連URL
バージョン
このブログ記事を書いた環境は以下の通りです。
- Windows10
- Godot4.2.0
対象読者
Godotにもシェーダーにも触れたことがない人でも動かすことはできると思います。
Godotとは
2Dも3Dもいけるオープンソースのゲームエンジンです。Win, Mac, LinuxのデスクトップPCやAndroidとiOS、WebGL用のビルドもできます。Switchのようなコンソールへの出力は公式ではサポートしていませんが不可能ということではありません。コンソールの開発に必要な情報に守秘義務があるためオープンソースのGodotに含めることができないということです。自力で対応したり、経験があるパブリッシャーを通せばリリースできます。※丁度この記事の公開日にW4 GamesからGodotのプロジェクトをSwitchやXBox, PS5といったコンソールに出力するサービスの開始予定と価格についての発表がありました!
開発言語はいくつか選択肢がありますがGDScriptという独自言語が推奨されています。インデントでコードのブロックを表すなどPythonに雰囲気が似ています。C#もスクリプトとして使えるように整備が進んでいますが、正式対応はデスクトップのみで4.2からiOSやAndroidでも実験的に動くようになりました、という段階です。
シェーダー言語はGLSLをベースにした独自のシェーダー言語とVisual Shaderが使えます。このブログではシェーダー言語でフラグメントシェーダーを書いてみます。
詳しくは公式ドキュメントをどうぞ。
Godotのインストール
Godotはオープンソースのゲームエンジンなので、管理者権限もインストールもアカウント作成も必要ありません。zipファイルをダウンロードして展開すればすぐに使えます。この手軽さは最高です!
- 公式ページを開きます
- Download Latestをクリックします
- 寄付(Donation)画面を×をクリックして閉じます。儲かったらぜひ寄付してください
ダウンロードしたzipファイルを任意の場所に移動して展開すれば完了です。フォルダーの中のGodot_v4.2-stable_win64.exe
をダブルクリックすればエディターが起動します。
プロジェクト作成
シェーダーを試すためのプロジェクトを作成します。
- 新規ボタンをクリックします
- レンダラーはデスクトップ用のForward+を選んで、フォルダーを選ぶために参照ボタンをクリックします
- プロジェクトを作成したいフォルダーの位置を指定して、新しいフォルダーを作成します
- プロジェクト名のフォルダー名を入力します。以下は
AdcaleFirstShader
とした例です
- フォルダーを作成したら、現在のフォルダーを選択をクリックします
- 作成して編集ボタンをクリックします
以上で新規のプロジェクトが作成できます。
※Godotは動作がまあまあ不安定で、まあまあいきなり落ちます。適度に保存をしながら作業を進めてください。特に間違った操作をしていなくても落ちるときがあるので、落ちても気にしないで起動し直して作業を継続して大丈夫です。
はじめてのシェーダープログラミング
細かいことはあとにして動かしてみます。シェーダーにはいくつか種類があります。はじめての今回は効果が分かりやすいフラグメントシェーダーを使ってみます。
シェーダーの描画先をColorRectで作成
フラグメント(=fragment)は断片というような意味です。フラグメントシェーダーは対象となる図形のピクセルの色を設定するためのプログラムです。塗りつぶす範囲を表す図形とシェーダープログラムを設定するマテリアルが必要です。まずはシンプルに2Dで試してみます。
- シーンの+をクリックして、検索欄に
color
と入力します。ColorRectが見つかったら選択して、作成ボタンをクリックします
- ColorRectを画面いっぱいに設定します。インスペクターのLayoutをクリックして開いて、Anchors Preset欄をクリックしてRect全面を選択します
- Ctrl+Sキーを押して保存します。ファイル名はそのままでも構いませんし、
first_2d_shader.tscn
などにしてもよいでしょう
- 実行してみます。F6キーを押すか、現在のシーンを実行ボタンをクリックします
成功するとGodotのロゴが表示されたあとに真っ白なウィンドウが表示されます。
確認したらウィンドウを閉じます。
これでフラグメントシェーダーで描画する先ができました!
はじめてのシェーダープログラミング
シェーダープログラムはマテリアルに設定します。そのためのマテリアルを作成します。
- インスペクターのCanvasItem欄のMaterialの横の空欄をクリックして、新規ShaderMaterialを選択します
- Material欄にあらわれた球体をクリックして、Shader欄の空欄をクリックして、新しいシェーダーをクリックします
- 作成ボタンを押して、デフォルトのままCanvasItemのシェーダーを作成します
- ファイルシステムから作成したgdshaderファイルをダブルクリックします
以上でシェーダーエディターに切り替わって、デフォルトのCanvasItemシェーダーが表示されます。最初は以下のようになっています。
// 1: shader_type canvas_item; void vertex() { // Called for every vertex the material is visible on. } void fragment() { // Called for every pixel the material is visible on. } void light() { // Called for every pixel for every light affecting the CanvasItem. }
3つの空のメソッドが用意されています。それぞれ次のようなコメントが書かれています。
- vertex()
- 表示されているマテリアルの頂点ごとに呼び出されます
- fragment()
- 表示されているマテリアルのピクセルごとに呼び出されます
- light()
- CanvasItemに影響を与えるすべてのライトごとに、すべてのピクセルに対して呼び出されます
今回利用するのはフラグメントシェーダーです。void fragment()
からはじまる部分を次のように変更してください。途中でエラーが表示されても構わずすべて入力を終えて下さい。
// 7: void fragment() { COLOR = vec4(1.0, 0.0, 0.0, 1.0); }
入力が終わってしばらく待つか、Ctrl+Sキーで保存すると白かったColorRectが赤くなります。
最初のシェーダーが完成しました!!
F6キーや現在のシーンを実行をすると、真っ赤な画面が実行されます。
実行後はシェーダーエディターから出力タブに切り替わります。シェーダーエディタータブをクリックするとシェーダーのコードが確認できます。
コードの観察と実験
書いたコードは以下の通りです。
// 8: COLOR = vec4(1.0 ,0.0 ,0.0 ,1.0);
COLORは色です。=
は一般的なプログラミング言語と同じく代入を意味しますので、右辺のvec4(1.0, 0.0, 0.0, 1.0)
をCOLOR
に代入したということになります。
vec4
は4つの要素を持つ4次元ベクトルです。コンピューターで色と言えばRGBが思い浮かびます。もう一つ値があるなら大抵はアルファ値、つまり透過値です。そこから推測すると、1つ目と4つ目が1.0
で、2つ目と3つ目が0.0
なので、赤が最大、緑と青がなしで、全く透過しない状態が予想できます。真っ赤になった結果と推測が一致します。
値を変更してさらに確認してみましょう。緑にしたければR=0, G=1, B=0, A=1にすればよいはずです。以下のように修正してみます。
// 8: COLOR = vec4(0.0, 1.0, 0.0, 1.0);
Ctrl + Sキーで保存すると緑色に変わりました。予想的中です!
他にも思いつく色を試してみましょう。青ならR=0, G=0, B=1、黄色ならR=1, G=1, B=0、水色ならR=0, G=1, B=1です。α値を0.5にすると色が薄くなることが確認できます。
画面の位置を色に反映させる
さきほどのコードは色を指定しただけで塗る場所を座標で指定しませんでした。ColorRect全体の色が変わったということは塗りつぶしの命令だったのでしょうか?
先ほどのコードを次のように変更します。
// 8: COLOR = vec4(UV.xy, 0.0, 1.0);
Ctrl+Sキーを押して保存するとカラフルになりました!
UVはCOLORと同じく、組み込み変数と呼ばれるGodotが自動的に用意してくれる変数です。組み込み変数や関数は便利なものが大量に用意されています。公式マニュアルにはシェーダーの種類ごとに使える組み込み変数や関数が記載されています。今回使っているのはシェーダーコードの1行目に書かれているcanvas_item
というシェーダーで、以下のページにリファレンスがあります。COLORやUVを探してみて下さい。
CanvasItemシェーダー — Godot Engine (4.x)の日本語のドキュメント
コードの観察と実験
コードは次の通りでした。
// 8: COLOR = vec4(UV.xy, 0.0, 1.0);
UV.xy
とはなんでしょうか。公式ドキュメントで調べます。
UVのところには「頂点関数からのUV。」と書かれています。頂点関数はvertex()
のことです。今回は実装を省略しているのでデフォルトの値が設定されたままfragment()に値が渡されます。
UVはテクスチャを貼り付ける位置を0から1の範囲で表した値です。描画された画像を見ると、左上が黒、右上が赤、左下が緑、右下が黄色です。これをRGBで表すと左上が0, 0, 0
、右上が1, 0, 0
、左下が0, 1, 0
、右下が1, 1, 0
ということになります。COLORに代入したのはvec4(UV.xy, 0.0, 1.0)
でした。左上のUV値が0,0
、右上が1, 0
、左下が0, 1
、右下が1, 1
なら色と一致します。
ここで面白いのがUV.xy
という書き方です。これはシェーダー言語でよく出てくる表現で、xとyの順の2次元ベクトルを表しています。CやC#ではこのような書き方はできませんが、シェーダーではベクトルを様々な方法で扱うので機能が拡張されているのです。例えば以下のようにxとyを入れ替えてみてください。
// 8: COLOR = vec4(UV.yx, 0.0, 1.0);
グラデーションの色が縦横で入れ替わりました。他にもUV.xx
やUV.yy
のような書き方もできます。
fragment()の動き方
最初のコードはColorRectを一色で塗りつぶすだけでしたが、今回はグラデーションになりました。これはfragment()がColorRectで囲まれているすべてのピクセルごとに実行されたからです。
UVには、ColorRectを構成する頂点からfragment()を実行するピクセルの位置に応じて、テクスチャーを貼り付けるための位置が線形補間されて渡されます。デフォルトでは小さい座標の頂点に0, 大きい座標の頂点に1が設定されるので、fragment()で描画する頂点の位置に応じて左上から右下に0から1の範囲でUV値が渡されます。それをCOLOR = vec4(UV.xy, 0.0, 1.0);
でピクセルごとに着色したのでグラデーションになったのです。
何か描きたいとき、通常は「どこに何を描画するか」を考えるのが自然です。それに対してフラグメントシェーダーでは塗りつぶす面を構成するすべてのピクセルごとに「そのピクセルを何色にするのか」を考えます。
マテリアルから値を渡す-Uniform入力-
色を設定するために毎回シェーダーのコードを書き換えるのは面倒です。マテリアルで色を設定できるようにします。GodotではUniform入力を使うことでマテリアルからシェーダーに値や素材を渡すことができます。
シェーダーコードを以下のように書き換えてください。vertex()とlight()は使わないので消しました。
// 1: shader_type canvas_item; uniform vec4 base_color : source_color = vec4(1.0, 0.0, 0.0, 1.0); void fragment() { COLOR = base_color; }
保存するとColorRectが最初のコードのように赤一色になります。
3行目のuniform
から始まる行がUniform入力の定義です。vec4は既出の4次元ベクトルを表していて、base_color
が名前です。ここまでがUniform入力定義の必須項目です。これ以降はオプションの指定です。
source_color
はシェーダーヒントやHintと呼ばれるものです。Uniformや変数の特性を指定するために使います。Godotは色をLinear空間で扱います。画像ファイルや色指定はsRGBなのでそのままでは色がおかしくなるので、source_colorヒントをつけてsRGBからLinearに変換するように指定しています。その後ろは初期値でこの例では赤を指定しています。これも省略可能です。
色をマテリアルから変えてみましょう。
- シーンでColorRectをクリックして選択します
- インスペクターのMaterial欄をクリックして、Shader Parameters欄をクリックします
- シェーダーに追加した
base_color
がBase Color
という名前で追加されています。Base Colorの色の四角をクリックするとカラーパネルが表示されます
色やα値を変化させるとエディターのColorRectの色も変わるようになりました。
Uniform入力ではさまざまなデータを設定することができます。詳しくはマニュアルの以下のページをご覧ください。
シェーディング言語 — Godot Engine (4.x)の日本語のドキュメント
COLORのデフォルトの色
ColorRectが最初に白くなるのは、デフォルトとしてインスペクターの一番上にあるColorが設定されるからです。
例えば以下のようにCOLORに色を掛ける式にすると、COLORとbase_colorの各要素を掛けた色になります。
// 6: COLOR *= base_color;
COLORの色を変えると、COLORとBaseColorのRGBの値をそれぞれ掛けた色になります。
マテリアルから画像を設定
画像を描画してみます。マテリアルで画像を設定するにはsampler2D
を使います。
// 3: uniform sampler2D image : source_color;
テクスチャ画像はsampler2D
型で受け取ります。これも色のデータなのでsource_color
ヒントをつけます。
テクスチャ画像から色を取り出すのは組み込み関数のtexture
を使います。fragment()を以下のように書き換えます。
// 5: void fragment() { COLOR *= texture(image, UV); }
texture関数は、第一引数で取り出し元の画像、第2引数で読み取るUV座標を指定すると該当する場所の色を返します。これで設定した画像にCOLORの色を掛けた絵が描画されるようになります。画像は以下の手順で設定します。
- シーンでColorRectをクリックして選択します
- インスペクターのShader Parameters欄のImage欄の空欄をクリックします
- クイックロードをクリックします
- デフォルトでGodotアイコンのSVGファイルが入っているので、選択して開きます
設定できたらColorRectにGodotのアイコンが表示されます。プロパティのColorで色を変えると画像の色も変わります。
The Book of Shadersを試す
フラグメントシェーダーの雰囲気を味わいました。よりフラグメントシェーダーの理解を深めるために公式ドキュメントでも紹介されているThe Book of ShadersにあるいくつかのコードをGodotで動かしてみます。
グラフを描く
The Book of Shaders: Shaping functionsにある数式のグラフを描くシェーダーをGodotで動かしてみます。新しいシェーダースクリプトに作成します。
- シーンでColorRectをクリックして選択します
- インスペクターのMaterialにあるShader欄の右の下矢印をクリックして、新しいシェーダーをクリックします
- パスを
algorithmic_draw
などに変更して作成します
- ファイルシステムから作成したシェーダーファイルをダブルクリックして開きます
- 以下のコードを入力します
// 1: shader_type canvas_item; float plot(vec2 st, float pct){ return smoothstep( pct-0.02, pct, st.y) - smoothstep( pct, pct+0.02, st.y); } void fragment() { // yに描きたいグラフの計算を代入 float y = pow(UV.x, 5.0); // 以降は共通 vec3 color = vec3(y); float pct = plot(UV, y); color = (1.0-pct)*color+pct*vec3(0.0,1.0,0.0); COLOR = vec4(color, 1.0); }
しばらく待つか保存すると記事のシェーダーが描画されます。
上下逆になっているのは、The Book of Shadersで動いている環境とGodotのColorRectでY軸の向きが逆だからです。
The Book of Shadersで紹介されているシェーダーのコードは少し手を加えれば動きます。以下、書き換えポイントです。
- 1行目に
shader_type
を加える - 4行目から12行目までのfloatの精度指定やPIなどの定義は不要なので削除
- Godotのシェーダーでは以下のような組み込み変数が使えます
- PIは
PI
- 2PIは
TAU
- 経過秒数は
TIME
- 自然対数は
E
- PIは
- stに算出しているピクセルの正規座標は
UV
に置き換えればよい
このコードでの10行目のyに、UV.x
をXとして図形を描画する式を代入すればそれを描画します。例えばサインカーブを時間で動かすなら以下のようにします。
// 10: float y = sin(UV.x*PI+TIME)*0.5+0.5;
The Book of Shadersに紹介されているさまざまなグラフや色、表現を動かして理解を深めましょう。
その他の式
Algorithmic drawingに掲載されていたその他の式を以下に抜粋します。
float y = step(0.5, UV.x); float y = smoothstep(0.1, 0.9, UV.x); float y = smoothstep(0.2, 0.5, UV.x) - smoothstep(0.5, 0.8, UV.x); float y = mod(UV.x, 0.5); // xを0.5で割った余り float y = fract(UV.x); // 小数部(the fraction part)のみ返す float y = ceil(UV.x); // 切り上げ float y = floor(UV.x); // 切り捨て float y = sign(UV.x); // 負なら-1, ゼロなら0, 正なら1 float y = abs(UV.x); // 絶対値 float y = clamp(UV.x, 0.25, 0.75); // 指定の範囲内におさめる float y = min(0.5, UV.x); // 小さい方の値を返す float y = max(0.5, UV.x); // 大きい方の値を返す
CellularNoise
最後にCellular Noiseのページから見た目が面白いCellularNoiseの例の実行例です。これも新しくシェーダーを作るとよいかも知れません。
// 1: shader_type canvas_item; // Author: @patriciogv // Title: CellularNoise vec2 random2( vec2 p ) { return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453); } void fragment() { vec2 st = UV; vec3 color = vec3(.0); // Scale st *= 3.; // Tile the space vec2 i_st = floor(st); vec2 f_st = fract(st); float m_dist = 1.; // minimum distance for (int y= -1; y <= 1; y++) { for (int x= -1; x <= 1; x++) { // Neighbor place in the grid vec2 neighbor = vec2(float(x),float(y)); // Random position from current + neighbor place in the grid vec2 point = random2(i_st + neighbor); // Animate the point point = 0.5 + 0.5*sin(TIME + 6.2831*point); // Vector between the pixel and the point vec2 diff = neighbor + point - f_st; // Distance to the point float dist = length(diff); // Keep the closer distance m_dist = min(m_dist, dist); } } // Draw the min distance (distance field) color += m_dist; // Draw cell center color += 1.-step(.02, m_dist); // Draw grid color.r += step(.98, f_st.x) + step(.98, f_st.y); // Show isolines // color -= step(.7,abs(sin(27.0*m_dist)))*.5; COLOR = vec4(color,1.0); }
まとめ
Godotのインストールからシェーダーの作成をして、The Book of Shadersのサンプルをいくつか動かしてみました。試した印象としてはUnityのシェーダーより約束事が少なくてスムーズに動かすことができました。フラグメントシェーダーでグラフや形状を描く必要があるのか?と思われるかも知れませんが、基本を知っていればさまざまなエフェクトに応用できます。
ある程度の雰囲気が掴めたら、あらためて公式ドキュメントを読みこむとよいです。必要な情報がしっかりと解説されています。
今回は2Dで試しましたが3Dでも考え方は同じです。利用できる組み込み変数が増えたり、色を表すだけではなくなるので変数名が変わったりするので、適宜ドキュメントを参照することになりますが使いやすさは変わらないと思います。
以上、Godot Engineのカレンダー | Advent Calendar 2023 - Qiitaの11日目の記事でした。
◆前の日は@NumAniCloudさんのC#プロジェクトを分割して開発しよう、の話です。
◆次の日はきゃべつさんのGodot(C#)シーン派生とクラス派生 #C# - Qiitaです。
よいGodotライフを!