Work in progress
The content of this page was not yet updated for Godot
4.2
and may be outdated. If you know how to improve this page or you can confirm
that it's up to date, feel free to open a pull request.
Animating thousands of fish with MultiMeshInstance3D¶
このチュートリアルでは、頂点アニメーションと静的メッシュのインスタンス化を使用して、何千もの魚をレンダリングおよびアニメーション化するために、ゲームで使用されるテクニック `ABZU <https://www.gdcvault.com/play/1024409/Creating-the-Art-of-ABZ>`_を探ります。
In Godot, this can be accomplished with a custom Shader and a MultiMeshInstance3D. Using the following technique you can render thousands of animated objects, even on low end hardware.
まず、1匹の魚をアニメーション化することから始めます。次に、そのアニメーションを何千もの魚に拡張する方法を見ていきます。
1匹の魚のアニメーション¶
We will start with a single fish. Load your fish model into a MeshInstance3D and add a new ShaderMaterial.
これがサンプル画像に使用する魚です。好きな魚モデルを使用できます。
注釈
The fish model in this tutorial is made by QuaterniusDev and is shared with a creative commons license. CC0 1.0 Universal (CC0 1.0) Public Domain Dedication https://creativecommons.org/publicdomain/zero/1.0/
Typically, you would use bones and a Skeleton3D to animate objects. However, bones are animated on the CPU and so you end having to calculate thousands of operations every frame and it becomes impossible to have thousands of objects. Using vertex animation in a vertex shader, you avoid using bones and can instead calculate the full animation in a few lines of code and completely on the GPU.
アニメーションは、4つのキーモーションで構成されます:
左右の動き
魚の中心の周を旋回する動き
揺ら揺らした波状の動き
揺ら揺らした捻る動き
アニメーションのすべてのコードは、モーションの量を制御するuniformを持つ頂点シェーダーにあります。uniformを使用してモーションの強さを制御するため、エディタでアニメーションを微調整し、シェーダーを再コンパイルせずにリアルタイムで結果を確認できます。
All the motions will be made using cosine waves applied to VERTEX
in model space. We want the vertices to
be in model space so that the motion is always relative to the orientation of the fish. For example, side-to-side
will always move the fish back and forth in its left to right direction, instead of on the x
axis in the
world orientation.
アニメーションの速度を制御するために、TIME
を使用して独自の時間変数を定義することから始めます。
//time_scale is a uniform float
float time = TIME * time_scale;
実装する最初のモーションは、左右のモーションです。これは、VERTEX.x
を TIME
の cos
でオフセットすることで作成できます。メッシュがレンダリングされるたびに、すべての頂点が cos(time)
の量だけ横に移動します。
//side_to_side is a uniform float
VERTEX.x += cos(time) * side_to_side;
結果のアニメーションは次のようになります:
次に、ピボットを追加します。魚の中心は (0, 0) なので、魚の中心の周りを回転させるために、VERTEX
に回転行列を掛けるだけで済みます。
次のような回転行列を作成します:
//angle is scaled by 0.1 so that the fish only pivots and doesn't rotate all the way around
//pivot is a uniform float
float pivot_angle = cos(time) * 0.1 * pivot;
mat2 rotation_matrix = mat2(vec2(cos(pivot_angle), -sin(pivot_angle)), vec2(sin(pivot_angle), cos(pivot_angle)));
そして、それを VERTEX.xz
に乗算して x
および z
軸に回転を適用します。
VERTEX.xz = rotation_matrix * VERTEX.xz;
ピボットのみを適用すると、次のように表示されます:
次の2つの動きは、魚の背骨をパンダウンする必要があります。そのためには、新しい変数 body
が必要です。 body
は、魚の尾が 0
、頭に 1
のfloatです。
float body = (VERTEX.z + 1.0) / 2.0; //for a fish centered at (0, 0) with a length of 2
次の動きは、魚の長さを下るコサイン波です。それを魚の背骨に沿って移動させるために、背骨に沿った位置で cos
への入力をオフセットします。これは上で定義した変数 body
です。
//wave is a uniform float
VERTEX.x += cos(time + body) * wave;
これは上で定義した左右の動きに非常に似ていますが、この例では、body
を使用して cos
をオフセットすることにより、脊椎に沿った各頂点が波の中で異なる位置を持ち、波は魚に沿って動いているように見えます。
The last motion is the twist, which is a panning roll along the spine. Similarly to the pivot, we first construct a rotation matrix.
//twist is a uniform float
float twist_angle = cos(time + body) * 0.3 * twist;
mat2 twist_matrix = mat2(vec2(cos(twist_angle), -sin(twist_angle)), vec2(sin(twist_angle), cos(twist_angle)));
We apply the rotation in the xy
axes so that the fish appears to roll around its spine. For
this to work, the fish's spine needs to be centered on the z
axis.
VERTEX.xy = twist_matrix * VERTEX.xy;
捻りが適用された魚は次のとおりです:
これらすべての動きを次々に適用すると、滑らかなゼリーのような動きが得られます。
通常の魚は主に体の後ろ半分で泳ぎます。したがって、パンの動きを魚の後ろ半分に制限する必要があります。これを行うために、新しい変数 mask
を作成します。
mask
is a float that goes from 0
at the front of the fish to 1
at the end using
smoothstep
to control the point at which the transition from 0
to 1
happens.
//mask_black and mask_white are uniforms
float mask = smoothstep(mask_black, mask_white, 1.0 - body);
以下は、mask
を COLOR
として使用した魚の画像です:
波については、動きに mask
を掛けて、後ろ半分に制限します。
//wave motion with mask
VERTEX.x += cos(time + body) * mask * wave;
In order to apply the mask to the twist, we use mix
. mix
allows us to mix the
vertex position between a fully rotated vertex and one that is not rotated. We need to
use mix
instead of multiplying mask
by the rotated VERTEX
because we are not
adding the motion to the VERTEX
we are replacing the VERTEX
with the rotated
version. If we multiplied that by mask
, we would shrink the fish.
//twist motion with mask
VERTEX.xy = mix(VERTEX.xy, twist_matrix * VERTEX.xy, mask);
4つのモーションを組み合わせると、最終的なアニメーションが作成されます。
さあ、魚の泳ぎ方を変えるためにunifomで遊んでください。これらの4つのモーションを使用して、さまざまな遊泳スタイルを作成できることがわかります。
魚の群れを作る¶
Godot makes it easy to render thousands of the same object using a MultiMeshInstance3D node.
A MultiMeshInstance3D node is created and used the same way you would make a MeshInstance3D node.
For this tutorial, we will name the MultiMeshInstance3D node School
, because it will contain
a school of fish.
Once you have a MultiMeshInstance3D add a MultiMesh, and to that MultiMesh add your Mesh with the shader from above.
MultiMeshは、インスタンスごとの3つの追加プロパティでメッシュを描画します: 幾何学変換(回転、移動、スケール)、色、およびカスタム。カスタムは、Color を使用して4つの多目的変数を渡すために使用されます。
instance_count
は、描画するメッシュのインスタンスの数を指定します。とりあえず、instance_count
が 0
より大きい間は他のパラメーターを変更できないため、instance_count
を 0
のままにします。後でGDScriptで instance_count
を設定します。
``Transform Format``は、使用される変換が3Dか2Dかを指定します。このチュートリアルでは、3Dを選択します。
For both color_format
and custom_data_format
you can choose between None
, Byte
, and
Float
. None
means you won't be passing in that data (either a per-instance COLOR
variable,
or INSTANCE_CUSTOM
) to the shader. Byte
means each number making up the color you pass in will
be stored with 8 bits while Float
means each number will be stored in a floating-point number
(32 bits). Float
is slower but more precise, Byte
will take less memory and be faster, but you
may see some visual artifacts.
さて、Instance Count
を持ちたい魚の数に設定してください。
次に、インスタンスごとのtransformを設定する必要があります。
There are two ways to set per-instance transforms for MultiMeshes. The first is entirely in editor and is described in the MultiMeshInstance3D tutorial.
2つ目は、すべてのインスタンスをループ処理し、コード内でそれらのtansformを設定することです。以下では、GDScriptを使用してすべてのインスタンスをループし、それらの変換をランダムな位置に設定します。
for i in range($School.multimesh.instance_count):
var position = Transform3D()
position = position.translated(Vector3(randf() * 100 - 50, randf() * 50 - 25, randf() * 50 - 25))
$School.multimesh.set_instance_transform(i, position)
Running this script will place the fish in random positions in a box around the position of the MultiMeshInstance3D.
注釈
If performance is an issue for you, try running the scene with fewer fish.
Notice how all the fish are all in the same position in their swim cycle? It makes them look very robotic. The next step is to give each fish a different position in the swim cycle so the entire school looks more organic.
魚の群れをアニメーション化する¶
cos
関数を使用して魚をアニメーション化する利点の1つは、1つのパラメーター time
でアニメーション化されることです。各魚に遊泳サイクルのユニークな位置を与えるために、``time``をオフセットする必要があります。
インスタンスごとのカスタム値 INSTANCE_CUSTOM
を time
に加算することでそれを行います。
float time = (TIME * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
次に、値を INSTANCE_CUSTOM
に渡す必要があります。そのためには、上から for
ループに1行追加します。for
ループでは、各インスタンスに使用する4つのランダムフロートのセットを割り当てます。
$School.multimesh.set_instance_custom_data(i, Color(randf(), randf(), randf(), randf()))
今、魚はすべて遊泳サイクルでユニークな位置を持っています。INSTANCE_CUSTOM
を使用して、TIME
を掛けることでより速くまたは遅く泳ぐことで、彼らにもう少し個性を与えることができます。
//set speed from 50% - 150% of regular speed
float time = (TIME * (0.5 + INSTANCE_CUSTOM.y) * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
インスタンスごとのカスタム値を変更したのと同じ方法で、インスタンスごとの色の変更を試すこともできます。
One problem that you will run into at this point is that the fish are animated, but they are not moving. You can move them by updating the per-instance transform for each fish every frame. Although doing so will be faster than moving thousands of MeshInstance3Ds per frame, it'll still likely be slow.
In the next tutorial we will cover how to use GPUParticles3D to take advantage of the GPU and move each fish around individually while still receiving the benefits of instancing.