banner



How To Blend Animations From End Of One To The Next Ue4

Written in collaboration with Andre Sato

The Vertex Animation Texture (VAT) technique, also known as Morphing Animation, consists of blistering animations into a texture map for use later on in the vertex shader. This technique enables united states of america to proceed the mesh completely static on the CPU side (changing the SkinnedMeshRenderer component to MeshRenderer component). And here at Wildlife, we are using this technique in our games.

If you are a Tennis Clash actor, you probably accept seen the crowds moving when you score. Aye, this is the technique being used. It'due south nice, isn't information technology? Let's check how the technique works.

We ran a examination using a low-end device (Samsung S6), the scene has more than than 2000 instances. Each one has 800 vertices, you can come across at that place is no frame drop even with many other systems running at the same time:

(merely illustrative video, non a real game)

Detailing the technique

By now, you should be wondering how this technique works. We volition tell yous. We run the animation, and for each frame, we read the local-space vertex position (ten, y, z). Nosotros shop this data in a matrix. This assortment volition be (Num_Verts ten Num_Frames) in size where each jail cell is a Vector3. Because normals are different at each frame of the blitheness we must also shop them in another matrix (same as the vertices).

The data of these arrays volition be accessed in the vertex shader. The almost common way to read matrix in the vertex shader is by using textures. Although textures are most commonly used to store color data (albedo, roughness map, specular map, tint map, etc.) textures can as well be used to store whatever kind of information (e.g. normal map).

To shop the vertical and normal position information nosotros chose to utilize the RGBAHalf texture. This format gives us 16bits per channel. We, then, map our matrix in such a manner that we write:

Vector3 (x, y, z) -> Vector3 (r, grand, b)

Generated normal texture

Shader

In the vertex shader, nosotros sample the texture using the tuple [vertexId, animFrame]. Beneath is an instance of a unproblematic vertex shader using vertexId and reading the commencement frame of the animation, the first line of the texture:

          struct appdata
{
uint vertexId : SV_VertexID;
};
v2f vert (appdata five)
{
float vertCoords = v.vertexId;
float animCoords = 0;
float4 texCoords = float4(vertCoords, animCoords, 0, 0);
float4 position = tex2Dlod(_AnimVertexTex, texCoords);

v2f o;
// Utilize position values equally a standard local space coordinates
o.vertex = UnityObjectToClipPos(position);

render o;
}

Note: Why tex2Dlod? In the vertex shader, it is not possible to use tex2D. tex2D is an abbreviation of tex2Dlod, however, letting the hardware choose the appropriate mip level. Such an supposition cannot be made in vertex shader. So we apply tex2Dlod and fix the lod level 0.

The fetched vertex position is in local infinite and must be transformed into the world so clip space. This way the grapheme will be rendered the first frame of the blitheness:

Animating

To breathing the character, we just need to vary the sampled line based on the current time. Nosotros accept modified the value of animCoords in the above code to:

          float animCoords            =            frac (_Time.y            *            _AnimVertexTex_TexelSize.y);        

Where

_AnimVertexTex_TexelSize = Vector4 (1/width, 1/superlative, width, height)

Nosotros're merely normalizing time to the texture height so the animation plays in a 1-second loop.

In this case, our grapheme has two animations. As they are vertically concatenated, they volition execute both in sequence.

The mesh orangish outline does non match the animation because in the CPU the grapheme is static. Unity is not enlightened of the CPU that the vertices will be moved past the GPU.

Bilinear filtering

Using a bilinear filter is very helpful to the technique as it will automatically interpolate betwixt two frames making the animation smoother.

This can drastically reduce the number of frames needed to be stored in the texture. Depression-frequency animations can use very few frames without noticeable quality loss due to the bilinear interpolation.

Withal, we must be very careful when using bilinear filtering as nosotros just want to interpolate vertically (between frames). We tin can never interpolate horizontally, as they represent different vertices and volition result in terrible artifacts.

Bilinear filtering interpolates one-half texel in each management. So we need to ensure that nosotros always sample the horizontal center of the texel. Permit (u, v) be the coordinates of the sample, the value of u must always be such that information technology falls in the center of a texel. In practice nosotros shift the value of vertexId half texel:

          float mapCurentVertexToTextureCoords(int            vertexId, float invTextureWidth)
{
// Normalize texture coords
float normalizedVertexId = vertexId * invTextureWidth;
// Get half of x coord textel size
bladder halfTextelCoord = 0.five * invTextureWidth;
// Sum one-half of x coord textel size to sample
// middle of textel (uv snapping)
return normalizedVertexId + halfTextelCoord;
}

Caveats

2048x2048 Limit (OpenGLES ii.0)

Since the texture has the width equal to the number of mesh vertices, we have the limitation of not being able to use meshes larger than 2048 vertices.

Because we utilize bilinear filtering over the blitheness frames, the texture height tin can be very minor.

We can and then divide the texture in half and concatenate information technology below making it equally shut to a foursquare as possible. This can be repeated resulting in the paradigm below using four splits.

This operation can exist applied recursively in order to use most of the 2048x2048 infinite. We just need to ship a compatible to the shader that tells how many times the texture has been divided.

Wrap mode

To avoid generating one texture for each animation and consequently having a controller alter this texture reference at runtime, nosotros vertically concatenate multiple animations. We tin ship another uniform to the shader informing the current animation to play.

Below is shown a texture with nine concatenated animations. Nosotros also use the technique described in the previous section to make amend use of the 2048x2048 space:

Since nosotros have several concatenated animations, we need to simulate a "Clamp Wrap Mode" vertically: we cannot sample the offset half of the beginning texel nor the last half of the last texel since bilinear filtering volition interpolate between animations.

Solution: We clamp they coordinate so every bit not to sample at the edges.

                      clamp (first_anim_textel half, last_anim_textel half, yCoord)                  

Thus, nosotros simulate a "wrap style clamp" for any region of the texture.

Blending Different Animations

Transitioning animations is a large trouble for this technique: if the character is in the eye of a standing blitheness and we take him perform a sitting blitheness he will "pop" (teleport from continuing up).

One way to mitigate this trouble would be to sample both animations (the current and the adjacent) and tween each other.

Nosotros have two bug, double the samples and interpolation volition always be linear: if in the current animation the character has his arm downwards and on the side by side animation has his arm up, he volition not perform the desired shoulder rotation: The vertices of his hand/arm will follow a directly line through the body.

An alternative and inexpensive solution is to apply the technique only when there is no need to blend animations.

In Tennis Clash we ask the animators to ensure the animations have the same final and initial frames and so they can be switched without popping.

The oversupply starts by playing a loop idle animation. When an animation effect occurs (due east.1000. clapping) each character waits for its current idle animation to finish then starts the handclapping blitheness. At the end of the clap, it rolls back to the idle loop. That is, we guarantee that the character will never start an animation while in the eye of another 1 and since the first and terminal frames are the same, no popping is visible.

Since the idle animation is brusque and out of sync with each other (past using an individual commencement time offset), the effect generated is quite organic.

Texture Format

There are some problems using RGBAHalf (64 bits/pixel) textures:

  • Slower to read compared to RGBA (32 bits/pixel) textures.
  • Some devices practice not back up bladder textures (e.yard. Mali 400 GPU devices crash the application if you lot attempt to load an RGBAHalf texture). A fallback solution is required for OpenGLES 2.0.
  • No support for RGBHalf (48 bits/pixels): nosotros only need 3 channels and the blastoff is unused.

The ideal solution would be to utilize but RGB24 (24 bits/pixel) textures supported past every device.

Encoding Normal to RGB24 texture

Normals can be easily encoded into an RGB24 texture as they are normalized vectors in the range [-one, ane].

We just need to encode them to the range [0, 1]:

normal.xyz = normal.xyz * 0.5f + 0.5f;

And in the shader, after reading the texture, we go back to the range [-1, 1]:

normal.xyz = (normal.xyz -0.5h) * two.0h;

Encoding Vertices to RGB24 Texture

To be able to use just RGB24 textures to encode the vertices, we need to encode each RGBAHalf value to RGB24.

  • RGBAHalf uses 16bits per aqueduct representing a float.
  • RGB24 uses 8 bits per channel representing an int.

Solution:

Ane style to encode/decode a Float into an RGBA texture:

                      //            Encoding            /            decoding [0..one) floats into 8 bit            /            aqueduct RGBA. Note that 1.0 will            not            be encoded properly.          inline float4 EncodeFloatRGBA (float v)
{
float4 kEncodeMul = float4 (1.0, 255.0, 65025.0, 16581375.0);
float kEncodeBit = one.0 / 255.0;
float4 enc = kEncodeMul * v;

enc = frac (enc);
enc — = enc.yzww * kEncodeBit;

return enc;
}

inline bladder DecodeFloatRGBA (float4 enc)
{
float4 kDecodeDot = float4 (one.0, ane / 255.0, 1 / 65025.0, one / 16581375.0);
render dot (enc, kDecodeDot);
}

Every bit our floats use only 16-bits, nosotros could optimize the above encoding to utilise merely two 8 chip channels instead of iv:

                      //            Encoding            /            decoding [0..1) floats into 8 chip            /            aqueduct RG.            
// Note that one.0 will not be encoded properly.
inline float2 EncodeFloatRG (float v)
{
float2 kEncodeMul = float2 (1.0, 255.0);
bladder kEncodeBit = 1.0 / 255.0;
float2 enc = kEncodeMul * v;

enc = frac (enc);
enc.x — = enc.y * kEncodeBit;

render enc;
}
inline float DecodeFloatRG (float2 enc)
{
float2 kDecodeDot = float2 (1.0, 1 / 255.0);
return dot (enc, kDecodeDot);
}

Thus we can use just ii channels to encode 1 float, so nosotros volition need 6 channels (2 RGB24 textures) to encode 3 floats.

A possible distribution of channels would be:

Ten bladder encoded in (R_texture_1 + G_texture_1) -> (X1, X2)

Y float encoded in (B_texture_1 + R_texture_2) -> (Y1, Y2)

Z float encoded in (G_texture_2 + B_texture_2) -> (Z1, Z2)

That is:

Texture ane (RGB24) = (X1, X2, Y1)

Texture ii (RGB24) = (Y2, Z1, Z2)

Then, to retrieve the original floats in the shader we employ the DecodeFloatRG defined above:

          float3 tex1 = tex2Dlod (_AnimVertexTex1, texCoords);            
float3 tex2 = tex2Dlod (_AnimVertexTex2, texCoords);
bladder positionX = DecodeFloatRG (tex1.rg);
float positionY = DecodeFloatRG (float2 (tex1.b, tex2.r));
float positionZ = DecodeFloatRG (tex2.gb);
float3 position = float3 (positionX, positionY, positionZ);

Nonetheless, this technique is but possible if the floats are in the range [0, 1) (http://marcodiiga.github.io/encoding-normalized-floats-to-rgba8-vectors). Thus nosotros generate a bound box for our animation and normalize the vertices inside that box. At runtime, after sampling the textures we scale back the normalized vertices to object space.

Fallback

Although we take mitigated many of the problems related to compatibility, the technique notwithstanding requires a specific hardware capability: sample texture in the vertex shader (tex2Dlod instruction).

If the hardware doesn't have this capability, we accept no option but to fall back to some other SubShader that just converts the input vertex to clip space without animation.

At unity, we can check this capability using

          #pragma target 3.0          #pragma require samplelod        

Mind that target 3.0 covers OpenGL ES ii.0 with extensions. (In item, the technique will be supported if the device has the extension EXT_shader_texture_lod). OpenGL ES devices version three.0 or above supports it natively.

Instancing

If the device supports instancing it is possible to ship different parameters for each instance, then each instance tin run a unlike animation at a different time.

          #pragma target 3.five          #pragma require instancing samplelod        

If not, in the case of Tennis Clash where the audience doesn't motion (transform matrix is static), we can still do static batch sending the blitheness parameters as uniform, however, anybody will animate in sync (which is not a large problem in the case of audiences).

Decision

The Vertex Blitheness Technique is very optimized and well suitable when you need a huge quantity of animated objects in the scene and you lot don't demand animations blending: you lot can await for an animation finish before starting another (equally the case of the audience in Tennis Clash).

With this post, we hope to have collaborated with the explanation and broadcasting of the technique through examples and details on the operation of vertex blitheness.

Source: https://medium.com/tech-at-wildlife-studios/texture-animation-techniques-1daecb316657

Posted by: weissfroned.blogspot.com

0 Response to "How To Blend Animations From End Of One To The Next Ue4"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel