diff --git a/tutorials/3d/index.rst b/tutorials/3d/index.rst index 5f0b13129..0152538f3 100644 --- a/tutorials/3d/index.rst +++ b/tutorials/3d/index.rst @@ -19,3 +19,4 @@ using_multi_mesh_instance csg_tools fps_tutorial/index + vertex_animation/index diff --git a/tutorials/3d/vertex_animation/animating_thousands_of_fish.rst b/tutorials/3d/vertex_animation/animating_thousands_of_fish.rst new file mode 100644 index 000000000..cfd4f7429 --- /dev/null +++ b/tutorials/3d/vertex_animation/animating_thousands_of_fish.rst @@ -0,0 +1,275 @@ +.. _doc_animating_thousands_of_fish: + +Animating thousands of fish with MeshInstance +============================================= + +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 :ref:`Shader ` and +a :ref:`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 ` +and add a new :ref:`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 `_ 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 ` 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 be 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 fishes 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 `, and to that +MultiMesh add your :ref:`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 `. + +``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 `. + +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 then moving thousands of MeshInstances per frame, it is still likely to be +slow. + +In the next tutorial we will cover how to use :ref:`Particles ` to take advantage +of the GPU and move each fish around individually while still receiving the benefits of instancing. diff --git a/tutorials/3d/vertex_animation/controlling_thousands_of_fish.rst b/tutorials/3d/vertex_animation/controlling_thousands_of_fish.rst new file mode 100644 index 000000000..3203c66d7 --- /dev/null +++ b/tutorials/3d/vertex_animation/controlling_thousands_of_fish.rst @@ -0,0 +1,144 @@ +.. _doc_controlling_thousands_of_fish: + +Controlling thousands of fish with Particles +============================================ + +The problem with :ref:`MeshInstances ` 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 ` node. Particles take advantage of GPU acceleration +by computing and setting the per-instance information in a :ref:`Shader `. + +.. note:: Particles are not available in GLES2, instead use :ref:`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 `. Then under "Process Material" create a new +:ref:`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 `. +They are used to generate a random number from each particle's ``RANDOM_SEED``. + +A unique thing about particle shaders is that some of the 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 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) { + //Initialition 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 = CUSTOM.xyz*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 ` +for more information on particle shaders. diff --git a/tutorials/3d/vertex_animation/img/all_motions.gif b/tutorials/3d/vertex_animation/img/all_motions.gif new file mode 100644 index 000000000..2e4e43b57 Binary files /dev/null and b/tutorials/3d/vertex_animation/img/all_motions.gif differ diff --git a/tutorials/3d/vertex_animation/img/all_motions_mask.gif b/tutorials/3d/vertex_animation/img/all_motions_mask.gif new file mode 100644 index 000000000..87abbd7ed Binary files /dev/null and b/tutorials/3d/vertex_animation/img/all_motions_mask.gif differ diff --git a/tutorials/3d/vertex_animation/img/fish.png b/tutorials/3d/vertex_animation/img/fish.png new file mode 100644 index 000000000..1b024979d Binary files /dev/null and b/tutorials/3d/vertex_animation/img/fish.png differ diff --git a/tutorials/3d/vertex_animation/img/mask.png b/tutorials/3d/vertex_animation/img/mask.png new file mode 100644 index 000000000..0644a8238 Binary files /dev/null and b/tutorials/3d/vertex_animation/img/mask.png differ diff --git a/tutorials/3d/vertex_animation/img/pivot.gif b/tutorials/3d/vertex_animation/img/pivot.gif new file mode 100644 index 000000000..c05dfe054 Binary files /dev/null and b/tutorials/3d/vertex_animation/img/pivot.gif differ diff --git a/tutorials/3d/vertex_animation/img/scene.gif b/tutorials/3d/vertex_animation/img/scene.gif new file mode 100644 index 000000000..852018cee Binary files /dev/null and b/tutorials/3d/vertex_animation/img/scene.gif differ diff --git a/tutorials/3d/vertex_animation/img/sidetoside.gif b/tutorials/3d/vertex_animation/img/sidetoside.gif new file mode 100644 index 000000000..0937e0a64 Binary files /dev/null and b/tutorials/3d/vertex_animation/img/sidetoside.gif differ diff --git a/tutorials/3d/vertex_animation/img/twist.gif b/tutorials/3d/vertex_animation/img/twist.gif new file mode 100644 index 000000000..becc4f22b Binary files /dev/null and b/tutorials/3d/vertex_animation/img/twist.gif differ diff --git a/tutorials/3d/vertex_animation/img/wave.gif b/tutorials/3d/vertex_animation/img/wave.gif new file mode 100644 index 000000000..56646cae6 Binary files /dev/null and b/tutorials/3d/vertex_animation/img/wave.gif differ diff --git a/tutorials/3d/vertex_animation/index.rst b/tutorials/3d/vertex_animation/index.rst new file mode 100644 index 000000000..446445845 --- /dev/null +++ b/tutorials/3d/vertex_animation/index.rst @@ -0,0 +1,9 @@ +Animating thousands of objects +============================== + +.. toctree:: + :maxdepth: 1 + :name: toc-vertex_animation + + animating_thousands_of_fish + controlling_thousands_of_fish