Secrets of working with fabric in the game Alan Wake
[Remedy animation programmer Henrik Enquist described how his team created a compelling tweed jacket simulation of the protagonist of the horror thriller Alan Wake.]
The main character of our action thriller is Alan Wake, a nightmare writer where he has to fight with dark forces and solve the mystery of the disappearance of his wife. He is not a well-trained action hero, but an ordinary person.
To emphasize the character, our art director wanted to dress him in an old tweed jacket with patches on his elbows. The game takes place in the entourage of the real world, therefore, unlike a fantasy game or a space shooter, characters are limited in the tools used. And this means that the clothes of our characters become much more important.
To convey the illusion of a thriller atmosphere, Alan Wake’s jacket should be as believable as possible. The jacket should flutter in the wind and add auxiliary movements to the character when moving through the forest. As a programmer, I immediately began to think about using tissue simulation.
Fabric simulation was used in many games before us, but the techniques often used there gave a feeling of silk or rubber - materials that are not suitable for us. Only very recently very good tissue simulation systems of third-party companies began to appear, but at the time when we needed a stable solution, such tools did not exist yet, or they did not meet our needs.
In this article I will talk about the problems that we had to face, and about the solutions for creating our own tissue simulation.
The jacket was modeled with the rest of the character like a regular skinning mesh. The bones that control the jacket mesh are a separate layer on top of a regular skeleton. Jacket sleeves use the usual pattern for the shoulder and forearm. Both the shoulders and forearms are divided into one main bone and one bend bone. The upper part of the jacket is controlled by look-at constraints, and the lower part is controlled by the Verlet simulation.
Figure 1. Rig jacket on top of a regular game skeleton.
The bones of the jacket have a hierarchy running from top to bottom (the lower ones are children of the upper ones), so when the upper bones move, the lower bones follow them. We were tempted to make the lower bones daughter directly to the chest, but that would cause a loss of movement, especially vertical movement, when the character lifts his shoulders.
In the upper part of the jacket we simulate the movement of the pads on the shoulders, moving the bones of the shoulders with the help of look-at constraints towards the bones of the shoulders. Thanks to this, the pads follow the shoulder, and when you raise your hand, the pad lifts the rest of the bones, as in a real jacket.
What the look-at costraint constraint looks like
Look-at costraint applied to red cone
Look-at costraint applied to red cone
The next bones in the chain is the layer between the upper part of the jacket and the simulated lower part. These bones are driven by look-at constraints directly down to compensate for the rotation that the shoulders create. We also added position constraints between the left and right bones to compensate for the stretching that occurs when the shoulder pads move.
Figure 2. The movement of the bones when raising a hand character.
This could be quite enough to implement the restrictions in the exporter of animations and bake the results in the animation data, but we still tried to control the bones in the game engine in real time.
Thanks to this, we could save a few bytes in the animation data, as well as easily transfer animations between the characters, regardless of whether there are jackets on them. In addition, the shoulder movements generated by game inverse kinematics (for example, at the time of aiming) when resolving real-time constraints would be applied correctly.
The bottom of the jacket
Having solved the problem with the upper part of the jacket, we proceeded to simulate the lower part. Most game simulations of fabrics use a one-to-one binding between the vertices in the tissue simulation and the vertices of the rendered mesh.
We wanted to keep the accuracy of the jacket mesh so that it would not interfere with any restrictions defined by the programmer. For example, if we decided to use the same mesh for fabric simulation as for rendering, then the silhouette of the pockets and front of the jacket would be lost.
Normal maps could be used to give volume to the jacket, but we felt that would not be enough. We wanted our artists to model the jacket the way they wanted it, and then let them use normal maps to add creases or other details, rather than compensate for lost geometry.
We came to this decision: create a mesh of low-resolution fabric to simulate a jacket, and then attach it to the bones of the skeleton used to control the skinning mesh.
Figure 3. Comparison of the silhouettes of our jacket and fabric that has the same vertices with a simulation.
First we look at the physics of Verlet, and then we learn how to create a match for the simulation of bones. Verlé Physics is currently the standard solution for simulating fabrics in games. If you are unfamiliar with the Verlet technique, then for a start I recommend reading one of these articles on Gamasutra: Devil in the Blue Faceted Dress: Real Time Cloth Animation or Advanced Character Physics .
Figure 4. A grid of 4x4 vertices and constraints for one of the vertices.
For the rest, I will briefly repeat the principle of work. Figure 4 shows a fabric mesh and spring constraints for one of its vertices. As you can see from the figure, each mesh vertex is connected to all neighboring vertices, as well as to their neighbors.
Constraints from immediate neighbors are called stretch constraints and are indicated by blue. Long constraints indicated in red are called shear / bend constraints.
It is important to store these restrictions in two groups, because later we will resolve them with different parameters. Please note that in our jacket the top row of fabric points is tied to the character by skinning and will not be controlled by the simulation.
The presence of a mesh mesh is not a requirement of the algorithm itself, however, to simulate a fabric with such a topology, it is easiest to work with. The foundation of tissue simulation consists of two parts. The first part is the Verlet integration, in which we calculate the velocity for each vertex and apply it to the position.
Vector3 vVelocity = vertex.vCurrentPosition - vertex.vPreviousPosition; vertex.vPreviousPosition = vertex.vCurrentPosition; vertex.vCurrentPosition += vVelocity * ( 1.0f - fDampingFactor ) + vAcceleration * fDeltaTime * fDeltaTime;
In our project, we
vAccelerationasked ourselves the sum of gravitational force and wind. Attenuation was used to adjust the appearance of the jacket, and to stabilize the simulation. A high attenuation
fDampingFactorgives the jacket a feeling of very light fabric, descending slowly and smoothly, and a low attenuation makes the jacket heavier, causing it to sway / oscillate longer after movement.
The second part of the algorithm is the resolution of spring constraints (this process is called relaxation). For each constraint, we attract or repulse vertices from each other so that they satisfy their original lengths. Here is a readable code snippet.
Vector3 vDelta = constraint.m_vertex1.m_vCurPos - constraint.m_vertex0.m_vCurPos; float fLength = vDelta.length(); vDelta.normalize(); Vector3 vOffset = vDelta * ( fLength - constraint.m_fRestLength ); constraint.m_vertex0.m_vCurrentPosition += vOffset / 2.0f; constraint.m_vertex1.m_vCurrentPosition -= vOffset / 2.0f;
Stretch constraints hold the tops of the fabric together, while tilt / bend constraints help maintain the shape of the fabric. As you can see, with an ideal solution to this system, the fabric will move too hard. That is why, before resolving new positions, we add a coefficient to the tilt / bend restrictions.
vOffset *= fStiffness; constraint.m_vertex0.m_vCurrentPosition += vOffset / 2.0f; constraint.m_vertex1.m_vCurrentPosition -= vOffset / 2.0f;
With a stiffness coefficient of 1.0, the fabric will be non-flexible, and at 0.0, the fabric will bend without any restrictions.
Fixed time step
You must have already noticed that Verlet integration suggests that the previous time step was exactly the same as the current one; otherwise, the calculated speed will be incorrect. When using Verlet integration, a variable time step can be dispensed with, but the resolution of constraints is very sensitive to changes in the time step.
Since the solver solves the problem by iteratively circumventing the restrictions, they can never be solved ideally. In the game, this inaccuracy will manifest itself as a stretch, and the shorter the time step, the less stretch the player will see.
Ultimately, this will be a compromise between accuracy and the amount of processor time that you can spend on clothes. If the time step is not constant, then the stretching of the clothes will vary, and we will introduce unwanted vibrations into the system. More importantly, the time step will affect the stiffness index and other fabric parameters: the shorter the time step, the more stiff the fabric will be, even when using the same stiffness coefficient.
In practice, this means that before you begin to customize the appearance of clothing with the help of fabric parameters, you yourself have to decide on a fixed time step. I know that there are games in which a variable time step is used for physics, but my personal experience tells me that life becomes much easier when the time step is fixed for both physics and game logic.
Before we get into the details of tissue simulation, let's take a quick look at how the hood is simulated. To skin the tops of the hood mesh, we used an extra bone. We created a pendulum from the center of the bone to the position behind the hood. The end of the pendulum is one particle controlled by Verlet physics. Then, using a look-at constraint, the bone is directed towards the pendulum.
Figure 5. Hood and pendulum.
Creating bone matrices
The hood gives us a hint about what to do next with the bottom of the jacket. We will use the vertex positions in the simulated mesh to calculate bone transformations.
The first thing we do is map the bones so that the hinge of each bone matches the top of the simulated mesh. Due to this, the task of the part of the matrix related to the displacement will be a trivial process.
Then we need to calculate the 3x3 rotation matrix. Each row (or column, depending on the configuration of the matrix) is defined by the x, y, and z axes of the bone.
We define the x-axis of the bone as the direction from the base vertex to the next below it. Then, the y axis is defined by the vector from the vertex on the left to the vertex on the right.
Figure 6. Bones attached to the fabric mesh.
In Figure 6, the x axis is shown in red, and the y axis is shown in green. Then, the z axis is calculated as the vector product of these vectors. In the end, we also orthonormalize the matrix to get rid of the distortion in the displacement data.
As you can see, in the vertical direction, we use each row of the fabric mesh (except the last) to adjust the bones, but in the horizontal direction only every second column is used. In addition to providing the artistic advantages described above, this method is also quite quick. Thanks to this, traditional skinning techniques can be used on the GPU side to render the mesh, because otherwise we would have to update the huge dynamic vertex buffer.
A fabric mesh can have a fairly low resolution, which reduces the load on the CPU. The only additional cost to our solution is to convert the low-resolution simulation to a high-resolution mesh, but in our scheme these costs will be negligible compared to the rest of the simulation.
To solve the problem of trimming tissue with legs and body, we use the recognition of collisions between an ellipsoid and a particle. Figure 7 shows the ellipsoids needed to resolve the truncation of a jacket by a character model.
Figure 7. The ellipsoid system for the Wake model.
The recognition of collisions of ellipsoids with particles is very fast. Collisions can be solved by transforming the space in which the ellipsoid and particle exist, so that the ellipsoid turns into a sphere. You can then perform a quick collision test of the sphere and particle.
In practice, this is accompanied by the creation of an inverse transformation based on the length, width and height of the ellipsoid with its application to the particle position. The only problem here is that the normal collision that we get after converting back to the original coordinate system is distorted.
We decided that we could come to terms with a slight inaccuracy in calculating the direction of the collision. In cases where a strongly stretched ellipsoid could cause incorrect reactions, we divided it into two more homogeneous ones.
The maximum distance to the particle
Another problem that needed to be solved was the stability of the jacket. The fabric with rapid movement could cause the creation of nodes or appear on the other side of the volume of collisions and pass through the body. We solved this problem by setting a safe distance for each vertex of the simulated tissue.
For each vertex, the initial resting position by skinning is attached to the nearest bone and we use it as a reference point. If the simulation exceeds the threshold value, then we simply move the vertex closer to the reference point. In our design, we allowed the peaks below to move a greater distance than the peaks closer to the shoulders.
The maximum distance that we can allow the peaks to move is about 40 cm, when this value is exceeded, rare cases of knots and truncation begin to appear. We also tried to use other techniques, for example, collision planes, but the maximum distance method turned out to be the best. It was fast, easy to set up and provided the greatest freedom of movement before noticeable errors began to appear in the fabric.
More Tweed, Less Rubber
So far, we have been able to find good ways to achieve our goals. Our artist modeled his jacket the way he liked; for animating the jacket, an animator was not needed, because everything was simulated in the game, and the processor was pleased that we had enough resources for other in-game calculations. But one thing bothered us - the fabric looked like rubber.
First, we need to get rid of the stretch. As I said above, the phenomenon of stretching is caused by errors that appear due to the iterative nature of the algorithm. This is a popular research topic and you can find many methods to solve this problem.
Unfortunately, all the available solutions would force us to allocate much more scarce CPU resources for tissue calculations. Therefore, we solved the problem of stretching by adding the last step to the tissue simulation, in which the so-called “hard constraints” are applied.
We made strict restrictions on the stretch restrictions (they are all directed vertically). These restrictions were sorted from top to bottom so that restrictions near the shoulders were resolved to restrictions near the legs.
Since we iterate over the constraints from above, we know that the top vertex in the pair has already been solved and does not cause any stretching, so we just need to move the lower vertex towards the upper one. Thanks to this, we can be sure that after a single iteration, the length from top to bottom will be exactly the same as the length at rest.
Vector3 vDelta = constraint.m_vertexTop.m_vCurPos - constraint.m_vertexDown.m_vCurPos; float fLength = vDelta.length(); vDelta.normalize(); Vector3 vOffset = vDelta * ( fLength - constraint.m_fRestLength ); constraint.m_vertexDown.m_vCurrentPosition += vOffset;
Figure 8. Tight restrictions.
As you can see, we do not take into account the horizontal stretching of the jacket. It is impossible to apply strict restrictions to the horizontal direction, because in this case the vertex will be resolved twice, that is, we will lose the results of the vertical calculation stage and the length of the fabric will not be kept at rest.
However, we noticed that in the case of a jacket, horizontal stretching actually remains invisible to the human eye, and because of the vertical stretching, the jacket looks very bad. This solution turned out to be quite good.
Secondly, we wanted the edges of the jacket to move a little more than the rest of it. For example, if you run in an open jacket, you will notice that air resistance affects the edges of the jacket more than the central part. This is because your body covers the rest of the jacket from the wind.
Edges can be easily found by the number of constraints attached to them. Any vertex that has less than four stretch constraints is an edge. Therefore, we can mark these vertices and simulate them with other parameters.
- Reduced attenuation.
- The global wind has a greater impact.
- Movement in world space has a greater impact (for more details on movement in world space, see below).
- The maximum permissible safety distance is higher.
Due to this, the internal frequency of the edges will be different from the rest of the jacket. Now the whole jacket does not respond to impulses like a large pendulum, and only the edges add a beautiful auxiliary movement to the movement.
Figure 9. The tops of the edges.
Movement in world space and in local space
Then we noticed that when moving a character, movement in world space has a rather large effect on the simulation, while small local body turns or shoulder movements go unnoticed.
In a traditional tissue simulation, the positions of the vertices are simulated in world space. Someone may say that simulating tissue is correct, but it feels unnatural. Therefore, we simulated a jacket on characters in local space and separately added a little movement in world space. We noticed that the results we need are obtained with 100% local animation of the skeleton with 10-30% movement in world space.
And finally, we wanted to exaggerate the contrast between the jacket in slow and fast movement. We wanted the jacket to be relatively motionless when walking, and when Alan jumps or dodges, the movement should be more lively.
We thought that when the jacket touches the body, it should move less due to the friction between the jacket and the shirt, and when the jacket rises, it should move harder because nothing limits it. We simulated this by applying an increased attenuation value to each vertex touching the ellipsoid. Thanks to this, the tops touching the body will appear a bit sticky, creating enough contrast between the jacket in normal situations and in fast movement.
Conclusion and further work
The first embodiment of tissue simulation was quite simple to implement: we just searched for the word “fabric” in the game development literature and applied the algorithms we found. The second stage, in which we tried to achieve a convincing sensation of a tweed jacket, required the study of scientific articles, a lot of trial and error, and even the removal of part of the code.
Of course, you can always improve something. For example, using low-resolution simulation and linking it to a high-resolution mesh complicates the solution to the problem of all truncations. We did not have enough time for other small details: for example, these are fold cards at the places of the folds of the jacket or the implementation of the correct interaction between the jacket and the tornado.
Ultimately, our efforts paid off - our fabric is very different from tissue simulation in other games. She looks much more like tweed than silk or rubber. In addition, our system turned out to be very flexible and allowed us to simulate other fabrics, for example, the down jacket of Barry Wheeler and the veil of the old lady. It seems that by adjusting the parameters you can achieve simulation and other types of tissue.
Figure 10. Tweed jacket.