@@ -74,6 +74,7 @@ GPU
|
||||
:name: toc-learn-features-3d-optimization
|
||||
|
||||
optimizing_3d_performance
|
||||
vertex_animation/index
|
||||
|
||||
|
||||
Threads
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
BIN
tutorials/performance/vertex_animation/img/all_motions.gif
Normal file
|
After Width: | Height: | Size: 667 KiB |
BIN
tutorials/performance/vertex_animation/img/all_motions_mask.gif
Normal file
|
After Width: | Height: | Size: 512 KiB |
BIN
tutorials/performance/vertex_animation/img/fish.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
tutorials/performance/vertex_animation/img/mask.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
tutorials/performance/vertex_animation/img/pivot.gif
Normal file
|
After Width: | Height: | Size: 630 KiB |
BIN
tutorials/performance/vertex_animation/img/scene.gif
Normal file
|
After Width: | Height: | Size: 6.9 MiB |
BIN
tutorials/performance/vertex_animation/img/sidetoside.gif
Normal file
|
After Width: | Height: | Size: 607 KiB |
BIN
tutorials/performance/vertex_animation/img/twist.gif
Normal file
|
After Width: | Height: | Size: 620 KiB |
BIN
tutorials/performance/vertex_animation/img/wave.gif
Normal file
|
After Width: | Height: | Size: 624 KiB |
9
tutorials/performance/vertex_animation/index.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
Animating thousands of objects
|
||||
==============================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:name: toc-vertex_animation
|
||||
|
||||
animating_thousands_of_fish
|
||||
controlling_thousands_of_fish
|
||||