Bone Animation Textures - GhislainGir/BlenderGameToolsDoc GitHub Wiki

vat

Bone Animation Texture (BAT) is a technique similar to Vertex Animation Texture (VAT), though likely more recent. It is also poorly documented and much less commonly implemented, despite offering several notable advantages over VAT. I'm not even sure BAT is its official name—it's quite common to broadly label any kind of vertex animation technique as VAT—but we'll roll with it.

VAT is highly versatile because it stores vertex offset and normal data, making it agnostic to the underlying animation technique. The baked animation can originate from a mesh animated and deformed using a skeleton or armature, shape keys or morph targets, modifiers, keyframes, and so on. As long as the topology and vertex order remain unchanged during the animation, VAT allows for a 1:1, non-destructive baking process.

Bone Animation Texture, on the other hand, is specifically designed for baking skeletal animations and can be destructive. While BAT may initially seem less flexible than VAT, it offers significant advantages—most notably, it requires much lower texture resolutions, resulting in a smaller memory footprint.

This can lead to notable performance improvements, especially since rendering skeletal meshes is typically the most resource-intensive way to display animated meshes. By using static meshes instead, you can take advantage of instancing and particle systems to efficiently render crowds, among other scenarios. This technique comes with its own set of pros and cons, which we'll explore further in this documentation.

Important

Bone Animation Textures share many principles with Vertex Animation Textures, so many technical considerations—such as texture filtering, texture format, and UV precision—apply equally to both. These topics are thoroughly covered in the Vertex Animation Texture article, so it's highly recommended to read that first—unless you're already familiar with them.

Important

This wiki includes a technical art compendium that covers side effects and important considerations when using vertex offsets. These considerations are not specific to BAT, so they are listed on their own dedicated page, which you should probably read after this page for a more complete understanding of BATs.

Note

Most of my understanding of BAT comes from reverse-engineering Unreal Engine’s AnimToTexture plugin. My own implementation follows similar techniques and principles. Credit goes to the original authors of the plugin for their work and insight. The same goes for the author(s) of BAT, whom I wasn’t able to identify.

Theory

It may help to break down the algorithm into two parts: what happens during baking, and what needs to be done to reconstruct the skeletal animation in the vertex shader. While the baking process is typically handled by the tool and isn’t a major concern for the end user, understanding how the algorithm works under the hood can be useful for troubleshooting when things go wrong.

Baking

The algorithm begins by baking the skinning dataper vertex—into a texture. Each vertex is assigned a unique UV coordinate—using a dedicated UV map—to be centered on a unique texel in the texture.

bat_01

The width of this texture is determined by the number of vertices in the mesh. If the vertex count exceeds a certain limit, such as 4K, resulting in an excessively wide texture, vertices can be distributed across multiple rows.

bat_02

For each vertex, the algorithm identifies the most influential weight groups and their corresponding bones. The number of bones considered is arbitrary and user-defined, though most implementations limit this to four. Using more bones is usually unnecessary and would affect performance, more on this topic further below.

bat_03

Note

This is why BAT can potentially be destructive. While most game engines support up to 8 bone weights per vertex, it's still quite common to use only 4. BAT can renormalize weights on the fly by skipping the least influential bones—sometimes using only the most influential one—which can change how the mesh deforms. This isn't necessarily a bad thing, as reducing the number of bones per vertex on distant meshes can significantly lower the vertex shader cost, with minimal impact on visual fidelity. At a distance, the resulting approximation in the animation is often barely noticeable, if at all.

Once the most influential bones for each vertex are identified, each bone is assigned a unique index—typically with index 0 for the root bone, 1 for the first child in the hierarchy, and so on.

bat_04

The indices of the four most influential bones are stored in the RGBA channels of the texel assigned to the vertex, ordered from most to least influential.

bat_05

The corresponding bone weights are stored in the texel just below. If a vertex was originally skinned to more than four bones, the weights are normalized so their total equals 1.0.

bat_06

The skinning texture is therefore quite lightweight—typically just two texels per vertex to store up to four bone indices and their corresponding weights. However, this isn’t a strict requirement. If each vertex is skinned to only two bones, for example, the indices could be stored in the R and G channels, and the weights in the B and A channels, reducing the texture size even further to just one texel per vertex. On the other hand, you could opt for a more expensive approach by using additional texels per vertex—or even a second texture—to store up to eight bone indices and weights. The sky's the limit.

Encoding four bone indices and weights in two separate texels is the typical approach, as it offers great flexibility. Close-up meshes benefit from high-fidelity animations, though at a higher vertex shader cost, while distant props and LODs can use fewer bones by renormalizing the bone weights and discarding the least influencing bones on the fly. With this method, each vertex can retrieve both the bone indices and their corresponding weights using just two texture samples.

Note

Vertex colors can also be used to encode four bone indices using the red, green, blue, and alpha channels, assuming the armature being baked contains fewer than 256 bones. Alternatively, bone weights can be encoded instead of bone indices—or a mix of both—but the low precision of 8-bit vertex colors may distort the bone weights slightly. Despite its constraints, using vertex colors to store bone indices and weights is an appealing approach, as it eliminates the need for a separate texture asset, texture sampling, and an additional UV map on the mesh for sampling that texture—which might even require 32-bit UVs depending on its resolution.

The next step is to bake the bone transforms so they can be accessed using the baked bone indices.

To do this, it's common to create two additional lightweight textures: one for bone positions and one for bone rotations. Their width matches the number of bones to bake and their height matches the number of animation frames. Scaling is usually omitted in skeletal animation within game engines, as it tends to cause various issues.

Position data is stored in the RGB channels of the first texture, while rotation is typically stored as an axis and angle in the RGBA channels of the second. However, implementations may vary—data might be bit-packed, rotations may be represented as quaternions, or the full transform could be baked as a matrix across multiple textures.

The bone index determines the column in which each bone’s data is stored: the root bone, index 0, in the first column, the first child, index 1, in the second, and so on. Each row stores the position and rotation data for a specific animation frame.

bat_07

Frames are typically stacked vertically, similar to how it's done in Vertex Animation Textures (VAT). VAT stores data per vertex per frame, which often results in textures wider than 4K—especially since it's now common to work with high-vertex-count meshes. In such cases, data for a single frame may need to span multiple rows or be stored sequentially, as explained in the VAT article.

bat_08

With Bone Animation Textures, this is rarely necessary, as even the most complex characters usually have no more than a couple of hundred deforming bones. However, if you're working with an edge case involving a very high bone count, the same approach used in VAT can be applied—stacking the data for a single frame across multiple rows. This, of course, prevents the use of pixel interpolation to achieve smooth frame blending. In any case, pixel interpolation is only applicable in certain scenarios, as some data types—such as bitpacked quaternions, and quaternions in general—cannot be linearly interpolated. We won’t go further into that here, as it’s already covered in the VAT article.

Sampling

Once the bone indices, weights, and transforms have been baked into textures, this data can be sampled in the vertex shader to reconstruct the skeletal animation.

This process can seem unintuitive at first, but it becomes clearer when simplified. Instead of starting with an organic object—typically skinned using multiple bone influences per vertex, like a human character with smooth deformations—it’s easier to imagine a mechanical skeletal character. In this simpler model, each joint is controlled by a single bone, and all vertices associated with that joint are influenced by that one bone only.

bat_10

This information is stored in the previously baked skinning texture. Each vertex UV points to a unique texel that contains the bone index in the red channel, and the bone weight in the red channel of the texel directly below.

bat_09

Note

For mechanical skeletal meshes where each vertex is influenced by only one bone, baking and sampling the bone weight is unnecessary, as it can simply be assumed to be 1.0.

Using the bone index, the textures storing the bone transforms can be sampled at the corresponding column to fetch the correct transform for each vertex—column 0 for bone index 0 (typically the root bone), column 1 for bone index 1, and so on—at the row corresponding to the desired animation frame.

bat_11

However, there’s a catch: the vertex movement required to reconstruct the animation must be relative to the mesh’s reference pose—typically the first frame of the animation. The first frame should therefore apply no offset or rotation, because the static mesh, as-is, already matches that pose. The second frame, and each one after that, must apply transforms relative to the first frame, applying only the necessary movement to match that specific pose.

bat_12

This is done in two steps: first, vertices must be rotated around the bone’s pivot point based on the difference in rotation between the current frame’s target pose and the reference pose. To make this clearer, we’ll illustrate the process one bone at a time, starting with the end bone.

bat_13

After that, vertices are offset by the amount of bone movement relative to the reference pose.

bat_14

This process is repeated for every vertex and, consequently, for every bone, effectively applying the animation pose. The bone rotation is applied first in the reference pose, followed by its offset.

bat_15

To summarize, we need bone data in both absolute space—to define the reference pose and thus the pivot points for rotation—and relative to the reference pose—to describe how much movement to apply. That’s why the first row in the texture(s) typically contains the bone transforms baked in absolute space, while the subsequent rows store the relative transforms for each animation frame. The vertex shader can simply ignore the data from that first row to preserve the static mesh already being in the reference pose.

bat_16

Note

Frames can be vertically stacked in ascending or descending order, depending on the UV coordinate system of your target application—whether it’s DirectX- or OpenGL-oriented—as explained in the VAT article. As a result, the reference pose may be stored in either the first or the last row of the texture.

This may seem inconsequential, but it causes problems when using pixel interpolation to achieve smooth frame blending. When UVs are vertically offset to play the animation, they eventually reach the final row of pixels. At that point, the UV coordinates wrap around (at least by default), causing the last frame to blend with the first row—which contains the reference pose—leading to visual artifacts.

bat_17

To avoid this, padding can be applied. This is thoroughly explained in the VAT article. One approach is to insert the last frame between the reference pose and the actual first frame. To be safe, the first frame can be duplicated at the end of the sequence as well. This effectively isolate the animation clip from the row of pixels storing the reference pose. This may be redundant if padding is already added to each NLA clip individually, as demonstrated in the VAT article.

bat_18

To reconstruct the skeletal animation in the vertex shader, the first row of the transform textures is sampled to get the bone data in the reference pose, and the row corresponding to the desired animation frame is sampled to retrieve the relative transform. Using the bone’s position from the reference pose, the bone’s relative rotation is applied first, followed by the relative position offset, to reconstruct the final animated position, as described above.

It's important to understand that this process does not rely on any hierarchy and does not reconstruct the armature. Bone transforms are baked as-is—relative to the mesh’s origin and independent of any parent—so they can be reapplied directly without considering parent-child relationships.

Since we’re working with pivot and rotation, the vertex normal can also be rotated, eliminating the need to store normal data per vertex. Applying bone scale is technically possible but is not typically implemented, as scaling bones in game engines is generally avoided for various technical reasons. Therefore, supporting bone scale is quite rare, if ever needed at all.

The principle illustrated above with a mechanical example applies equally to meshes that are organically skinned, with multiple bone influences per vertex. The additional bone indices and weights are stored in the green, blue, and alpha channels of the skinning texture, and each index can be used to fetch the necessary bone transforms to apply.

Each transform (rotation and offset) is modulated by its corresponding bone weight, and the sum of all these transformations reconstructs the final pose, taking all bone influences into account. The intermediate steps are difficult to illustrate because applying the rotation and offset of a partially influencing bone results in a distorted mesh. However, once all bone influences are combined, the mesh correctly forms the final pose—since the bone weights always sum to 1.0.

What and when for?

Just like VAT, BAT is not meant to replace skeletal animations. The reason skeletal meshes are expensive is due to the many features they offer: skeletal animations allow real-time manipulation of bone data, adding physics, hand/foot IK, retargeting skeletons, complex LOD behavior, and much more. In comparison, BAT rely on a vertex-shader reading baked data and inherit all the constraints that come with any baked workflow. What's baked is baked, period.

BAT is especially effective for rendering crowds, large numbers of animated meshes, or particle-based VFX. While it is more technically involved—and requires a more complex vertex shader compared to the simpler VAT approach, where offsets and normals are stored per vertex per frame—it offers a much lighter memory footprint by storing data per bone per frame instead of per vertex per frame.

There's no absolute right or wrong choice between VAT and BATit depends on the use case. BAT requires several texture fetches: two to retrieve the bone indices and weights, plus three more per influencing bone—one to get the bone’s position in the reference pose (pivot point), one to fetch the rotation for the current animation frame, and one to retrieve the offset for that frame. For each bone, two rotations need to be computed, involving costly trigonometric calculations—one for the vertex offset and one to fix the vertex normal.

Because of this, it's generally best to limit the number of bone weights to two. Using only one weight can produce a more mechanical look, though this may be acceptable for distant LODs. Since the textures involved are quite small, this technique is likely very GPU cache-friendly. Even with four bone weights, it often results in a static mesh that is significantly cheaper to render than the traditional skeletal mesh pipeline found in most game engines. Ultimately, vertex count is likely one of the key factors in deciding whether BAT or VAT is the better choice. Both methods have their pros and cons, so when in doubt, profile your specific case and choose the approach that best suits your needs.

Documentation

A lot of effort went into exposing a wide range of options and giving users maximum freedom to create flexible workflows. This does make the UI a bit cluttered, but don’t let that intimidate you. The process of baking Bone Animation Textures should be really straightforward for most use cases. But before we dive in, let’s quickly review the common options and panels.

Panel - Frames

A panel called 'Frames' lets you customize how the frame range and reference frame are set.

  • The NLA mode allows the tool to automatically derive the animation range based on the NLA tracks of the armature.
  • The Scene mode uses the scene’s current frame settings: start, end, and step.
  • The Custom mode lets you specify a custom animation range and step.

bat_panel_frames

Not much needs to be said about the Scene and Custom modes, but the NLA mode offers additional features and comes with extra constraints.

When using the NLA mode, objects must all have an Armature modifier pointing to the same Armature. That armature must have an NLA track, which in turn must contain NLA strips.

The NLA mode also allows you to add padding in between clips. It also allows you to specify a list of NLA clips to exclude when determining the frame range. This can be useful when using the retargeting feature (more on that further below) and needing to provide a single-frame T-Pose clip as the reference pose, a clip that would otherwise need to be excluded from the bake.

Also, when using the NLA mode, the step setting may be applied differently:

  • Global: Bakes every nth frame across the entire animation
  • NLA Clip: Bakes every nth frame, starting from the start frame of each clip.

Important

Clips that are looping should likely be clipped to remove the duplicated start and end frames. It’s also recommended to separate each clip by one frame; otherwise, the end frame of one clip will be replaced by the start frame of the next.

bat_add_clip

Note

NLA tracks will be scanned regardless of the mode used in order to extract animation information that may be useful when baking a BAT for use in a state machine. It may be important to know when each NLA clip starts and ends, etc. Note that their start/end range will be clamped to the specified start/end. This has no impact on the bake whatsoever and is purely informative.

Important

You may adjust Blender's scene frame rate settings and scale your animations to baking smoother animations (at the cost of baking more frames, resulting in heavier BATs).

Panel - Textures: Skinning Data

A panel called 'Skinning Data', located in the 'Textures' panel, lets you customize several options related to generating and exporting the skinning texture(s), which contain the bone indices and weights.

bat_panel_skinningdata

  • Max Width: Maximum allowed texture width. Exceeding this may cancel the bake.
  • Max Height: Maximum allowed texture height. Exceeding this may cancel the bake.
  • Mode: "Select how the texture resolution is derived from the vertex count

Textures can be added to or removed from a list, with each texture requiring a unique name (this includes textures listed in the Animation Data panel—more on that below).

For each texture, another list lets you specify how many texels to allocate per vertex, as well as what to store in each texel. As discussed in this article, it's common to use two texels per vertex to encode the indices and weights of the four most influential bones.

bat_panel_skinningdata_row

Panel - Textures: Animation Data

A panel called 'Animation Data', located in the 'Textures' panel, lets you customize several options related to generating and exporting the animation textures, which may store the bone positional data, as well as rotational data, and more.

bat_panel_animationdata

  • Max Width: Maximum allowed texture width. Exceeding this may cancel the bake.
  • Max Height: Maximum allowed texture height. Exceeding this may cancel the bake.
  • Power of Two: Force textures to be power-of-two sizes. Not recommended, as non-power-of-two textures ensure tight packing and are widely supported
  • Square: Force texture width and height to be equal if 'Power of Two' is enabled. Typically unnecessary, but provided as an option for specific use cases
  • Mode: Control how frames are arranged in the texture when there's extra space (underflow) or not enough space (overflow). Underflow occurs when the number of bones per frame is less than the image width, causing gaps at the end of the line ('Power of Two' might cause this). Overflow happens when there are too many bones for a single line, and the data is spread across multiple lines, possibly leaving gaps. This setting determines how to handle these empty spaces

Panel - Textures: Export

A panel called 'Export', located in the 'Textures' panel, lets you customize how the generated textures (skinning & animation) are exported, if exported at all.

bat_panel_texexport

  • Export: Enable to export the generated textures to an EXR file upon bake completion
    • Export|Filename: Name for the texture file (without the .exr extension). is a placeholder tag that can be used to be replaced with the object's name. is a placeholder tag that can be used to be replaced with the texture's custom name
    • Export|Path: Texture file path, excluding the file name. The path is relative to the Blender file if saved, or absolute otherwise
      • Export|Advanced|Override: Enable to override any existing .exr file

Panel - Mesh

A panel called 'Mesh' lets you customize several options related to generating & exporting the baked mesh.

bat_panel_mesh

  • Scale: Scale factor for the baked offsets/positions. This compensates for Blender's default unit (1 meter) and aligns with the target application's unit system. A default factor of 100 is used to convert from meters to centimeters, Unreal's default unit
  • Invert X/Y/Z: Invert the world X/Y/Z axis (Y set to True for Unreal Engine compatibility)
  • Name: Name of the baked object
  • Materials: Enable to copy materials
    • Previz|Anim: Enable to use geonodes to visualize the bake inside Blender. Still needs to be implemented. Disabled for now
    • Previz|Bounds: Enable to visualize the animation's overall extents
    • UV|Name: Name of the UVMap to be created or used for baking mesh UVs
    • UV|Invert V: Invert the V axis of the UVMap and flip the VAT texture(s) upside down. Typically True for exporting to Unreal Engine or DirectX apps, False for Unity or OpenGL apps
  • Export: Enable to export the generated mesh to an FBX file upon bake completion
    • Export|Name: Name for the exported FBX file (without the .fbx extension). is a placeholder tag that can be used to be replaced with the object's name
    • Export|Path: File path for the exported FBX, excluding the file name. The path is relative to the Blender file if saved, or absolute otherwise
    • Export|Advanced|Override: Enable to override any existing .fbx file

Panel - XML

A panel called 'XML' lets you customize the export options for the XML file.

bat_panel_xml

  • Export: Enable to export an XML file containing information about the bake process (recommended)
    • Export|Mode: Select how the XML file name and path are generated
      • Mesh Path: Use the same FBX file name and path for the XML file. Defaults to 'Custom' if mesh is not exported
      • Custom Path: Specify a custom XML file name and path
  • Export|Filename: Name for the exported XML file (without the .xml extension)
  • Export|Path: Path for the exported XML file, excluding the file name. The path is relative to the Blender file
  • Export|Override: Enable to override any existing .xml file

Retargeting

This feature allows the animation of a single high-resolution mesh to be baked into a bone animation based on a low-resolution mesh. While this may lead to approximations or unintended results, it can be extremely useful for generating LODs, simplifying meshes, and similar use cases. The process is identical to the one used in the Vertex Animation Tool, so you're encouraged to read its retargeting chapter for more details.

Bake - Animation

The first step is to ensure that all objects are properly skinned to an armature, and that they share a single animation modifier pointing to that unique armature object. This armature must contain animation clips in an NLA track.

bat_bake_setup

Next, set up the skinning texture. This process is straightforward, as it’s assumed that most users will want to bake the indices and weights of the four most influential bones. Clicking the '+' icon will automatically populate and configure the skinning texture's row list—which appears below—to bake the bone indices and weights.

bat_panel_bake_skinning

Using a custom solution, such as baking indices and weights into vertex colors or using a single row to store two indices and two weights, is also possible and should be simple enough, but won’t be covered here.

Important

Each texture must have a unique name! You can rename a texture by double-clicking it or using the exposed 'Name' property.

Next, set up the animation texture. This step involves a bit more clicking, as it offers a high degree of flexibility. Begin by clicking the '+' icon to add a new texture to the list, and rename it as desired.

bat_panel_bake_animation

Then, configure what to store in the red channel. We'll assume most users will follow the common BAT approach: storing XYZ position in the RGB channels of one texture, and axis-angle rotation in the RGBA channels of a second texture. To start, set the red channel to Position to store the X component.

bat_bake_pos_01

Configure the green and blue channels to Position as well, to store the Y and Z components.

bat_bake_pos_03

Next, create a second texture, rename it as needed, and configure its RGBA channels to store the axis-angle rotation data accordingly.

bat_bake_rot

Most data types can be remapped to the [0:1] range using the 'Remap' feature, allowing them to be stored in 8-bit textures—provided you're aware of the limitations that come with lower precision. When reading this data back from the texture, it must be remapped to its original [min:max] range using the reported offset and range values.

The tool also allows you to swizzle world axes to account for coordinate system differences between Blender and your target application.

As a final step, review the settings in the Mesh panel. You may need to invert certain world axes to match your target application, or flip the UV's V axis.

Once everything is set, click Bake—and voilà!

Bake - Result

The bake should generate a new mesh object, with the skinning & animation image(s) saved within the Blender file.

UVMap

It's a good idea to check the generated mesh's vertex-to-texel UV map. Each vertex should be centered on a unique texel in the first row of pixels, and the number of rows should match the configuration used for the bake.

bat_bake_result_uv

Visualization

This feature has not been implemented yet, but it’s expected to eventually allow you to visualize the bake directly in Blender.

Mesh

Simply import the .fbx into your game engine. In Unreal Engine, you may want to check the following import options.

bat_mesh_import

Note

If it wasn’t automatically exported, you’ll need to manually export the generated mesh to .fbx. Blender’s default export settings should work fine.

You'll also probably want to tweak the mesh's bounds using the info stated in the 'report' panel and/or XML file.

bat_bake_mesh_offset

Important

Special care must be taken when reading the offset value, as it depends on the 'Scale' bake setting and how the Blender scene units are configured. Most game engines, including Unreal Engine, provide ways to visualize a mesh's bounds, so it’s usually quite clear if a multiplier needs to be applied when setting the bounds offset.

Textures

Simply import the .exr textures into your game engine.

Each may need to be compressed in different formats:

  • Uncompressed 8-bit RGBA: Unlikely, but possible if data was remapped to the [0:1] range and you're aware of the limitations of working within the 8-bit range.
  • Uncompressed 16-bit HDR: Likely the best choice for most use cases. This however exclude texture(s) storing an XYZW quaternion bit-packed into a single 32-bit float.
  • Uncompressed 32-bit HDR: BATs are unlikely to require 32-bit HDR textures, unless the texture stores an XYZW quaternion bit-packed into a single float. Most bone-related data—such as indices, weights, and transforms—can typically be represented accurately enough with 16-bit precision.

bat_bake_tex

For skinning textures that store indices and weights, Nearest sampling is required to prevent pixel interpolation. Each element must sample a specific texel, and any blending with neighboring data can corrupt the result. This may also be the cause for animation textures, except in a few cases where pixel interpolation can be used without artifacts to achieve frame interpolation for free, as discussed in this article.

bat_texture_filter_small

Note

If the textures weren’t automatically exported, you’ll need to manually export them as .exr using the following settings.

bat_export_img_settings

Report

Once a bake is attempted, a report panel will appear in the 'BAT Baker' main panel, providing valuable insights into the baked information.

bat_panel_report

  • Export: Exports the report to an XML file following the XML export settings.
  • Clear: Clears the report. This may be useful as the report holds pointers to the baked object(s), which will make them persist in the Blender file even though they are later deleted.

This panel provides global information about the bake that won’t be covered in detail in this documentation to avoid unnecessary clutter, as most of the information should be self-explanatory. Just make sure you’re aware of the coordinate system used by both Blender and your target application, so you can account for differences in world axes, UV orientation, and world scale.

For data that has been remapped to the [0:1] range for potential storage in 8-bit RGBA texture(s), it must be converted back to its original [-min:max] range using the reported 'offset' and 'range' values: $(value * range) + offset$. This information is still displayed—though grayed out—for values that have not been remapped to the [0, 1] range, serving purely as a reference.

Unreal Engine

The process of reconstructing the skeletal animation can be puzzling at first. Although it has been described in the 'Theory' section, here's the process explained using material nodes.

Important

This tool offers great flexibility, allowing users to bake data in various ways—such as using vertex colors or packing it into one or more textures in different formats. As a result, the process of reconstructing skeletal animation can vary depending on how the data was originally baked. It’s recommended to develop a solid understanding of how this algorithm works before moving forward. For the sake of simplicity, we’ll assume the most common approach was used: one texture storing XYZ position data, and a second texture storing rotation as an XYZ axis and a W angle. The skinning data is stored in a texture with two rows. The first row contains the indices of the four most influential bones, listed in descending order of influence. The second row holds the corresponding weights.

The first step is to retrieve the bone indices and weights from the skinning texture. This is done using a special vertex-to-texel UV map created specifically for this purpose—typically the second UV Map. The texture is sampled twice: once to read the upper row, which contains the indices of the four most influential bones stored in the RGBA channels, and once to read the lower row, which holds the corresponding weights, also in the RGBA channels.

bat_ue_01

Note

The purple nodes are called Named Reroutes. They allow you to reuse connections in different parts of the graph, helping to keep the layout clean and avoid a spaghetti mess.

Weights should have been normalized during the baking process, but to be safe, they can be normalized again in the vertex shader to ensure they sum up to 1. This guarantees that the vertex skinning is correctly reconstructed. Renormalizing is actually mandatory if you choose to skip some influencing bones—for example, to support level of detail (LOD) optimizations or to create a more efficient vertex shader at the cost of reduced animation fidelity.

bat_ue_02

Renormalizing weights is actually an inexpensive and quite straightforward process. It simply involves summing all the relevant weights and dividing each individual weight by that total.

bat_ue_03

Next, the animation textures that store the bone transforms need to be sampled to retrieve the animation data. The column to sample corresponds to the bone index, while the row to sample corresponds to the frame. Determining which frame to access is often a function of time, or it may be provided by external systems such as Niagara—so the exact implementation can vary. Here’s one possible approach that allows you to isolate a specific clip within the baked frames.

bat_ue_04

With the frame and bone index available, the textures storing the bone positional and rotational data can be sampled. The position texture need to be sampled twice: first to retrieve the row containing the absolute transforms baked in reference pose, and then to get the relative transforms at the desired animation frame. The rotation texture, on the other hand, only needs to be sampled once at the corresponding animation frame.

Using the sampled positional and rotational data, the vertex's local position can be rotated around the bone’s pivot point in the reference pose using the axis-angle rotation. After the rotation, the position is offset based on the bone’s translation. The same rotation should also be applied to the vertex’s local normal to preserve correct shading and deformation. Both the rotation and the offset must be weighted by the bone's influence to accurately replicate skinning.

bat_ue_05

Note

Note that the Bone Animation Textures include one extra frame to represent the reference pose. This row of pixels must be skipped when playing the animation, so the function assumes a one-frame offset. As a result, the reference pose can be accessed by specifying a frame index of -1.

This setup needs to be repeated for each influencing bone, with the results accumulated to compute the final vertex offset and play the skeletal animation. The normal offset should be added to the original vertex local normal and then normalized.

bat_ue_06

Keep in mind that all of this is performed in local space—both the position offset and the normal are rotated using the vertex’s local space position and normal. As a result, the final normal typically needs to be transformed to either tangent space or world space—depending on whether the material uses world-space or tangent-space normals—to account for the static mesh's rotation. Meanwhile, the final vertex position offset should be transformed to world space when writing to World Position Offset (WPO) to account for the static mesh's rotation & scale.

It’s recommended to use a vertex interpolator to move the normal computation from the pixel shader to the vertex shader, reducing the overall cost. Computing this trigonometry per pixel is unnecessary since the pixel shader can interpolate the result calculated per vertex.

It's essential to understand the algorithm's inner workings to make the most of it. You need to know the different ways bone data can be stored in texture(s), and how it must be sampled and processed to accurately reconstruct the skeletal animation. Here's an example using a texture that stores rotational data as a quaternion instead of an axis and angle. The principle, however, is generally the same: retrieve the bone's position in reference pose, apply the rotation (converted from the quaternion to an axis and angle), and then apply the bone offset.

bat_ue_07

Troubleshooting

Many things can go south when baking a skeletal animation in texture(s). Here are some things to look out for.

  • Double-check that the differences between Blender’s coordinate systems and those of your target application have been properly accounted for:
    • Blender’s world axes are +X+Y+Z, while Unreal Engine’s world axes are +X-Y+Z.
    • Blender uses OpenGL, meaning UV(0.0) is at the bottom-left corner, whereas Unreal Engine uses DirectX, where UV(0,0) is at the top-left corner.
    • Blender’s default unit is 1 meter (though it can be adjusted), while Unreal Engine’s default unit is 1 centimeter, so a scale factor may need to be applied.
  • Try 32-bit UVs in your targeted application if you think the issue you experience is related to 16-bit UVs imprecision.
  • Double check the texture compression settings in your targeted application. In doubt, try uncompressed 32-bit HDR textures first, then 16-bit HDR textures. RGBA8 may be an option if you know what you're doing.
  • Double check the texture sampling settings in your targeted application. Nearest sampling is mandatory in certain cases.
  • Double check the generated mesh BAT UVs are laid out as expected.
  • Ensure BAT UVs are not overriden in your targeted application by lightmap UVs or any other process automated during mesh import.
  • Disable any geometry-related optimization features in your game engine initially. This includes virtualized geometry systems like UE's Nanite, which may need to be deactivated both project-wide and on a per-asset basis.
  • Double check the bake settings and export/import options.
  • Rebuilding the skeletal animation in a shader is a multi-step process where even the smallest mistake can lead to major visual errors or completely break the system.
  • ...

XML

The report can be exported to XML, which may potentially be used to automate the import process in a game engine through a custom importer that parses this file.

vat_xml

Presets

The BAT tool's configuration and settings can be saved as a preset, which can be easily applied with a single click at any time. Presets can be added or removed using the button located to the right of the BAT tool header.

preset

This feature uses Blender's internal preset system, which creates Python files to store all the necessary data. These files are stored in the following directories:

- Windows: C:\Users\<your username>\AppData\Roaming\Blender Foundation\Blender\<version number>\scripts\presets\operator\databaker_bat\
- MAC: Library\Application Support\Blender\<version number>\scripts\presets\operator\databaker_bat\
- Linux: ~/.config/blender/<version number>/scripts/presets/operator/databaker_bat/

Warning

Preset .py files can be copied, pasted, and shared across different computers. However, only install presets from trusted sources, as these files can execute malicious Python code on your machine.

⚠️ **GitHub.com Fallback** ⚠️