void SkinnedMesh::Loadchar fileName[] { BoneHierarchyLoader boneHierarchy; //Load a bone hierarchy from a file D3DXLoadMeshHierarchyFromXfileName, D3DXMESH_MANAGED, pDevice, &boneHiera
Trang 1void SkinnedMesh::Load(char fileName[]) {
BoneHierarchyLoader boneHierarchy;
//Load a bone hierarchy from a file D3DXLoadMeshHierarchyFromX(fileName,
D3DXMESH_MANAGED, pDevice,
&boneHierarchy, NULL,
&m_pRootBone, NULL);
//Update all Bone transformation matrices D3DXMATRIX i;
D3DXMatrixIdentity(&i);
UpdateMatrices((Bone*)m_pRootBone, &i);
}
Sometimes it can be useful to locate a specific bone in a hierarchy—for example, if you would like to find the neck bone of a character and apply a rotation transformation matrix and make the head turn The following D3DX function is then very useful:
LPD3DXFRAME D3DXFrameFind(
CONST D3DXFRAME * pFrameRoot, //The root bone LPCSTR Name //Name of bone you are looking for );
This function returns a pointer to the correct bone in the hierarchy or returns
NULLif the bone wasn’t found Try to use this function in Example 3.1 to find the neck bone
Hopefully you know by now how to load a bone hierarchy by implementing the
ID3DXAllocateHierarchyinterface Later on in the book, you’ll see how you can use the same interface to load several different morph targets from a single x file rather than keeping these meshes in separate files However, for now it is time to actually apply a mesh to the bone hierarchy
Trang 2APPLYING A MESH TO THE BONE HIERARCHY
As you probably know, a mesh consists of several polygons that in turn consist of one or more triangles Each triangle in turn is defined by three vertices—i.e., three points in 3D space Before you look at how to skin a complex character mesh to a bone hierarchy, first just look at a single vertex A vertex can be linked (influenced)
by one or more bones in the bone hierarchy The amount a bone influences a vertex
is determined by a weight value as shown in Figure 3.4
EXAMPLE 3.1
Okay, I’ve think you’ve had enough theory for a while Here’s an actual code example for you to look at You’ll find Example 3.1 on the CD-ROM In this example, a bone hierarchy is loaded from an x file An ID3DXAllocateHierarchy interface is also implemented, and you’ll find the first rough version of the SkinnedMesh class Note that in this example there’s also a temporary function for rendering a bone hierarchy using spheres and lines Make sure you completely understand this example, because things are about to get a lot harder.
Trang 3It is important that the combined weights for a vertex equal 1.0 (100%).
In more mathematical terms, the transformation matrix applied to a vertex is defined as follows:
M Tot =(w 0 M 0 +w 1 M 1 …+W n M n )
This formula multiplies the bone weight (wx) with the bone transformation matrix (Mx) for all influencing bones and sums up the result (MTot) The resulting matrix is then used to transform the vertex In DirectX, the information about which bones influence which vertices, as well as their respective weights, etc., is stored and controlled with the ID3DXSkinInfointerface One way of creating this interface is by using the following D3DX function:
HRESULT D3DXCreateSkinInfo(
DWORD NumVertices, CONST D3DVERTEXELEMENT9 * pDeclaration, DWORD NumBones,
LPD3DXSKININFO * ppSkinInfo );
This function takes the amount of vertices in a mesh, their vertex declaration (i.e., what information each vertex contains), and the number of bones that will be used to skin this mesh If you are making something in code that requires skinning, this would be the best approach However, characters will most definitely be created and skinned in a 3D software such as 3D Studio Max, Maya, or similar Luckily, when you export a character like this to the x file format, the skinning information
FIGURE 3.4
An example of how a vertex (the cross) is affected by two bones (B1 and B2) with the weights 20% and 80%, respectively Notice how the vertex follows B2 more than B1 due
to the weights.
Trang 4is exported as well If you look again at the CreateMeshContainer()function of the
ID3DXAllocateHierarchyinterface, you’ll notice that one of the parameters to this function is indeed a pointer to a ID3DXSkinInfoobject So all you need to do when reading an x file is to store this object and use it later when you skin a character Soon the CreateMeshContainer()function will be implemented, and through
it the process of loading a character with bones and all will be complete First, however, you need to understand the two major choices you have for how to render a skinned character The first option is to do the skinning using the CPU—
a.k.a software skinning The other option is to do the skinning directly with the
GPU (graphics processing unit, i.e., the graphics card) as the mesh is being
rendered—a.k.a hardware skinning (There are other variations of these two
techniques, but these two are the major options)
S OFTWARE S KINNING O VERVIEW
With the first option, software skinning, the positions of each vertex in a mesh are calculated using the mathematical formula covered earlier The result is stored in
a temporary mesh that is then consequently rendered Simple, straightforward, but also very slow compared to hardware skinning So if it is so slow compared to hardware skinning, why use it? Well, the fact that the character is stored as a mesh
in memory is the major upside of software skinning With this temporary mesh, things like shadow casting, picking, etc., become a bit easier With software skinning, it also doesn’t matter how many bones are influencing a vertex If you were making a first-person shooter (FPS) game, you might want to test to see whether or not a bullet you fired hit one of the enemy soldiers With software skinning, this would be easy to test using a simple mesh–ray intersection test
H ARDWARE S KINNING O VERVIEW
With hardware skinning, you can, of course, also do shadow casting, picking, etc., but then it requires a little more effort to get it to work Hardware skinning also has some limits as to how many bones can influence a vertex as well as how many bones you can have per character without having to split up the mesh into several parts However, what you lose in functionality, you make up readily in speed Remember to choose your skinning method based on the particular game you are making In the following two sections, both software and hardware skinning will
be looked at in more detail
For a simple mesh–ray intersection test, check out the D3DXIntersect()function in the D3DX library See the DirectX documentation for more info.
Trang 5S OFTWARE S KINNING I MPLEMENTATION
Let’s first look at software skinning, since this is the more straightforward and easier method to implement Here’s a brief overview of the steps needed to render a skinned mesh with software skinning:
1 (Optional) Overload D3DXFRAME
2 (Optional) Overload D3DXMESHCONTAINER
3 Implement the ID3DXAllocateHierarchyinterface
4 Load a bone hierarchy and associated meshes, skinning information, etc., with the D3DXLoadMeshHierarchyFromX()function
5 For each frame, update the skeleton pose (i.e., the SkinnedMesh:: UpdateMatrices()function)
6 Update the target mesh using ID3DXSkinInfo::UpdateSkinnedMesh()
7 Render the target mesh as a common static mesh
Loading the Skinned Mesh
The first step on the path of skinning a mesh is to create your own mesh container structure You do this by overloading the D3DXMESHCONTAINERstructure defined as follows:
struct D3DXMESHCONTAINER { LPSTR Name;
D3DXMESHDATA MeshData;
LPD3DXMATERIAL pMaterials;
LPD3DXEFFECTINSTANCE pEffects;
DWORD NumMaterials;
DWORD * pAdjacency;
LPD3DXSKININFO pSkinInfo;
D3DXMESHCONTAINER * pNextMeshContainer;
}
The D3DXMESHCONTAINERcontains the mesh itself (in the D3DXMESHDATAstructure)
as well as all the necessary stuff needed to render the mesh (materials, textures, and shaders) The texture filenames are stored as a member of the D3DXMATERIALstructure and must be loaded separately Another notable member of this structure is the
pSkinInfovariable, which will contain skinning information for any skinned meshes loaded There are, however, some things that we want to add to this structure to make it easier to render the mesh using software skinning Therefore I’ve created the
BoneMeshstructure, defined as follows:
Trang 6struct BoneMesh: public D3DXMESHCONTAINER {
ID3DXMesh* OriginalMesh; //Reference mesh vector<D3DMATERIAL9> materials; //List of materials vector<IDirect3DTexture9*> textures; //List of textures DWORD NumAttributeGroups; //Number attribute groups D3DXATTRIBUTERANGE* attributeTable; //Attribute table
D3DXMATRIX** boneMatrixPtrs; //Pointers to bone matrices D3DXMATRIX* boneOffsetMatrices; //Bone offset matrices D3DXMATRIX* currentBoneMatrices; //Current bone matrices };
As you can see, quite a lot of extra information has been added to the BoneMesh
structure compared to what was added in the Bonestructure The first three members should be quite easy to understand; the others may seem a little bit more obscure Table 3.1 provides more details about the members
TABLE 3.1 THE BONEMESH MEMBERS
OriginalMesh A copy of the original mesh in the bind pose materials A vector of D3DMATERIAL9 materials (used instead of the
pMaterials pointer stored in the D3DXMESHCONTAINER structure) textures A vector of textures—each texture corresponding to a material in
the materials vector NumAttributeGroups The number of attribute groups in the mesh (i.e., parts of the
mesh using different materials/textures, etc.) boneMatrixPtrs An array of matrix pointers, pointing to the transformation matrix
of each bone in the hierarchy boneOffsetMatrices A matrix for each bone that transforms the mesh into bone space;
this is retrieved from the ID3DXSkinInfo currentBoneMatrices When the character is animated, this array will contain the
combined transformation matrices of all the bones
Trang 7Now that you’ve seen the overloaded D3DXMESHCONTAINERstructure, take a look
at how the CreateMeshContainer()of the ID3DXAllocateHierarchyis implemented
to load a mesh into a BoneMeshobject
HRESULT BoneHierarchyLoader::CreateMeshContainer(
LPCSTR Name, CONST D3DXMESHDATA *pMeshData, CONST D3DXMATERIAL *pMaterials, CONST D3DXEFFECTINSTANCE *pEffectInstances, DWORD NumMaterials,
CONST DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER *ppNewMeshContainer) {
//Create new Bone Mesh BoneMesh *boneMesh = new BoneMesh;
memset(boneMesh, 0, sizeof(BoneMesh));
//Get mesh data boneMesh->OriginalMesh = pMeshData->pMesh;
boneMesh->MeshData.pMesh = pMeshData->pMesh;
boneMesh->MeshData.Type = pMeshData->Type;
//Add Reference so the mesh is not deallocated pMeshData->pMesh->AddRef();
//To be continued
First, a new BoneMeshobject is created and all its members are set to zero and
NULLusing the memset()function Next, a reference is added to the mesh data for both the OriginalMeshand the MeshDatamember You need to keep a copy of the mesh in its original form when you do software skinning (more about this when the rendering of the mesh is covered)
IDirect3DDevice9 *pDevice = NULL;
pMeshData->pMesh->GetDevice(&pDevice); //Get pDevice ptr //Copy materials and load textures (just like with a static mesh) for(int i=0;i<NumMaterials;i++)
{ D3DXMATERIAL mtrl;
memcpy(&mtrl, &pMaterials[i], sizeof(D3DXMATERIAL));
boneMesh->materials.push_back(mtrl.MatD3D);
IDirect3DTexture9* newTexture = NULL;
Trang 8if(mtrl.pTextureFilename != NULL) {
char textureFname[200];
strcpy(textureFname, "meshes/");
strcat(textureFname, mtrl.pTextureFilename);
//Load texture D3DXCreateTextureFromFile(pDevice,
textureFname,
&newTexture);
} boneMesh->textures.push_back(newTexture);
} //To be continued again
In this section of the CreateMeshContainer() function, you first get a pointer
to the current device After that, you copy all the materials over to the BoneMesh
structure, and if necessary any textures needed are loaded with the associated materials (this is why you needed to retrieve the device pointer) Next, the skinning information sent as a parameter to the CreateMeshContainer()will have to be stored
if(pSkinInfo != NULL) {
//Get Skin Info boneMesh->pSkinInfo = pSkinInfo;
//Add reference so SkinInfo isn't deallocated pSkinInfo->AddRef();
//Clone mesh and store in boneMesh->MeshData.pMesh pMeshData->pMesh->CloneMeshFVF(D3DXMESH_MANAGED,
pMeshData->pMesh->GetFVF(), pDevice,
&boneMesh->MeshData.pMesh); //Get Attribute Table
boneMesh->MeshData.pMesh->GetAttributeTable(
NULL, &boneMesh->NumAttributeGroups);
boneMesh->attributeTable =
new D3DXATTRIBUTERANGE[boneMesh->NumAttributeGroups];
Trang 9boneMesh->attributeTable, NULL); //Create bone offset and current matrices
int NumBones = pSkinInfo->GetNumBones();
boneMesh->boneOffsetMatrices = new D3DXMATRIX[NumBones];
boneMesh->currentBoneMatrices = new D3DXMATRIX[NumBones]; //Get bone offset matrices
for(int i=0;i < NumBones;i++) boneMesh->boneOffsetMatrices[i] =
*(boneMesh->pSkinInfo->GetBoneOffsetMatrix(i)); }
//Set ppNewMeshContainer to the newly created boneMesh container
*ppNewMeshContainer = boneMesh;
return S_OK;
}
In this last part of the CreateMeshContainer() function, you check whether there’s any skinning info available If so, make a clone of the mesh stored in the
pMeshDatamember of your BoneMeshstructure This cloned mesh will later be the actual skinned mesh rendered Also remember that a pointer to the original mesh (OriginalMeshmember) is stored This mesh will be used as a reference to create the skinned mesh stored in pMeshDataeach frame In this piece of code, the number of attribute groups as well as the attribute table itself is stored Then the matrix array
is created according to how many bones are defined in the skinning information (note that you copy the bone offset matrix from the ID3DXSkinInfoobject) Lastly, you simply store the created BoneMeshobject and return S_OK
The attribute table is stored using an array of D3DXATTRIBUTERANGEobjects:
struct D3DXATTRIBUTERANGE { DWORD AttribId; //which material/texture to use DWORD FaceStart; //Face start
DWORD FaceCount; //Num of faces in this attribute group DWORD VertexStart; //Vertex start
DWORD VertexCount; //Num of vertices in this attribute group }
Trang 10Later, when you render the mesh you loop through the attribute table, get the
AttribId, and use this to set which material and which texture to use to render that subset of the mesh This basically means that you can have several combinations of materials and textures when you render a character
Rendering the Skinned Mesh with Software Skinning
Now you know how to load a bone hierarchy and any meshes that are attached to a bone using the extended Boneand BoneMeshstructures as well as the BoneHierarchy-Loader Now is finally where the fun begins! Now you are finally coming to the point where you’ll see a skinned character on the screen
To render a BoneMesh we need to calculate the current matrices for all the influencing bones and store these in the BoneMesh boneMatrixPtrsarray So just after loading a mesh with the D3DXLoadMeshHierarchyFromX()function we call the following function to set up these matrix pointers:
void SkinnedMesh::SetupBoneMatrixPointers(Bone *bone) {
//Find all bones containing a mesh if(bone->pMeshContainer != NULL) {
BoneMesh *boneMesh = (BoneMesh*)bone->pMeshContainer;
//For the bones with skinned meshes, set up the pointers if(boneMesh->pSkinInfo != NULL)
{ //Get num bones influencing this mesh int NumBones = boneMesh->pSkinInfo->GetNumBones();
//Create an array of pointers with numBones pointers boneMesh->boneMatrixPtrs = new D3DXMATRIX*[NumBones]; //Fill array
for(int i=0;i < NumBones;i++) {
//Find influencing bone by name Bone *b = (Bone*)D3DXFrameFind(
m_pRootBone, boneMesh->pSkinInfo->GetBoneName(i));