X File Hierarchy Loading

Introduction to X file loading

This page describes how you can load mesh data in the x file format maintaining a mesh hierarchy. If you are not interested in keeping the hierarchy you can simply use the D3DXLoadMeshFromX set of functions that are described in the X File Simple notes. However if you wish to do animation or skinning you will need to read on. These notes are meant to be read in conjunction with the demo application.

An easy way to use .x files is to use my XAnimator library.

Demo Application (with source code)

skinning demo screenshot

New: after a lot of requests I have finally got around to reworking this code so that it does not use the DirectX frame work or Unicode. Hopefully this simplifies the code a lot and makes it easier to learn from

Demo code download: XFileAnimationCode.zip
If you do not have the means to compile it you can get just the executable here: XFileAnimationExe.zip

The demo has the following features

  • Loads x files and maintains their hierarchy
  • Handles animation and allows merging between animations.
  • Handles skinning with software rendering.

This demo was created using Visual Studio 2005 and the August Update 2007 version of the DirectX SDK. The bouncy skinned model was created using 3DS Max. The process of creating it and exporting it as a DirectX x file is described in more detail here: Art Creation

The demo has a couple of limitations: it does not load effect files that are referenced in the x file and it does not do hardware skinning.

Contents

Hierarchy Concept

In order to animate our 3D models we need to maintain a model hierarchy. The hierarchy consists of frames containing matrix and mesh data for different parts of the model (when skinning these frames are seperate from the mesh and known as bones).

E.g. if we had a 3D model of the human body it might be divided into head, body, arms and legs. The body might be the top frame with each part in frames below defining their own matrix and mesh data. The frame matrix provides the offset position and orientation from the body matrix.

skeleton

Ignoring the legs and head the hierarchy for the above may look like this:

skeleton hierarchy

When rendering we start from the top and recursively travel down through the hierarchy. Each frame has an associated matrix, so you can see that by altering the rotation of the left arm frame all the upper and lower arm are also rotated. Note: in skinning frames are called bones and there tends to be just one mesh controlled by these bones.

When the model is positioned in the 3D world the first frame (known as the root) is transformed to the correct position. Each part of the model is positioned based on the frame matrix translating and rotating it from the frame above. By maintaining this structure we can animate parts of the model simply by altering one frame matrix. We could name frames in our 3D package and then use the names in our code to find the correct frames to animate. Alternatively we can create animation in our 3D package and load it into our game and use the D3DX helper functions to animate it. This process is described in LINK below

Loading the X File

The main method for loading animation data, skin info and hierarchy is D3DXLoadMeshHierarchyFromX.

Note: there is another similar function called D3DXLoadSkinMeshFromXof which loads from a ID3DXFileData object. We could load the x file data into our own formats and bypass the Direct3D functionality using this object and the ID3DXFileEnumObject. This method is not covered by these notes but if you interested there is an article on Gamedev about this (see further reading).

The D3DXLoadMeshHierarchyFromX function

HRESULT D3DXLoadMeshHierarchyFromX(
    LPCTSTR Filename,
    DWORD MeshOptions,
    LPDIRECT3DDEVICE9 pDevice,
    LPD3DXALLOCATEHIERARCHY pAlloc,
    LPD3DXLOADUSERDATA pUserDataLoader,
    LPD3DXFRAME* ppFrameHeirarchy,
    LPD3DXANIMATIONCONTROLLER* ppAnimController );

  • Filename - the file name of the .x file to be loaded
  • MeshOptions - various options can be specified here defining creation.
  • pDevice - a D3D Device pointer.
  • pAlloc - allows you to specify a class that will handle the allocation of memory for all the frames and mesh data. More on this below.
  • pUserDataLoader - as mentioned previously the .x file format is highly flexible using a template system to define data chunks. If you add your own templates you can specify a class here that knows how to load your specific data. If you do not use this then you can ignore it and set it to 0.
  • ppFrameHeirarchy - once loading complete this points to the top of the loaded frame hierarchy.
  • ppAnimController - once loading complete this points to an animation controller containing animation data loaded from the x file (if it has any). The animation section describes this.

Before calling this function we need to create a ID3DXAllocateHierarchy derived class to handle the creation of memory for our frame and mesh data. This is where a lot of our work will take place. ID3DXAllocateHierarchy is an interface class that defines the functions we must implement. We must implement the following functions:

  1. CreateFrame - requests allocation of a frame object
  2. CreateMeshContainer - requests allocation of a mesh container object
  3. DestroyFrame - deallocation of frame object
  4. DestroyMeshContainer - deallocation of mesh container object

These will be called during the internal processing of the x file carried out by the D3DXLoadMeshHierarchyFromX function. The header for our class will look like this:

class CMeshHeirarchy : public ID3DXAllocateHierarchy
{
public:
    // The format of these interfaces is defined by D3DXLoadMeshHierarchyFromX

    // callback to create a D3DXFRAME derived object and initialize it
    STDMETHOD( CreateFrame )( THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame );

    // callback to create a D3DXMESHCONTAINER derived object and initialise it
    STDMETHOD( CreateMeshContainer )( THIS_ LPCSTR Name, CONST D3DXMESHDATA *pMeshData,  CONST D3DXMATERIAL * pMaterials, CONST D3DXEFFECTINSTANCE * pEffectInstances, DWORD NumMaterials, CONST DWORD * pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER * ppNewMeshContainer );

   // callback to release a D3DXFRAME derived object
   STDMETHOD( DestroyFrame )( THIS_ LPD3DXFRAME pFrameToFree );

   // callback to release a D3DXMESHCONTAINER derived object
   STDMETHOD( DestroyMeshContainer )( THIS_ LPD3DXMESHCONTAINER pMeshContainerToFree );
};

The implementation of each of these functions is described below and can be seen, in full, in the sample code.

CreateFrame

This function is called during processing of the .x file by the D3DXLoadMeshHierarchyFromX call when a new frame is encountered. If you look back at the diagram above you can see that a frame contains either other frames (children or siblings) and / or the mesh data itself. By maintaining this hierarchy we can carry out animation and other functions on our mesh data.

This function is called with a frame name and requires us to create a frame in memory. This memory is returned to the caller via the ppNewFrame parameter. Therefore we will need to look at this frame structure in more detail.

The D3DXFRAME Structure

This is a structure provided by Direct 3D to hold information for a frame in our hierarchy. It contains all the basic data required:

  • LPSTR Name - the frame name
  • D3DXMATRIX TransformationMatrix - a transformation matrix that should be applied to this and child frames
  • LPD3DXMESHCONTAINER pMeshContainer - if this frame has mesh data this will point to a container of mesh data.
  • D3DXFRAME *pFrameSibling - if this frame has a sibling this points to it
  • D3DXFRAME *pFrameFirstChild - if this frame has a child this points to it

When we come to render our mesh hierarchy we will traverse the frame tree applying each frame transformation matrix and rendering our mesh data. We will combine transformation matrix as we go down the hierarchy. This allows us to, for example, move the top part of the arm and have the lower part and hand and fingers all transform accordingly. It is useful therefore to also maintain a combined transformation matrix per frame (updated during our FrameMove function). This information is not held in the frame structure but we can extend the structure ourselves and add this bit of data:

struct D3DXFRAME_DERIVED: public D3DXFRAME
{
    D3DXMATRIXA16 exCombinedTransformationMatrix;
};

You could of course add more data if required for a particular purpose.

Note: Direct3D provides a number of functions for working with frame hierarchies like D3DXFrameDestroy, D3DXFrameFind, D3DXFrameCalculateBoundingSphere etc.

CreateMeshContainer

This function is called during processing of the x file by the D3DXLoadMeshHierarchyFromX call when mesh data is encountered. This function is called with a large number of parameters:

HRESULT CreateMeshContainer(
    LPCSTR Name,
    const D3DXMESHDATA *pMeshData,
    const D3DXMATERIAL *pMaterials,
    const D3DXEFFECTINSTANCE *pEffectInstances,
    DWORD NumMaterials,
    const DWORD *pAdjacency,
    LPD3DXSKININFO pSkinInfo,
    LPD3DXMESHCONTAINER *ppNewMeshContainer);

  • Name - name of the mesh
  • pMeshData -  pointer to a mesh data structure containing details on this mesh
  • pMaterials - an array of material structures describing the materials used by this mesh
  • pEffectInstances - array of effect instance structures used by this mesh
  • NumMaterials - number of materials in the mesh
  • pAdjacency - array of adjacency information for the mesh
  • pSkinInfo - if the mesh uses skinning this points to an ID3DXSkinInfo object containing skinning data
  • ppNewMeshContainer - once we have created a mesh container we return the pointer via this parameter

All but the last parameter are input data defining the mesh. Our function needs to take this data and create and return a new mesh container structure. This is a Direct3D provided structure and is described below:

The D3DXMESHCONTAINER structure

This is a structure provided by D3D to hold information about a mesh in our hierarchy. The data is:

  • LPSTR Name - the mesh name
  • D3DXMESHDATA MeshData - a structure containing the mesh triangle data itself in one of the D3DX mesh interface objects (ID3DXMesh, ID3DXPMesh or ID3DXPatchMesh)
  • LPD3DXMATERIAL pMaterials - array of mesh materials (we will not use this) *
  • LPD3DXEFFECTINSTANCE pEffects - array of effect instances
  • DWORD NumMaterials - number of materials
  • DWORD *pAdjacency - adjacency information
  • LPD3DXSKININFO pSkinInfo - skinning information
  • D3DXMESHCONTAINER *pNextMeshContainer - pointer to a sibling mesh structure

It is our responsibility to allocate and fill out this structure. It is not as complex as it may look at first as a lot of this information can be directly copied from the data that is passed to us. Note: we must make a copy of this data i.e. we must leave the caller data alone.

As with the D3DXFRAME structure we can add more information to this structure based on our application needs. We will normally load any textures specified for this mesh and so the addition of a texture pointer is useful. Other information commonly added includes skinning data. The structure used in the demo is shown below:

struct D3DXMESHCONTAINER_EXTENDED: public D3DXMESHCONTAINER
{
    IDirect3DTexture9**  exTextures;  // Array of texture pointers 
    D3DMATERIAL9*   exMaterials;      // Array of materials

    // Skinned mesh variables
    ID3DXMesh*     exSkinMesh;           // The skin mesh
    D3DXMATRIX*    exBoneOffsets;        // The bone matrix Offsets
    D3DXMATRIX**   exFrameCombinedMatrixPointer; // Array of frame matrix
};

The base D3DXMESHCONTAINER has a pMaterials member which is a D3DXMATERIAL structure that contains a texture filename and material data. It is easier to ignore this and instead store the data in arrays of created textures and materials in our derived structure (exTextures and exMaterials).

The final three variables are for skinning purposes only and are described in the section on skinning below.

DestroyFrame and DestroyMeshContainer

These functions should deallocate the memory we created in the above two functions. They are invoked when we destroy the frame hierarchy. This is achieved using the D3DXFrameDestroy function. Look at the demo code for the implementation.

Rendering the Hierarchy

After loading the x file we have our hierarchy loaded in as a tree of frames and mesh data (like the diagram). We hold a pointer to the top of our frame hierarchy (returned from D3DXLoadMeshHierarchyFromX in the ppFrameHeirarchy pointer). In addition, if our x file contained animation, we have a pointer to an animation controller (returned from D3DXLoadMeshHierarchyFromX in the ppAnimController pointer).

I will look first at how we can render this hierarchy without animation or skinning. I will talk about animation and skinning later.

Rendering the Hierarchy

To render the model with a hierarchy involves traversing the hierarchy maintaining matrix and rendering mesh as we encounter them. At regular intervals the demo calls CXFileEntity::UpdateFrameMatrices in order to calculate the combined matrix for each frame in the hierarchy. This could be done in the render function itself but later on we will also want to handle animation so we keep this separate.

The function Render simply calls DrawFrame with the topmost frame in the hierarchy. DrawFrame renders any mesh that it owns via a call to DrawMeshContainer. Then if the frame has any siblings it recursively calls DrawFrame with them (these calls will only return once that branch has been completed) and then any child frames.

Animation

As already mentioned if we have a model hierarchy we can carry out animation simply by altering a frame matrix. E.g. if we alter a shoulder matrix then the rest of the arm below will be transformed as well.

The provided x file bones_all.x contains a number of animation sets that is worth viewing while understanding these notes.

In Direct3D animation is held in animation sets maintained by an animation controller object. This is passed initially to our application in the D3DXLoadMeshHierarchyFromX function (m_animController). This controller wraps a lot of the functionality required to carry out animation.

In FrameMove we tell the animation controller what time it is by calling AdvanceTime. This allows the controller to determine the matrices for each frame in our hierarchy at that particular time. The frame controller will alter the frame matrices (the TransformationMatrix held in the frame structure) and when we update our frame hierarchy matrices during UpdateFrameMatrices we use this new matrix to create our combined frame matrix. The controller object carries out interpolation of matrix between key frames for us.

We can change which animation set is active by calling the frame controller SetTrackAnimationSet with the new animation set. First of all we have to retrieve this set via a call to GetAnimationSet.

Switching between animations abruptly would look wrong. Imagine our skeleton went from running to dying  - the arms and legs etc, would suddenly change position. So Direct3D provides a way of interpolating into the new animation smoothly.

This is achieved via the use of tracks. Our current track is running the original animation so we use a second track for our new animation. We slowly fade out the effect of the first animation and fade in the second. We do this by inserting keys into the track using the animation controller functions. We can turn tracks on and off, change the track speed and change how much that track effects the animation (via a weighting value).

This process can be seen in the demo code SetAnimationSet function. The speed with which the transition between animations occurs is held in a global constant variable: kMoveTransitionTime. To understand how this process works try changing this value. Setting it to a higher value will slow down the transition and help you to see what is happening.

Note: the provided file bones_all.x has a number of animation sets that were added using the Mesh View tool from the SDK. If you have a number of .x files produced from an art package with the same model but different animations you can combine them into one using the Mesh View tool or do it programmatically using the animation controller RegisterAnimationSet function.

Skinning

Introduction to Skinning

Skinning takes data in a mesh-skeleton hierarchy and applies geometry blending to transform the mesh vertices. Skinning uses a set of interconnected bones (frames) that form a hierarchy as shown above. When bones are moved or rotated the mesh surface moves or rotates accordingly (like skin does). The mesh surface is simply described with vertices forming triangles and so skinning works by altering the vertex position dependant on the bones attached to it. If you run the demo and turn on wireframe mode you can see how the vertex positions are altered.

If you think about the skin on your elbow it is affected both by the movement of the lower arm and the movement of the upper arm. Hence the position of the elbow vertices depends on two inputs (bones). The transformation of the vertices is calculated by combining a matrix from the upper arm with the one from the lower arm. How much each matrix affects each vertex is controlled by a blending weight.

Advanced: the actual geometry blending operation applies each bones matrix to the vertices to create a number of differently positioned vertices and then interpolates between them based on the blending weight.

Skinned Mesh Matrices

The skeleton is a hierarchical set of bones. Each bone has a matrix which translates and rotates that bone relative to its parent (this is just the same process as the frame hierarchy discussed above but the mesh is normally separate to the hierarchy). Since the matrix are relative to the parent rather than the character itself we say they are defined in bone space. We want to place the bones relative to the character itself so must convert these matrices into character space by combining them with their parent matrix.

We add a variable to our extended mesh container structure for this combined matrix (called exFrameCombinedMatrixPointer - one entry per bone).  Since we already maintain a combined matrix for each frame in the hierarchy (exCombinedTransformationMatrix) we do not need to duplicate it therefore our exFrameCombinedMatrixPointer is an array of pointers to the correct frames' exCombinedTransformationMatrix. Finding the correct frame is carried out during SetupBoneMatrices using the Direct3D D3DXFrameFind function.

Combined Bone Matrix = Local Bone Matrix * Parent Combined Bone Matrix.

There is one further matrix required when doing skinning and that is a matrix to connect the bones to the mesh, this is known as a bone offset matrix. We add this to our structure as exBoneOffsets. These are assigned during the CMeshHierarchy::CreateMeshContainer function - one per bone. This allows us to create our final matrix:

Final Bone Matrix = Bone Offset Matrix * Combined Bone Matrix

This final matrix transforms the mesh into bone space then applies the bone matrix to the mesh. This is calculated during the frame move function and fed into the UpdateSkinnedMesh function.

Note: the amount each vertex is affected by the bones is determined by a weighting. This is contained in the ID3DXSKININFO object and is automatically calculated into the equation for us by the function UpdateSkinnedMesh.

To summarise this rather complicated process: we have a mesh that needs to be altered based on a skeleton hierarchy of bones (frames). Each bone is positioned and oriented to its parent via a local bone matrix. We store a pointer to the correct frames combined matrix in our mesh container structure (exFrameCombinedMatrixPointer). In order to wrap the mesh skin around the bones we use a bone offset matrix. By combining these two matrices we can create a final matrix that can be fed into the Direct3D UpdateSkinnedMesh function. This function will alter the mesh vertices based on the matrix and a set of weights to carry out the actual skinning.

Skinning Mesh Rendering

There are a number of methods of carrying out skinning with Direct3D. The demo code uses software skinning but there are other methods that use hardware acceleration for greater speed. The Skinned Mesh sample that comes with the DirectX SDK describes three other methods: Fixed Function Non Indexed Skinning, Fixed Function Indexed Skinning and Shader Based Skinning.

The software skinning method used by the demo is carried out in FrameMove. The process is:

/* Create the bone matrices that transform each bone from bone space into character space (via exFrameCombinedMatrixPointer) and also wraps the mesh around the bones using the bone offsets in exBoneOffsetsArray */

for (UINT i = 0; i < Bones; ++i)
   D3DXMatrixMultiply(&m_boneMatrices[i],&pMesh->exBoneOffsets[i], pMesh->exFrameCombinedMatrixPointer[i]);

void *srcPtr;
pMesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&srcPtr);

void *destPtr;
pMesh->exSkinMesh->LockVertexBuffer(0, (void**)&destPtr);

// Update the skinned mesh
pMesh->pSkinInfo->UpdateSkinnedMesh(m_boneMatrices, NULL, srcPtr, destPtr);

// Unlock the meshes vertex buffers
pMesh->exSkinMesh->UnlockVertexBuffer();
pMesh->MeshData.pMesh->UnlockVertexBuffer();

We lock the vertex buffer of our original unaltered mesh for read. We also lock the vertex buffer of the destination mesh vertices to write into (this is why we stored a copy of the mesh in our D3DXMESHCONTAINER_EXTENDED structure. It was copied during SetupBoneMatrices). We then call the Direct3D UpdateSkinnedMesh function which takes the transformation matrices and our locked vertex buffers and carries out the skinning. It does this by combining the matrix and a set of bone weights to alter the position of the original vertices and write them into the output buffer.

Locking vertex buffers is to be avoided as much as possible if we want our graphic card to work at maximum speed. A lock can cause a stall in the graphics processing leading to slow down. To avoid this there are a number of methods that use the hardware to carry out the skinning itself. As previously mentioned these methods are beyond the scope of these notes (for the time being - I may add notes on them later) and if you are interested I suggest you look at the skinned mesh sample that comes with the DirectX SDK.

Art Creation

The creation of x files requires an art package that can save the .x file format or export to the format. Exporters exist for all the major packages. There are some free 3D editors available that also support the .x file format. For example the DeleD 3D Editor and a light version of gameSpace or the popular blender.

In order to help other programmers I will describe here the process I used to create the skinnedBouncyThing.x model. I say programmers because I am in no way an artist myself! That is why I created a bouncy thing rather than any serious art and why this process may not be the most optimal. I used 3DS Max 6.0 to create the bouncy thing.

In max there are obviously many ways of creating models, the one thing you need to remember when creating models for export to the .x file format is to make sure you end up with a mesh. If you use splines or something to create the model make it into a mesh before export.

Creating the cylinder

To create the basic mesh I simply created a cylinder. I made sure there were plenty of vertices for later skinning by setting high values for the segments (height segments 24, cap segments 6, and sides 24). I also added a basic texture.

Creating the bone hierarchy

The next step is to create the bone hierarchy. I did this by selecting the bones system from the systems tab. I then created 6 bones vertically. If one of these bones is transformed the others in the hierarchy will transform accordingly. I moved these bones so they were right in the middle of the cylinder mesh.

Skinning

In order for the bones to control the skinning of the mesh I selected the cylinder mesh and added a skin modifier. In the skin modifier parameters I added all the bones. Each bone can affect the skinning of the mesh around it depending on weightings etc. so by clicking the Edit Envelopes button I manually altered the bone envelopes to cover the mesh correctly.

At this stage moving a bone will cause the mesh to deform (skinning). This would be fine but rather than manually edit each bone during an animation I wanted more control so I added an IK Solver (Inverse Kinematics). I simply selected the top bone, added the HI solver and then selected the bottom bone. The solver now controls the interaction between bones.

Animating the mesh

To animate the mesh I first of all turned on Auto Key so keys would be inserted in the animation for me whenever I made a change. I then advanced the time in steps of 5. At each step I moved the IK solver to transform the mesh. These keys are interpolated between automatically during rendering by Direct3D.

Exporting the mesh to the x file format

To export the final animated skinned mesh I used the excellent panda exporter (available free from Pandasoft). In the '3DS max objects' tab of the exporter dialog I turned on 'Include Animation' and 'Bones'. Under the 'animation' tab I made sure it was set to export 'matrix keys' (otherwise discontinuity problems can happen with the animation). Under 'Textures & Effect Files' I chose to 'Copy the texture map' - this makes it easier with texture finding etc. when loading the x file. Finally under 'X File Settings' I made sure the 'Sub Frame' Hierarchy was being saved.

I then loaded the .x file into the DirectX mesh view program to make sure it worked.

Final Words

The simple way of loading x files is fine for loading basic models but unfortunately does not maintain the hierarchy of the model. In order to do any animation or skinning we must load the full hierarchy. The process of loading this hierarchy is quite complicated and involved so I hope these notes and the sample code have helped to clarify the process.

Further Reading

On this site

Internet

Books



© 2004-2016 Keith Ditchburn