一篇通俗易懂的.x文件解析+骨骼动画实现的文章(需要国外代理看)
网址:http://www.informikon.com/various/the-simplest-skeletal-animation-possible.html
推荐代理:http://www.9i7.cn/(http://www.orzin.com/)
该站内还有一些ManagedCode环境下使用DirectShow的经验文章
The simplest skeletal animation possible |
This document presents, possibly, the simplest skeletal animation sample. A zip file with code can be found here. Eversince I got my hands on the application Poser, I wanted to create my own utilities to do something similar. At the time, the only information in the DirectX SDK about this stuff could be found in the "SkinnedMesh sample". I recall searching the DirectX mailing list for the string "Craig Peeper" because he was the MS developer who answered the questions about this sample. Then Jim Adams "Advanced Animation" book helped clear further the subject and Tom Miller's "KickStart" contains a chapter about skeletal animation. Since I had to refresh my mind about this stuff recently, I decided to create the simplest possible code to get "Miss Tiny" up and walking. The sample is strongly influenced by Miller's chapter. Another very good reference about skin meshes is this pdf. Before starting to look at the code, I believe it is useful to take a look at the content of an x file with bones and animation. Only after being familiar with all the data that need to be processed, we can start to take a look at the functionality provided in the DirectX Extension library. So if you open "tiny.x" in a text editor, you'll find, after some header and template definitions, the following: You have a "Frame" named "Scene_Root" followed by a "FrameTransformMatrix" then the beginning of another frame (named "body"). A little further, you'll find the mesh information: The mesh contains 4432 vertices; the coordinates for these vertices are all listed. Inside the curly braces for the mesh data, you'll find many other template instances. You'll find materials, textures, normals informations like in any other mesh but also what make a skin mesh special the "XSkinMeshHeader" which tell how many bones we can find and the "SkinWeights" template instances with information about one particular bone. Here, we have a mesh with 35 bones and the bone named "Bip01_R_UpperArm" affects 156 vertices. These bones information are contained inside the "curly braces" for the mesh object. After the mesh and its "frame object" ends, you'll find frames with the bone names, for example: These are the "TransformMatrix" for each bone. They represent the transformation that has to be apply to the vertices in order to be properly positioned with respect to the parent bone. Closer to the end of the file, we find the animation data: We have an "AnimationSet" followed by an "Animation". Then the "AnimationKey" applies to the "Scene_Root" frame. The "AnimationKey" has a keytype 4 (for matrix keys) with 2 keys at "time" 0 and 4960. Our code needs to handle all this data. Now that we had a peek at the kind of data involved in skeletal animation, let's have a look at the facilities provided by DirectX to deal with such data. One of the first structure we'll encounter is the AnimationRootFrame, if you look up its documentation, you'll see only two properties: the AnimationController and the "FrameHierarchy" which is just a Frame object (a name wrapped around a transformation). The AnimationController will only be used for its AdvanceTime method which takes care of interpolation for times during the animation between keyframes. The "FrameHierarchy" property will point to the "top level" transformation in our x file. To build this hierachy of frames will call the static method LoadHierachyFromFile of the class Mesh. One of the arguments to this method is an object of type AllocateHierarchy. This is an abstract class with two important methods: CreateFrame and CreateMeshContainer. The method LoadHierarchyFromFile is responsible for parsing all the data in the x file. The "Create..." methods of the AllocateHierarchy object that we pass to LoadHierarchyFromFile will be called for every "Frame"s and "Mesh"es template instances found in the x file. Both of these "Create..." methods return objects derived from an abstract class; so we'll have to implement these two abstract classes by deriving new classes from Frame and MeshContainer to store the information that we need for our processing. The MeshContainer class contains some important properties: MeshData, SkinInformation The MeshData is just a structure for the mesh data found in the x file (stored in the MeshData property Mesh). The SkinInformation is used to store the information of the "SkinWeights" template instances found in the x file. After these preliminaries, we can look at the code. We start with some standard using statement and VS-generated code. Then we found some fields: We'll use a "Windows Form" timer to be reminded to refresh the display, we need a DirectX Device object. The "radius" and "center" variables will be used in setting the "View" and "Projection" transform. And the "rootFrame" is used to access its FrameHierarchy and AnimationController properties. After some pretty standard code for a Managed DirectX application, we call, in "InitializeGraphics", the method "LoadAnim": "LoadAnim" create our derived AllocateHierarchy object and call the static LoadHierarchyFromFile with this object for one of its arguments. This method returns our "rootFrame" object. Then we calculate the values for the variables "radius" and "center" and we call the method "AttachBones". This method recursively goes through the "FrameHierarchy" and calls itself or the the method with the same name but using a MeshContainer as argument. The "AttachBones" with a MeshContainer method retrieves the bone transformations and stores it away in a field our MeshContainer derived class. If you look back at "tiny.x", you'll see that the "Frame" template instance contains the "Mesh" template instance and inside the "Mesh" template instance you find all the "SkinWeights" template instances. But after the "Frame" template instance ends, you have a bunch of "Frame" template instances wrapped around a bone name. The "AttachBones" method connect the "SkinWeight" template instance inside the "Mesh" template with the "Frame" transform of the same name. Looking at this code, we notice that our MeshContainer derived class will need a field for an array of transform (named "FrameMatrices"). Now that we have all our matrices setup properly, we can draw our scene: We override OnPaint by first calling AdvanceTime then we have to update all our transforms in the "FrameHierarchy" to reflect the new transforms in the bones. This is done with the call to "UpdateFrameMatrices": "UpdateFrameMatrices update the "Combined" matrix with the "parentMatrix" passed as argument. When we have a sibling transform, we update with this same "parentMatrix" and when we have a child transform, we update with the newly combined matrix. After that, we are ready to call "DrawFrame". The "DrawFrame" method is in two parts. First, we draw each mesh container with the method "DrawMeshContainer" then we just walk the tree recursively. "DrawMeshContainer" is where the "real" work gets done. Before we look at the code for this method, we'll first start to look at its argument: an object of type "MyMeshContainer" which is defined as follow: It's just a MeshContainer with some extra data. The objects of type "MyMeshContainer" are created when we call LoadHierarchyFromFile with our AllocateHierarchy-derived object. This last object calls CreateMeshContainer whose code is next: We'll concentrate on what is new with skin meshes. First, we assign to the property SkinInformation the argument of type "SkinInformation" passed into the method. This method gets called when we load the hierarchy from an x file. If the mesh is a skin mesh, it will contain a "XSkinMeshHeader" template instance; so we store this information away. Then the matrix at the end of each "SkinWeight" template instance is also stored away in the field "OffsetMatrices". Now, we have to look at the call ConvertToBlendedMesh. When we add "bones" to a mesh (in a modeling application), we have vertices that are affected by more than one bone (so we have a smooth deformation when the elbow bends, for example). The method ConvertToBlendedMesh takes all the vertices and all the bones and creates a new mesh with subsets that can be drawn in a single call. Moreover, it returns a BoneCombination table that we'll use to perform "geometry blending". We define the field NumberAttibutes to be the "BoneTable" length. We can look at the code for "DrawMeshContainer":
We grab the "FrameMatrices" and the "OffsetMatrices" and for every subsets, we set the "world matrix" to perform "geometry blending" (there is a good discussion of this technique in the DirectX documentation). This "world matrix" is obtained by composing the transformations for the bone offset and the updated transform matrix whose index is given by the property BoneId. The BoneCombination table returned by ConvertToBlendedMesh contains an entry for each subset, and each entry has an AttributeId and a BoneId. The BoneId is an array of indexes to perform "geometry blending". Then we set the RenderState property VertexBlend to the length of the BoneId array, and after we have set the materials and textures and we are ready to draw the corresponding subset. This might be the simplest skeletal animation program in MDX. The program is not efficient nor robust. But it serves to illustrate the minimal amount of code needed to perform such animation in Managed DirectX. |