上一篇翻译的文章,主要讲的是表现刚体运动的骨骼动画。相比之下,刚体的运动要简单得多,因为刚体的运动不涉及到形变。组成模型的网格只是在世界坐标系中平移、旋转或者翻转。而仅仅有刚体的骨骼动画对于3D游戏是远远不够的,所以这里我来分享一下本人学习蒙皮动画的成果。
其实蒙皮动画就是更高级的骨骼动画,一样有Frame,同样有父子关系。Frame中有关联矩阵,它代表的是这个Frame和上一级Frame发生相对运动时,它所应遵循的变换规律。所以在更新frame的关联矩阵是,应从根Frame开始以递归方式层层遍历,规则是:新矩阵=父辈矩阵*本身矩阵。
而当Frame的相对位置发生变化时,Mesh不仅仅是空间位置发生变化,还要发生形变。网格模型中的每个顶点不一定都只和一块骨骼关联起来,所以我们要做的就是:对每一个顶点,遍历它的骨骼连接表,找出它相对于每块骨骼的偏移权重(通常由美工建好),将权重乘以骨骼的偏移矩阵,对每个顶点。都将组合完成后的变化矩阵运用到生成新的蒙皮网络模型中。
这就是蒙皮动画的基本原理,下面来看看代码吧:
Tiny.x和Tiny.BMP可以在DirectX的SDK中,下载后一并放在工程文件目录下即可
程序代码:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Animation { public class Form1 : System.Windows.Forms.Form { private Device device = null; private AnimationRootFrame rootFrame;//使用根Frame来引用整个模型 //用来设置摄像机的位置 private Vector3 objectCenter;//模型的中心 private float objectRadius;//模型包围球的半径 private System.ComponentModel.Container components = null; public Form1() { InitializeComponent(); this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); } public bool InitializeGraphics() { // 设置Present Parameters参数,这基本就是最简单的设置方法 PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true;//应用程序在窗口模式下运行 presentParams.SwapEffect = SwapEffect.Discard;//后台交链缓冲区的设置 presentParams.AutoDepthStencilFormat = DepthFormat.D16;//深度缓存的格式化方式 presentParams.EnableAutoDepthStencil = true;//深度缓存以托管方式运行 bool canDoHardwareSkinning = true; Caps hardware = Manager.GetDeviceCaps(0,DeviceType.Hardware);//获取当前默认渲染设备(硬件)的性能 if(hardware.MaxVertexBlendMatrices >= 4)//这个模型中,顶点混合矩阵最多为4个 { //默认情况下,顶点处理方式为软件处理 CreateFlags flags = CreateFlags.SoftwareVertexProcessing; //如果硬件性能达标,怎改为硬件处理 if(hardware.DeviceCaps.SupportsHardwareTransformAndLight) flags = CreateFlags.HardwareVertexProcessing; if(hardware.DeviceCaps.SupportsPureDevice) flags |= CreateFlags.PureDevice; device = new Device(0,DeviceType.Hardware,this,flags,presentParams); } else { canDoHardwareSkinning = false; device = new Device(0,DeviceType.Reference,this,CreateFlags.SoftwareVertexProcessing,presentParams); } Device.IsUsingEventHandlers=false; //读取文件,开始渲染动画 CreateAnimation(@"../../tiny.x",presentParams); device.DeviceReset += new EventHandler(OnDeviceReset); OnDeviceReset(device,null); return canDoHardwareSkinning; } private void OnDeviceReset(object sender, EventArgs e) { Device dev = (Device)sender;//消息的发送者即渲染设备 //设置view矩阵 Vector3 vEye = new Vector3(0,0,-1.8f*objectRadius); Vector3 vUp = new Vector3(0,1,0); dev.Transform.View = Matrix.LookAtLH(vEye,objectCenter,vUp); //设置Projection矩阵 float aspectRatio = (float)dev.PresentationParameters.BackBufferWidth / (float)dev.PresentationParameters.BackBufferHeight; dev.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4,aspectRatio,objectRadius/64.0f,objectRadius * 200.0f); //设置灯光效果,这基本是最简单的效果 dev.Lights[0].Type = LightType.Directional; dev.Lights[0].Direction = new Vector3(0.0f,0.0f,1.0f); dev.Lights[0].Diffuse = Color.White; dev.Lights[0].Update(); dev.Lights[0].Enabled = true; } //重写Form类的OnPaint方法,但是实际应该叫OnRender方法更好 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { //处理下一刻的Frame和Mesh ProcessNextFrame(); device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0); device.BeginScene(); //渲染蒙皮动画模型 DrawFrame((FrameDerived)rootFrame.FrameHierarchy); device.EndScene(); device.Present(); this.Invalidate(); } private void ProcessNextFrame() { //设置世界矩阵 Matrix worldMatrix = Matrix.Translation(objectCenter); device.Transform.World = worldMatrix; if (rootFrame.AnimationController != null) rootFrame.AnimationController.AdvanceTime(3.0f, null);//如果想要加快模型步行的速度,可以改变第一个参数的大小,越大则越快 UpdateFrameMatrices((FrameDerived)rootFrame.FrameHierarchy, worldMatrix);//更新Frame矩阵 } //使用递归调用的方式,更新各个Frame的变换矩阵 private void UpdateFrameMatrices(FrameDerived frame,Matrix parentMatrix) { //frame的变化就是将自己的关联矩阵,和父Frame的变换矩阵相乘 frame.CombinedTransformationMatrix = frame.TransformationMatrix * parentMatrix; if(frame.FrameSibling != null)//处理兄弟frame UpdateFrameMatrices((FrameDerived)frame.FrameSibling,parentMatrix); if(frame.FrameFirstChild != null)//处理子frame UpdateFrameMatrices((FrameDerived)frame.FrameFirstChild,frame.CombinedTransformationMatrix); } //开始渲染,同样是递归的方法 private void DrawFrame(FrameDerived frame) { //模型是由Mesh表现出来的,所以首先要把Frame所包含的MeshContainer找出来 MeshContainerDerived mesh = (MeshContainerDerived)frame.MeshContainer; //处理MeshContainer了 while(mesh != null) { DrawMeshContainer(mesh, frame); mesh = (MeshContainerDerived)mesh.NextContainer; } //递归处理兄弟Frame if (frame.FrameSibling != null) { DrawFrame((FrameDerived)frame.FrameSibling); } //递归处理子Frame if (frame.FrameFirstChild != null) { DrawFrame((FrameDerived)frame.FrameFirstChild); } } //渲染Mesh private void DrawMeshContainer(MeshContainerDerived mesh, FrameDerived frame) { //如果模型是动态的 if(mesh.SkinInformation != null) { int attribIdPrev = -1; //开始渲染,遍历MeshContainer中的各个成员 for(int iattrib = 0; iattrib<mesh.NumberAttributes;iattrib++) { //读取骨骼连接表信息,确定将使用的混合权重的数量为numBlend int numBlend = 0; BoneCombination[] bones = mesh.GetBones(); for(int i=0; i<mesh.NumberInfluences; i++) { if(bones[iattrib].BoneId[i] != -1) numBlend = i; } //这里也是检查渲染设备能力 if(device.DeviceCaps.MaxVertexBlendMatrices >= numBlend +1) { //提取Mesh的偏移矩阵和对应Frame的关联矩阵 Matrix[] offsetMatrices = mesh.GetOffsetMatrices(); FrameDerived[] frameMatrices = mesh.GetFrames(); for(int i=0;i<mesh.NumberInfluences;i++) { int matrixIndex = bones[iattrib].BoneId[i]; if(matrixIndex != -1) { //对应Frame的关联变换矩阵和偏移矩阵的乘积 Matrix tempMatrix = offsetMatrices[matrixIndex]*frameMatrices[matrixIndex].CombinedTransformationMatrix; //设置索引世界矩阵 device.Transform.SetWorldMatrixByIndex(i,tempMatrix); } } //使用设置好的索引世界矩阵来混合顶点 device.RenderState.VertexBlend = (VertexBlend)numBlend; //处理Mesh的材质和贴图信息 if((attribIdPrev != bones[iattrib].AttributeId) || (attribIdPrev == -1)) { device.Material = mesh.GetMaterials()[bones[iattrib].AttributeId].Material3D; device.SetTexture(0,mesh.GetTextures()[bones[iattrib].AttributeId]); attribIdPrev = bones[iattrib].AttributeId; } mesh.MeshData.Mesh.DrawSubset(iattrib); } } } //对于静态的Mesh,处理纹理和贴图就可以了 else { ExtendedMaterial[] mtrl = mesh.GetMaterials(); for(int iMaterial = 0; iMaterial<mtrl.Length;iMaterial++) { device.Material = mtrl[iMaterial].Material3D; device.SetTexture(0,mesh.GetTextures()[iMaterial]); mesh.MeshData.Mesh.DrawSubset(iMaterial); } } } private void CreateAnimation(string file, PresentParameters presentParams) { // 声明AllocateHierarchyDerived的实例 AllocateHierarchyDerived alloc = new AllocateHierarchyDerived(this); // 把模型文件读入缓存 rootFrame = Mesh.LoadHierarchyFromFile(file, MeshFlags.Managed,device, alloc, null); // 得到模型包围球的半径大小 objectRadius = Frame.CalculateBoundingSphere(rootFrame.FrameHierarchy, out objectCenter); // 设置动画有关矩阵 SetupBoneMatrices((FrameDerived)rootFrame.FrameHierarchy); } private void SetupBoneMatrices(FrameDerived frame) { //处理frame的meshcontainer中的矩阵 if (frame.MeshContainer != null) { SetupBoneMatrices((MeshContainerDerived)frame.MeshContainer); } //兄弟frame if (frame.FrameSibling != null) { SetupBoneMatrices((FrameDerived)frame.FrameSibling); } //子frame if (frame.FrameFirstChild != null) { SetupBoneMatrices((FrameDerived)frame.FrameFirstChild); } } //处理mesh的矩阵 private void SetupBoneMatrices(MeshContainerDerived mesh) { //只有包含骨骼信息才会有下面的步骤 if (mesh.SkinInformation != null) { int numBones = mesh.SkinInformation.NumberBones; FrameDerived[] frameMatrices = new FrameDerived[numBones]; //遍历frame列表,找到名称相符的frame,并且保存之 for(int i = 0; i< numBones; i++) { FrameDerived frame = (FrameDerived)Frame.Find( rootFrame.FrameHierarchy, mesh.SkinInformation.GetBoneName(i)); if (frame == null) throw new ArgumentException(); frameMatrices[i] = frame; } mesh.SetFrames(frameMatrices); } } /// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// </summary> private void InitializeComponent() { // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(632, 390); this.Name = "Form1"; this.Text = "Form1"; } #endregion /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { using (Form1 frm = new Form1()) { frm.Show(); if (!frm.InitializeGraphics()) { MessageBox.Show("Your card can not perform skeletal animation on " + "this file in hardware. This application will run in " + "ref mode instead."); } Application.Run(frm); } } public void GenerateSkinnedMesh(MeshContainerDerived mesh) { if (mesh.SkinInformation == null) throw new ArgumentException(); int numInfl = 0; BoneCombination[] bones; //运用骨骼连接表和顶点混合权重产生一个可以被渲染的Mesh MeshData m = mesh.MeshData; m.Mesh = mesh.SkinInformation.ConvertToBlendedMesh(m.Mesh, MeshFlags.Managed | MeshFlags.OptimizeVertexCache, mesh.GetAdjacencyStream(), out numInfl, out bones); mesh.NumberInfluences = numInfl; mesh.SetBones(bones); mesh.NumberAttributes = bones.Length; mesh.MeshData = m; } } public class FrameDerived : Frame { // 存储关联变换矩阵 private Matrix combined = Matrix.Identity; public Matrix CombinedTransformationMatrix { get { return combined; } set { combined = value; } } } /// <summary> /// 继承于MeshContainer /// </summary> public class MeshContainerDerived : MeshContainer { private Texture[] meshTextures = null; private int numAttr = 0; private int numInfl = 0; private BoneCombination[] bones; private FrameDerived[] frameMatrices; private Matrix[] offsetMatrices; public Texture[] GetTextures() { return meshTextures; } public void SetTextures(Texture[] textures) { meshTextures = textures; } public BoneCombination[] GetBones() { return bones; } public void SetBones(BoneCombination[] b) { bones = b; } public FrameDerived[] GetFrames() { return frameMatrices; } public void SetFrames(FrameDerived[] frames) { frameMatrices = frames; } public Matrix[] GetOffsetMatrices() { return offsetMatrices; } public void SetOffsetMatrices(Matrix[] matrices) { offsetMatrices = matrices; } public int NumberAttributes { get { return numAttr; } set { numAttr = value; } } public int NumberInfluences { get { return numInfl; } set { numInfl = value; } } } /// <summary> /// 继承于AllocateHierarchy /// </summary> public class AllocateHierarchyDerived : AllocateHierarchy { Form1 app = null; /// <summary> /// 构造函数 /// </summary> /// <param name="parent">得到对应用程序的引用</param> public AllocateHierarchyDerived(Form1 parent) { app = parent; } /// <summary> /// 实例化Frame /// </summary> public override Frame CreateFrame(string name) { FrameDerived frame = new FrameDerived(); frame.Name = name; frame.TransformationMatrix = Matrix.Identity; frame.CombinedTransformationMatrix = Matrix.Identity; return frame; } /// <summary> /// 实例化MeshContainer /// </summary> public override MeshContainer CreateMeshContainer(string name, MeshData meshData, ExtendedMaterial[] materials, EffectInstance[] effectInstances, GraphicsStream adjacency, SkinInformation skinInfo) { //检查mesh信息是否为空 if (meshData.Mesh == null) throw new ArgumentException(); // 必须为顶点设置格式 if (meshData.Mesh.VertexFormat == VertexFormats.None) throw new ArgumentException(); MeshContainerDerived mesh = new MeshContainerDerived(); mesh.Name = name; int numFaces = meshData.Mesh.NumberFaces; //得到渲染Mesh的设备 Device dev = meshData.Mesh.Device; // 处理法线 if ((meshData.Mesh.VertexFormat & VertexFormats.Normal) == 0) { Mesh tempMesh = meshData.Mesh.Clone(meshData.Mesh.Options.Value, meshData.Mesh.VertexFormat | VertexFormats.Normal, dev); meshData.Mesh = tempMesh; meshData.Mesh.ComputeNormals(); } //保存材质 mesh.SetMaterials(materials); mesh.SetAdjacency(adjacency); Texture[] meshTextures = new Texture[materials.Length]; //增加贴图 for (int i = 0; i < materials.Length; i++) { if (materials[i].TextureFilename != null) { meshTextures[i] = TextureLoader.FromFile(dev, @"../../" + materials[i].TextureFilename); } } mesh.SetTextures(meshTextures); mesh.MeshData = meshData; //如果包含骨骼信息,存储必要的数据 if (skinInfo != null) { mesh.SkinInformation = skinInfo; //骨骼连接表中骨骼个数 int numBones = skinInfo.NumberBones; //偏移矩阵 Matrix[] offsetMatrices = new Matrix[numBones]; for (int i = 0; i < numBones; i++) offsetMatrices[i] = skinInfo.GetBoneOffsetMatrix(i); mesh.SetOffsetMatrices(offsetMatrices); //进一步为骨骼动画做处理 app.GenerateSkinnedMesh(mesh); } return mesh; } } }
运行效果:
转自:http://www.cnblogs.com/DonkeyWugui/archive/2009/02/07/1385990.html