Move vertex animation files to performance

Closes #4123
This commit is contained in:
Nathan Lovato
2020-10-07 21:31:58 -06:00
parent 6cbc820ef0
commit 0e0c3f4382
17 changed files with 4 additions and 4 deletions

View File

@@ -74,6 +74,7 @@ GPU
:name: toc-learn-features-3d-optimization
optimizing_3d_performance
vertex_animation/index
Threads

View File

@@ -0,0 +1,275 @@
.. _doc_animating_thousands_of_fish:
Animating thousands of fish with MultiMeshInstance
==================================================
This tutorial explores a technique used in the game `ABZU <https://www.gdcvault.com/play/1024409/Creating-the-Art-of-ABZ>`_
for rendering and animating thousands of fish using vertex animation and
static mesh instancing.
In Godot, this can be accomplished with a custom :ref:`Shader <class_Shader>` and
a :ref:`MultiMeshInstance <class_MultiMeshInstance>`. 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 :ref:`MeshInstance <class_MeshInstance>`
and add a new :ref:`ShaderMaterial <class_ShaderMaterial>`.
Here is the fish we will be using for the example images, you can use any fish model you like.
.. image:: img/fish.png
.. note:: The fish model in this tutorial is made by `QuaterniusDev <http://quaternius.com>`_ 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 :ref:`Skeleton <class_Skeleton>` 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:
1. A side to side motion
2. A pivot motion around the center of the fish
3. A panning wave motion
4. 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
world orientation.
In order to control the speed of the animation, we will start by defining our own time variable using ``TIME``.
.. code-block:: glsl
//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 ``VERTEX.x`` by
``cos`` of ``TIME``. Each time the mesh is rendered, all the vertices will move to the side by the amount
of ``cos(time)``.
.. code-block:: glsl
//side_to_side is a uniform float
VERTEX.x += cos(time) * side_to_side;
The resulting animation should look something like this:
.. image:: img/sidetoside.gif
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:
.. code-block:: glsl
//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 ``x`` and ``z`` axes by multiplying it by ``VERTEX.xz``.
.. code-block:: glsl
VERTEX.xz = rotation_matrix * VERTEX.xz;
With only the pivot applied you should see something like this:
.. image:: img/pivot.gif
The next two motions need to pan down the spine of the fish. For that, we need a new variable, ``body``.
``body`` is a float that is ``0`` at the tail of the fish and ``1`` at its head.
.. code-block:: glsl
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, ``body``.
.. code-block:: glsl
//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
using ``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.
.. image:: img/wave.gif
The last motion is the twist, which is a panning roll along the spine. Similarly to the pivot,
we first construct a rotation matrix.
.. code-block:: glsl
//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.
.. code-block:: glsl
VERTEX.xy = twist_matrix * VERTEX.xy;
Here is the fish with twist applied:
.. image:: img/twist.gif
If we apply all these motions one after another, we get a fluid jelly-like motion.
.. image:: img/all_motions.gif
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``.
``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.
.. code-block:: glsl
//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 ``COLOR``:
.. image:: img/mask.png
For the wave, we multiply the motion by ``mask`` which will limit it to the back half.
.. code-block:: glsl
//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.
.. code-block:: glsl
//twist motion with mask
VERTEX.xy = mix(VERTEX.xy, twist_matrix * VERTEX.xy, mask);
Putting the four motions together gives us the final animation.
.. image:: img/all_motions_mask.gif
Go ahead and play with the uniforms in order to alter the swim cycle of the fish. You will
find that you can create a wide variety of swim styles using these four motions.
Making a school of fish
-----------------------
Godot makes it easy to render thousands of the same object using a MultiMeshInstance node.
A MultiMeshInstance node is created and used the same way you would make a MeshInstance node.
For this tutorial, we will name the MultiMeshInstance node ``School``, because it will contain
a school of fish.
Once you have a MultiMeshInstance add a :ref:`MultiMesh <class_MultiMesh>`, and to that
MultiMesh add your :ref:`Mesh <class_Mesh>` with the shader from above.
MultiMeshes draw your Mesh with three additional per-instance properties: Transform (rotation,
translation, scale), Color, and Custom. Custom is used to pass in 4 multi-use variables using
a :ref:`Color <class_Color>`.
``instance_count`` specifies how many instances of the mesh you want to draw. For now, leave
``instance_count`` at ``0`` because you cannot change any of the other parameters while
``instance_count`` is larger than ``0``. We will set ``instance count`` in GDScript later.
``transform_format`` specifies whether the transforms used are 3D or 2D. For this tutorial, select 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.
Now, set ``instance_count`` to the number of fish you want to have.
Next we need to set the per-instance transforms.
There are two ways to set per-instance transforms for MultiMeshes. The first is entirely in editor
and is described in the :ref:`MultiMeshInstance tutorial <doc_using_multi_mesh_instance>`.
The second is to loop over all the instances and set their transforms in code. Below, we use GDScript
to loop over all the instances and set their transform to a random position.
::
for i in range($School.multimesh.instance_count):
var position = Transform()
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
MultiMeshInstance.
.. note:: If performance is an issue for you, try running the scene with GLES2 or 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.
Animating a school of fish
--------------------------
One of the benefits of animating the fish using ``cos`` functions is that they are animated with
one parameter, ``time``. In order to give each fish a unique position in the
swim cycle, we only need to offset ``time``.
We do that by adding the per-instance custom value ``INSTANCE_CUSTOM`` to ``time``.
.. code-block:: glsl
float time = (TIME * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
Next, we need to pass a value into ``INSTANCE_CUSTOM``. We do that by adding one line into
the ``for`` loop from above. In the ``for`` loop we assign each instance a set of four
random floats to use.
::
$School.multimesh.set_instance_custom_data(i, Color(randf(), randf(), randf(), randf()))
Now the fish all have unique positions in the swim cycle. You can give them a little more
individuality by using ``INSTANCE_CUSTOM`` to make them swim faster or slower by multiplying
by ``TIME``.
.. code-block:: glsl
//set speed from 50% - 150% of regular speed
float time = (TIME * (0.5 + INSTANCE_CUSTOM.y) * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
You can even experiment with changing the per-instance color the same way you changed the per-instance
custom value.
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 MeshInstances per frame, it'll still likely be
slow.
In the next tutorial we will cover how to use :ref:`Particles <class_Particles>` to take advantage
of the GPU and move each fish around individually while still receiving the benefits of instancing.

View File

@@ -0,0 +1,144 @@
.. _doc_controlling_thousands_of_fish:
Controlling thousands of fish with Particles
============================================
The problem with :ref:`MeshInstances <class_MeshInstance>` is that it is expensive to
update their transform array. It is great for placing many static objects around the
scene. But it is still difficult to move the objects around the scene.
To make each instance move in an interesting way, we will use a
:ref:`Particles <class_Particles>` node. Particles take advantage of GPU acceleration
by computing and setting the per-instance information in a :ref:`Shader <class_Shader>`.
.. note:: Particles are not available in GLES2, instead use :ref:`CPUParticles <class_CPUParticles>`,
which do the same thing as Particles, but do not benefit from GPU acceleration.
First create a Particles node. Then, under "Draw Passes" set the Particle's "Draw Pass 1" to your
:ref:`Mesh <class_Mesh>`. Then under "Process Material" create a new
:ref:`ShaderMaterial <class_ShaderMaterial>`.
Set the ``shader_type`` to ``particles``.
.. code-block:: glsl
shader_type particles
Then add the following two functions:
.. code-block:: glsl
float rand_from_seed(in uint seed) {
int k;
int s = int(seed);
if (s == 0)
s = 305420679;
k = s / 127773;
s = 16807 * (s - k * 127773) - 2836 * k;
if (s < 0)
s += 2147483647;
seed = uint(s);
return float(seed % uint(65536)) / 65535.0;
}
uint hash(uint x) {
x = ((x >> uint(16)) ^ x) * uint(73244475);
x = ((x >> uint(16)) ^ x) * uint(73244475);
x = (x >> uint(16)) ^ x;
return x;
}
These functions come from the default :ref:`ParticlesMaterial <class_ParticlesMaterial>`.
They are used to generate a random number from each particle's ``RANDOM_SEED``.
A unique thing about particle shaders is that some built-in variables are saved across frames.
``TRANSFORM``, ``COLOR``, and ``CUSTOM`` can all be accessed in the Spatial shader of the mesh, and
also in the particle shader the next time it is run.
Next, setup your ``vertex`` function. Particles shaders only contain a vertex function
and no others.
First we will distinguish between code that needs to be run only when the particle system starts
and code that should always run. We want to give each fish a random position and a random animation
offset when the system is first run. To do so, we wrap that code in an ``if`` statement that checks the
built-in variable ``RESTART`` which becomes ``true`` for one frame when the particle system is restarted.
From a high level, this looks like:
.. code-block:: glsl
void vertex() {
if (RESTART) {
//Initialization code goes here
} else {
//per-frame code goes here
}
}
Next, we need to generate 4 random numbers: 3 to create a random position and one for the random
offset of the swim cycle.
First, generate 4 seeds inside the ``RESTART`` block using the ``hash`` function provided above:
.. code-block:: glsl
uint alt_seed1 = hash(NUMBER + uint(1) + RANDOM_SEED);
uint alt_seed2 = hash(NUMBER + uint(27) + RANDOM_SEED);
uint alt_seed3 = hash(NUMBER + uint(43) + RANDOM_SEED);
uint alt_seed4 = hash(NUMBER + uint(111) + RANDOM_SEED);
Then, use those seeds to generate random numbers using ``rand_from_seed``:
.. code-block:: glsl
CUSTOM.x = rand_from_seed(alt_seed1);
vec3 position = vec3(rand_from_seed(alt_seed2) * 2.0 - 1.0,
rand_from_seed(alt_seed3) * 2.0 - 1.0,
rand_from_seed(alt_seed4) * 2.0 - 1.0);
Finally, assign ``position`` to ``TRANSFORM[3].xyz``, which is the part of the transform that holds
the position information.
.. code-block:: glsl
TRANSFORM[3].xyz = position * 20.0;
Remember, all this code so far goes inside the ``RESTART`` block.
The vertex shader for your mesh can stay the exact same as it was in the previous tutorial.
Now you can move each fish individually each frame, either by adding to the ``TRANSFORM`` directly
or by writing to ``VELOCITY``.
Let's transform the fish by setting their ``VELOCITY``.
.. code-block:: glsl
VELOCITY.z = 10.0;
This is the most basic way to set ``VELOCITY`` every particle (or fish) will have the same velocity.
Just by setting ``VELOCITY`` you can make the fish swim however you want. For example, try the code
below.
.. code-block:: glsl
VELOCITY.z = cos(TIME + CUSTOM.x * 6.28) * 4.0 + 6.0;
This will give each fish a unique speed between ``2`` and ``10``.
If you used ``CUSTOM.y`` in the last tutorial, you can also set the speed of the swim animation based
on the ``VELOCITY``. Just use ``CUSTOM.y``.
.. code-block:: glsl
CUSTOM.y = VELOCITY.z * 0.1;
This code gives you the following behavior:
.. image:: img/scene.gif
Using a ParticlesMaterial you can make the fish behavior as simple or complex as you like. In this
tutorial we only set Velocity, but in your own Shaders you can also set ``COLOR``, rotation, scale
(through ``TRANSFORM``). Please refer to the :ref:`Particles Shader Reference <doc_particle_shader>`
for more information on particle shaders.

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

View File

@@ -0,0 +1,9 @@
Animating thousands of objects
==============================
.. toctree::
:maxdepth: 1
:name: toc-vertex_animation
animating_thousands_of_fish
controlling_thousands_of_fish