max骨骼的一些知识Word下载.docx

上传人:b****5 文档编号:19703822 上传时间:2023-01-09 格式:DOCX 页数:17 大小:168.55KB
下载 相关 举报
max骨骼的一些知识Word下载.docx_第1页
第1页 / 共17页
max骨骼的一些知识Word下载.docx_第2页
第2页 / 共17页
max骨骼的一些知识Word下载.docx_第3页
第3页 / 共17页
max骨骼的一些知识Word下载.docx_第4页
第4页 / 共17页
max骨骼的一些知识Word下载.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

max骨骼的一些知识Word下载.docx

《max骨骼的一些知识Word下载.docx》由会员分享,可在线阅读,更多相关《max骨骼的一些知识Word下载.docx(17页珍藏版)》请在冰豆网上搜索。

max骨骼的一些知识Word下载.docx

\3dsmax\maxsdk\howto\3dsmaxPluginWizard下,仔细阅读一遍这个目录下的ReadMe.txt文件的Installing一小节,就可安装好3DsMaxPluginWizard.这时候打开VisualStudio2005.在新建工程中就可以看到3DsMaxPluginWizard一项,选择后,看到标签页一共有三页,在第一页Plugin-Type里,选择Utility项,在接下来的PluginDetail里填入详细信息如图2所示。

最后在ProjectDetials选项卡里填入maxsdk的路径,插件输出路径和3dsmax.exe所在的路径就可以生成一个utility工程了。

生成的工程仅仅是一个架子,它包含了两个类和一个IDD_PANEL的对话框。

第一个类MyMaxSkinExporter是从UtilityObj派生下来的,代表了插件本身。

另外一个类从ClassDesc2派生,用来描述这个插件的一些信息。

IDD_PANEL则是我们插件的主界面,我们可以简单的理解它就是我们插件的主窗口。

MyMaxSkinExporter有两个重要的函数:

BeginEditParams(Interface*ip,IUtil*iu)和EndEditParams(Interface*ip,IUtil*iu)这两个函数。

BeginEditParams可以简单的理解成插件的初始化函数,EndEditParams则在插件退出时候被调用。

参数Interface*ip则代表整个Max对象,用它可以访问到MAX程序的所有功能。

编译这个工程,一个简单的utility插件就已经生成了,你可以在刚才ProjectDetials选项卡里填入的插件输出路径里找到生成的插件。

3DsMAX的场景组织和几何管道

要编写一个导出骨骼动画的插件,必须先了解MAX是如何组织场景的,并了解MAX中一个mesh对象从建立到最终输出都经历那些阶段。

下面首先介绍一下MAX的场景组织。

MAX的整个场景是一个树状结构,树的节点用INode来表示,整个树的根节点可以通过Interface:

:

GetRootNode来获得,场景中的所有物体都是INode。

INode中的NumberOfChildren函数和GetChildNode则用来访问INode的子节点。

要遍历整个场景中对象,只需要通过Interface:

GetRootNode和GetChildNode做一个递归Ñ

¬

环就可以了。

如果仅仅是想获得在视口中选定的物体,则可以使用Interface:

GetSelNodes函数。

INode仅仅是一个虚拟的节点,它本身仅仅包含一些标记和变换信息,并不表示实际的Object。

实际的Object需要附着在INode上,并以INode的坐标系为Object的本地坐标系。

Max中常见的Object有形状(各种参数曲线),Camera,Mesh等。

Object有自己的变换矩阵(TM),在很多情况下这个矩阵都是单位矩阵。

INode的变换矩阵可以通过INode:

GetNodeTM来获得,而附着在INode上的Object的变换矩阵则通过INode:

GetObjectTM来获得,因为Object相对于Node的变换矩阵通常是单位矩阵,GetNodeTM和GetObjectTM获得的矩阵通常也是一样的,但是在必要的时候一定要加以区别。

关于INode和Object的变换矩阵问题的详细讨论可以参考我blog上的一篇文章:

MAXSDK的INode的变换矩阵,以及Object的一些常识(

接下来我来看一下3DsMax一个几何物体的Pipeline。

前面说过Object是附着在INode上的,在MAX里,Node有一个ObjectReference的指针,指向一个物体对象。

熟悉MAX的操作方式的读者都知道,我们在MAX里建立一个对象后,可以在上面添加各种修改器-Modifier。

在Max的几何管道中,我们建立的对象通常称为BaseObject。

所有施加在这个物体上的修改器形成一个修改器堆栈-ModStack。

BaseObject经过这个ModStack后形成一个新的ObjectReference。

ModStack中的每个Modifier都是输入一个ObjectReference,输出一个ObjectReference,并且在应用第一个Modifier的时候会自动在几何管道里插入一个DerivedObject。

最终INode的ObjectReference将指向这个DerivedObject。

Modifier在管道中的应用实例是ModApp对象,一个ModApp代表一个应用在Object上Modifier修改,ModApp包含一个ModContext的数据对象,Modifier用ModContext中的数据来对Object进行修改,以生成最终数据。

修改器按照应用的坐标系不同分成局部空间修改器和世界空间修改器(WorldSpaceModifier)。

局部空间修改器仅仅在Object的局部空间中修改Object,不会对坐标系造成影响。

世界空间的修改器比如水波纹修改器则要求先将物体变换到世界空间后再进行修改,修改完成后的坐标也是世界空间的坐标。

相对来说处理世界空间修改器会麻烦的多。

(如果一个物体应用了世界空间修改器,则通过Mesh对象取得的坐标已经是世界坐标系中的了。

不需要再乘以INode:

GetObjectTM了)。

导出骨骼动画数据

了解了MAX的场景管理和几何管道以后,我们就可以很方便的建立一个如何取得MAX场景中定点数据的流程了。

骨骼动画系统,首先应该包括物体的蒙皮数据和顶点与骨骼的绑定信息。

我们分两部分介绍皮肤数据的导出。

第一步,我们要导出蒙皮数据,为了简单起见,在这里只导出蒙皮的位置,法向量与切向量纹理坐标等信息留给读者自己去研究。

在3DsMax里。

要建立骨骼动画模型,可以使用两种修改器Skin和,Physique。

其中Physique是属于CharacterStudio的,他的API相对比较复杂,本文只介绍使用Skin修改器制作的骨骼动画模型文件。

在界面上增加一些按钮

在了解了那么多理论后,我们可以开始做一些实质上的事情了,首先我们要给我们的插件增加一些按钮,通过这些按钮,使用可以下达保存/加载骨架,导出动作,导出皮肤等任务。

我们在第三节中生成的IDD_PANEL的对话框中加入几个按钮,分别用于保存骨架,加载骨架,导出动作,导出皮肤。

并在对话框的WM_COMMAND消息中加入按钮响应代码。

对话框的窗口过程为MyMaxSkinExporterDlgProc。

增加完的IDD_PANEL对话框看上去如图4。

定义顶点数据类型

骨骼动画的顶点数据应该包含顶点位置,纹理坐标,法向量,影响的骨骼编号和权重,一般影响到某个顶点的骨骼数目不会超过4个。

同时,顶点位置也有两种记录方法:

相对于世界空间的和相对骨骼空间的,这里我们采用相对于世界空间的记录方法,因为这种方案比较直观,只需要记录一个顶点位置就可以。

麻烦的地方在于,因为骨骼的变换矩阵要求顶点是相对于该骨骼的局部空间的,因此顶点在参与骨骼蒙皮计算的时候,需要先乘上骨骼的初始位置的矩阵的逆,以变换到骨骼空间。

struct 

Vertex_t

{

Point 

pos 

 

normal 

texCoord;

int 

matID;

nEffBone;

struct{

boneIdx;

float 

weight;

}Bone[4];

};

导出骨架

骨骼动画系统中骨架为动画的载体,所有的蒙皮都附着在骨架之上。

同时要保证属于一个角色的所有的蒙皮都使用同一个骨架来建立和导出,这是一套换装系统的基本需求。

因此骨架的导出和保存通常是一次性的,后续导出皮肤的时候都应该以这个骨架为基准。

这也要求我们在导出骨架的时候就需要导出所有的骨骼。

骨架上的骨骼其实也是一个INode,骨骼仅仅是一些变换矩阵的信息而已。

目前没有特别好的办法鉴定哪些INode是骨骼,比较可行的办法是把所有Skin修改器使用到的INode都列为骨骼,同时美工还可以手动指定哪些Node为骨骼,并把这些标记用INode:

SetUserPropBool("

IsBone"

,bIsBone);

记录到MAX文件中。

保存骨架的时候,需要保存骨骼的父子关系。

并需要保存这个骨骼的第一帧数据。

这要求如果美工在两个不同的MAX文件里制作不同的动作的时候,除了保证骨架相同以外,第一帧也需要完全相同.

骨架的保存和加载代码如下:

Bone_t{

Matrix 

NodeInitTM;

char 

Name[32],ParantName[32];

class 

CSkeleton 

public:

vector<

Bone_t>

m_Bones;

void 

loadSkeleton(const 

char* 

skeFileName){

ifstream 

in(skeFileName 

ios:

binary);

while(!

in.eof()){

Bone_t 

_bone;

in.read((char*)&

_bone 

sizeof(Bone_t));

m_Bones.push_back(_bone);

}

in.close();

findBoneIndex(INode* 

pNode) 

{//

for(int 

;

<

m_Bones.size() 

++)

if(string(m_Bones[i].Name) 

== 

pNode->

GetName() 

) 

return 

i;

-1;

saveBone(ostream&

out 

INode* 

pNode 

bool 

bRoot){

_bone.NodeInitTM 

GetNodeTM(0) 

strncpy(_node.Name, 

32);

if(bRoot) 

_bone.ParantName[0] 

0;

else{

pPNode 

=pNode->

GetParantNode();

strncpy(_bone.ParantName, 

pPNode->

out.write( 

(char*)&

sizeof(Bone_t) 

);

NumberOfChildren() 

saveBone(out,pNode->

GetChildNode(i), 

false);

saveSkeleton(const 

skeFileName 

pRootNode){

ofstream 

out(skeFileName 

saveBone(out 

pRootNode 

true);

out.close();

findBoneIndex函数的目的是在把从文件中加载的骨骼和MAX中的Node对应起来.因为是根据名字来进行查找比较,因此要求所有的Node都必须要有唯一的名字.同时,骨骼之间的父子关系也是通过名字来标记的.每个Bone都记录了它的父节点的名字.SaveSkeleton骨架的按钮响应代码如下:

OnSaveSkeleton()

CSkeleton* 

pSkeleton 

GetGlobalSkeleton();

Assert(ip->

GetSelNodeCount() 

1);

//导出骨架的时候只能选择一个节点

const 

filename 

GetSaveFileName() 

if(filename){

pSkeleton->

saveSkeleton(filename 

ip->

GetSelNode(0) 

导出骨架动作

骨架导出后,我们需要进一步导出这个骨架的动作。

在导出动作的时候,需要加载一个事先已经导出的骨架。

然后遍历这个骨架中所有的骨骼,找到这个骨骼对应的INode对象。

然后确定动画的长度和帧数,为每一个骨头的保存一个变换矩阵。

OnExportAnimation()

fileName 

GetSaveFileName();

out(fileName 

Interval 

ARange 

GetAnimRange();

//获得动画的长度

TimeValue 

tAniTime 

ARange.End() 

ARange.Start();

tTime 

nFrame 

tAniTime/GetTicksPerFrame();

//计算动画帧数

out.write((char*)&

sizeof(int));

//记录有多少frame;

++ 

tTime 

+= 

GetTicksPerFrame()){

iBone 

++){

Bone_t&

bone 

m_Bones[iBone];

pBoneNode 

GetNodeByName(bone.Name);

//通过名字获得INode指针

mat 

pBoneNode->

GetNodeTM(tTime,NULL);

out((char*)&

sizeof(Matrix));

这里演示里我们记录的是骨骼的绝对变换矩阵,而不是相对父骨骼的变换矩阵,这省去了我们从根骨骼开始计算骨架的麻烦,但是也多了很多限制,比如不能进行动作混合,不能做动作的插值等,使用相对父骨骼的局部矩阵的算法留给读者自己去实现,也可以参考Cal3D和我开源的XReal3D的导出插件。

此外,因为我们在顶点数据中只保存了相对世界空间的位置,所以骨骼中的NodeInitTM将用来把相对世界空间的顶点位置变换到骨骼的局部空间中,皮肤混合的时候计算公式将如下:

其中M(t,i)为第i块骨头在t时刻的变换矩阵。

同样的,我们只是简单的导出每一帧的变换矩阵,而没有处理关键帧,使用关键帧加上相对父节点的局部变换矩阵的四元数插值,在保准动作的准确性前提下能大大的降低动作文件磁盘占用。

游戏程序中的骨骼插件(下)

查找Skin修改器

要找到一个Mesh上是不是有Skin修改器,根据MAX的几何管道的结构,需要遍历整个ModStack中的DerivedObject。

判断应用在这些DerivedObject上的修改器的类型。

MAX中所有的对象都有一个类似COM的GUID的唯一标记ClassID。

Skin修改器的ClassID为SKIN_CLASSID,在获得DerivedObject的修改器后只需要检查修改器的ClassID是不是SKIN_CLASSID即可。

示例代码如下:

ISkin 

FindSkinModifier(INode 

*pINode){

Object 

pObject 

pINode->

GetObjectRef();

if(pObject 

0) 

// 

循环检测所有的DerivedObject

while(pObject->

SuperClassID() 

GEN_DERIVOB_CLASS_ID)

IDerivedObject 

pDerivedObject 

static_cast<

*>

(pObject);

stackId 

pDerivedObject->

NumModifiers();

stackId++)

Modifier 

pModifier 

GetModifier(stackId);

//检测ClassID是不是Skin修改器

if(pModifier->

ClassID() 

SKIN_CLASSID) 

(ISkin*) 

pModifier->

GetInterface(I_SKIN);

//下一//个Derived 

GetObjRef();

获取Mesh对象

根据第四节中描述的,要从一个INode中获得Mesh对象,首先应该从INode中获得Object对象,然后再转成Mesh对象。

具体代码如下:

Mesh* 

GetMesh(INode* 

iMaxTime){

NullView 

view;

//NullView是自定义的View类。

详细参见完整的插件代码

BOOL 

bNeedDelete 

false;

ObjectState 

os 

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