浏览代码

added tutorial on animating thousands of objects

clayjohn 6 年之前
父节点
当前提交
0bbb62df2d

+ 1 - 0
tutorials/3d/index.rst

@@ -19,3 +19,4 @@
    using_multi_mesh_instance
    csg_tools
    fps_tutorial/index
+   vertex_animation/index

+ 275 - 0
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 <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 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 <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 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 <class_Particles>` to take advantage 
+of the GPU and move each fish around individually while still receiving the benefits of instancing.

+ 144 - 0
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 <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 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 <doc_particle_shader>`
+for more information on particle shaders.

二进制
tutorials/3d/vertex_animation/img/all_motions.gif


二进制
tutorials/3d/vertex_animation/img/all_motions_mask.gif


二进制
tutorials/3d/vertex_animation/img/fish.png


二进制
tutorials/3d/vertex_animation/img/mask.png


二进制
tutorials/3d/vertex_animation/img/pivot.gif


二进制
tutorials/3d/vertex_animation/img/scene.gif


二进制
tutorials/3d/vertex_animation/img/sidetoside.gif


二进制
tutorials/3d/vertex_animation/img/twist.gif


二进制
tutorials/3d/vertex_animation/img/wave.gif


+ 9 - 0
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