ImageVerifierCode 换一换
格式:DOCX , 页数:17 ,大小:168.55KB ,
资源ID:6686897      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/6686897.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(max骨骼的一些知识.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

max骨骼的一些知识.docx

1、max骨骼的一些知识我在程序员2008年2月刊上看到的(上),一搜果然搜到了。好文共分享!游戏程序中的骨骼插件(上)文/潘李亮引言在3D引擎中,骨骼动画系统是非常重要的一个组成部分,虽然在一个游戏的真正开发过程中,一个优秀的游戏引擎也许不需要用户去关心它的骨骼动画系统是如何实现的,但是还是有很多人希望了解这样的一个技术。本文将会介绍骨骼动画系统里的一个基础部件:3Ds MAX 的骨骼动画导出插件。3Ds Max SDK和插件系统最新版本MAX9的MAXSDK包含在安装光盘里,在安装完MAX后直接安装SDK,并在工程里添加maxsdk的包含路径和库的路径就可以开始编译max插件了。MAX SDK

2、还提供了3Ds Max Help for Visual Studio,这个帮助系统可以集成到Visual Studio .NET的帮助系统中,非常方便。建议在安装的时候一起装上。MaxSDK主要目的就是用来开发MAX插件,虽然Max也提供了MaxScript,也可以用来做插件,但是对C+程序原来说,MaxSDK则更顺手一些。Max插件根据用途分为好几种,每种对应不同的扩展名,在游戏开发中,我们通常比较关心三种类型的插件,他们分别是: 导入/导出插件,对应扩展名为dli/dle, utility 插件,对应扩展名为dlu,以及扩展名为dlm的modifier。导入导出插件基本上说是MAX与其它工

3、具交互的接口。Utility插件则可以为MAX增加很多操作功能面板。Modifier则是3DsMAX3DsMax自带的插件放在X:3DsMaxmaxsdkstdplugs目录下,而我们自己编写的插件通常会放到X:3DsMaxmaxsdk plugins目录下。只要把插件放到这两个目录下,Max在启动的时候就会自动加载你的插件。很多初学者可能会问dlm/dle这些插件是怎么生成的呢?其实这些都是一些标准的dll程序,只是扩展名不同而已。跟编译一个普通的Windows DLL没有区别。初学MaxSDK最好的例子应该就是MAXSDK自带的sample。在maxsdk的安装目录下可以找到,一般是X:3

4、DsMaxmaxsdksamples 下。这个目录下已经对插件的种类进行了分类。一般在做骨骼动画导出插件的时候,我们不会选择导出插件而是选择utility插件,这样做的目的是ultility插件在max启动的时候就处于激活状态, 而导出插件则只会在用户选择export命令的那一刻,并且这些插件都可以访问到MAX的整个环境,因此,使用utility插件会让用户更加的方便,本文的例子就是采用utility插件。构造第一个3Ds Max 插件 本节我将讲述如何快速的建立一个utility插件的框架, 因为关心的是导出插件本身的功能,而不是插件框架本身,因此我给大家提供一个个比较简洁的方法:使用3ds

5、maxPluginWizard. 这是MaxSDK提供的一个组件,位于X:3dsmaxmaxsdkhowto3dsmaxPluginWizard下, 仔细阅读一遍这个目录下的ReadMe.txt文件的Installing一小节,就可安装好3DsMaxPluginWizard. 这时候打开Visual Studio 2005.在新建工程中就可以看到3Ds Max Plugin Wizard一项,选择后,看到标签页一共有三页,在第一页Plugin-Type里,选择Utility项,在接下来的Plugin Detail里填入详细信息如图2所示。最后在Project Detials 选项卡里填入max

6、sdk的路径,插件输出路径和3dsmax.exe所在的路径就可以生成一个utility工程了。生成的工程仅仅是一个架子,它包含了两个类和一个IDD_PANEL的对话框。第一个类MyMaxSkinExporter是从UtilityObj派生下来的,代表了插件本身。另外一个类从ClassDesc2派生,用来描述这个插件的一些信息。IDD_PANEL则是我们插件的主界面,我们可以简单的理解它就是我们插件的主窗口。MyMaxSkinExporter有两个重要的函数: BeginEditParams(Interface *ip,IUtil *iu)和EndEditParams(Interface *ip

7、,IUtil *iu)这两个函数。BeginEditParams可以简单的理解成插件的初始化函数,EndEditParams则在插件退出时候被调用。参数Interface *ip 则代表整个Max对象,用它可以访问到MAX程序的所有功能。编译这个工程,一个简单的utility插件就已经生成了,你可以在刚才Project Detials选项卡里填入的插件输出路径里找到生成的插件。3Ds MAX的场景组织和几何管道要编写一个导出骨骼动画的插件,必须先了解MAX是如何组织场景的,并了解MAX中一个mesh对象从建立到最终输出都经历那些阶段。下面首先介绍一下MAX的场景组织。MAX的整个场景是一个树状

8、结构,树的节点用INode来表示,整个树的根节点可以通过Interface:GetRootNode来获得,场景中的所有物体都是INode。INode中的NumberOfChildren函数和GetChildNode则用来访问INode的子节点。要遍历整个场景中对象,只需要通过Interface:GetRootNode和GetChildNode做一个递归环就可以了。如果仅仅是想获得在视口中选定的物体,则可以使用Interface:GetSelNodes函数。INode仅仅是一个虚拟的节点,它本身仅仅包含一些标记和变换信息,并不表示实际的Object。实际的Object需要附着在INode上,并以

9、INode的坐标系为Object的本地坐标系。Max中常见的Object有形状(各种参数曲线),Camera,Mesh等。Object有自己的变换矩阵(TM), 在很多情况下这个矩阵都是单位矩阵。INode的变换矩阵可以通过INode:GetNodeTM来获得,而附着在INode上的Object的变换矩阵则通过INode:GetObjectTM来获得,因为Object相对于Node的变换矩阵通常是单位矩阵,GetNodeTM和GetObjectTM获得的矩阵通常也是一样的,但是在必要的时候一定要加以区别。关于INode和Object的变换矩阵问题的详细讨论可以参考我blog上的一篇文章:MAX

10、 SDK的INode的变换矩阵,以及Object的一些常识(接下来我来看一下3DsMax一个几何物体的Pipeline。前面说过Object是附着在INode上的,在MAX里,Node有一个Object Reference的指针,指向一个物体对象。熟悉MAX的操作方式的读者都知道,我们在MAX里建立一个对象后,可以在上面添加各种修改器-Modifier。在Max的几何管道中,我们建立的对象通常称为Base Object。所有施加在这个物体上的修改器形成一个修改器堆栈-ModStack。BaseObject经过这个ModStack后形成一个新的Object Reference。ModStack中

11、的每个Modifier都是输入一个Object Reference,输出一个Object Reference, 并且在应用第一个Modifier的时候会自动在几何管道里插入一个Derived Object。最终INode的Object Reference将指向这个Derived Object。Modifier在管道中的应用实例是ModApp对象,一个ModApp代表一个应用在Object上Modifier修改,ModApp包含一个ModContext的数据对象,Modifier用ModContext中的数据来对Object进行修改,以生成最终数据。修改器按照应用的坐标系不同分成局部空间修改器和

12、世界空间修改器(World Space Modifier)。局部空间修改器仅仅在Object的局部空间中修改Object,不会对坐标系造成影响。世界空间的修改器比如水波纹修改器则要求先将物体变换到世界空间后再进行修改,修改完成后的坐标也是世界空间的坐标。相对来说处理世界空间修改器会麻烦的多。(如果一个物体应用了世界空间修改器,则通过Mesh对象取得的坐标已经是世界坐标系中的了。不需要再乘以INode:GetObjectTM了)。导出骨骼动画数据 了解了MAX的场景管理和几何管道以后,我们就可以很方便的建立一个如何取得MAX场景中定点数据的流程了。骨骼动画系统,首先应该包括物体的蒙皮数据和顶点与

13、骨骼的绑定信息。我们分两部分介绍皮肤数据的导出。第一步,我们要导出蒙皮数据,为了简单起见,在这里只导出蒙皮的位置,法向量与切向量纹理坐标等信息留给读者自己去研究。在3DsMax里。要建立骨骼动画模型,可以使用两种修改器Skin和,Physique。其中Physique是属于CharacterStudio的,他的API相对比较复杂,本文只介绍使用Skin修改器制作的骨骼动画模型文件。在界面上增加一些按钮在了解了那么多理论后,我们可以开始做一些实质上的事情了,首先我们要给我们的插件增加一些按钮,通过这些按钮,使用可以下达保存/加载骨架,导出动作,导出皮肤等任务。我们在第三节中生成的IDD_PANE

14、L的对话框中加入几个按钮,分别用于保存骨架,加载骨架,导出动作,导出皮肤。并在对话框的WM_COMMAND消息中加入按钮响应代码。对话框的窗口过程为MyMaxSkinExporterDlgProc。增加完的IDD_PANEL对话框看上去如图4。定义顶点数据类型 骨骼动画的顶点数据应该包含顶点位置,纹理坐标,法向量,影响的骨骼编号和权重,一般影响到某个顶点的骨骼数目不会超过个。同时,顶点位置也有两种记录方法:相对于世界空间的和相对骨骼空间的,这里我们采用相对于世界空间的记录方法,因为这种方案比较直观,只需要记录一个顶点位置就可以。麻烦的地方在于,因为骨骼的变换矩阵要求顶点是相对于该骨骼的局部空间

15、的,因此顶点在参与骨骼蒙皮计算的时候,需要先乘上骨骼的初始位置的矩阵的逆,以变换到骨骼空间。 structVertex_tPointpos,normal,texCoord;intmatID;intnEffBone;structintboneIdx;floatweight;Bone4;导出骨架 骨骼动画系统中骨架为动画的载体,所有的蒙皮都附着在骨架之上。同时要保证属于一个角色的所有的蒙皮都使用同一个骨架来建立和导出,这是一套换装系统的基本需求。因此骨架的导出和保存通常是一次性的,后续导出皮肤的时候都应该以这个骨架为基准。这也要求我们在导出骨架的时候就需要导出所有的骨骼。 骨架上的骨骼其实也是一个

16、INode,骨骼仅仅是一些变换矩阵的信息而已。目前没有特别好的办法鉴定哪些INode是骨骼,比较可行的办法是把所有Skin修改器使用到的INode都列为骨骼,同时美工还可以手动指定哪些Node为骨骼,并把这些标记用INode: SetUserPropBool(IsBone,bIsBone);记录到MAX文件中。保存骨架的时候,需要保存骨骼的父子关系。并需要保存这个骨骼的第一帧数据。这要求如果美工在两个不同的MAX文件里制作不同的动作的时候,除了保证骨架相同以外,第一帧也需要完全相同骨架的保存和加载代码如下: structBone_tMatrixNodeInitTM;charName32,Par

17、antName32;classCSkeletonpublic:vectorm_Bones;voidloadSkeleton(constchar*skeFileName)ifstreamin(skeFileName,ios:binary);while(!in.eof()Bone_t_bone;in.read(char*)&_bone,sizeof(Bone_t);m_Bones.push_back(_bone);in.close();intfindBoneIndex(INode*pNode)/for(inti=0;iGetName()returni;return-1;voidsaveBone(o

18、stream&out,INode*pNode,boolbRoot)Bone_t_bone;_bone.NodeInitTM=pNode-GetNodeTM(0);strncpy(_node.Name,pNode-GetName(),32);if(bRoot)_bone.ParantName0=0;elseINode*pPNode=pNode-GetParantNode();strncpy(_bone.ParantName,pPNode-GetName(),32);out.write(char*)&_bone,sizeof(Bone_t);for(inti=0;iNumberOfChildren

19、();i+)saveBone(out,pNode-GetChildNode(i),false);voidsaveSkeleton(constchar*skeFileName,INode*pRootNode)ofstreamout(skeFileName,ios:binary);saveBone(out,pRootNode,true);out.close();findBoneIndex函数的目的是在把从文件中加载的骨骼和MAX中的Node对应起来.因为是根据名字来进行查找比较,因此要求所有的Node都必须要有唯一的名字同时,骨骼之间的父子关系也是通过名字来标记的每个Bone都记录了它的父节点的名

20、字Save Skeleton骨架的按钮响应代码如下:voidOnSaveSkeleton()CSkeleton*pSkeleton=GetGlobalSkeleton();Assert(ip-GetSelNodeCount()=1);/导出骨架的时候只能选择一个节点constchar*filename=GetSaveFileName();if(filename)pSkeleton-saveSkeleton(filename,ip-GetSelNode(0);导出骨架动作骨架导出后,我们需要进一步导出这个骨架的动作。在导出动作的时候,需要加载一个事先已经导出的骨架。然后遍历这个骨架中所有的骨骼,

21、找到这个骨骼对应的INode对象。然后确定动画的长度和帧数,为每一个骨头的保存一个变换矩阵。voidOnExportAnimation()constchar*fileName=GetSaveFileName();ofstreamout(fileName,ios:binary);IntervalARange=ip-GetAnimRange();/获得动画的长度TimeValuetAniTime=ARange.End()-ARange.Start();TimeValuetTime=ARange.Start();intnFrame=tAniTime/GetTicksPerFrame();/计算动画帧

22、数out.write(char*)&nFrame,sizeof(int);/记录有多少frame;for(inti=0,;inFrame;i+,tTime+=GetTicksPerFrame()CSkeleton*pSkeleton=GetGlobalSkeleton();for(intiBone=0;iBonem_Bones.size();iBone+)Bone_t&bone=pSkeleton-m_BonesiBone;INode*pBoneNode=GetNodeByName(bone.Name);/通过名字获得INode指针Matrixmat=pBoneNode-GetNodeTM(t

23、Time,NULL);out(char*)&mat,sizeof(Matrix);out.close();这里演示里我们记录的是骨骼的绝对变换矩阵,而不是相对父骨骼的变换矩阵,这省去了我们从根骨骼开始计算骨架的麻烦,但是也多了很多限制,比如不能进行动作混合,不能做动作的插值等,使用相对父骨骼的局部矩阵的算法留给读者自己去实现,也可以参考Cal3D和我开源的XReal3D的导出插件。 此外,因为我们在顶点数据中只保存了相对世界空间的位置,所以骨骼中的NodeInitTM将用来把相对世界空间的顶点位置变换到骨骼的局部空间中,皮肤混合的时候计算公式将如下:其中M(t,i)为第i块骨头在t时刻的变换矩

24、阵。同样的,我们只是简单的导出每一帧的变换矩阵,而没有处理关键帧,使用关键帧加上相对父节点的局部变换矩阵的四元数插值,在保准动作的准确性前提下能大大的降低动作文件磁盘占用。游戏程序中的骨骼插件(下)查找Skin修改器要找到一个Mesh上是不是有Skin修改器,根据MAX的几何管道的结构,需要遍历整个ModStack中的Derived Object。判断应用在这些Derived Object上的修改器的类型。MAX中所有的对象都有一个类似COM的GUID的唯一标记ClassID。Skin修改器的ClassID为SKIN_CLASSID,在获得Derived Object的修改器后只需要检查修改器

25、的ClassID是不是SKIN_CLASSID即可。示例代码如下:ISkin*FindSkinModifier(INode*pINode)Object*pObject=pINode-GetObjectRef();if(pObject=0)return0;/循环检测所有的DerivedObjectwhile(pObject-SuperClassID()=GEN_DERIVOB_CLASS_ID)IDerivedObject*pDerivedObject=static_cast(pObject);for(intstackId=0;stackIdNumModifiers();stackId+)Mod

26、ifier*pModifier=pDerivedObject-GetModifier(stackId);/检测ClassID是不是Skin修改器if(pModifier-ClassID()=SKIN_CLASSID)return(ISkin*)pModifier-GetInterface(I_SKIN);/下一/个DerivedObjectpObject=pDerivedObject-GetObjRef();return0;获取Mesh对象根据第四节中描述的,要从一个INode中获得Mesh对象,首先应该从INode中获得Object对象,然后再转成Mesh对象。具体代码如下:Mesh*Get

27、Mesh(INode*pNode,intiMaxTime)NullViewview;/NullView是自定义的View类。详细参见完整的插件代码BOOLbNeedDelete=false;ObjectStateos=pNode-EvalWorldState(iMaxTime);Object*pObj=os.obj;TriObject*triObject=(TriObject*)pObj-ConvertToType(iMaxTime,triObjectClassID);GeomObject*pGeoObj=(GeomObject*)pObj;Mesh*pMesh=pGeoObj-GetRenderMesh(iMaxTime,m_pNode,

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1