Up to date

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

Your first 3D shader

You have decided to start writing your own custom Spatial shader. Maybe you saw a cool trick online that was done with shaders, or you have found that the StandardMaterial3D isn't quite meeting your needs. Either way, you have decided to write your own and now you need to figure out where to start.

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

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

This is a two-part tutorial. In this first part we will create terrain using vertex displacement from a heightmap in the vertex function. In the second part we will take the concepts from this tutorial and set up custom materials in a fragment shader by writing an ocean water shader.

注釈

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

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

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

There are multiple node types that you can use to draw a mesh. The main one is MeshInstance3D, but you can also use GPUParticles3D, MultiMeshes (with a MultiMeshInstance3D), or others.

Typically, a material is associated with a given surface in a mesh, but some nodes, like MeshInstance3D, allow you to override the material for a specific surface, or for all surfaces.

If you set a material on the surface or mesh itself, then all MeshInstance3Ds that share that mesh will share that material. However, if you want to reuse the same mesh across multiple mesh instances, but have different materials for each instance then you should set the material on the MeshInstance3D.

For this tutorial we will set our material on the mesh itself rather than taking advantage of the MeshInstance3D's ability to override materials.

セットアップ

Add a new MeshInstance3D node to your scene.

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

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

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

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

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

Now set Subdivide Width and Subdivide Depth of the PlaneMesh to 32.

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

You can see that there are now many more triangles in the MeshInstance3D. This will give us more vertices to work with and thus allow us to add more detail.

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

PrimitiveMeshes, like PlaneMesh, only have one surface, so instead of an array of materials there is only one. Click beside "Material" where it says "[empty]" and select "New ShaderMaterial". Then click the sphere that appears.

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

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

シェーダーマジック

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

The new shader is already generated with a shader_type variable and the fragment() function. The first thing Godot shaders need is a declaration of what type of shader they are. In this case the shader_type is set to spatial because this is a spatial shader.

shader_type spatial;

For now ignore the fragment() function and define the vertex() function. The vertex() function determines where the vertices of your MeshInstance3D appear in the final scene. We will be using it to offset the height of each vertex and make our flat plane appear like a little terrain.

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

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 provides the NoiseTexture2D resource for generating a noise texture that can be accessed from a shader.

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

uniform sampler2D noise;

This will allow you to send a noise texture to the shader. Now look in the inspector under your material. You should see a section called "Shader Params". If you open it up, you'll see a section called "noise".

Click beside it where it says "[empty]" and select "New NoiseTexture2D". Then in your NoiseTexture2D click beside where it says "Noise" and select "New FastNoiseLite".

注釈

FastNoiseLite is used by the NoiseTexture2D to generate a heightmap.

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

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

Now, access the noise texture using the texture() function. texture() takes a texture as the first argument and a vec2 for the position on the texture as the second argument. We use the x and z channels of VERTEX to determine where on the texture to look up. Note that the PlaneMesh coordinates are within the [-1,1] range (for a size of 2), while the texture coordinates are within [0,1], so to normalize we divide by the size of the PlaneMesh by 2.0 and add 0.5. texture() returns a vec4 of the r, g, b, a channels at the position. Since the noise texture is grayscale, all of the values are the same, so we can use any one of the channels as the height. In this case we'll use the r, or x channel.

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

Right now it is too spiky, we want to soften the hills a bit. To do that, we will use a uniform. You already used a uniform above to pass in the noise texture, now let's learn how they work.

Uniform(ユニフォーム)

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

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

uniform float height_scale = 0.5;

Godot lets you initialize a uniform with a value; here, height_scale is set to 0.5. You can set uniforms from GDScript by calling the function set_shader_parameter() on the material corresponding to the shader. The value passed from GDScript takes precedence over the value used to initialize it in the shader.

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

注釈

Changing uniforms in Spatial-based nodes is different from CanvasItem-based nodes. Here, we set the material inside the PlaneMesh resource. In other mesh resources you may need to first access the material by calling surface_get_material(). While in the MeshInstance3D you would access the material using get_surface_material() or material_override.

Remember that the string passed into set_shader_parameter() must match the name of the uniform variable in the Shader. You can use the uniform variable anywhere inside your Shader. Here, we will use it to set the height value instead of arbitrarily multiplying by 0.5.

VERTEX.y += height * height_scale;

今ではずっと良く見えます。

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

Using uniforms, we can even change the value every frame to animate the height of the terrain. Combined with Tweens, this can be especially useful for animations.

ライトとの相互作用

まず、ワイヤーフレームをオフにします。これを行うには、ビューポートの左上にある[透視投影]と表示された場所をもう一度クリックし、[通常表示]を選択します。

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

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

First, we will add an OmniLight3D to the scene.

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

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

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

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

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

uniform sampler2D normalmap;

Set this second uniform texture to another NoiseTexture2D with another FastNoiseLite. But this time, check As Normalmap.

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

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

void fragment() {
}

When we have normals that correspond to a specific vertex we set NORMAL, but if you have a normalmap that comes from a texture, set the normal using NORMAL_MAP. This way Godot will handle the wrapping of texture around the mesh automatically.

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

Above the vertex() define a vec2 called tex_position. And inside the vertex() function assign VERTEX.xz to tex_position.

varying vec2 tex_position;

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

And now we can access tex_position from the fragment() function.

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