Matrix and transformation in game development

Introduction

Before reading this tutorial, it is recommended that you read through and understand the vector mathematics tutorial I posted earlier , because this tutorial requires vector knowledge.

This tutorial explains transformations and how to use matrices to represent them in Godot. It is not a complete in-depth guide to matrices. Transformations are applied in the form of translation, rotation, and scaling in most cases, so we will focus on how to represent those transformations in a matrix.

Most of this guide uses Transform2D and Vector2 for 2D research, but the way it works in 3D is very similar.

note

As in the tutorial mentioned earlier, it is important to remember that in the tuo, the Y-axis point is very important to fall in 2D. This is contrary to the way most schools teach linear algebra, where the Y axis points upward.

note

The convention is that the X axis is red, the Y axis is green, and the Z axis is blue. This tutorial uses color coding to match these conventions, but we will also use blue to represent the original vector.

Matrix components and identity matrix

The identity matrix represents the transformation without translation, rotation and scaling. Let's start with the relationship between the identity matrix and its composition and visual appearance.

../../_images/identity.png

The matrix has rows and columns, and the transformation matrix has specific conventions about each function.

In the above figure, we can see that the red X vector is represented by the first column of the matrix, and the green Y vector is also represented by the second column. Changing the column will change these vectors. In the next few examples, we will see how to operate it.

You don't have to worry about directly manipulating rows, because we usually use columns. However, you can think of the rows of the matrix as showing which vectors help to move in a given direction.

When we quote values ​​such as txy, this is the Y component of the X column vector. In other words, the bottom left corner of the matrix. Similarly, txx is at the top left, tyx is at the top right, and tyy is at the bottom right, where t is Transform2D.

Scaling conversion matrix

Applying a scale is one of the easiest operations to understand. First, place the Godot logo below the vector so that we can visually see the effect on the object:

../../_images/identity-godot.png

Now, to scale the matrix, all we have to do is to multiply each component by the required scale. Let's enlarge it by 2. 1 times 2 becomes 2, and 0 times 2 becomes 0, so we come to the following conclusion:

../../_images/scale.png

To do this, we can simply multiply each vector:

Transform2D t = Transform2D.Identity;
// Scale
t.x *= 2;
t.y *= 2;
Transform = t; // Change the node's transform to what we just calculated.

If we want to restore it to the original scale, we can multiply each component by 0.5. The scaling conversion matrix is ​​almost all of these.

To calculate the scale of an object from an existing transformation matrix, you can use length() on each column vector.

note

In actual projects, you can use the scaled() method to perform scaling.

Rotation transformation matrix

We will start in the same way as before, adding the Godot logo below the identity matrix:

../../_images/identity-godot.png

For example, suppose we want to rotate the Godot logo 90 degrees clockwise. Now, the X axis points to the right and the Y axis points down. If we rotate these buttons on the head, logically, the new X axis should point down and the new Y axis should point to the left.

You can imagine that you grabbed the Godot logo and its vector at the same time and then rotated it around the center. No matter where you complete the rotation, the direction of the vector will determine what the matrix is.

We need to represent "down" and "left" in normal coordinates, so this means we set X to (0, 1) and Y to (-1, 0). These are also the values ​​of Vector2.DOWN and Vector2.LEFT. When we do this, we get the expected result of rotating the object:

../../_images/rotate1.png

If you have difficulty understanding the above, please try the following exercise: cut a piece of paper, draw the X and Y vectors on top of it, place it on graph paper, and then rotate and pay attention to the endpoints.

In order to perform code rotation, we need to be able to calculate the value programmatically. This figure shows the formula required to calculate the transformation matrix from the rotation angle. If this part seems complicated, don’t worry, I promise this is the hardest thing you need to know.

../../_images/rotate2.png

note

Godot expresses all rotations in radians instead of degrees. A full circle is TAU or PI * 2 radians, and a quarter circle is TAU / 4 or PI /
2 radians. Using TAU usually makes the code more readable.

note

Fun fact: In addition to Y falling in Godot, the rotation is also clockwise. This means that all math and trigger functions behave the
same as the Y-is-up CCW system because these differences will "cancel out". You can think of the rotation in both systems as "from X to Y".

In order to perform a rotation of 0.5 radians (about 28.65 degrees), we only need to insert the value of 0.5 into the above formula and evaluate it to find out what the actual value should be:

../../_images/rotate3.png

This is how it is done in code (place the script on Node2D):

float rot = 0.5f; // The rotation to apply.
Transform2D t = Transform2D.Identity;
t.x.x = t.y.y = Mathf.Cos(rot);
t.x.y = t.y.x = Mathf.Sin(rot);
t.y.x *= -1;
Transform = t; // Change the node's transform to what we just calculated.

To calculate the rotation of the object from the existing transformation matrix, you can use atan2(txy, txx), where t is Transform2D.

note

In actual projects, you can use the rotation () method to perform rotation.

The basis of the transformation matrix

So far, we have only used x and y vectors, which are responsible for representing rotation, scaling, and/or clipping (advanced, introduced at the end). The X and Y vectors together are called the basis of the transformation matrix. The terms "basic" and "basic vector" are important.

You may have noticed that Transform2D actually has three Vector2 values: x, y, and origin. The origin value is not part of the foundation, but part of its transformation, we need it to represent the position. From now on, we will track the original vector in all examples. You can think of the origin as another column, but it's usually best to separate it completely.

Please note that in 3D, Tuo has a separate basis to maintain the basic values ​​of the three-structure Vector3, because the code may be complicated, it makes sense to transform it from the separation (this is composed of a basis and an additional Vector3 The origin of).

Translation Conversion Matrix

Changing the origin vector is called the transformation matrix. Translation is basically a technical term for "moving" an object, but it obviously does not involve any rotation.

Let us use an example to help understand this. We will start with the identity transformation as last time, the difference is that this time we will track the original vector.

../../_images/identity-origin.png

If we want the object to move to the position (1, 2), we only need to set its origin vector to (1, 2):

../../_images/translate.png

There is also a translation() method, which performs a different operation from directly adding or changing the origin. The translation() method converts the object's rotation relative to itself. For example, when using Vector2.UP translation(), an object rotated 90 degrees clockwise will move to the right.

note

Godot's 2D uses pixel-based coordinates, so in a real project, you will need to pan in hundreds of units.

Put it all together

We will apply everything mentioned so far to a transformation. Next, create a simple project with a Sprite node and use the Godot logo as a texture resource.

Let's set the translation to (350, 150), rotate -0.5 rad, and scale 3. I have posted the screenshot and provided the copy code, but I suggest you try to copy the screenshot instead of looking at the code!

../../_images/putting-all-together.png

Transform2D t = Transform2D.Identity;
// Translation
t.origin = new Vector2(350, 150);
// Rotation
float rot = -0.5f; // The rotation to apply.
t.x.x = t.y.y = Mathf.Cos(rot);
t.x.y = t.y.x = Mathf.Sin(rot);
t.y.x *= -1;
// Scale
t.x *= 3;
t.y *= 3;
Transform = t; // Change the node's transform to what we just calculated.

Shear transformation matrix (advanced)

note

If you are just looking for how to use the conversion matrix, please feel free to skip this section. This section explores an uncommon aspect of transformation matrices to establish an understanding of them.

You may have noticed that transformation has more freedom than the combination of the above actions. The basis of the 2D transformation matrix has four totals in two Vector2 values, while the rotation value and scale Vector2 only have three numbers. The advanced concept that lacks degrees of freedom is called shear.

Generally, you will always make the fundamental vectors perpendicular to each other. However, clipping can be useful in some situations, and understanding clipping can help you understand how the transformation works.

To visualize the appearance, let's overlay a grid on the Godot logo:

../../_images/identity-grid.png

Each point on the grid is obtained by adding the basic vectors. The lower right corner is X + Y, and the upper right corner is XY. If you change the basic vector, the entire grid will move with it, because the grid is composed of basic vectors. No matter what changes we make to the basic vector, all parallel lines on the current grid will remain parallel.

For example, we set Y to (1, 1):

../../_images/shear.png

Transform2D t = Transform2D.Identity;
// Shear by setting Y to (1, 1)
t.y = Vector2.One;
Transform = t; // Change the node's transform to what we just calculated.

note

You cannot set the original value of Transform2D in the editor, so if you want to cut the object, you must use code.

Since the vector is no longer vertical, the object has been clipped. The bottom center of the grid is (0, 1) relative to itself and is now at the world position (1, 1).

The coordinates within the object are called UV coordinates in the texture, so we borrow that term here. In order to find the world position from the relative position, the formula is U * X + V * Y, where U and V are numbers, and X and Y are basis vectors.

The bottom right corner of the grid is always at the UV position of (1,1) and the world position of (2,1). This position is calculated by X * 1 + Y * 1, that is (1,0) + (1, 1) or (1 + 1, 0 + 1) or (2, 1). This is consistent with our observation of the lower right corner of the image.

Similarly, the upper right corner of the grid is always at the UV position of (1, -1) and the world position of (0, -1). The position is based on X * 1 + Y *- 1, that is (1, 0)- (1,1) or (1-1,0-1) or (0,-1). This is consistent with our observation of the position of the upper right corner of the image.

Hope you now fully understand how the transformation matrix affects the object, and the relationship between the basis vectors and how the "UV" or "in-coordinates" of the object changes its world position.

note

In Godot, all transformation math is done relative to the parent node. When we refer to "world position", if a node has a parent, it will be relative to the node's parent.

If you need additional clarification, you should check out 3Blue1Brown’s great video on linear transformation: https://www.youtube.com/watch?v=kYB8IZa5AuE

Practical application of conversion

In actual projects, the conversion in the conversion will usually be handled by making multiple Node2D or Spatial nodes parent to each other.

However, sometimes it is useful to manually calculate the value we need. We will introduce how to use Transform2D or Transform to manually calculate the transformation of a node.

Switch position between conversions

In many cases, you want to perform position conversion during conversion. For example, if you have a position relative to the player and want to find the world (parent relative) position, or you have a world position and want to know its position relative to the player.

We can use the "xform" method to find the definition of the vector relative to the player in world space:

// World space vector 100 units below the player.
GD.Print(Transform.Xform(new Vector2(0, 100)));

We can use the "xform_inv" method to find the world space position relative to the player's definition:

// Where is (0, 100) relative to the player?
GD.Print(Transform.XformInv(new Vector2(0, 100)));

note

If you know in advance that the transformation is located at (0, 0), you can use the "basis_xform" or "basis_xform_inv" methods instead, which will skip translation.

Move the object relative to itself

A common operation (especially in 3D games) is to move an object relative to itself. For example, in a first-person shooter game, you want the character to move forward (-Z axis) W when pressed.

Since the basic vector is the direction relative to the parent object, and the origin vector is the position relative to the parent object, we can simply add multiple basic vectors to move the object relative to itself.

This code moves an object 100 units to the right:

Transform2D t = Transform;
t.origin += t.x * 100;
Transform = t;

To move in 3D, you need to replace "x" with "basis.x".

note

In actual projects, you can use translate_object_local in 3D or use move_local_x and move_local_y in 2D.

Apply transformation to transformation

One of the most important things about conversion is how to use several of them together. The transformation of the parent node will affect all its child nodes. Let us analyze an example.

In this image, the child node has a "2" after the component name to distinguish it from the parent node. So many numbers may seem a bit overwhelming, but remember that each number is displayed twice (next to the arrow and in the matrix), and almost half of the numbers are zero.

../../_images/apply.png

The only transformation performed here is that the ratio of the parent node is (2, 1), the ratio of the child node is (0.5, 0.5), and the positions of both nodes are assigned positions.

All child transitions are affected by the parent transition. The ratio of the child item is (0.5, 0.5), so you want it to be a square with a ratio of 1:1, and it is (but only relative to the parent item). The X vector of the child ends up being (1, 0) in world space because it is scaled by the base vector of the parent. Similarly, the origin vector of the child node is set to (1, 1), but due to the base vector of the parent node, it is actually moved (2, 1) in the world space.

To manually calculate the world space transformation of the subtransformation, this is the code we will use:

// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);

// Calculate the child's world space transform
// origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
Vector2 origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin;
// basisX = (2, 0) * 0.5 + (0, 1) * 0 = (0.5, 0)
Vector2 basisX = parent.x * child.x.x + parent.y * child.x.y;
// basisY = (2, 0) * 0 + (0, 1) * 0.5 = (0.5, 0)
Vector2 basisY = parent.x * child.y.x + parent.y * child.y.y;

// Change the node's transform to what we just calculated.
Transform = new Transform2D(basisX, basisY, origin);

In the actual project, we can use the * operator to apply one transformation to another transformation to find the child's world transformation:

// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);

// Change the node's transform to what would be the child's world transform.
Transform = parent * child;

note

When multiplying matrices, order matters! Don't mix them together.

In the end, applying identity conversion will never help.

If you need additional clarification, you can check out 3Blue1Brown’s excellent video on matrix composition: https://www.youtube.com/watch?v=XkY2DOUCWMU

Inverted conversion matrix

The "affine_inverse" function returns a conversion that "undo" the previous conversion. In some cases this may be useful, but it is easier to provide only some examples.

Multiplying the inverse transformation and the normal transformation will undo all transformations:

Transform2D ti = Transform.AffineInverse();
Transform2D t = ti * Transform;
// The transform is the identity transform.

Transforming the position by transform and its inverse transform will result in the same position (same as "xform_inv"):

Transform2D ti = Transform.AffineInverse();
Position = Transform.Xform(Position);
Position = ti.Xform(Position);
// The position is the same as before.

How does this all work in 3D?

The big advantage of conversion matrices is that they work very similarly between 2D and 3D conversion. All the codes and formulas above for 2D work the same in 3D, but there are 3 exceptions: a third axis is added, each axis is of type Vector3, and Godot stores the datum and Transform separately, because math can It becomes complicated and it makes sense to separate it.

Compared with 2D, all concepts about how translation, rotation, scaling, and clipping work in 3D are the same. To scale, we multiply each component; to rotate, we change the position pointed to by each base vector; for translation, we manipulate the origin; for cutting, we change the base vector to be non-vertical.

../../_images/3d-identity.png

If you want, it's best to try transformations to understand how they work. Godot allows you to edit the 3D transformation matrix directly from the inspector. You can download the project with colored lines and cubes to help visualize the base vector and origin in 2D and 3D: https://github.com/godotengine/godot-demo-projects/tree/master/misc/matrix_transform

note

In
the "Matrix" part of Spatial in Godot 3.2's inspector, the matrix is ​​displayed in transposition, the column is horizontal and the row is vertical. In future versions of Godot, this can be changed to reduce confusion.

note

You cannot directly edit the transformation matrix of Node2D in Godot 3.2's inspector. This may change in future versions of Godot.

If you need additional clarification, you can check out 3Blue1Brown’s wonderful video on 3D linear transformation: https://www.youtube.com/watch?v=rHLEWRxRGiM

Represents 3D rotation (advanced)

The biggest difference between 2D and 3D conversion matrices is how to represent the rotation by itself without a basis vector.

Using 2D, we have a simple method (atan2) to switch between the transformation matrix and the angle. In 3D, we cannot simply represent the rotation as a number. There is something called Euler angles that can represent rotation as a set of 3 numbers, but they are limited and it is not very useful except for trivial situations.

In 3D, we usually don't use angles, or use transform bases (used almost everywhere in Godot), or use quaternions. Godot can use Quat structure to represent quaternion. I suggest you completely ignore the way they work in the background because they are very complex and unintuitive.

However, if you really have to know how it works, here are some useful resources:

https://www.youtube.com/watch?v=mvmuCPvRoWQ

https://www.youtube.com/watch?v=d4EgbgTm0Bg

https://eater.net/quaternions

Guess you like

Origin blog.csdn.net/qq_44273429/article/details/111061547