Work in progress
The content of this page was not yet updated for Godot
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¶
This tutorial explores a technique used in the game ABZU for rendering and animating thousands of fish using vertex animation and static mesh instancing.
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.
We will start by animating one fish. Then, we will see how to extend that animation to thousands of fish.
Animating one Fish¶
We will start with a single fish. Load your fish model into a MeshInstance3D and add a new ShaderMaterial.
Here is the fish we will be using for the example images, you can use any fish model you like.
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.
The animation will be made of four key motions:
A side to side motion
A pivot motion around the center of the fish
A panning wave motion
A panning twist motion
All the code for the animation will be in the vertex shader with uniforms controlling the amount of motion. We use uniforms to control the strength of the motion so that you can tweak the animation in editor and see the results in real time, without the shader having to recompile.
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
In order to control the speed of the animation, we will start by defining our own time variable using
//time_scale is a uniform float float time = TIME * time_scale;
The first motion we will implement is the side to side motion. It can be made by offsetting
TIME. Each time the mesh is rendered, all the vertices will move to the side by the amount
//side_to_side is a uniform float VERTEX.x += cos(time) * side_to_side;
The resulting animation should look something like this:
Next, we add the pivot. Because the fish is centered at (0, 0), all we have to do is multiply
VERTEX by a
rotation matrix for it to rotate around the center of the fish.
We construct a rotation matrix like so:
//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)));
And then we apply it in the
z axes by multiplying it by
VERTEX.xz = rotation_matrix * VERTEX.xz;
With only the pivot applied you should see something like this:
The next two motions need to pan down the spine of the fish. For that, we need a new variable,
body is a float that is
0 at the tail of the fish and
1 at its head.
float body = (VERTEX.z + 1.0) / 2.0; //for a fish centered at (0, 0) with a length of 2
The next motion is a cosine wave that moves down the length of the fish. To make
it move along the spine of the fish, we offset the input to
cos by the position
along the spine, which is the variable we defined above,
//wave is a uniform float VERTEX.x += cos(time + body) * wave;
This looks very similar to the side to side motion we defined above, but in this one, by
body to offset
cos each vertex along the spine has a different position in
the wave making it look like a wave is moving along the fish.
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
VERTEX.xy = twist_matrix * VERTEX.xy;
Here is the fish with twist applied:
If we apply all these motions one after another, we get a fluid jelly-like motion.
Normal fish swim mostly with the back half of their body. Accordingly, we need to limit the
panning motions to the back half of the fish. To do this, we create a new variable,
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
//mask_black and mask_white are uniforms float mask = smoothstep(mask_black, mask_white, 1.0 - body);
Below is an image of the fish with
mask used as
For the wave, we multiply the motion by
mask which will limit it to the back half.
//wave motion with mask VERTEX.x += cos(time + body) * mask * wave;
In order to apply the mask to the twist, we use
mix allows us to mix the
vertex position between a fully rotated vertex and one that is not rotated. We need to