大学生研究计划结题报告-基于directx sdk的三维人体建模与运动仿真
http://www.ustc.edu.cn/zh_CN/中国科学技术大学大学生研究计划结题报告课题名称三维人体建模与运动仿真姓名学院信息科学与技术学院系别电子工程与信息科学系(EEIS)专业(未分配)年级07级学号PB指导老师导师单位中国科学院自动化研究所2010年9月30日信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/目录1.引言22.概述22.1DirectX简介22.2三维人体模型的获取22.3基于DirectX三维人体运动仿真的原理简介22.4MFC应用程序框架上的三维模型编辑软件的构建23.人体建模与运动仿真实现原理23.1三维人体模型关节运动控制原理23.1.1三维人体模型结构剖析23.1.2变换矩阵作用原理23.1.2.1蒙皮网格模型中变换矩阵的作用原理23.1.2.2标准网格模型中变换矩阵的作用原理23.2.X文件中的模型及其读取方法23.2.1.X文件的结构说明23.2.2.X文件的解析方法概述23.3多种模式下的模型运动控制23.3.1模型运动控制原理23.3.2三种类型变换的作用效果和施加变换的次序规则23.3.34×4顶点变换矩阵的生成与解析24.运动仿真控制应用程序设计24.1编程环境的配置24.2程序设计框图24.3创建内嵌Direct3D的MFC应用程序项目24.4主要的数据结构及功能函数24.5程序用户界面(GUI)简介25.自我拓展之XSceneStudio梦想平台26.结论27.致谢2信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/三维人体建模与运动仿真——基于DirectXSDK***中国科学技术大学信息科学与技术学院电子工程与信息科学系,安徽省合肥市()shilx@mail.ustc.edu.cn摘要:本文给出了一种三维人体模型的形态编辑及运动仿真控制的方法。该方法采用DirectXSDK函数库,在MFC应用程序框架上,通过程序设计,实现对.X文件中存储的三维模型的读取,以及通过键盘控制,对具有规范部位层次结构的模型进行尺寸、角度、方位上的编辑操作。同时本文特别讲述骨骼动画的原理和4x4特定变换矩阵的剖析问题。关键词:剖析.X文件结合MFC与Direct3D骨骼动画4x4变换矩阵剖析中图分类号:1.引言近年来,三维仿真技术被广泛的应用。尤其在娱乐多媒体领域,从由来已久的3D游戏到气势恢宏倍受青睐的3D电影《阿凡达》(AFATAR),再到由此掀起的当今的3D热潮,无不展现出3D的巨大魅力。而在工程研究领域,3D仿真同样得到了广泛的运用,从城市规划的模拟视图,到机械设计中的运动仿真,3D都起到了重要的辅助的作用。然而,在三维设计中,商业领域,有3DsMAX、MAYA两大软件;在工程运用上,有AutoCAD、SolidWorks等专业建模工具。它们分别都有各自的设计标准和文件格式定义,在需要通过外部接口控制某一特定模型时,便遇到软件接口以及通用性的问题,在信息处理领域尤为如此。微软公司(Microsoft©)的DirectX在解决这一问题上起到重要的作用,由其定义的.X文件能够存储三维模型信息,而DirectXSDK程序开发库或OpenGL开源函数库中的相关函数允许我们通过C++语言的程序设计,对.X文件中的模型做一定程度上的编辑。在某些信息处理领域已经可以满足需求。所以深入研究三维模型的建模规则,熟练掌握DirectX的使用方法,实现对三维模型的灵活编辑控制,尤其将Direct3D与同用的MFC应用程序框架相结合,可以使得3D仿真技术在信息处理领域的运用变得更加方便。当然,目前在使用这两类函数库进行模型编辑的运用中,大多为直接替换整个作用在模型某一部位的变换矩阵,而不是对模型的尺度、角度及方位分别进行控制。本文介绍的方法可以实现对特定规则生成的综合变换矩阵的反解析,从而获取上述三方面变换的矩阵,通过将原变换矩阵与新添的控制变换矩阵的各自分别组合后相乘,得到最终变换矩阵,可以达到对模型的精确控制。2.概述2.1DirectX简介DirectX是微软推出的一套基于Windows系统的多媒体应用程式接口APIs函式。信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/在开发中,DirectX分为两个部分,一个是运行库,通过DirectX编译出来的程式必须要有运行库的支持;另外一个是开发库,也就是常说的SDK,这部分是在编译DirectX程序中是必需的。图1Direct3D与其他系统组件之间的关系示意图从功能角度,DirectX是一种图形应用程序接口(API),简单的说它是一个辅助软件,一个提高系统性能的加速软件。Direct意为直接,X代表多样性,组合起来就是一个具有某种共性的组件的集合,这个共性就是直接,微软定义它为“硬件设备无关性”。DirectX又是图形及媒体加速接口,只有安装了它,系统中软件才能比较直接的利用硬件加速资源(高速访问硬件),目前该软件最高版本为DirectX11.0。DirectX包含如下组件:DirectXGraphics(包含DirectDraw&Direct3D)、DirectSound、DirectPlay、Direct3D、DirectInput、DirectSetup、AutoPlay等。表1DirectX成员及其作用DirectX成员作用简述DirectXGraphics集成了以前的DirectDraw和Direct3D技术。DirectDraw主要负责2D加速,以实现对显卡内存和系统内存的直接操作;Direct3D主要提供三维绘图硬件接口,它是开发三维DirectX游戏的基础。DirectInput主要支持输入服务(包括鼠标、键盘、游戏杆等),同时支持输出设备。DirectPlay主要提供多人网络游戏的通信、组织功能。DirectSetup主要提供自动安装DirectX组件的API功能。DirectMusic主要支持MIDI音乐合成和播放功能。DirectSound主要提供音频捕捉、回放、音效处理、硬件加速、直接设备访问等功能。信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/DirectShow为Windows平台上处理各种格式的媒体文件的回放、音视频采集等高性能要求的多媒体应用,提供了完整的解决方案。DirectXMediaObjectsDirectShowFilter的简化模型,提供更方便的流数据处理方案。总之,DirectX的主要好处有两个:为软件开发者提供与硬件的无关性;为硬件开发提供策略。本文所介绍的三维人体建模与运动仿真主要使用其中的Direct3D组件。2.2三维人体模型的获取微软公司(Microsoft©)为DirectX定义的.X文件具有存储三维模型的作用,Direct3D的库函数中也具备一定的三维建模功能。然而在精细建模以及使用、操作便捷程度方面,DirectX尚未能与专业的三维建模软件,如3DsMAX、MAYA等,相匹敌。好在微软公司等为这些大型三维建模软件研发了模型格式转换插件,使得用户可以在3DsMAX等常用三维建模软件中设计优质的三维模型,然后利用第三方插件,将建好的模型导出到.X文件中,如PandaDirectXMaxExporter_x86_6.2010.71.0就是用于将3DsMAX建模软件生成的.max文件转换为.X文件的插件之一。值得特别说明的是,由于对模型的形态编辑与控制时基于对作用在模型上的变换矩阵的编辑实现的,故而所建立的模型必须按照严格的设计规则,尤其是各个部位间的链接关系和层次关系,它们将直接关系到模型是否能得到正常、合理的控制。具体的建模规则由下文“三维人体模型的结构分析部分”介绍。2.3基于DirectX三维人体运动仿真的原理简介所谓运动仿真,初步地说,可以看作是让一个人体模型做出真实人体能够做出的运动状态;高级地说,还需嵌入一些重力、机械动力学等物理因素到其中,使之更贴切地模拟真实世界中人体的运动。当然前者是后者的基础,有了对模型的灵活控制技术,才能够使之应对于不同的力学条件,更趋于真实地做运动仿真。在实现效果上,如果可以做到让模型做出人们所期望的动作,那么运动便可以是一系列动作的按时序的组合形成的一套动画,因此如果达到可以任意控制模型动作的目的,那么运动仿真的初步实现问题就基本突破了。三维模型由点、线、面按照特定的规则组合而成。两点确定一条线段,各条线段通过公共顶点连接成模型网格。其中常见的三个顶点确定一张三角平面,按这三点的某一种“绕序”确定该张平面的法向量,逆其法向量观察平面可见,顺其法向量观察平面则不可见。众多三角平面的拼接,围成一个三维、立体的模型外壳,通过为三角面配置不同的材质,为顶点确定不同的纹理,得到一个具有特色的仿真模型。.X信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/文件对模型的存储不仅有确定模型的顶点信息、平面信息、材质信息,还具有用于控制顶点位置的变换矩阵。归属于某一模型部位的顶点组成一个集合,一个集合对应一组变换矩阵。通过对变换矩阵的解析、重建,便可以改变相应顶点的位置,对应的模型部位便在空间位置上做出改变,整体上看,便像是模型做出了某一个动作一般。如上所述,一个模型部位对应一个顶点集合,一个顶点集合对应一组变换矩阵,而变换矩阵作用在顶点上时是对集合中所有的顶点做同样的变换,从而相应的模型部位以一个整体在空间中做出相应的变化。换言之,用.X文件存储的三维模型中,对模型的编辑精细程度只能停留在模型建立过程中确定的最小单元上。例如,在模型建立中,如果头部被当作一个整体后,作为人体的一个单元,与其他身体部位连接构成整个人体,存储在.X文件里,那么构成头部的各个顶点组成一个顶点集合,这个集合对应一组变换矩阵。在模型编辑与控制过程中,对应于头部,我们只能够做到使之点头扭头等以整个头部为控制单元的动作,而不能够使之皱眉头、扮笑脸、转眼珠之类的动作;要实现这些目的,只能通过将建模的单元细化到能表现这些特征的程度,比如将眼珠作为一个部位存储,那么通过单独改变与之相应的变换矩阵,使得眼珠的方位相对于眼眶发生变化,便可以实现“转眼珠”的效果了。2.4MFC应用程序框架上的三维模型编辑软件的构建MFC应用程序框架是工程以及信息处理领域专业应用型程序编写所基于的主要程序框架。由此框架编写的应用程序具有较强的通用性,其特殊而强大的程序架构更有利于工程中其他功能的添加,使得相关应用得以拓展。然而DirectX中Direct3D应用程序的开发,以游戏编程领域基于Win32的应用程序为主,结合MFC应用程序框架的Direct3D应用程序的开发并不多见,所以熟练掌握MFC与Direct3D的结合方法至关重要。在应用程序中要实现对三维模型的编辑以及运动仿真控制功能,首先要解决模型读取的问题;其次是根据模型控制原理,实现相应的模型控制算法;最后完成模型控制的人机交互界面。1.人体建模与运动仿真实现原理3.1三维人体模型关节运动控制原理信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/结合真实人体运动的特点可以清楚地知道,模型中的关节运动主要是各部位绕着彼此间连接处的关节点的旋转运动,在单个关节的运动过程中要保证模型的整体性,则要确保与之连接的上一个关节和下一个关节仍然保持原有的连接关系,即一个关节的运动,势必影响其他与之相关的关节的变化,形象的说,这种变换带来的影响是传递的。而在模型的简单编辑中,对模型的操作综合了对关节的尺度、角度及方位等三方面的变换处理。为了能够模拟的人体达到多样化,对模型某关节的尺度、位置做细微的调整也是必不可少的。比如,当我们只想要一个女性模型的上手臂细一些,而保持其臂长及其他部位的形状,那么我们需要对手臂在垂直于手臂方向上的尺度分量做缩减,在手臂方向上的尺度分量保持不变;并且在这个问题上,对上臂的编辑所带来的影响不期望被传递,一面影响模型的其他部位。又如,当我们发现一个模型整条手臂的比例都很协调很美观,然而它相对于身体的其他部位显得小了一些,那么我们是期望可以对整个手臂做一个比例缩放,使之与其他的关节在尺度上协调起来。如此又是连接,又有独立,又是整体的,那么模型关节的运动到底是如何实现的呢?首先来了解一下人体模型各个关节的连接关系。3.1.1三维人体模型结构剖析根据真实人体结构,我们知道当我们举起下手臂时,与之连接的手掌是一起被举起来的,而当我们举起同样与下手臂连接的上手臂时,则是手掌和下手臂都一起连带着被举起;当我们扭动脖子时,与之连接的头部便随之转动一定角度,而当我们转动与同样与脖子连接的脊椎时,则脖子和头部都将随之转动一定角度。似乎作用在某一个关节上的控制都向下传递给了与之相连接的关节,同时接收到简介控制的关节又将控制信息传递给与它连接的下一个关节,直到底层的关节。由此两例可以看出,人体结构具有特殊而严格的连接关系,这种连接关系影响着机体运动的方式。结合真实人体结构,我们可以给出图2所示的人体模型及其连接关系示意图:图2三维人体模型结构及连接关系示意图如图所示,箭头方向显示了作用的传递方向,由盆骨出发,首先连接的是底下的第一根脊椎骨(暂且称之为脊椎0);脊椎0连接了脊椎1和左右两只大腿;大腿连接小腿,再到脚掌,再往脚趾;而脊椎1则往上连接脊椎2,再到脊椎3,然后是脖子;此时脖子连接着头部和左右锁骨;锁骨连接上臂,再到下手臂,然后是手掌,再是手指。其层次关系可由如下图3表示出来:信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图3三维人体模型关节层析结构图从而,层次结构图中上层关节的运动,则带动与之有连接关系的下层关节的运动。这便体现了人体关节运动的关联特性。常言道“牵一发而动全身”,那是因为被弄疼了做出的夸张反应,而此处的“动盆骨则动全身”,则是我们的身体结构所决定的,否则我们的骨架就要脱臼了。3.1.2变换矩阵作用原理常见三维模型有标准网格模型和蒙皮网格模型。其中标准网格模型只是单纯地以每一个关节部位为单元,存储对应的所有的顶点、相应的材质、纹理信息,以及一组变换矩阵;因此在这种网格模型中,修改变换矩阵所带来的影响是针对一个部位所有的顶点,并且作用的程度是完全一样的。而在蒙皮网格模型中,采用了另外一种存储方式,其中规定了一系列的骨骼,每个骨骼对应整个人体模型中一组特定的顶点,比如手掌骨骼对应模型中手掌上的一系列顶点;同样的,相应于标准网模型中的顶点集合,在蒙皮网格中顶点也按照归属的骨骼的不同而被分配到不同的顶点集合中。稍有不同的是,标准网格中的顶点只能绝对地属于某一特定部位,从而归属于某一特定的集合;而蒙皮网格中的顶点则可以归属于多个与之相关的骨骼,如上臂和下手臂之间连接处的顶点就同时归属于两个骨骼,从而这些连接处的顶点就受到多重变换矩阵的控制。由此观之,在蒙皮网格与标准网格模型中变换矩阵作用方式是所有区别的,下文分别说明之。首先介绍蒙皮网格中模型的存储方式和变换矩阵作用原理。3.1.2.1蒙皮网格模型中变换矩阵的作用原理1、蒙皮网格模型的存储方式信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/蒙皮网格模型在建立过程中便如绘画一般将模型以一个整体的形式构建起来,得到一个完全静态的,不具备受控能力的完整的人体模型。随后才分块定义不同的骨骼,相当于将一个模型根据所需的精细程度,按部位的不同分割开,然后通过变换矩阵An将各个骨骼移动到世界坐标系的中心处,并确保其中与上层关节的连接点与原点重合。于是在世界坐标系的中心,我们得到一组零散的人体骨骼。在重新显示完整的人体模型时,再次通过变换矩阵Bn将各个骨骼还原到原来的位置上。当矩阵Bn为矩阵An的逆矩阵时,模型可以完全还原到原来的模样,当矩阵Bn有别于矩阵An的逆矩阵时,便得到有别于原始模型的新模型。所以在蒙皮网格模型的存储过程中,原始模型上的所有的顶点被原样记录下来,其次是各个骨骼对应的变换矩阵An,然后是将骨骼还原到原始位置或近似原始位置的矩阵Bn。2、蒙皮网格模型的变换矩阵作用原理[1]图4蒙皮网格模型的变换矩阵作用原理讲解图一如图4所示,左上角顶端的坐标系为世界坐标系。模型上肢的网格分为上臂、前和手掌3个部分。逐次在上臂、前臂和手掌的Mesh内部选定骨骼,形成图中的3条骨骼。其中,从连接关系上,骨骼1是骨骼2的父骨骼,骨骼2是骨骼3的父骨骼。角色网格的每一个顶点都有一个局部坐标,这些顶点局部坐标构成.X文件的Mesh对象的数据。在角色网格中相继点击鼠标,为角色网格设置一系列的骨骼。鼠标的首次点击位置就是该骨骼的首关节点,尾关节点由再次点击鼠标选定子骨骼时确定,尾关节点则是子骨骼的首关节点。信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/在每一条骨骼上建立一个骨骼坐标系。由于骨骼坐标系会随着骨骼的运动而运动,骨骼上的皮肤顶点的骨骼坐标在骨骼的运动过程中保持不变。图5蒙皮网格模型的变换矩阵作用原理讲解图二如图5中的点P,在骨骼2的坐标系下的坐标保持不变。由于任意两个父子骨骼的坐标系,一定存在一个坐标变换矩阵Aframe,使得子骨骼蒙皮上顶点的骨骼坐标Xchild与该顶点在父骨骼坐标系中的坐标Xfather满足如下关系式Xfather=Xchild*Aframe,矩阵Aframe称为子骨骼的骨骼矩阵。如果将根骨骼(层次关系中处于最上层的那块骨骼)的父骨骼坐标系定义为世界坐标系(包含网格顶点坐标的Frame所在的坐标系实际就是所谓的局部坐标系),那么,每个骨骼都有一个骨骼矩阵,折叠矩阵就是在.X文件中看到的一系列具有层次关系的Frame对象中的变换矩阵。利用骨骼矩阵,可计算出骨骼蒙皮顶点所在骨骼坐标系下的坐标和世界坐标。信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图6蒙皮网格模型的变换矩阵作用原理讲解图三其中,矩阵“(AnAn-1…Am…AM+1)-1”称为骨骼的权重矩阵,是从局部坐标系所在的框架开始遍历到该骨骼,通过各层的骨骼矩阵左乘,然后求逆计算出来的。骨骼的权重矩阵就是在.X文件中各个骨骼的SkinWeights对象中的偏移矩阵,用来反响求出顶点在所属骨骼的坐标系下的坐标。现在烤炉骨骼绕关节点的旋转。如图5所示,骨骼2绕关节点2抬起,即骨骼2绕关节点2轴线的一个旋转变换。旋转后,骨骼2的骨骼矩阵(即相对于父骨骼1的坐标系)发生改变,而其他子骨骼的骨骼矩阵维持不变。由此看出,应用反应父子相对关系的骨骼矩阵来存储骨骼动画的运动变化,需要较少的数据更新。在.X文件中,骨骼在某一帧时刻的AnimationKey矩阵对象就是这些骨骼矩阵。此时,通过骨骼矩阵自定向下进行乘法累积,可获得旋转后的各个骨骼的世界坐标变换矩阵。设旋转后的骨骼2世界坐标变换矩阵为W2,顶点旋转后的世界坐标Xworld_new和骨骼坐标Xframe_new满足关系式Xworld_new=Xframe_new*W2,由于顶点相对于它的骨骼坐标系的坐标在旋转过程中保持不变,从而Xframe_new=Xframe_0,因此有Xworld_new=Xframe_0*W2。顶点坐在骨骼的权重矩阵为H,顶点最初始的Mesh局部坐标为X0。根据权重矩阵的定义,有Xframe_0=X0*H。从而Xworld_new=Xframe_0*W2=X0*H*W2,这说明旋转后的顶点世界坐标等于顶点最初始状态下的局部坐标与权重矩阵以及所在骨骼的世界坐标变换矩阵的乘积。通常,关节点附近的网格顶点可以收到多个骨骼的控制。如图7的点P信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图7蒙皮网格模型的变换矩阵作用原理讲解图四当模型伸手取物时,骨骼2和骨骼3均会同时绕着各自的关节点抬起,此时关节点3附近的顶点P,会随着前臂的骨骼2和手掌的骨骼3的运动而运动。一次,考虑顶点受到多个骨骼影响的情形,顶点运动后的坐标还要与该顶点的权重值想成。顶点的权重由3D建模软件在绘图阶段确定下来,并保存在.X文件的各个骨骼SkinWeights对象的weights数组中。假定顶点P受骨骼2影响的权重值为α,受骨骼3影响的权重值为β,骨骼2运动后计算出的顶点P的局部坐标为XLocal_2,骨骼3运动后计算出的顶点P的局部坐标为XLocal_3,那么顶点P最终的坐标应该为XLocal_2×α+XLocal_3×β,其中α+β=1。更一般地,一个顶点所有受骨骼影响的权重值之和为1。3.1.2.2标准网格模型中变换矩阵的作用原理1、标准网格模型的存储方式标准网格模型的建立过程更像是在制作一台机器,首先我们需要制作构成机器的各个零部件,即人体模型的各个关节,然后再将这些关节拼装起来。每个关节都将被建立在世界坐标系的中心位置,并且,依据该关节与其他关节的连接关系,确保与上层关节的连接点与原点重合。由于关节模型的建立并不考虑所需建立的人体模型的尺度问题,所以关节模型中顶点的最大坐标值不超过1.0,即这些关节都被建立在由(1,1,1)、(1,1,-1)、(1,-1,1)、、(1,-1,-1)、(-1,1,1)、(-1,1,-1)、(-1,-1,1)、(-1,-1,-1)八个顶点链接构成的以原点(0,0,0)为中心,边长为2的立方体内。在构建完整的人体模型时,首先依据所要创建的人体模型的尺度特征,对各个关节分别作一次比例变换An信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/;其次再利用由旋转变换和平移变换组合成的变换矩阵Bn分别将它们迁移到世界坐标系中的不同的部位,拼接到一起得到人体模型的样子。所以在标准网格模型的存储过程中,各个关节模型上的各个顶点被原样记录下来,其次是比例变换An,再次是用于拼接的变换矩阵Bn。2、标准网格模型的变换矩阵作用原理类比蒙皮网格模型变换矩阵作用的原理,只需将蒙皮网格模型中开始的分解模型那一块省去,然后将标准网格模型的比例变换、旋转变换及平移变换综合在一起类比蒙皮网格模型变换中的拼接,就得到了标准网格模型变换矩阵的作用方式了。此处不再赘述。3.2.X文件中的模型及其读取方法X文件则是由Microsoft为DirectX量身定制的一种用于存储数据的文件格式类型,其文件扩展名为.X。Direct3D中也使用了这种文件格式来存储3D模型的各个信息,如网格数据,纹理数据,动画关键帧数据等。以下从结构和解析方法两方面对.X文件做一个简单介绍。3.2.1.X文件的结构说明理解.X文件的结构有助于了解.X文件中包含有哪些数据,更有助于理解Direct3D的库函数可能是怎么读取、解析.X文件的,甚至在知道.X文件的结构之后,我们可以明白如何去扩展它的功能,让其作用尽可能的得到发挥。从文件整体结构上.X文件分为文件头、数据模板定义和数据对象三个方面。1、.X文件头.X文件头包含文件的类型标识符、文件版本、数据记录格式以及数据中浮点数值的位数等信息。如“xof0303txt0032”,表示DirectX3.3版本的,以文本格式记录数据,浮点数值为32位的.X文件。2、数据模板定义.X文件中的数据模板用于定义.X文件中数据对象的布局。它由“template”关键字、模板名称、一个GUID(GloballyUniqueIdentificationNumber全局唯一标识号)以及数据变量的定义几个部分组成。[2]对其理解可以类比C/C++中结构体数据类型的定义,当然还要结合一点编译原理中正则表达式的思想,因为.X文件数据模板是允许嵌套定义的。最具代表性的是DirectX标准模板中的“Frame”模板,它允许嵌套所有其他的模板。这就意味着在定义模板时,若包含有另一个模板,那么被包含模板所定义的数据对象就可以嵌入到外层模板所定义的数据对象当中去。依据模板是否嵌套定义,嵌套的原则如何,模板分为普通模板和嵌套模板,其中嵌套模板又分为封闭式、开放式和受限式三种。重点说明一下模板的全局唯一标识号(GUID):GUID是一个用于唯一标识模板的编号。当读取一个.X文件到程序中时,只会访问到每个模板的GUIDs信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/而不是模板的名称。模板的名称只是在.X文件的数据对象定义中时起到模板类型的代称的作用。因此当解析.X文件时,我们是通过这些定义的模板的GUID来识别模板的类型,从而依模板的不同用不同的方式来读取模板中的数据信息。特别介绍一下DirectX标准模板:DirectX.X标准模板由Microsoft公司定义并打包到SDK里。其中Direct3D相关的模板可用来包含网格模型的所有相关数据,所以这些标准模板是相当实用的。以下着重讲解与本文相关的四个关键模板,“Frame”、“Mesh”、“FrameTransformMatrix”以及“SkinWeights”的定义:·FrametemplateFrame{<3D82AB46-62DA-11cf-AB39-0020AF71E433>[...]}用于定义框架容器,可用来装载Mesh对象,也可以包含子框架。在骨骼动画中可以装载一块或一系列骨骼(如手臂),(表示骨骼时)可以没有Mesh对象。通常包括两部分:①Mesh对象。②转换矩阵,本地转换矩阵,初始化最初动作状态。·MeshtemplateMesh{<3D82AB44-62DA-11cf-AB39-0020AF71E433>DWORDnVertices;arrayVectorvertices[nVertices];DWORDnFaces;arrayMeshFacefaces[nFaces];[...]}用于定义一个Mesh对象。通常会有9个部分组成:①包含的顶点数②顶点列表,一个顶点包含三个浮点值③面数④面的顶点索引列表,每个面包含三个顶点⑤MeshFaceWraps结构,暂时无用⑥MeshTextureCoords纹理坐标,可选⑦MeshNormals法向,可选⑧MeshVertexColors顶点颜色,默认为白色⑨MeshMaterialList材质,不提供的话默认为白色·FrameTransformMatrix信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/templateFrameTransformMatrix{
Matrix4x4frameMatrix;}用于定义框架的本地转换矩阵,是一个4X4矩阵(Matrix4x4类型),共16个浮点数据(注意这里的4X4矩阵是行主的)。·SkinWeightstemplateSkinWeights{<6f0d123b-bad2-4167-a0d0-80224f25fabb>STRINGtransformNodeName;DWORDnWeights;arrayDWORDvertexIndices[nWeights];arrayFLOATweights[nWeights];Matrix4x4matrixOffset;}用于定义骨骼影响权重。包括以下几个部分:①骨骼的名字②有多少个权重值③顶点的索引列表④相对应的影响顶点索引列表⑤本地转换矩阵,转换到骨骼空间3、数据对象数据对象是.X文件中,按照上述模板规定的数据组织形式所定义的一系列被具体化的数据信息。由上文标准网格模型和蒙皮网格模型的结构解析中可知,在.X文件中不同的模型对应有不同的数据信息来存储模型的所有信息。蒙皮网格模型的数据对象结构如图8所示;标准网格模型的数据对象结构如图9所示:信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图8蒙皮网格模型对应的.X文件中数据对象的结构图9标准网格模型对应的.X文件中数据对象的结构(注:其中的框架层次的包含关系体现在框架分布的里外层次中。)信息科学与技术学院电子工程与信息科学系51(EEIS)http://www.ustc.edu.cn/zh_CN/由上图可知蒙皮网格模型中只具备一组网格数据,骨骼信息被包含在网格数据所在的子框架中,而骨骼的连接及层次关系则由另一个框架中以相应骨骼名称命名的子框架间的嵌套关系体现出来。而标准网格模型中对应一个关节便有一个套完整的网格数据,并且这些数据又分别被封装在不同的子框架中,不同的关节间的连接及层次关系则由用于封装的子框架之间的嵌套关系来体现。3.2.2.X文件的解析方法概述将.X文件中的模型读取的过程辨识.X文件的解析过程,大致分为以下步骤:1、定义一个框架容器或直接用Direct3DSDK中的D3DXFRAME结构体,定义一个网格容器或直接用Direct3DSDK中的D3DXMESHCONTAINER。本文介绍的解析方法中分别以D3DXFRAME和D3DXMESHCONTAINER为基类,按需拓展其原有的功能,定义专用的框架容器XSSD3DXFRAME_EX和网格容器XSSD3DXMESHCONTAINER_EX。[2]其中XSSD3DXFRAME的定义如下:信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN///DeclareanextendedversionofD3DXFRAME//thatcontainsaconstructoranddestructor//aswellasacombinedtransformationmatrixtypedefstructXSSD3DXFRAME_EX:publicD3DXFRAME{D3DXMATRIXmatCombined;//CombinedmatrixD3DXMATRIXmatOriginal;//Originaltransformationfrom.XD3DXMATRIXmatNewFeature;//NewTransformationforConfiguringtheDummyD3DXMATRIXmatNewControl;//NewTransformationforControllingtheDummyD3DXMATRIXmatNewSeparate;//NewTransformationforSpecifyingtheDummyD3DXMATRIXmatFeatureT;//TranslationTransformationtoconfigurethedummyD3DXMATRIXmatFeatureR;//RotationTransformationtoconfigurethedummyD3DXMATRIXmatFeatureS;//ScalingTransformationtoconfigurethedummyD3DXMATRIXmatControlT;//TranslationTransformationtocontrolthedummyD3DXMATRIXmatControlR;//RotationTransformationtocontrolthedummyD3DXMATRIXmatControlS;//ScalingTransformationtocontrolthedummyD3DXMATRIXmatSeparateS;//TostoreseparateScalingTransformationtoconfigureorControlthedummyD3DXMATRIXmatSeparateR;//TostoreseparateRotateTransformationtoconfigureorControlthedummyD3DXMATRIXmatSeparateT;//TostoreseparateTranslateTransformationtoconfigureorControlthedummyXSSD3DXFRAME_EX*pFrameParent;DWORDnFrameDepth;DWORDnRangeIndex;XSSD3DXFRAME_EX();~XSSD3DXFRAME_EX();//FunctiontoscanhierarchyformatchingframenameXSSD3DXFRAME_EX*Find(constchar*FrameName);//FunctiontoscanhierarchyformatchingframeRangeIndexXSSD3DXFRAME_EX*FindByRangeIndex(DWORDnTheRangeIndex);//ResettransformationmatricestoRecentConfigvoidResetToRecentConfig();//ResettransformationmatricestoRecentfeatureonlyvoidResetToRecentFeature();//ResettransformationmatricestorecentfeatureandconfigurationvoidResetToRecent();//ResettransformationmatricestoOriginalConfigvoidResetToOriginalConfig();//Resettransformationmatricestooriginalfeatureonly信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/voidResetToOriginalFeature();//ResettransformationmatricestooriginalfeatureandconfigurationvoidResetToOriginal();//CountthenumberofframesinthexfilevoidCount(DWORD*Num);boolSetNewTransform(DWORDnNumMatrix,DWORD*pnMatrixIndex,D3DXMATRIX*pRootMatrix,DWORDnDummyConfigType=XSSDUMMYCONFIG_BOTH);boolApplyNewFeature(DWORDnNumMatrix,D3DXMATRIX*pRootMatrix,DWORDnDummyConfigType=XSSDUMMYCONFIG_BOTH);boolDownLoadTransform(DWORDnNumMatrix,DWORD*pnMatrixIndex,D3DXMATRIX*pRootMatrix,DWORDnDummyConfigType=XSSDUMMYCONFIG_BOTH);boolDownLoadState(DWORDnNumMatrix,D3DXMATRIX*pRootMatrix,DWORDnDummyConfigType=XSSDUMMYCONFIG_BOTH);}XSSD3DXFRAME_EX,*LPXSSD3DXFRAME_EX;从XSSD3DXFRAME_EX的定义中可以看到,我们为框架容器添加了用于查找的索引信息、表现框架层次的深度信息、记录与其他框架连接关系的指针变量以及一系列用于控制模型的变换矩阵。功能函数上添加有统计框架数量,查找特定框架,更新变换信息,设置变换信息和导出变换信息等。XSSD3DXMESHCONTAINER_EX的定义如下://DeclareanextendedversionofD3DXMESHCONTAINER//thatcontainsaconstructoranddestructor//aswellasanarrayoftextures,ameshobject//thatcontainsthegeneratedskinmesh,and//matricesthatmaptotheframehierarchy'sand//forupdatingbones.typedefstructXSSD3DXMESHCONTAINER_EX:publicD3DXMESHCONTAINER{IDirect3DTexture9**pTextures;ID3DXMesh*pSkinMesh;XSSD3DXFRAME_EX*pPackCellFrame;BOOLbIfSelected;DWORDnMeshType;DWORDnMeshRangeIndex;D3DXMATRIX**ppFrameMatrices;D3DXMATRIX*pBoneMatrices;XSSD3DXMESHCONTAINER_EX();~XSSD3DXMESHCONTAINER_EX();//FindthemeshbynameXSSD3DXMESHCONTAINER_EX*Find(char*MeshName);//FindthemeshbyMeshIndexXSSD3DXMESHCONTAINER_EX*FindByIndex(DWORDnMeshIndexToSeek);信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN///CountthenumberofMeshesinthexfilevoidCount(DWORD*Num);}XSSD3DXMESHCONTAINER_EX,*LPXSSD3DXMESHCONTAINER_EX;从XSSD3DXMESHCONTAINER_EX的定义中可以看到,我们为网格容器添加了用于查找网格的索引信息、区别网格类型的标识信息、一个指向包含该网格的框架的指针以及一组指向网格纹理信息的指针。功能函数方面添加有统计网格数量,查找特定网格等。2、定义一个.X文件解析器,完成所需的解析任务。本文介绍的解析方法中所使用的解析器CXInternalParser定义如下://Declareaninternal.XfileparserclassforloadingmeshesandframesclassCXInternalParser{public://InformationpassedfromcallingfunctionIDirect3DDevice9*m_pD3DDevice;char*m_pTexturePath;DWORDm_NewFVF;DWORDm_LoadFlags;//Flagsforwhichdatatoload//1=mesh,2=frames,3=bothDWORDm_Flags;//HierarchiesusedduringloadingXSSD3DXMESHCONTAINER_EX*m_pRootMesh;XSSD3DXFRAME_EX*m_pRootFrame;DWORDm_nNumFrameCount;DWORDm_nNumMeshCount;protected://FunctioncalledforeverytemplatefoundBOOLParseObject(ID3DXFileData*pDataObj,ID3DXFileData*pParentDataObj,DWORDDepth,void**Data,BOOLReference);//FunctioncalledtoenumeratechildtemplatesBOOLParseChildObjects(ID3DXFileData*pDataObj,DWORDDepth,void**Data,BOOLForceReference=FALSE);public://ConstructoranddestructorCXInternalParser();信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/~CXInternalParser();//Functiontostartparsingan.XfileBOOLParse(char*Filename,void**Data=NULL);//FunctionstohelpretrievetemplateinformationconstGUID*GetObjectGUID(ID3DXFileData*pDataObj);char*GetObjectName(ID3DXFileData*pDataObj);void*GetObjectData(ID3DXFileData*pDataObj,DWORD*Size);};CXInternalParser在解析.X文件时,首先定义一个用于识别.X文件中数据类型的ID3DXFileEnumObject类型的枚举对象pDXEnum,由pDXEnum统计.X文件中ID3DXFileData类型数据对象的个数;然后调用ParseObject()和ParseChildObject()功能函数依次解析每一个数据对象。在对数据对象的解析过程中,首先由GetObjectGUID()功能函数获取数据对象的GUID,此处的GUID正是.X文件开始处的模板定义中所说的全局唯一标识号(GUID:GloballyUniqueIdentificationNumber);然后根据标识号区分出所解析的数据对象由哪一种模板定义,从而将各数据对象分门别类,与此同时通过GetObjectName()和GetObjectData()功能函数将数据对象中的信息导出并记录到不同的数据容器里。当然,在解析数据对象的同时,根据应用的需求应将框架容器与网格容器中的框架索引、网格索引、层次关系等填充清楚。3.3多种模式下的模型运动控制对三维模型的控制效果最终表现在绘制出的模型的视觉表现。所以对模型的控制便在骨骼的拼接方式或关节的绘制方式上面实现。3.3.1模型运动控制原理1、标准网格模型的控制原理由上文提到的标准网格模型的存储方式可以知道,从.X文件中我们可以获取到原始的关节模型,为了拼接得到所需比例的人体模型而作用在关节上的比例变换矩阵,以及为了将各个关节移动到准确位置以得到人体模型而作用在关节上的旋转变换和平移变换矩阵。在Direct3D的网格模型绘制中,通过pDeviceIDirect3DDevice9::SetTransform(D3DTS_WORLD,&WorldMatrix)方法来设置网格绘制的位置以及绘制的方式,比如旋转多少度绘制,按什么比例绘制等。所有绘制方式的参数都体现在了4×4的变换矩阵WorldMatrix中。所以在绘制一个关节时,首先调用SetTransform()函数,以封装该关节的网格信息的框架包含的综合变换矩阵(XSSFRAME_EX中的matCombined矩阵)为参数,设置好绘制方式,然后才调用ID3DXMesh::DrawSubset()方法绘制相应的网格。由此可知,对模型的控制体现在对变换矩阵matCombined的设置上。由上文变换矩阵作用原理可知道变换矩阵matCombined信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/是本地变换矩阵和上层框架坐标系变换矩阵相乘的结果。修改本地变换矩阵便可以仅对独立关节的绘制状态进行特别设置,从而表现出对单独关节的控制。而修改上层框架的变换矩阵,则由于变换的向下传递作用,其子层的所有关节的绘制都将受到影响,表现为对相关联的一系列关节的控制。例如,如果仅仅对直接包含上臂网格的子框架中的变换矩阵做设置,那么结果表现为仅对上臂的控制;然而,如果是对同式包含有上臂、下手臂、手掌及手指的框架中的变换矩阵做设置,则结果表现为对整条手臂的控制。所以在做变换矩阵的设置时,一定要严格根据控制的目的,正确地选择设置的框架对象以及变换矩阵对象。2、蒙皮网格模型的控制原理相比于标准网格模型的控制,蒙皮网格模型的控制将采取稍有不同的方法,这是由蒙皮网格模型的绘制方式所决定的。同样的,由上文蒙皮网格的存储方式可知,从.X文件中我们可以得到原始的完整的人体网格,将人体网格分解成不同骨骼部位的骨骼变换矩阵,以及将各个骨骼重新拼接成人体模型的重组变换矩阵。在蒙皮网格的绘制过程中,首先调用ID3DXMESH::CloneMeshFVF()方法将原始人体网格做一个备份;然后将骨骼变换矩阵作用到备份的人体网格上,得到被放置在世界坐标中心位置的各块骨骼,接着将重组变换矩阵分别作用在这些骨骼上,得到一个新的人体网格整体,并将新网格数据覆盖原始的人体网格数据,这一过程由ID3DXMESH.pSkinInfor::UpdateSkinnedMesh()方法实现;最终直接调用ID3DXMesh::DrawSubset()方法,一次性将整个人体网格绘制出来。所以对模型的设置只能在重组变换矩阵上做文章。与标准网格模型的控制方式相似,在做变换矩阵的设置时,同样要严格根据控制的目的,选择好设置的框架对象以及变换矩阵对象。3、网格模型的三种控制模式、两种控制形式对网格模型的控制可以归纳为以下三种模式:个体独立模式、组合整体模式和组合联动模式。所谓个体独立模式,就是说仅对某一部位做独立的控制,而该控制不影响任何其他的部位。比如,只希望模型的上臂细小一些,而不影响下手臂和手掌等部位的特征。在标准网格模型中,要实现这个控制目的,只需找到直接包含上臂网格信息的子框架,然后对该框架中变换矩阵在垂直于上臂方向的比例分量上做一个缩减就可以做到。而在蒙皮网格模型中,则需要为这个独立变换,在网格容器中额外添加一组变换矩阵变量,并且这些矩阵变换的作用是不传给与之连接的下层网格的。这就是为什么在XSSFRAME_EX框架容器的定义中添加了三个matSeparate矩阵。组合整体模式,是指对一个部位做一个控制,则与该部位想连接的下层部位都受到相同的控制作用,从而表现出像是对整个关联部位的控制。比如,“正步走”信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/的踢腿动作中,对大腿施加一个旋转控制,则小腿及脚掌也将绕着盆骨和大腿的连接点做一相同的旋转,以此保持整条下肢处于笔直状态。不论是标准网格模型还是蒙皮网格模型的模拟控制中,我们都可以采用选择高层部位对应的框架,然后设置其中的可传递的变换矩阵,通过作用的传递性,让同样的变换作用到与之连接的下层的部位,达到一个变换作用多个部位的效果。组合联动模式,是指对一个部位做一个控制,然而这一控制不会作用到与该部位连接的下层部位中,但是受其影响,下层部位需要做相应的平移变换,使自己与受控部位保持连接关系。比如,假设一个人的手臂在上臂和下手臂的连接处脱臼了,下手臂不受人体控制处于下垂状态,那么当此人忍痛举起上臂时,下手臂便由于皮肤的连接作用,受上臂的牵动,经过一段移动后被悬吊起来,但是仍然处于下垂状态。三维模型运动仿真在这一模式下的控制可由以下方式实现:首先用组合整体模式的变换方式对控制部位做一个控制变换,然后同样用组合整体模式的变换方式对控制部位直接连接的部位做一个旋转反变换控制,将所有下层部位旋转到原来的朝向,得到的效果便像是只对一个部位做一个控制,而其他连接部位只是随之移动以保持连接关系了。在具体的变换矩阵的设置中,根据是否继承原有的变换矩阵,网格模型的控制又分为绝对控制和相对控制两种形式。所谓绝对控制形式,是指不考虑原有的变换矩阵如何,重新为变换矩阵赋值而得到新的变换矩阵,再使新矩阵作用到模型上,从而获取控制模型运动的效果。比如,当前人体模型脸部的朝向是前方右偏45°(约定正前方偏角为0°,左偏为负角度,右偏为正角度),现在希望将脸部的朝向改变为前方左偏45°,即偏角-45°,那么只需直接将变换矩阵通过数值计算重新赋为偏角为-45°时的变换矩阵即可,而不必考虑原来的脸部朝向是什么。在骨骼动画中模型的控制就是使用绝对控制的方式来实现的,即,在不同的时间点上,用.X文件中动画数据的变换矩阵,分别直接赋给相应框架的变换矩阵里,然后更新框架信息和网格,绘制出具有特定形态的新模型。而相对控制形式下,对模型的控制则要参考变换矩阵的当前值。还是以上述偏转脸部的朝向为例,当前朝向为45°,目标朝向为-45°,那么其间的转动差值便为-90°。在变换矩阵的计算过程中则采用如下的方法:首先定义一个临时矩阵,计算出-90°的旋转变换对应的矩阵保存到临时矩阵中;然后将当前的变换矩阵与临时矩阵相乘,并将相乘的结果赋给框架的变换矩阵。由此便得到了与绝对控制形式中得到的-45°的偏角对应的旋转变换相同的变换矩阵,从而达到相同的控制效果。在实时模型控制的应用中,相对控制方式就会更实用一些。3.3.2三种类型变换的作用效果和施加变换的次序规则不论是标准网格模型还是蒙皮网格模型,它们都具有一个相同的状态,即分解成零散的关节或骨骼后被置放在世界坐标系的中心位置,而且特别强调关节与上层关节,骨骼与上层骨骼的连接点几乎是与坐标原点(0,0,0)重叠在一起的,甚至连关节或骨骼的几何对称轴都近乎与坐标轴重合。这样做会有什么特别的用意呢?这就要从三种类型的变换,即比例变换、旋转变换和平移变换,的作用效果信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/及它们的作用次序规则说起了。1、三种类型变换的作用效果[3](1)平移变换图10演示了平移变换的作用效果。图104×4的正方形沿X轴平移8个单位,沿Y轴平移-8个单位要想将向量(x,y,z,1)沿X轴平移Px个单位,沿Y轴平移Py个单位或者沿Z轴平移Pz个单位,我们只需要将该向量与如下平移变换矩阵相乘:(2)旋转变换图11演示了旋转变换的作用效果。图11按我们的视角,正方形沿Z轴方向逆时针旋转60°我们可以利用如下3个矩阵讲一个向量分别绕着X,Y,Z轴旋转弧度。注:当沿着旋转轴指向原点的方向观察时,角度是按顺时针方向度量的。信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/(3)比例变换图12演示了比例变换的作用效果。图1210×10的正方形沿X轴昂想缩小1/2,沿Y轴方向放大2倍如果想让一个向量沿X,Y,Z轴分别放大Qx、Qy和Qz倍,可令该向量与如下矩阵相乘。2、施加三种类型变换的次序规则当我们从.X文件中读取框架包含的变换矩阵时,得到的是一个综合了平移变换、旋转变换和比例变换三种变换的一个组合变换矩阵。那么这个组合矩阵到底代表了怎样的平移变换、旋转变换和比例变换的作用,即,这个组合变换反应了对关节或骨骼的怎样的一种控制过程呢?弄清楚这个问题对我们控制模型时在变换矩阵的设置上会有绝对的指导意义。因为,一旦知道了某个变换让模型缩放了多少、旋转了多少或平移了多少,那么我们就可以按照我们自己需求的变化来对这些变量进行修改,从而达到按我们的意愿控制模型的目的。比如我们知道图12中的比例变换矩阵中第一行第一列的Qx表示X轴方向的缩放比例,那么我们就可以通过修改Qx值来设置模型在X轴方向上的缩放比例,从而得到一个在X轴方向的尺度改变了的模型。当然,对模型的控制远不是单纯的几个参数的设置问题,其中还联系到三种变换的作用次序,以下从对一个简单的手臂模型的控制实现方式说起,展开论述。由上文的“变换矩阵作用原理”可知,一个关节或骨骼在移动到最终位置之前首先被本地变换移动到与之连接的上一层关节或骨骼的坐标系中,变换流程举例如图13-18所示。信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图13下手臂的旋转运动示意图(红点为关节连接处、手掌有放大)图14骨骼变换将完整的手臂以具体功能部位为单元分解成三个子模型图15三个子模型在各自的坐标系中信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图16骨骼3经放大、平移与骨骼2相连,骨骼2与骨骼3的整体旋转一定角度图17骨骼2和骨骼3的组合经平移与骨骼1连接图18骨骼1、骨骼2以及骨骼3的组合旋转一定角度得到最终模型信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/以上是对于实现图13所示的控制而设计的一种可行的变换流程。那么不禁要问,可行的变换流程是否唯一呢?比如对骨骼3的控制,上述流程是先缩放后旋转,然后再平移;那么是否可以先平移或先旋转,然后再在缩放之类的呢?下面说明其他的变换顺序为何不妥。图19希望达到的控制目标:三角形与矩形连接信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图20六种可能的变换次序由上述六种可能的变换次序尝试的结果可以看出,唯独按“缩放→旋转→平移”的次序所做的变换可以达到期望达到的控制目的。最后一种,按照“旋转→缩放→平移”的次序做变换,得到了一种近似正确的结果,但仔细查看可以发现变换后的三角形变形了,这是我们在控制中不希望看到的。平移变换中平移量的多少是参考连接点与上层关节或骨骼的被连接点之间的偏移决定的。如图19中的控制中,平移量便为(0,-b)。旋转变换的作用是将控制对象绕着世界坐标的坐标轴(此处二维演示中对应为原点)旋转一定的角度。当与上层关节或骨骼的连接点和坐标原点重合时,连接点的位置便不会因为旋转变换儿改变,从而不影响平移变换的正常进行。而且,通常情况下,我们所谓的将某一关节或骨骼旋转都是说绕着连接点的旋转,所以在作旋转变换时保证连接点与坐标原点重合可以更直观地设定旋转角度的大小。对控制对象作比例缩放时,在该对象的几何对称轴与坐标轴近似重合的情况下,缩放之后仍然可以得到一个与缩放前的模型具有近似匀称特性的新模型,这是我们所期望的。而与上层关节或骨骼的连接点与原点重合,即其坐标为(或近似为)(0,0,0),这是一个特殊点:在比例缩放中是不会被移动的。这就保证了作比例缩放变换不影响到旋转变换和平移变换的正常进行。这就解释了为什么我们应该遵循“缩放→旋转→平移”的变换作用次序来准确地控制模型。3.3.34×4顶点变换矩阵的生成与解析如前所述,在实时调整控制中,相对控制方式会更实用一些。而在相对控制方式中我们是通过原有的变换矩阵与用微调量生成的矩阵(暂且简称为微调矩阵)得到新变换矩阵的形式设置框架中的变换矩阵的。那么在这一过程中就将打破上述“缩放→旋转→平移”次序的变换规则。因为原有的变换矩阵就是按照“比例变换矩阵*旋转变换矩阵*平移变换矩阵”的顺序将三个变换矩阵相乘得到的。如果再将微调矩阵与之右乘,则得到一个变换次序为“缩放1→旋转1→平移1→缩放2→旋转2→平移2”的组合变换。由上文的分析可知,“平移1”信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/所引起的连接点位置偏移势必影响到“旋转2”的准确进行,继而影响“平移2”的作用效果。然而,如果我们可以解析出原有变换的比例变换矩阵分量S1、旋转变换矩阵分量R1以及平移变换矩阵分量T1,我们便可以在根据微调量计算出相应的微调比例变换矩阵S2、微调旋转变换矩阵R2以及微调平移变换矩阵T2之后,以“S1*S2*R1*R2*T1*T2”的次序将这六个矩阵相乘,那么我们仍然可以得到一个次序为“缩放→旋转→平移”的新的变换矩阵。这样又实现了各个变换的微调,又保证了每个变换都能够正常地进行。问题转移到如何解析一个组合变换矩阵上。一般的顶点变换矩阵普遍有如下形式(约定旋转变换的作用次序为:绕X轴旋转→绕Y轴旋转→绕Z轴旋转):其中a11=;a12=;a13=;a21=;a22=;a23=;a31=;a32=;a33=;从组合变换矩阵A我们首先可以很容易的求得平移变换矩阵T。取矩阵A中的a41、a42、a43元素分别添加到4×4的单位矩阵第四行的一、二、三列便可以得到平移变换矩阵。将矩阵A中的a41、a42、a43元素置零,则得到一个仅含有比例变换作用和旋转变换作用的组合变换矩阵。比例变换矩阵的解析可以通过以下方式计算得到。首先约定:沿X轴方向上允许缩放比例为负值,缩放比例绝对值不小于0.。沿Y轴和Z轴方向上缩放比例必须大于0.。这个规定在实用层面上是合理的。(之所以在沿X轴方向上允许负值缩放比例,是因为原想着对模型做负值缩放可以得到一个“照镜子”的正反效果。然而,由于三角面是具有方向性的,做负值缩放后三角面做了一次翻转,导致不可见,从而未能达到预期的效果。)信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/我们知道,旋转变换对模型的控制是保形的,即它并不改变控制对象的形态结构,而只是将其方向做一些调整;而比例变换矩阵则会对控制对象的形态结构产生影响,具体表现在改变了模型中点与点之间的举例。那么我们就可以从这两种变换作用效果的差别上入手将比例变换矩阵和旋转变换矩阵区分开来。不妨在世界坐标系中取下面四个特殊而又极具代表性的顶点为变换的作用对象:(1,1,1)、(1,1,-1)、(1,-1,1)和(-1,1,1),计算过程如图21所示。图21比例变换矩阵的解析过程示意图从图21我们可以知道,只要计算得到图中点A、B、C、D经变换后的坐标,然后求出新的向量、、,分别用这三个向量的模,除以原向量、、的模“2”,便得到了X轴、Y轴和Z轴方向的缩放比例Sx、Sy、Sz。上文提到,我们允许X轴方向上的缩放比例为负值,那么我们可以用如下方法来判断实际变换中X方向上的缩放比例到底是否为负值。显然,如果我们知道图21中,右上方子图里由经过比例变换后得到的与反向,那么X轴方向上的缩放比例便为负值,同向则说明为正值。然而由于比例变换和旋转变换的组合变换矩阵是一次性作用在模型上的,即我们只能从图21中的变换前的状态直接跳到最终变换状态,而无法获取中间那个只经过比例变换的状态。并且,从图中可以明显的看到,最终状态下的向量显然是与向量既不同向也不反向的。这就意味着我们无法通过最终状态下获得的来帮助我们判断X方向上的缩放比例的正负符号问题。不过此时向量间叉乘的特性给了我们解决问题的突破口。由于我们选取的变换试验顶点具有一定的特殊性,即由此得到的、、在方向上满足信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/的方向与×的方向相同;而且X轴方向上的比例缩放只会影响的模和方向,Y轴方向上的比例缩放只会影响的模和方向,Z轴方向上的比例缩放只会影响的模和方向。由于我们约定在Y轴、Z轴方向上的缩放比例必须大于0.,而且之后的旋转变换是可以保持原有顶点相对位置关系的,这就意味着如果将向量、和看作一个在方向上互相关联的整体,那么在Y轴和Z轴方向上不论做怎样的正值的比例缩放,通过和的叉乘我们仍然可以计算得到X轴方向上不做比例缩放时的方向。由于X轴方向上的比例缩放只会影响的模和方向,当X轴方向上的缩放比例为正值时,该变换只会改变向量的模的大小,此时的方向便和由得到的向量的方向相同;当X轴方向上的缩放比例为负值时,该变换不仅改变向量的长度,而且会将其方向置为反向,从而的方向便和由得到的向量的方向相反了。综合上述内容,我们只需判断经变换之后得到是否与得到的向量方向相同便可以得出在X轴方向上缩放比例是否为负值,且同向为正,反向为负。在解析出X、Y、Z轴方向上的缩放比例Sx、Sy、Sz后我们便可以通过这三个数值一次替换单位矩阵的a11、a22、a33便可以得到组合变换矩阵中所包含的比例变换矩阵了。将比例变换矩阵的逆矩阵,左乘比例变换和旋转变换的组合变换矩阵,便得到了旋转变换矩阵。比例变换矩阵和旋转变换矩阵解析过程的代码实现如以下程序清单所示。//ScalingD3DXMATRIXmatTempPartInput;matTempPartInput=*pmatInput;matTempPartInput._41=0.0f;matTempPartInput._42=0.0f;matTempPartInput._43=0.0f;D3DXVECTOR3vertexPreferOne(1.0f,1.0f,1.0f);D3DXVECTOR3vertexPreferTwo(-1.0f,1.0f,1.0f);D3DXVECTOR3vertexPreferThree(1.0f,-1.0f,1.0f);D3DXVECTOR3vertexPreferFour(1.0f,1.0f,-1.0f);D3DXVECTOR3vectorPreferX;D3DXVECTOR3vectorPreferY;D3DXVECTOR3vectorPreferZ;信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/D3DXVECTOR3vectorPreferTempX;D3DXVec3TransformCoord(&vertexPreferOne,&vertexPreferOne,&matTempPartInput);D3DXVec3TransformCoord(&vertexPreferTwo,&vertexPreferTwo,&matTempPartInput);D3DXVec3TransformCoord(&vertexPreferThree,&vertexPreferThree,&matTempPartInput);D3DXVec3TransformCoord(&vertexPreferFour,&vertexPreferFour,&matTempPartInput);vectorPreferX=vertexPreferOne-vertexPreferTwo;vectorPreferY=vertexPreferOne-vertexPreferThree;vectorPreferZ=vertexPreferOne-vertexPreferFour;D3DXVec3Cross(&vectorPreferTempX,&vectorPreferY,&vectorPreferZ);if(D3DXVec3Dot(&vectorPreferTempX,&vectorPreferX)<0.0f)pmatOutputS->_11=-D3DXVec3Length(&vectorPreferX)/2.0f;elsepmatOutputS->_11=D3DXVec3Length(&vectorPreferX)/2.0f;pmatOutputS->_22=D3DXVec3Length(&vectorPreferY)/2.0f;pmatOutputS->_33=D3DXVec3Length(&vectorPreferZ)/2.0f;//RotationD3DXMATRIXmatTempInverseS;D3DXMatrixInverse(&matTempInverseS,0,pmatOutputS);*pmatOutputR=matTempInverseS*matTempPartInput;returnS_OK;在得到了旋转变换矩阵之后,我们甚至还可以通过数学的方法分别将绕X轴、Y轴、和Z轴方向旋转的角度求解出来,当然由于不同的角度组合可以达到同样的综合旋转的效果,即,一个旋转变换矩阵可能对应多种的旋转变换过程,因此我们所求得的只是其中的一种合理值。求解过程中主要采用了分类讨论的思想。具体实现过程可由以下代码来表达://由旋转变换矩阵计算绕X、Y、Z轴方向的旋转变换矩阵boolCXSSTMatrixParser::matrixRToMatrixRx_y_z(){D3DXMatrixIdentity(&m_matRotateX);D3DXMatrixIdentity(&m_matRotateY);D3DXMatrixIdentity(&m_matRotateZ);floatRxs,Rxc,Rys,Ryc,Rzs,Rzc;Rxs=Rys=Rzs=0.0f;Rxc=Ryc=Rzc=1.0f;if(ISZERO(m_matRotateXYZ._11)&&ISZERO(m_matRotateXYZ._12)&&ISFABSONE(m_matRotateXYZ._13)\&&ISZERO(m_matRotateXYZ._23)&&ISZERO(m_matRotateXYZ._33))信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/{//A11=0,A12=0,|A13|=1,A23=0,A33=0,Ryc=0.0f;if(ISONE(m_matRotateXYZ._13)){//A13=1.0Rys=-1.0f;if(ISPOWSUMONE(m_matRotateXYZ._21,m_matRotateXYZ._22)&&ISEQUAL(m_matRotateXYZ._21,m_matRotateXYZ._32)&&ISEQUAL(m_matRotateXYZ._22,(-m_matRotateXYZ._31))){//sin(x+z)=-A21,cos(x+z)=A22Rxs=-m_matRotateXYZ._21;Rxc=m_matRotateXYZ._22;Rzs=0.0f;Rzc=1.0f;}elsereturnfalse;}elseif(ISMINUSONE(m_matRotateXYZ._13)){//A13=-1.0Rys=1.0f;if(ISPOWSUMONE(m_matRotateXYZ._21,m_matRotateXYZ._22)&&ISEQUAL(m_matRotateXYZ._21,(-m_matRotateXYZ._32))&&ISEQUAL(m_matRotateXYZ._22,m_matRotateXYZ._31)){//sin(x-z)=A21;cos(x-z)=A22Rxs=m_matRotateXYZ._21;Rxc=m_matRotateXYZ._22;Rzs=0.0f;Rzc=1.0f;}elsereturnfalse;}elsereturnfalse;}elseif(ISZERO(m_matRotateXYZ._11)&&ISFABSONE(m_matRotateXYZ._12)&&ISZERO(m_matRotateXYZ._13)\&&ISZERO(m_matRotateXYZ._22)&&ISZERO(m_matRotateXYZ._32)){//A11=0,|A12|=1,A13=0,A22=0,A32=0Rys=0.0f;Rzc=0.0f;if(ISONE(m_matRotateXYZ._12)){//cos(y)*sin(z)=1.0fRyc=1.0f;Rzs=1.0f;if(ISPOWSUMONE(m_matRotateXYZ._21,m_matRotateXYZ._23)&&ISEQUAL(m_matRotateXYZ._21,(-m_matRotateXYZ._33))&&ISEQUAL(m_matRotateXYZ._23,m_matRotateXYZ._31)){Rxc=-m_matRotateXYZ._21;Rxs=m_matRotateXYZ._23;}elsereturnfalse;}elseif(ISMINUSONE(m_matRotateXYZ._12))信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/{//cos(y)*sin(z)=-1.0fRyc=1.0f;Rzs=-1.0f;if(ISPOWSUMONE(m_matRotateXYZ._21,m_matRotateXYZ._23)&&ISEQUAL(m_matRotateXYZ._21,m_matRotateXYZ._33)&&ISEQUAL(m_matRotateXYZ._23,(-m_matRotateXYZ._31))){Rxc=m_matRotateXYZ._21;Rxs=m_matRotateXYZ._23;}}elsereturnfalse;}elseif(ISZERO(m_matRotateXYZ._11)&&ISFABSSMALLERTHANONE(m_matRotateXYZ._12)&&ISFABSSMALLERTHANONE(m_matRotateXYZ._13)){//A11=0,A12!=0,A13!=0Rzc=0.0f;Rys=-m_matRotateXYZ._13;Rzs=1.0f;//令¢?sin(z)=1.0fRyc=m_matRotateXYZ._12;if(!ISPOWSUMONE(Rys,Ryc))returnfalse;if(ISPOWSUMONE(m_matRotateXYZ._21,m_matRotateXYZ._31)\&&ISEQUAL((-m_matRotateXYZ._21),(m_matRotateXYZ._33/Ryc))&&ISEQUAL((-m_matRotateXYZ._21),(m_matRotateXYZ._32/Rys))\&&ISEQUAL(m_matRotateXYZ._31,(m_matRotateXYZ._23/Ryc))&&ISEQUAL(m_matRotateXYZ._31,(m_matRotateXYZ._22/Rys))){Rxc=-m_matRotateXYZ._21;Rxc=m_matRotateXYZ._31;}elsereturnfalse;}elseif(ISFABSONE(m_matRotateXYZ._11)&&ISZERO(m_matRotateXYZ._12)&&ISZERO(m_matRotateXYZ._13)\&&ISZERO(m_matRotateXYZ._21)&&ISZERO(m_matRotateXYZ._31)){//A11!=0,A12=0,A13=0if(ISONE(m_matRotateXYZ._11)){Ryc=1.0f;Rzc=1.0f;if(ISPOWSUMONE(m_matRotateXYZ._22,m_matRotateXYZ._23)&&ISEQUAL(m_matRotateXYZ._22,m_matRotateXYZ._33)&&ISEQUAL(m_matRotateXYZ._23,(-m_matRotateXYZ._32))){Rxs=m_matRotateXYZ._23;Rxc=m_matRotateXYZ._33;信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/}elsereturnfalse;}elseif(ISMINUSONE(m_matRotateXYZ._11)){Ryc=1.0f;Rzc=-1.0f;if(ISPOWSUMONE(m_matRotateXYZ._22,m_matRotateXYZ._23)&&ISEQUAL(m_matRotateXYZ._22,(-m_matRotateXYZ._33))&&ISEQUAL(m_matRotateXYZ._23,m_matRotateXYZ._32)){Rxs=m_matRotateXYZ._23;Rxc=m_matRotateXYZ._33;}elsereturnfalse;}elsereturnfalse;}elseif(ISFABSSMALLERTHANONE(m_matRotateXYZ._11)&&ISZERO(m_matRotateXYZ._12)&&ISFABSSMALLERTHANONE(m_matRotateXYZ._13)){//A11!=0,A12=0,A13!=0Rzs=0.0f;Rys=-m_matRotateXYZ._13;Rzc=1.0f;//令¢?Rzc=1.0fRyc=m_matRotateXYZ._11;if(!ISPOWSUMONE(Rys,Ryc))returnfalse;if(ISEQUAL((m_matRotateXYZ._21/Rys),(-m_matRotateXYZ._32))&&ISEQUAL((m_matRotateXYZ._23/Ryc),(-m_matRotateXYZ._32))\&&ISEQUAL((m_matRotateXYZ._31/Rys),m_matRotateXYZ._22)&&ISEQUAL((m_matRotateXYZ._33/Ryc),m_matRotateXYZ._22)\&&ISPOWSUMONE((-m_matRotateXYZ._32),m_matRotateXYZ._22)){Rxc=m_matRotateXYZ._22;Rxs=-m_matRotateXYZ._32;}elsereturnfalse;}elseif(ISFABSSMALLERTHANONE(m_matRotateXYZ._11)&&ISFABSSMALLERTHANONE(m_matRotateXYZ._12)&&ISZERO(m_matRotateXYZ._13)){//A11!=0,A12!=0,A13=0Rys=0.0f;Ryc=1.0f;//令¢?Ryc=1.0fif(ISPOWSUMONE(m_matRotateXYZ._11,m_matRotateXYZ._12)&&ISPOWSUMONE(m_matRotateXYZ._23,m_matRotateXYZ._33)\&&ISEQUAL(m_matRotateXYZ._21,(-m_matRotateXYZ._33*m_matRotateXYZ._12))\信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/&&ISEQUAL(m_matRotateXYZ._22,m_matRotateXYZ._33*m_matRotateXYZ._11)\&&ISEQUAL(m_matRotateXYZ._31,m_matRotateXYZ._23*m_matRotateXYZ._12)\&&ISEQUAL(m_matRotateXYZ._32,(-m_matRotateXYZ._23*m_matRotateXYZ._11))){Rzc=m_matRotateXYZ._11;Rzs=m_matRotateXYZ._12;Rxs=m_matRotateXYZ._23;Rxc=m_matRotateXYZ._33;}elsereturnfalse;}elseif(ISFABSSMALLERTHANONE(m_matRotateXYZ._11)&&ISFABSSMALLERTHANONE(m_matRotateXYZ._12)&&ISFABSSMALLERTHANONE(m_matRotateXYZ._13)){//A11!=0,A12!=0,A13!=0Rys=-m_matRotateXYZ._13;Ryc=sqrt(1.0f-pow(Rys,2.0f));Rxs=m_matRotateXYZ._23/Ryc;Rxc=m_matRotateXYZ._33/Ryc;Rzs=m_matRotateXYZ._12/Ryc;Rzc=m_matRotateXYZ._11/Ryc;floatA21,A22,A31,A32;A21=Rxs*Rys*Rzc-Rxc*Rzs;A22=Rxs*Rys*Rzs+Rxc*Rzc;A31=Rxc*Rys*Rzc+Rxs*Rzs;A32=Rzs*Rxc*Rys-Rxs*Rzc;if(!(ISPOWSUMONE(Rxs,Rxc)&&ISPOWSUMONE(Rzs,Rzc)\&&ISEQUAL(m_matRotateXYZ._21,A21)\&&ISEQUAL(m_matRotateXYZ._22,A22)\&&ISEQUAL(m_matRotateXYZ._31,A31)\&&ISEQUAL(m_matRotateXYZ._32,A32))){Ryc=-sqrt(1.0f-pow(Rys,2.0f));Rxs=m_matRotateXYZ._23/Ryc;Rxc=m_matRotateXYZ._33/Ryc;Rzs=m_matRotateXYZ._12/Ryc;Rzc=m_matRotateXYZ._11/Ryc;A21=Rxs*Rys*Rzc-Rxc*Rzs;A22=Rxs*Rys*Rzs+Rxc*Rzc;A31=Rxc*Rys*Rzc+Rxs*Rzs;A32=Rzs*Rxc*Rys-Rxs*Rzc;if(!(ISPOWSUMONE(Rxs,Rxc)&&ISPOWSUMONE(Rzs,Rzc)\&&ISEQUAL(m_matRotateXYZ._21,A21)\&&ISEQUAL(m_matRotateXYZ._22,A22)\&&ISEQUAL(m_matRotateXYZ._31,A31)\&&ISEQUAL(m_matRotateXYZ._32,A32)))returnfalse;}}elsereturnfalse;m_matRotateX._22=Rxc;m_matRotateX._23=Rxs;m_matRotateX._32=-Rxs;m_matRotateX._33=Rxc;信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/m_matRotateY._11=Ryc;m_matRotateY._13=-Rys;m_matRotateY._21=Rys;m_matRotateY._33=Ryc;m_matRotateZ._11=Rzc;m_matRotateZ._12=Rzs;m_matRotateZ._21=-Rzs;m_matRotateZ._22=Rzc;returntrue;}信息科学与技术学院电子工程与信息科学系51(EEIS)http://www.ustc.edu.cn/zh_CN///由绕X轴的旋转变换矩阵计算绕X轴的旋转角度voidCXSSTMatrixParser::matrixRxToRx(){floatfarccos,farcsin;farccos=acos(m_matRotateX._22);farcsin=asin(m_matRotateX._23);if(m_matRotateX._23>=0.0f)m_fRx=farccos;else{if(m_matRotateX._22>=0.0f)m_fRx=farcsin;elsem_fRx=-(farcsin+D3DX_PI);}}//由绕Y轴的旋转变换矩阵计算绕Y轴的旋转角度voidCXSSTMatrixParser::matrixRyToRy(){floatfarccos,farcsin;farccos=acos(m_matRotateY._11);farcsin=asin(m_matRotateY._31);if(m_matRotateY._31>=0.0f)m_fRy=farccos;else{if(m_matRotateY._11>=0.0f)m_fRy=farcsin;elsem_fRy=-(farcsin+D3DX_PI);}}信息科学与技术学院电子工程与信息科学系51(EEIS)http://www.ustc.edu.cn/zh_CN///由绕Z轴的旋转变换矩阵计算绕Z轴的旋转角度voidCXSSTMatrixParser::matrixRzToRz(){floatfarccos,farcsin;farccos=acos(m_matRotateZ._11);farcsin=asin(m_matRotateZ._12);if(m_matRotateZ._12>=0.0f)m_fRz=farccos;else{if(m_matRotateZ._11>=0.0f)m_fRz=farcsin;elsem_fRz=-(farcsin+D3DX_PI);}}信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/1.运动仿真控制应用程序设计基于DirectXSDK开发的三维人体模型运动仿真应用程序需要专门的编程环境。结合上述三维模型的控制原理,程序中应着重模拟出各个关节或骨骼的控制过程中控制信息的传递关系;同时,要为修改模型的变换矩阵设计一套合适的控制数据输入方案。具体实现见下文所述。4.1编程环境的配置1、安装相关软件(1)安装VisualStudio2005/2008/2010,默认设置为VisualC++工作模式。(2)安装MicrosoftDirectXSDK(June2010)。2、配置编程环境(1)打开VisualStudio2005/2008/2010,“工具(tools)”->“选项(options)”->“项目和解决方案”->“VC++目录”->“包含文件”添加“C:\ProgramFiles\MicrosoftDirectXSDK(June2010)\Include”项->“库文件”添加“C:\ProgramFiles\MicrosoftDirectXSDK(June2010)\Lib\x86”项->“确定”。(注:VisualStudio2010中该配置在新建项目之后,选择“项目”->“XXX属性”->“VC++目录”中设置)(2)新建一个项目:“文件”->“新建”->“项目”->“VisualC++”->(MFC应用程序)->“完成”。配置连接库:“项目”->“XXX属性”->“连接器”->“输入”->“附加依赖项”输入“d3d9.libd3dx9.libd3dxof.libdxguid.libwinmm.lib”->“确定”。(也可以通过一如下语句来添加附加依赖项链接库:“#pragmacomment(lib,“d3d9.lib”)”)。(3)为避免如下错误:“1>c:\users\asus\documents\visualstudio2008\projects\fb11\fb11\main.cpp(25):errorC2664:“CreateWindowExW”:不能将参数2从“constchar[7]”转换为“LPCWSTR”1>与指向的类型无关;转换要求reinterpret_cast、C样式转换或函数样式转换”需对项目作以下设置:“项目”->“XXX属性”->“常规”->“字符集”选择“未设置”或“使用多字节字符集”。4.2程序设计框图本文所介绍的“模型运动仿真控制”应用程序由结合Direct3D和MFC应用程序框架编写而成。从程序的整体架构上具有下图所示的结构关系:信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/图22D3DDummyEditor应用程序结构框图D3DDummyEditorView类是以CView类和CXSSDirect3DBasic类为基类派生得到的视图类。其中CXSSDirect3DBasic类由作者自定义,里面包含有Direct3D相关的变量和初始化Direct3D之类的功能函数。D3DDummyEditorDoc类是以CDocument类和CXSSSceneContainer类为基类派生得到的文档类。其中CXXSceneContainer类由作者自定义,里面包含有用于存储和管理场景中模型的相关变量以及功能函数。UtilityParts模块中包含有程序的核心功能函数,如矩阵解析函数、.X文件解析函数以及模型控制实现函数等。根据各功能函数间的差异和联系,它们都被分别封装不同的类中,如CXSSMatrixParser,CXSSDummyContainer,CXSSSceneContainer等。DummyEditRelatedDialog是为用户控制模型而定义的应用程序交互窗口。这些对话框以非模态对话框为主,根据用户的指示,调用UtilityParts中相应的功能函数实现对模型的编辑和控制。程序运行时,首先将程序中唯一的D3DDummyEditorDoc的指针传递给各个模型控制对话窗口。当用户在对话框中选择不同的功能时,非模态对话框首先向视图类发送特定的窗口消息,然后再视图类中激活对话框里相应的处理函数。而这些处理函数则通过D3DDummyEditorDoc的指针,直接调用CD3DDummyEditorDoc中包含的功能函数,并根据用户设定的变量向相关功能函数传入所需的参数。函数调用成功,则用户控制模型的目的也就达成了。信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/4.3创建内嵌Direct3D的MFC应用程序项目本文开篇提到如何解决“结合Diret3D与MFC的应用程序设计”问题。下面参照FrankD.Luna[美]的《IntegratingDirect3D9.0WithMFC》[4]一文,简要解说一下“结合Diret3D与MFC的应用程序设计”的核心步骤。当然,首先我们需要按照上述“配置编程环境”中所介绍的方法将DirectXSDK中的函数库包含到MFC程序框架中。1.通过令MFC应用程序的专用视图类(基类为CView)包含Direct3D相关的接口,将Direct3D与MFC结合起来。比如,让视图类中包含一个IDirect3DDevice9*_device指针变量,用于指向一个Direct3D设备,以便在视图的绘制过程中使用该设备。这样做之所以有意义,是应为CDirect3D9MFCView负责绘制数据,它需要对Direct3D的直接访问来绘制3D模型数据。然后在视图类中添加如下功能函数:private:HRESULTinitD3D(HWNDhwnd,intwidth,intheight,boolwindowed,3DDEVTYPEdeviceType);HRESULTsetup(intwidth,intheight);HRESULTcleanup();public:HRESULTupdate(floattimeDelta);HRESULTrender();其中函数initD3D负责Direct3D的初始化,即,获取一个指向IDirect3DDevice9接口的指针。该IDirect3DDevice9接口基于输入的各个参数配置;setUp()函数负责处理程序中仅与Direct3D相关的代码。其中包括:分配Direct3D资源、确认设备性能以及设置设备状态;cleanup函数用于清除在调用IDirect3DDevice9::Reset函数前需要释放的所有资源。值得注意的是,在调用IDirect3DDevice9::Reset函数前只有一些特定的资源是需要释放的,比如在D3DPOOL_DEFAULT内存池中的那些资源。update函数中,我们可以运行一些用于帧的更迭过程中处理信息的操作,例如,动画,碰撞检测,测试用户输入等。需要注意的是,帧与帧之间的时间差将作为函数的参数引入到函数中去。这样您就可以让各个操作基于时间同步起来。在render函数中,我们运行一些与绘制相关的代码。值得注意的是,在初始化Direct3D时我们需要一个有效的绘制窗口的句柄,因此Direct3D的初始化应该在该句柄可用的地方,比如CDirect3D9MFCView::OnInitialUpdate函数中。2.在处理WM_SIZE消息时,更新Direct3D中与窗口尺寸紧密相关的成员,比如缓存、视口和投影矩阵等。3.处理WM_ERASEBKGND消息,阻止MFC在窗口被重绘时更新背景。BOOLCDirect3D9MFCView::OnEraseBkgnd(CDC*pDC){returnFALSE;信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/}1.重载CWinApp::OnIdle函数,将您要执行的代码嵌入其中,比如更新和绘制3D场景。1.4主要的数据结构及功能函数1、用于保存和控制单个模型对象的CXSSDummyContainer类:classCXSSDummyContainer{public:CXSSDummyContainer(void);~CXSSDummyContainer(void);public:CStringm_strDummyFilePathName;CStringm_strDummyFilePath;CStringm_strDummyFileName;CStringm_strDummyName;DWORDm_nNumFrameCount;DWORDm_nNumMeshCount;D3DMATERIAL9m_vTempMaterial;structXSSD3DXMESHCONTAINER_EX*m_pMesh;structXSSD3DXFRAME_EX*m_pFrame;//保À¡ê留¢?位?置?:êostructXSSD3DXANIMATIONQUEUE*m_pAnimationboolm_bIfUpdateData;boolm_bIfUpdateMesh;boolm_bIfSelected;boolm_bIfUseMarkedMode;DWORDm_nDummyIndex;DWORDm_nSelectedFrameIndex;DWORDm_nAsMAinFrameIndex;//保À¡ê留¢?位?置?:êoDWORDm_nRecentAnimationIndexpublic:boolDisplay();boolLoadXFile(IDirect3DDevice9*pD3DDevice,CString*pstrFileName=NULL,CString*pstrTexturePath=NULL);boolLoadXFile(IDirect3DDevice9*pD3DDevice,char*pszFileName=NULL,char*pTexturePath=NULL);voidSetIfUseMarkedMode(boolbIfUseMarkedMode);HRESULTControlDummyMain(DWORDnFrameIndex,D3DXMATRIX*pmatScaling,D3DXMATRIX*pmatRotate,D3DXMATRIX*pmatTranslate,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);HRESULTControlDummyMain(CString*pstrFrameName,D3DXMATRIX*pmatScaling,信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/D3DXMATRIX*pmatRotate,D3DXMATRIX*pmatTranslate,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);HRESULTControlDummyMain(structXSSD3DXFRAME_EX*pFrameToModify,D3DXMATRIX*pmatScaling,D3DXMATRIX*pmatRotate,D3DXMATRIX*pmatTranslate,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);HRESULTControlDummy(DWORDnFrameIndex,D3DXVECTOR3*pvecScaling,D3DXVECTOR3*pvecRotate,D3DXVECTOR3*pvecTranslate,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);HRESULTControlDummy(DWORDnFrameIndex,D3DXMATRIX*pmatScaling,D3DXMATRIX*pmatRotate,D3DXMATRIX*pmatTranslate,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);HRESULTControlDummy(DWORDnFrameIndex,D3DXMATRIX*pmatTransform,DWORDnTranformTypeFlag=XSSTSFT_COMBINED,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);//0:combined;1:Scaling;2:Rotate;3:TranslateHRESULTControlDummy(DWORDnFrameIndex,D3DXVECTOR3*pvecTransform,DWORDnTransformType=XSSTSFT_COMBINED,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);//0:combined;1:Scaling;2:Rotate;3:TranslateHRESULTControlDummy(CString*pstrFrameName,D3DXVECTOR3*pvecScaling,D3DXVECTOR3*pvecRotate,D3DXVECTOR3*pvecTranslate,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);HRESULTControlDummy(CString*pstrFrameName,D3DXMATRIX*pmatScaling,D3DXMATRIX*pmatRotate,D3DXMATRIX*pmatTranslate,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);HRESULTControlDummy(CString*pstrFrameName,D3DXMATRIX*pmatTransform,DWORDnTranformTypeFlag=XSSTSFT_COMBINED,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);//0:combined;1:Scaling;2:Rotate;3:TranslateHRESULTControlDummy(CString*pstrFrameName,D3DXVECTOR3*pvecTransform,DWORDnTransformType=XSSTSFT_COMBINED,DWORDnTransformMode=XSSTSFM_RELATIVE,DWORDnTransformEffectMode=XSSTSFEM_ENTIRE,DWORDnTransformPurpose=XSSTSFP_CONTROL);//0:combined;1:Scaling;2:Rotate;3:Translate};2、用于构成模型对象队列的XSSDummyQueueNode结构体:typedefstructXSSDummyQueueNode{CXSSDummyContainer*pDummyContainer;XSSDummyQueueNode*pNextDummyNode;XSSDummyQueueNode*pPreDummyNode;信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/XSSDummyQueueNode(){pDummyContainer=NULL;pNextDummyNode=NULL;pPreDummyNode=NULL;}}XSSDummyQueueNode;3、用于保存和控制整个场景中模型的CXSSSceneContainer类:classCXSSSceneContainer{public:CXSSSceneContainer(void);~CXSSSceneContainer(void);public:CStringm_strSceneFilePathName;CStringm_strSceneFilePath;CStringm_strSceneFileName;CStringm_strSceneName;DWORDm_nKeyControlEffectMode;floatm_fRotateSpeed;floatm_fMoveSpeed;DWORDm_nNumDummyCount;DWORDm_nRecentDummyIndex;XSSDummyQueueNode*m_pDummyQueue;public:boolInitialSceneContainer();DWORDFindDummyIndexByName(CString*pstrDummyFilePathName);XSSDummyQueueNode*FindNodeByName(CString*pstrDummyFilePathName);XSSDummyQueueNode*FindNodeByIndex(DWORDnDummyIndexToFind);HRESULTAddDummy(IDirect3DDevice9*pD3DDevice,CString*pstrDummyFilePathName,CString*pstrDummyFilePath);HRESULTRemoveDummy(XSSDummyQueueNode*pDummyNodeToRemove);HRESULTRemoveDummy(CString*pstrDummyFilePathName);HRESULTRemoveDummy(DWORDnDummyIndexToRemove);HRESULTSelectDummy(DWORDnDummyIndexToSelect,boolbIfMultiSelect=false);HRESULTSelectDummy(CString*pstrDummyFilePathName,boolbIfMultiSelect=false);HRESULTSetControlPart(DWORDnDummyIndexToHold,DWORDnFrameIndex);HRESULTSetControlPart(CString*pstrDummyFilePathName,DWORDnFrameIndex);HRESULTSetControlMain(DWORDnDummyIndexToHold,DWORDnAsMainFrameIndex);信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/HRESULTSetControlMain(CString*pstrDummyFilePathName,DWORDnAsMainFrameIndex);voidSetIfUseMarkedMode(DWORDnDummyIndexToHold,boolbIfUseMarkedMode);HRESULTRentDummy();HRESULTControlDummyByKey(intnKeyCode,floatfDeltaTime);};4、变换矩阵解析器CXSSTMatrixParser类:classCXSSTMatrixParser{public:CXSSTMatrixParser(void);~CXSSTMatrixParser(void);public:floatm_fSx,m_fSy,m_fSz;floatm_fRx,m_fRy,m_fRz;floatm_fTx,m_fTy,m_fTz;D3DXMATRIXm_matScaling;D3DXMATRIXm_matRotateX;D3DXMATRIXm_matRotateY;D3DXMATRIXm_matRotateZ;D3DXMATRIXm_matRotateXYZ;D3DXMATRIXm_matTranslate;D3DXMATRIXm_matTransformSRT;public:voidparseMatrixS_R_T(D3DXMATRIX*pmatSInput,D3DXMATRIX*pmatRInput,D3DXMATRIX*pmatTInput);voidparseMatrixSRT(D3DXMATRIX*pmatSRTInput);voidparseDirect(floatfSxInput,floatfSyInput,floatfSzInput,floatfRxInput,floatfRyInput,floatfRzInput,floatfTxInput,floatfTyInput,floatfTzInput);voidparseDirect(D3DXVECTOR3*pvScalingInput,D3DXVECTOR3*pvRotateInput,D3DXVECTOR3*pvTranslateInput);public:voiddirectToMatrixS_R_T();voidmatrixS_R_TToMatrixSRT();voidmatrixS_R_TToDirect();boolmatrixRToMatrixRx_y_z();voidmatrixRxToRx();voidmatrixRyToRy();voidmatrixRzToRz();voidmatrixSRTToS_R_T();HRESULTParseTransform(D3DXMATRIX*pmatInput=NULL,D3DXMATRIX*pmatOutputS=NULL,D3DXMATRIX*pmatOutputR=NULL,D3DXMATRIX*pmatOutputT=NULL);D3DXVECTOR3getResultVectorS(D3DXVECTOR3*pVectorOutput=NULL);D3DXVECTOR3getResultVectorR(D3DXVECTOR3*pVectorOutput=NULL);信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/D3DXVECTOR3getResultVectorT(D3DXVECTOR3*pVectorOutput=NULL);D3DXMATRIXgetResultMatrixS(D3DXMATRIX*pMatrixOutput=NULL);D3DXMATRIXgetResultMatrixRX(D3DXMATRIX*pMatrixOutput=NULL);D3DXMATRIXgetResultMatrixRY(D3DXMATRIX*pMatrixOutput=NULL);D3DXMATRIXgetResultMatrixRZ(D3DXMATRIX*pMatrixOutput=NULL);D3DXMATRIXgetResultMatrixRXYZ(D3DXMATRIX*pMatrixOutput=NULL);D3DXMATRIXgetResultMatrixT(D3DXMATRIX*pMatrixOutput=NULL);D3DXMATRIXgetResultMatrixSRT(D3DXMATRIX*pMatrixOutput=NULL);};5、用于辅助“结合Direct3D与MFC应用程序设计”的CXXDirect3DBasic类classCXSSDirect3DBasic{public:CXSSDirect3DBasic(void);virtual~CXSSDirect3DBasic(void);boolInitializeD3D(HWNDvMainWindowHandle,intnWidth,intnHeight,boolbWindowed);virtualboolSetUpScene(void)=0;virtualboolResizeWindow(void)=0;virtualboolUpdateSetUp()=0;virtualboolRender(void)=0;public:IDirect3DDevice9*m_pD3DDevice;D3DDEVTYPEm_vDeviceType;floatm_fDeltaTime;CXSSD3DCameram_vTheCamera;CXSSD3DPickRaym_vPickRay;CXSSD3DBasicScenem_vBasicScene;};6、其他功能函数,载入网格、更新网格以及绘制网格的实现函数等://LoadavertexshaderHRESULTLoadVertexShader(IDirect3DVertexShader9**ppShader,IDirect3DDevice9*pDevice,char*Filename,D3DVERTEXELEMENT9*pElements=NULL,IDirect3DVertexDeclaration9**ppDecl=NULL);//Loadasinglemeshfroman.Xfile(compactmultiplemeshesintoone)HRESULTLoadMesh(XSSD3DXMESHCONTAINER_EX**ppMesh,IDirect3DDevice9*pDevice,char*Filename,char*TexturePath=".\\",DWORDNewFVF=0,DWORDLoadFlags=D3DXMESH_SYSTEMMEM);信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN///Loadasinglemesh(regularorskinned)fromameshtemplateHRESULTLoadMesh(XSSD3DXMESHCONTAINER_EX**ppMesh,IDirect3DDevice9*pDevice,ID3DXFileData*pDataObj,char*TexturePath=".\\",DWORDNewFVF=0,DWORDLoadFlags=D3DXMESH_SYSTEMMEM);//Loadallmeshesandframesfroman.XfileHRESULTLoadMesh(XSSD3DXMESHCONTAINER_EX**ppMesh,XSSD3DXFRAME_EX**ppFrame,IDirect3DDevice9*pDevice,char*Filename,char*TexturePath=".\\",DWORDNewFVF=0,DWORDLoadFlags=D3DXMESH_SYSTEMMEM);//UpdateaskinnedmeshHRESULTUpdateSkinnedMesh(XSSD3DXMESHCONTAINER_EX*pMesh);//UpdateaNormalmeshHRESULTUpdateNormalMesh(XSSD3DXMESHCONTAINER_EX*pMesh);//UpdateameshHRESULTUpdateMesh(XSSD3DXMESHCONTAINER_EX*pMesh);//DrawthefirstmeshinalinkedlistofobjectsHRESULTDrawMesh(XSSD3DXMESHCONTAINER_EX*pMesh,D3DMATERIAL9*pMaterialTemp,boolbIfUseMarkedMode);//Drawthefirstmeshinalinkedlistofobjects//usingthespecifiedvertexshaderanddeclarationHRESULTDrawMesh(XSSD3DXMESHCONTAINER_EX*pMesh,IDirect3DVertexShader9*pShader,IDirect3DVertexDeclaration9*pDecl);//DrawallmeshesinalinkedlistofobjectsHRESULTDrawMeshes(XSSD3DXMESHCONTAINER_EX*pMesh,D3DMATERIAL9*pMaterialTemp,boolbIfUseMarkedMode);//Drawallmeshesinalinkedlistofobjects//usingthespecifiedvertexshaderanddeclarationHRESULTDrawMeshes(XSSD3DXMESHCONTAINER_EX*pMesh,IDirect3DVertexShader9*pShader,IDirect3DVertexDeclaration9*pDecl);7、MFC编程中的一些细节问题(1)从CFileDialog信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/中如何获取文件路径(注:该类中获取文件路径的功能函数不便使用)staticCStringszFilter="AllSupportedFiles(*.XSS;*.x)|*.XSS;*.x|DirectXSceneStudioFiles(*.XSS)|*.XSS|DirectXFiles(*.x)|*.x|AllFiles(*.*)|*.*||";CFileDialogFileDlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,szFilter);if(FileDlg.DoModal()==IDOK){CStringstrXFileName;CStringstrPathAndName;CStringstrPathOnly;CStringstrFileType;strPathAndName=FileDlg.GetPathName();strXFileName=FileDlg.GetFileName();strPathOnly=strPathAndName.Left(strPathAndName.GetLength()-strXFileName.GetLength());……}(2)CString*型指针向char*型指针的转换CString*pstrFileNameS;char*pszFileNameC;pszFileNameC=pstrFileNameS->GetBuffer(pstrFileName->GetLength()+1);(3)通过CFileDialog类获取保存文件的路径和名称CFileDialogFileSaveDlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,"XSSD3DDummyFeatureFiles(*.xssf)|*.xssf|AllFiles(*.*)|*.*||");if(!(FileSaveDlg.DoModal()==IDOK))returnE_FAIL;CStringstrSaveFilePathName=FileSaveDlg.GetPathName();strSaveFilePathName.Replace("\\","\\\\");usingnamespacestd;ifstreamfileToLoadFeature;fileToLoadFeature.open(strSaveFilePathName);if(!fileToLoadFeature){CStringstrMSG="对不起!未能打开配置文件:";strSaveFilePathName.Replace("\\\\","\\");strMSG+=strSaveFilePathName;::MessageBox(0,strMSG,"应用程序提示",MB_ICONINFORMATION);returnE_FAIL;}1.4程序用户界面(GUI)简介应用程序D3DDummyEditor实现了对.X信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/文件中标准网格模型和蒙皮网格模型的读取和运动仿真控制功能,而且允许用户为特定的模型对象保存当前形态和加载配置新的形态特征。由于程序中添加有对多个模型的管理功能,因此允许用户同时载入多个模型,并且可以同时或独立地对各个模型进行编辑处理。程序中还添加有对模型材质和纹理信息的编辑功能,使得对模型的控制可以从更多的方面进行。D3DDummyEditor三维人体运动仿真应用程序的主界面如图23所示。图23D3DDummyEditor应用程序主界面主要对话框列表:信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/1、功能区主页2、场景设置3、键盘控制列表4、设置键盘控制模型的规则5、摄像机设置6、模型对象浏览器7、综合属性编辑8、快捷运动控制9、普通运动控制功能区模型编辑10、变换矩阵解析器11、模型管理器12、键盘控制规则设置信息科学与技术学院电子工程与信息科学系51(EEIS)\nhttp://www.ustc.edu.cn/zh_CN/1.自我拓展之XSceneStudio梦想平台我希望可以在做毕业设计时,综合运用DirectX的DirectInput等组件,结合电子技术及信息处理的知识,类比微软的“Natal”搭建一个自己的,基于DirectX开发的多媒体运用平台——XSceneStudio。图24XsceneStudio示意图2.结论本文深入分析了用.X文件存储的三维人体模型的运动仿真控制原理,并给出了一种基于DirectXSDK的三维人体仿真运动控制的应用方法。结合MFC应用程序框架,简要地介绍了Direct3D应用程序的设计方法。3.致谢本次大学生研究计划的学习过程中得到中国科学院自动化研究所高科技创新实验室杜清秀老师(副研究员)和弭鹏师兄的悉心辅导,在此表示衷心的感谢!在Direct3D的入门学习过程中,FrankD.Luna编著的《DirectX9.03D游戏开发编程基础》[3]给予我至关重要启蒙作用;在结合MFC与Direct3D应用程序的开发中,他的著作《IntegratingDirect3D9.0WithMFC》[4]让我顺利地解决了MFC与Direct3D结合的问题。在此对FrankD.Luna先生表示感谢!参考文献[1]叶至军.VisualC++/DirectX93D游戏开发引导[M].北京:人民邮电出版社,2006.276-279[2]JimAdams.AdvancedAnimationwithDirectX[M].DirectX高级动画制作[M].刘刚.重庆:重庆大学出版社,2005.1[3]FrankD.Luna.Introductionto3DGameProgrammingwithDirectX9.0[M].DirectX9.03D游戏编程[M].段菲.北京:清华大学出版社,2007[4]FrankD.Luna.IntegratingDirect3D9.0WithMFC[EB/OL].http://www.moon-labs.com,2003/3/16信息科学与技术学院电子工程与信息科学系51(EEIS)