Up to date

This page is up to date for Godot 4.3. If you still find outdated information, please open an issue.

初めての3Dシェーダー

あなたは独自のカスタムSpatialシェーダーの作成を開始することにしました。たぶんシェーダーを使って行われたクールなトリックをオンラインで見たか、あるいは、StandardMaterial3D が自分のニーズを十分に満たしていないことに気づいたかもしれません。いずれにしても、あなたは独自のコードを作成することに決めたので、どこから始めればいいかを理解する必要があります。

このチュートリアルでは、Spatialシェーダーの作成方法を説明し、CanvasItem チュートリアルよりも多くのトピックを扱います。

Spatialシェーダーには、CanvasItemシェーダーよりも多くの機能が組み込まれています。Spatialシェーダーで期待されるのは、Godotが一般的なユースケースの機能をすでに提供しており、シェーダーでユーザーが行う必要があるのは、適切なパラメーターを設定することだけです。これは、特にPBR(物理ベースのレンダリング)ワークフローに当てはまります。

これは2部構成のチュートリアルです。この最初のパートでは、頂点関数のハイトマップからの頂点変位を使用して、簡単な地形を作成する方法を説明します。第2部 では、このチュートリアルの概念を取り入れ、水面シェーダーを作成してフラグメントシェーダーでカスタムマテリアルを設定する方法を説明します。

注釈

このチュートリアルでは、タイプ(vec2floatsampler2D)、関数などのシェーダーの基本的な知識を前提としています。これらの概念に不安がある場合は、このチュートリアルを完了する前に、`The Book of Shaders <https://thebookofshaders.com>`_のやさしい紹介を読むことをお勧めします。

マテリアルを割り当てる場所

3Dでは、オブジェクトは メッシュ を使用して描画されます。メッシュは、「サーフェス」と呼ばれる単位でジオメトリ(オブジェクトの形状)とマテリアル(色とオブジェクトが光に反応する方法)を格納するリソースタイプです。メッシュには、複数のサーフェス、または1つのサーフェスを含めることができます。通常、別のプログラム(Blenderなど)からメッシュをインポートします。しかし、Godotには、メッシュをインポートせずにシーンに基本的なジオメトリを追加できる PrimitiveMeshes もいくつかあります。

メッシュの描画に使用できるノードタイプは複数あります。主なものは MeshInstance3D ですが、GPUParticles3DMultiMeshes を使用することもできます)、またはその他です。

通常、マテリアルはメッシュ内の特定のサーフェスに関連付けられていますが、MeshInstance3Dなどの一部のノードでは、特定のサーフェスまたはすべてのサーフェスのマテリアルをオーバーライドできます。

サーフェスまたはメッシュ自体にマテリアルを設定すると、そのメッシュを共有するすべてのMeshInstance3Dはそのマテリアルを共有します。ただし、複数のメッシュインスタンスで同じメッシュを再利用したいが、インスタンスごとに異なるマテリアルを使用する場合は、MeshInstance3Dでマテリアルを設定する必要があります。

このチュートリアルでは、MeshInstance3Dのマテリアルをオーバーライドする機能を利用するのではなく、メッシュ自体にマテリアルを設定します。

セットアップ

新しい MeshInstance3D ノードをシーンに追加します。

[インスペクタ]タブの[Mesh]の横で[空]をクリックし、[新規 PlaneMesh]を選択します。次に、表示される平面の画像をクリックします。

これにより、シーンに PlaneMesh が追加されます。

次に、ビューポートで、「透視投影」というボタンの左上隅をクリックします。メニューが表示されます。メニューの中央には、シーンの表示方法のオプションがあります。 「ワイヤフレーム表示」を選択します。

これにより、平面を構成する三角形を見ることができます。

../../../_images/plane.png

次に、PlaneMeshSubdivide WidthSubdivide Depth32 に設定します。

../../../_images/plane-sub-set.webp

MeshInstance3D には、さらに多くの三角形があることがわかります。これにより多くの頂点を操作できるようになり、さらに詳細を追加できるようになります。

../../../_images/plane-sub.png

PrimitiveMeshes は、PlaneMeshのように、1つのサーフェスしか持たないため、マテリアルは配列の代わりに1つしかありません。[空]と表示されている[Material]の横をクリックし、[新規 ShaderMaterial]を選択します。次に、表示される球をクリックします。

[Shader]横の[空]と表示されている場所をクリックし、[新規 Shader]を選択します。

作成された"Shader"をクリックするとシェーダーエディタが表示され、最初のSpatialシェーダーの作成を開始する準備ができました!

シェーダーマジック

../../../_images/shader-editor.webp

新しいシェーダーは shader_type の宣言と fragment() 関数を使用してすでに生成されています。 Godotシェーダーに最初に必要なのは、どのタイプのシェーダーであるかを宣言することです。今回これは空間シェーダであるため shader_typespatial に設定します。

shader_type spatial;

今のところ fragment() 関数を無視し、 vertex() 関数を定義します。 vertex() 関数は、 MeshInstance3D の頂点が最終シーンのどこに表示されるかを決定します。これを使用して各頂点の高さをオフセットし、平らな平面を小さな地形のように見せます。

頂点シェーダーを次のように定義します:

void vertex() {

}

vertex() 関数に何もない場合、Godotはデフォルトの頂点シェーダーを使用します。 1行追加するだけで簡単に変更を開始できます。

void vertex() {
  VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
}

この行を追加すると、次のような画像が表示されます。

../../../_images/cos.png

さて、これを紐解きましょう。 VERTEXy 値は増加しています。そして、cossin への引数として、VERTEXxz コンポーネントを渡します。これにより、x 軸と z 軸に波のような外観が与えられます。

とりあえず、私たちが達成したいのは、小さな丘の様な外観です。cossin はすでに丘のように見えます。そのためには、cos および sin 関数の入力にスケーリングを施します。

void vertex() {
  VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
}
../../../_images/cos4.png

これは良い感じですが、それでもまだとがりすぎて反復的です。もう少し面白くしましょう。

ノイズハイトマップ

ノイズは、地形の外観を模倣するための非常に人気のあるツールです。これは、各丘の高さが異なることを除いて、丘が繰り返されるコサイン関数に似ていると考えてください。

Godotはシェーダーからアクセスできるノイズテクスチャを生成するための NoiseTexture2D リソースを提供します。

シェーダーのテクスチャにアクセスするには、シェーダーの上部近くの vertex() 関数の外側に次のコードを追加します。

uniform sampler2D noise;

これによりノイズテクスチャをシェーダーに送信できるようになります。次にマテリアルの下のインスペクタを見てください。 [Shader Params]というセクションが表示されるはずです。それを開くと、[Noise]というセクションが表示されます。

その横にある[空]をクリックして、[新規 NoiseTexture2D]を選択します。次に NoiseTexture2D で[Noise]と表示されている場所の横をクリックし、[新規 FastNoiseLite]を選択します。

注釈

FastNoiseLite は、高さマップを生成するためにNoiseTexture2Dによって使用されます。

設定すると、次のようになります。

../../../_images/noise-set.webp

次に、 texture() 関数を使用してノイズテクスチャにアクセスします。 texture() は最初の引数としてテクスチャを取り、2番目の引数としてテクスチャ上の位置の vec2 を取ります。 VERTEXの x および z チャンネルを使用して、テクスチャ上のどこを検索するかを決定します。PlaneMesh の座標は [-1,1] の範囲内 (サイズ 2 の場合) であるのに対し、テクスチャ座標は [0,1] の範囲内にあることに注意してください。そのため正規化するには、PlaneMesh のサイズを 2.0 で割って0.5を加算します 。 texture() は、その位置にある `` r, g, b, a`` チャンネルの vec4 を返します。ノイズテクスチャはグレースケールであるため、すべての値が同じなので、チャネルのいずれか1つを高さとして使用できます。この場合 r または x チャンネルを使用します。

void vertex() {
  float height = texture(noise, VERTEX.xz / 2.0 + 0.5).x;
  VERTEX.y += height;
}

注: xyzw はGLSLの rgba と同じなので、上記の texture().x の代わりに texture().r を使用できます。詳細については、 OpenGLのドキュメント を参照してください。

このコードを使用すると、テクスチャがランダムに見える丘を作成することがわかります。

../../../_images/noise.png

現時点では尖りすぎているので、丘を少し柔らかくしたいと考えています。そのためにUniformを使います。ノイズテクスチャを渡すために上記のUniformをすでに使用しました。次にそれらがどのように機能するかを学びましょう。

Uniform(ユニフォーム)

Uniform変数を使用すると、ゲームからシェーダーにデータを渡すことができます。シェーダーの効果を制御するのに非常に便利です。Uniform は、シェーダーで使用できるほぼすべてのデータ型です。Uniformを使用するには、キーワード uniform を使用して Shader で宣言します。

地形の高さを変えるユニフォームを作りましょう。

uniform float height_scale = 0.5;

GodotではUniformを値で初期化できます。ここでは height_scale0.5 に設定されています。シェーダーに対応するマテリアルで関数 set_shader_parameter() を呼び出すことで、GDScriptからUniformを設定できます。 GDScriptから渡される値は、シェーダーで初期化するために使用される値よりも優先されます。

# called from the MeshInstance3D
mesh.material.set_shader_parameter("height_scale", 0.5)

注釈

SpatialベースのノードでUniformを変更することは、CanvasItemベースのノードとは異なります。ここではPlaneMeshリソース内にマテリアルを設定します。他のメッシュリソースでは、最初に surface_get_material() を呼び出してマテリアルにアクセスする必要があります。 MeshInstance3Dで get_surface_material() または material_override を使用してマテリアルにアクセスします。

set_shader_parameter() に渡される文字列は Shader のUniform変数の名前と一致しなければならないことに注意してください。Shader 内のどこでもUniform変数を使用できます。ここでは、0.5 を任意に乗算する代わりに、これを使用して高さの値を設定します。

VERTEX.y += height * height_scale;

今度はずっと良くなりました。

../../../_images/noise-low.png

Uniformを使用すると、フレームごとに値を変更して、地形の高さをアニメーション化することもできます。Tween と組み合わせると、アニメーションに特に役立ちます。

ライトとの相互作用

First, turn wireframe off. To do so, click in the upper-left of the Viewport again, where it says "Perspective", and select "Display Normal". Additionally in the 3D scene toolbar, turn off preview sunlight.

../../../_images/normal.png

メッシュの色が平坦になることに注意してください。これは、照明が平坦だからです。ライトを追加しましょう!

まずシーンに OmniLight3D を追加します。(訳注: 追加後に少し上に持ち上げると説明画像と同じようになります)。

../../../_images/light.png

ライトが地形に与える影響を見ることができますが、奇妙な見た目です。問題は、ライトが平面であるかのように地形に影響していることです。これは、ライトシェーダーが Mesh の法線を使用してライトを計算するためです。

法線はメッシュに保存されますが、シェーダーでメッシュの形状を変更しているため、法線は正しくありません。これを修正するには、シェーダーで法線を再計算するか、ノイズに対応する法線テクスチャを使用します。 Godotは両方を簡単におこなえます。

頂点関数で新しい法線を手動で計算し、NORMAL に設定することができます。NORMAL に設定すると、Godotは面倒な照明計算をすべて行います。この方法については、このチュートリアルの次の部分で説明しますが、今はテクスチャから法線を読み取ります。

その代わりに、再びNoiseTextureを利用して法線を計算します。これを行うために、2番目のノイズテクスチャで値を渡します。

uniform sampler2D normalmap;

この2番目のUniformテクスチャを、別の NoiseTexture2D と別の FastNoiseLite に設定します。ただし今回は[As Normalmap]をチェックしてください。

../../../_images/normal-set.webp

さて、これは頂点ごとの法線ではなく法線マップなので、fragment() 関数で割り当てます。fragment() 関数については、このチュートリアルの次のパートで詳しく説明します。

void fragment() {
}

特定の頂点に対応する法線がある場合、NORMAL を設定しますが、テクスチャからの法線マップがある場合は、NORMAL_MAP を使用して法線を設定します。この方法で、Godotはメッシュの周りのテクスチャのラッピングを自動的に処理します。

最後に、ノイズテクスチャと法線マップテクスチャの同じ場所から確実に読み込むために、vertex() 関数から fragment() 関数に ``VERTEX.xz``の位置を渡します。varyingを使ってそれを行います。

vertex() の上に tex_position と呼ばれる vec2 を定義します。そして、vertex() 関数内で VERTEX.xztex_position に割り当てます。

varying vec2 tex_position;

void vertex() {
  ...
  tex_position = VERTEX.xz / 2.0 + 0.5;
  float height = texture(noise, tex_position).x;
  ...
}

そして、fragment() 関数から tex_position にアクセスできるようになりました。

void fragment() {
  NORMAL_MAP = texture(normalmap, tex_position).xyz;
}

法線を設定すると、ライトはメッシュの高さに動的に反応します。

../../../_images/normalmap.png

ライトをドラッグすることもでき、照明の状態が自動的に更新されます。

../../../_images/normalmap2.png

このチュートリアルの完全なコードは次のとおりです。 Godotが面倒な部分のほとんどを処理するので、それほど長くないことがわかります。

shader_type spatial;

uniform float height_scale = 0.5;
uniform sampler2D noise;
uniform sampler2D normalmap;

varying vec2 tex_position;

void vertex() {
  tex_position = VERTEX.xz / 2.0 + 0.5;
  float height = texture(noise, tex_position).x;
  VERTEX.y += height * height_scale;
}

void fragment() {
  NORMAL_MAP = texture(normalmap, tex_position).xyz;
}

これがこの部分のすべてです。 Godotの頂点シェーダーの基本を理解できたと思います。このチュートリアルの次の部分では、この頂点関数に付随するフラグメント関数を作成し、この地形を動く波の海に変えるためのより高度なテクニックをカバーします。