篇章一、搭建环境与简单介绍
前段时间做了一个多月的3DS MAX导入插件设计,终于从完全不懂到了现在的懵懵懂懂,期间遇到了一些困难,国内的3DS MAX导入插件的相关资料几乎为零,上网查几乎都是为导出插件的资料,这也难怪,大家都只有导出的需求,没有导入的需求。翻墙到国外也只是有一些零零散散的一星半点的片段知识,经过本人的搜集与摸索,终于完成了整个骨骼动画的导入,现将整个3DS MAX导入骨骼动画的整个流程分享给大家,如果能给广大的程序员朋友们带来些许的帮助,将感到无比的欣慰。
我用的环境是3DS MAX 2009 +VS2008,具体的环境配置就不多说了,网上这方面资料很多,这里提供一个地址,如有需要可以查看http://blog.sina.com.cn/s/blog_5c68ccb801017wmm.html;
接下来让我们开始正式工作。
首先使用向导生成了一堆导入插件的文件,导入插件继承于SceneImport类,这个类里面的方法大多可以不管,只需要知道三个方法就好:
const TCHAR *Ext(int n) 用于定义导入插件的文件格式
TCHAR* ShortDesc() 用来给导入的文件格式做一些简短的说明
int DoImport(const TCHAR *filename,ImpInterface *i,Interface *gi, BOOL suppressPrompts)
这个是导入插件的核心方法,所有的导入操作都在这个方法里进行,实际就是进行读取文件的操作。
生成了向导后还要进行一些项目属性的设置,项目才能正常运行:
1、在项目属性-配置属性-C++-命令行-附加选项里给文件路径加上双引号/LD @"C:\Program Files\Autodesk\3ds Max 2009 SDK\maxsdk\ProjectSettings\AdditionalCompilerOptions.txt"
2、在项目属性-配置属性-调试-命令 加上3DS MAX.exe的文件路径 比如我的是:C:\Program Files\Autodesk\3ds Max 2009\3dsmax.exe
另外在介绍下3DS MAX中最常用的类:节点类INode;在下面的讲解中这个类将经常要用到
1、节点概念
在3ds max中,结点与场景中的对象存在着一一对应的关系,每一个过程对象,包括灯光、摄像机、辅助对象等等,出现在视口中的每个对象都关联一个结点。结点存储 着它关联的对象在场景中的相关属性信息,这些属性包括空间变换控制器、用于渲染的材质、可见性控制器、隐藏/非隐藏状态、冻结/解冻状态、线宽显示颜色以 及其他属性。
2、节点方法
结点存储了两个变换矩阵,一个是结点的变换矩阵,另一个是结点关联对象相对于结点的偏移矩阵。另外,结点类是不能被实例化的,要取得结点的实例,必须实例化结点所指向的对象,所以,当实例化结点所指向的对象后,结点就相当于被实例化了。
结点类层次关系的主要方法有:
int NumberOfChildren(); //返回子结点的个数
INode* GetChildNode(int i); //返回第i-th个子结点的指针
INode* GetParentNode(); //返回父结点指针
void AttachChild(INode* node, int keepPos=1)=0; //关联子结点
void Detach(TimeValue t, int keepPos=1)=0; //解除与父结点层次关系
int IsRootNode(); //判断是否为场景根结点
篇章二、开始导入
骨骼动画可以由四部分组成 骨骼、网格、蒙皮、材质,下面我将分四部分介绍整个骨骼动画的导入流程。
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">if(false == OnImportBone(fp,gi))
- {
- return false;
- }
- if(false == OnImportMesh(fp,gi))
- {
- return false;
- }
- if(false == OnImportSkin(fp,gi))
- {
- return false;
- }
- if(false == OnImportTexture(fp,gi))
- {
- return false;
- }</SPAN>
if(false == OnImportBone(fp,gi))
{
return false;
}
if(false == OnImportMesh(fp,gi))
{
return false;
}
if(false == OnImportSkin(fp,gi))
{
return false;
}
if(false == OnImportTexture(fp,gi))
{
return false;
}
一 、骨骼
要导入骨骼得先创建骨骼对象,通过Interface *i这个对象可以调用到3DS MAX中的全部方法,下面是创建对象的方法:
Object *pBone = (Object*)i->CreateInstance(GEOMOBJECT_CLASS_ID ,BONE_OBJ_CLASSID);
这个函数两个参数的类型分别为:
Class_ID:唯一标识一个插件类,SClass_ID:指定插件类的超类。
BONE_OBJ_CLASSID:表示这个插件对象为一个骨骼对象
GEOMOBJECT_CLASS_ID:表示这个对象将显示为几何形状
INode *pNode = i->CreateObjectNode(pBone);//通过对象获得节点
pNode->SetName(boneName);//设置节点名称
pNode->SetNodeTM(0,tm);//设置节点的变换矩阵,这个矩阵是相对于世界坐标原点的
通过上述方法就创建完了一个节点,一个骨骼显然是有许多个节点构成的,那么我们就要为创建的节点简历骨骼的层次结构,在3DS MAX场景中跟节点是一直存在的,可以通过Interface类的方法GetRootNode获取。
attachNode = i->GetRootNode();//获得跟节点
attachNode->AttachChild(pNode);//将刚刚创建的节点挂载在根节点下
通过这种方法可以创建一个完整的骨骼层次结构。
通过上述方法构建了一帧骨骼的形状,接下来介绍怎么导入多帧骨骼:
- <SPAN style="FONT-FAMILY: 'Microsoft YaHei'; FONT-SIZE: 14px">//设置帧范围
- Interval AnimateRange = Interval(0,(numFrame-1)*frame_time);
- gi->SetAnimRange(AnimateRange);
- /*gi->SetTime(5*frame_time);*///定位到当前帧
- //设置每一帧的时间间隔
- SetTicksPerFrame(frame_time);
- //不管有没有开启动画先停止当前动画
- SuspendAnimate();
- //开启当前动画,进行每一帧骨骼层次的录制
- AnimateOn();//
- //开启了动画就可以导入整个骨骼层次的数据了
- for(iFrame = 0; iFrame < numFrame; iFrame++)
- {
- fscanf(fp, "f ");
- for(iNode = 0; iNode < boneVec.size(); iNode++)
- {
- fscanf(fp,"%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,",
- &point[0].x,&point[1].x,&point[2].x,&point[3].x,&point[0].y,&point[1].y,&point[2].y,&point[3].y,&point[0].z,&point[1].z,&point[2].z,&point[3].z);
- CurrentMatrix = Matrix3(point[0],point[1],point[2],point[3]);
- Matrix3 MtrixX = (CurrentMatrix)*(boneVec.at(iNode)->GetParentNode()->GetNodeTM(iFrame*frame_time));
- boneVec[iNode]->SetNodeTM(iFrame*frame_time,MtrixX);
- boneVec[iNode]->ShowBone(1);
- boneVec[iNode]->SetBoneNodeOnOff(true,iFrame*frame_time);
- }
- fscanf(fp, "\n");
- }
- //导入完别忘了重新开启当前动画
- ResumeAnimate();</SPAN>
//设置帧范围
Interval AnimateRange = Interval(0,(numFrame-1)*frame_time);
gi->SetAnimRange(AnimateRange);
/*gi->SetTime(5*frame_time);*///定位到当前帧
//设置每一帧的时间间隔
SetTicksPerFrame(frame_time);
//不管有没有开启动画先停止当前动画
SuspendAnimate();
//开启当前动画,进行每一帧骨骼层次的录制
AnimateOn();//
//开启了动画就可以导入整个骨骼层次的数据了
for(iFrame = 0; iFrame < numFrame; iFrame++)
{
fscanf(fp, "f ");
for(iNode = 0; iNode < boneVec.size(); iNode++)
{
fscanf(fp,"%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,",
&point[0].x,&point[1].x,&point[2].x,&point[3].x,&point[0].y,&point[1].y,&point[2].y,&point[3].y,&point[0].z,&point[1].z,&point[2].z,&point[3].z);
CurrentMatrix = Matrix3(point[0],point[1],point[2],point[3]);
Matrix3 MtrixX = (CurrentMatrix)*(boneVec.at(iNode)->GetParentNode()->GetNodeTM(iFrame*frame_time));
boneVec[iNode]->SetNodeTM(iFrame*frame_time,MtrixX);
boneVec[iNode]->ShowBone(1);
boneVec[iNode]->SetBoneNodeOnOff(true,iFrame*frame_time);
}
fscanf(fp, "\n");
}
//导入完别忘了重新开启当前动画
ResumeAnimate();
这样骨骼部分就算导入完成了,附一张效果图:
二、网格
所谓网格即铺在骨骼上的一层外衣了,但是这件外衣其实是独立存在的,就像一个人有很多件外衣我们想穿哪件就可以披上哪一件,披上哪一件外衣呢,这个就涉及到第三节所写的蒙皮过程了。衣服还可以涂上不同的颜色,这就涉及到第四节提示的材质了。这一节不介绍穿衣过程,也不介绍衣服着色,只介绍织衣服的过程。既然要织衣服就肯定要知道衣服的结构了,下面介绍下衣服(网格)的几何结构。
网格顶点
网格在3DS MAX中的代表的类为Mesh,mesh的顶点数组成员为verts,verts为顶点三维坐标的一维数组, Mesh::numVerts为顶点的数目。下面介绍一些网格顶点相关的方法:
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">int getNumVerts(); //返回当前顶点的数目
- BOOL Mesh::setNumVerts(int ct, BOOL keep = FALSE, BOOL synchSel = TRUE); //该方法用来设置网格顶点的数目,它负责为网格重新分配顶点数组,而keep则标识是否保留以前的顶点数据;synchSel标识是否重置顶点选择状态位数组的长度,这个变量通常为TRUE。
- void setVert(int i, const Point3& xyz); //设置顶点的坐标
- void setVert(int i, float x, float y, float z); //设置顶点坐标
- Point3& getVert(int i); //得到顶点坐标
- Point3* getVertPtr(int i); //得到顶点坐标的指针 </SPAN>
int getNumVerts(); //返回当前顶点的数目
BOOL Mesh::setNumVerts(int ct, BOOL keep = FALSE, BOOL synchSel = TRUE); //该方法用来设置网格顶点的数目,它负责为网格重新分配顶点数组,而keep则标识是否保留以前的顶点数据;synchSel标识是否重置顶点选择状态位数组的长度,这个变量通常为TRUE。
void setVert(int i, const Point3& xyz); //设置顶点的坐标
void setVert(int i, float x, float y, float z); //设置顶点坐标
Point3& getVert(int i); //得到顶点坐标
Point3* getVertPtr(int i); //得到顶点坐标的指针
面片(Face)
Mesh网格中的面片均为三角形,有专门的Face类描述,Face类记录了一个面片的三个顶点在顶点数组中的索引,三条边的可见性、该面的可见性、所属的光滑组、以及关联的材质ID。而在Mesh类中,面片数组为faces,它是一个Face类型的数组,数组长度由Mesh::numFaces决定。相关联的方法有:
int Mesh::getNumFaces() const //返回网格面片的数目
BOOL Mesh::setNumFaces(int ct, BOOL keep=FALSE, BOOL synchSel=TRUE); //设置面片数目并分配面片数组
面片数组分配后,可以设置面片对应的顶点索引,该方法由Face类提供:
void Face::setVerts(int a, int b, int c);
当然Face类提供了设置边可见性、面可见性、光滑组、关联材质ID的存取方法。在Mesh类中,仅仅存在这样一个Face数组,有关面片具体的信息完全由Face类记录。有些人可能奇怪既然有了网格顶点不就就可以构成网格了为什么要有面片的,这是为第四节的衣服着色做准备,面片用来记录了材质顶点与网格顶点一一对应的关系,为着色提供精准的控制。
材质顶点
Mesh也提供了几个设置材质顶点的方法
BOOLsetNumTVerts(int ct, BOOL keep=FALSE);//用来设置材质顶点的个数
voidsetTVert(int i, float x, float y, float z); //设置材质顶点
法线
这里法线不做介绍,请自行查阅相关资料。
介绍完了这么多开始织衣服咯:
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">TriObject *triObj = new TriObject;//创建三角形对象
- INode *pNode = gi->CreateObjectNode((Object *)triObj);//获得对象节点
- gi->GetRootNode()->AttachChild(pNode);//载入场景中
- pNode->SetName(buf);//设置网格对象节点名称</SPAN>
TriObject *triObj = new TriObject;//创建三角形对象
INode *pNode = gi->CreateObjectNode((Object *)triObj);//获得对象节点
gi->GetRootNode()->AttachChild(pNode);//载入场景中
pNode->SetName(buf);//设置网格对象节点名称
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">Mesh &mesh = ((TriObject*)pNode->GetObjectRef())->GetMesh();//获取网格对象Mesh
- </SPAN>
Mesh &mesh = ((TriObject*)pNode->GetObjectRef())->GetMesh();//获取网格对象Mesh
衣服的雏形创建出来了下面开始添砖加瓦:
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">if(false == OnImportVertex(fp,mesh))
- {
- return false;
- }
- if(false == OnImportNormal(fp,mesh))
- {
- return false;
- }
- if(false == OnImportTVert(fp,mesh))
- {
- return false;
- }
- if(false == OnImportFace(fp,mesh))
- {
- return false;</SPAN>
if(false == OnImportVertex(fp,mesh))
{
return false;
}
if(false == OnImportNormal(fp,mesh))
{
return false;
}
if(false == OnImportTVert(fp,mesh))
{
return false;
}
if(false == OnImportFace(fp,mesh))
{
return false;
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">bool OnImportVertex(FILE *fp,Mesh &mesh)
- {
- //获取顶点个数
- int vNum = 0;
- fscanf(fp,"v %d\n",&vNum);
- _cprintf("rvnum:%d\n",vNum);
- //设置顶点个数
- mesh.setNumVerts(vNum);
- _cprintf("vnum:%d\n",mesh.getNumVerts());
- //设置顶点坐标
- Point3 v_point;
- for (int i = 0;i<vNum;i++)
- {
- //依次读取顶点坐标
- fscanf(fp,"%f,%f,%f\n",&v_point.x,&v_point.y,&v_point.z);
- mesh.setVert(i,v_point);
- }
- return true;
- }
- </SPAN>
bool OnImportVertex(FILE *fp,Mesh &mesh)
{
//获取顶点个数
int vNum = 0;
fscanf(fp,"v %d\n",&vNum);
_cprintf("rvnum:%d\n",vNum);
//设置顶点个数
mesh.setNumVerts(vNum);
_cprintf("vnum:%d\n",mesh.getNumVerts());
//设置顶点坐标
Point3 v_point;
for (int i = 0;i<vNum;i++)
{
//依次读取顶点坐标
fscanf(fp,"%f,%f,%f\n",&v_point.x,&v_point.y,&v_point.z);
mesh.setVert(i,v_point);
}
return true;
}
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">bool OnImportTVert(FILE *fp,Mesh &mesh)
- {
- //设置材质
- int tvNum = 0;
- fscanf(fp,"t %d\n",&tvNum);
- _cprintf("tvNum:%d\n",tvNum);//读取网格纹理顶点数目
- mesh.setNumTVerts(tvNum); //设置网格纹理顶点数目
- float x = 0.0f,y = 0.0f,z = 0.0f;
- for (int i = 0;i<tvNum;i++)
- {
- fscanf(fp,"%f,%f,%f\n",&x,&y,&z); //依次读取纹理顶点坐标
- mesh.setTVert(i,x,y,z);
- }
- return true;</SPAN>
bool OnImportTVert(FILE *fp,Mesh &mesh)
{
//设置材质
int tvNum = 0;
fscanf(fp,"t %d\n",&tvNum);
_cprintf("tvNum:%d\n",tvNum);//读取网格纹理顶点数目
mesh.setNumTVerts(tvNum); //设置网格纹理顶点数目
float x = 0.0f,y = 0.0f,z = 0.0f;
for (int i = 0;i<tvNum;i++)
{
fscanf(fp,"%f,%f,%f\n",&x,&y,&z); //依次读取纹理顶点坐标
mesh.setTVert(i,x,y,z);
}
return true;
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">bool OnImportFace(FILE *fp,Mesh &mesh)
- {
- int fNum = 0;
- fscanf(fp,"f %d\n",&fNum);
- mesh.setNumFaces(fNum);
- mesh.setNumTVFaces(fNum);
- int x = 0, y = 0, z = 0;
- int nx = 0, ny = 0, nz = 0;
- int tx = 0, ty = 0, tz = 0;
- for (int i = 0;i < fNum;i++)
- {
- fscanf(fp, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &x, &nx, &tx, &y, &ny, &ty, &z, &nz, &tz);
- mesh.faces[i].setVerts(x,y,z);//设置网格顶点
- mesh.faces[i].setSmGroup(1);//设置光滑组
- mesh.faces[i].setEdgeVisFlags(1,1,0);//设置可见边
- mesh.tvFace[i].setTVerts(tx,ty,tz);//设置用来映射的材质顶点
- }
- return true;</SPAN>
bool OnImportFace(FILE *fp,Mesh &mesh)
{
int fNum = 0;
fscanf(fp,"f %d\n",&fNum);
mesh.setNumFaces(fNum);
mesh.setNumTVFaces(fNum);
int x = 0, y = 0, z = 0;
int nx = 0, ny = 0, nz = 0;
int tx = 0, ty = 0, tz = 0;
for (int i = 0;i < fNum;i++)
{
fscanf(fp, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &x, &nx, &tx, &y, &ny, &ty, &z, &nz, &tz);
mesh.faces[i].setVerts(x,y,z);//设置网格顶点
mesh.faces[i].setSmGroup(1);//设置光滑组
mesh.faces[i].setEdgeVisFlags(1,1,0);//设置可见边
mesh.tvFace[i].setTVerts(tx,ty,tz);//设置用来映射的材质顶点
}
return true;
衣服织完了。附一张效果图:
三、蒙皮
1、获取蒙皮修改器的接口指针
要判断一个对象上是不是有Skin修改器,根据3DSMax几何管线的结构,需要遍历整个修改器堆栈ModStack中的派生对象Derived Object。判断应用在这些Derived Object上的修改器的类型。3DSMax中的所有对象都有一个唯一的标记ClassID,Skin修改器的ClassID=SKIN_CLASSID。在获得的Derived Object的修改器后只需检查修改器的ClassID即可。示例代码如下:
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">/*
- 函数功能:获取Skin修改器的接口指针
- 参数:skinNode为所要查看是否有Skin修改器的节点
- 返回值:如果节点的下的引用对象有Skin修改器,返回接口指针;如果无,返回NULL
- */
- Modifier *GetSkinMod(INode* skinNode)
- {
- if(!skinNode)
- {
- return NULL;
- }
- Object *obj = skinNode->GetObjectRef(); //获取节点的对象引用
- if (!obj)
- {
- return NULL;
- }
- if(obj->SuperClassID()==GEN_DERIVOB_CLASS_ID)
- {
- IDerivedObject *pDerivedObj = NULL;
- pDerivedObj = static_cast<IDerivedObject*>(obj); //转换为派生对象
- int nMod = pDerivedObj->NumModifiers(); //获取修改器堆栈中包含的修改器数目
- Modifier *pModifier = NULL;
- for (int i = 0; i<nMod; i++)
- {
- pModifier = pDerivedObj->GetModifier(i);
- if (pModifier->ClassID() == SKIN_CLASSID) //是蒙皮修改器
- {
- return pModifier;
- }
- }
- }
- return NULL;
- }</SPAN>
/*
函数功能:获取Skin修改器的接口指针
参数:skinNode为所要查看是否有Skin修改器的节点
返回值:如果节点的下的引用对象有Skin修改器,返回接口指针;如果无,返回NULL
*/
Modifier *GetSkinMod(INode* skinNode)
{
if(!skinNode)
{
return NULL;
}
Object *obj = skinNode->GetObjectRef(); //获取节点的对象引用
if (!obj)
{
return NULL;
}
if(obj->SuperClassID()==GEN_DERIVOB_CLASS_ID)
{
IDerivedObject *pDerivedObj = NULL;
pDerivedObj = static_cast<IDerivedObject*>(obj); //转换为派生对象
int nMod = pDerivedObj->NumModifiers(); //获取修改器堆栈中包含的修改器数目
Modifier *pModifier = NULL;
for (int i = 0; i<nMod; i++)
{
pModifier = pDerivedObj->GetModifier(i);
if (pModifier->ClassID() == SKIN_CLASSID) //是蒙皮修改器
{
return pModifier;
}
}
}
return NULL;
}
2、ISkin类简介
根据上一节获取的Skin修改器可以得到ISkin接口指针,方法如下:
ISkin *pSkin = (ISkin *)skinMod->GetInterface(I_SKIN);
下面介绍一下ISkin类的一些主要的方法。
1. virtual int GetNumBones()=0;
功能:返回此蒙皮修改器中的骨骼节点数目;
2. virtual INode *GetBone(int idx)=0;
功能:获取蒙皮修改器中指定索引的骨骼节点指针;
1. virtual DWORD GetBoneProperty(int idx)=0;
功能:获取指定骨骼节点的属性;
3. virtual ISkinContextData *GetContextInterface(INode *pNode)=0;
功能:获取指定节点的蒙皮数据的接口指针
4. virtual TCHAR *GetBoneName(int index) = 0;
功能:获得质点骨骼节点的名字;
5. virtual Matrix3 GetBoneTm(int id) = 0;
功能:获取指定骨骼节点的矩阵
3、ISkinContextData类简介
ISkinContextData类提供了一个接口以获取蒙皮数据,这些数据以顶点蒙皮权重表的形式存储。每一个顶点都包含了影响这个顶点的骨骼索引及其对应的影响权重值。
下面介绍一下该接口提供的一些常用的方法:
1. virtual int GetNumPoints()=0;
功能:返回蒙皮修改器影响的顶点数目;
2. virtual int GetNumAssignedBones(int vertexIdx)=0;
功能:返回影响指定顶点的骨骼数目;
3. virtual int GetAssignedBone(int vertexIdx, int boneIdx)=0;
功能:返回影响指定顶点的某根骨骼的索引值。
4. virtual float GetBoneWeight(int vertexIdx, int boneIdx)=0;
功能:返回影响指定顶点的骨骼的权重值。
4、ISkinImportData类简介
ISkinImportData接口用于向蒙皮修改器中导入蒙皮数据,包括添加蒙皮的骨骼节点、设置顶点的蒙皮权重等。
通过下面的方式可以获取该接口的指针:
ISkin *pSkin = (ISkin *)skinMod->GetInterface(I_SKIN);
ISkinImportData *iSkinImport = NULL;
iSkinImport = (ISkinImportData*)skinMod->GetInterface(I_SKINIMPORTDATA);
下面介绍一下该接口的一些常用的方法:
1. virtual BOOL AddBoneEx(INode *node, BOOL update) = 0;
功能:向Skin修改器中添加骨骼节点。
2. virtual BOOL AddWeights(INode *skinNode, int vertexID, Tab<INode*> &boneNodeList, Tab<float> &weights)=0;
功能:设置蒙皮修改器中指定顶点的蒙皮权重。注意,weights表中所有值之和必须为1.0,否则设置权重会失败!
导入蒙皮代码:
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">bool OnImportTexture(FILE *fp, Interface *gi)
- {
- // Create a material to the node
- StdMat2 *mtl = NewDefaultStdMat();
- float gr, gg, gb, dr, dg, db, rr, rg, rb, a;
- float s;
- fscanf(fp, "g %f %f %f\n", &gr, &gg, &gb); // 全局系数
- Color ambient(gr, gg, gb);
- mtl->SetAmbient(ambient,0);
- fscanf(fp, "d %f %f %f\n", &dr, &dg, &db); // 漫射光系数
- Color diffuse(dr,dg,db);
- mtl->SetDiffuse(diffuse,0);
- fscanf(fp, "r %f %f %f\n", &rr, &rg, &rb); //高光系数
- Color specular(rr,rg,rb);
- mtl->SetSpecular(specular,0);
- fscanf(fp, "a %f\n", &a); //透明度
- /*mtl->Set*/
- fscanf(fp, "s %f\n", &s); //光滑度
- mtl->SetShininess(s,0);
- // Set The Texture
- BitmapTex *bmtex = (BitmapTex*)NewDefaultBitmapTex();
- bmtex->SetName(buf);
- bmtex->SetMapName(buf);
- mtl->SetSubTexmap(ID_DI,bmtex);
- mtl->SetSubTexmap( ID_OP, bmtex);
- mtl->EnableMap( ID_OP, FALSE );
- mtl->SetActiveTexmap( bmtex );
- mtl->SetMtlFlag(MTL_TEX_DISPLAY_ENABLED);
- INode *pNode = gi->GetRootNode()->GetChildNode(1);
- // 给网格对象设置材质
- pNode->SetMtl(mtl);
- // 设置完之后重绘,才能看到材质效果贴图
- gi->RedrawViews(gi->GetTime());
- return true;
- }</SPAN>
bool OnImportTexture(FILE *fp, Interface *gi)
{
// Create a material to the node
StdMat2 *mtl = NewDefaultStdMat();
float gr, gg, gb, dr, dg, db, rr, rg, rb, a;
float s;
fscanf(fp, "g %f %f %f\n", &gr, &gg, &gb); // 全局系数
Color ambient(gr, gg, gb);
mtl->SetAmbient(ambient,0);
fscanf(fp, "d %f %f %f\n", &dr, &dg, &db); // 漫射光系数
Color diffuse(dr,dg,db);
mtl->SetDiffuse(diffuse,0);
fscanf(fp, "r %f %f %f\n", &rr, &rg, &rb); //高光系数
Color specular(rr,rg,rb);
mtl->SetSpecular(specular,0);
fscanf(fp, "a %f\n", &a); //透明度
/*mtl->Set*/
fscanf(fp, "s %f\n", &s); //光滑度
mtl->SetShininess(s,0);
// Set The Texture
BitmapTex *bmtex = (BitmapTex*)NewDefaultBitmapTex();
bmtex->SetName(buf);
bmtex->SetMapName(buf);
mtl->SetSubTexmap(ID_DI,bmtex);
mtl->SetSubTexmap( ID_OP, bmtex);
mtl->EnableMap( ID_OP, FALSE );
mtl->SetActiveTexmap( bmtex );
mtl->SetMtlFlag(MTL_TEX_DISPLAY_ENABLED);
INode *pNode = gi->GetRootNode()->GetChildNode(1);
// 给网格对象设置材质
pNode->SetMtl(mtl);
// 设置完之后重绘,才能看到材质效果贴图
gi->RedrawViews(gi->GetTime());
return true;
}
蒙皮效果完成图:
四、材质
在3DS MAX中,每个结点都关联一个材质,INode::GetMtl()和INode::SetMtl()可以获取和设置结点的材质。GetMtl()返回一 个Mtl类指针,如果该方法返回NULL,则说明用户还没有为结点关联材质,这样的话渲染器会自动使用结点的线框颜色来渲染物体。GetMtl()返回的 材质可以是用户关联的任何材质,可以是3ds max中的标准材质或者是第三方开发的材质。可以查看每个材质的类标识符来区分各种不同类型的材质,比如3ds max的标准材质的类标识符为DMTL_CLASS_ID,而多材质类型材质的类标识符为MULTI_CLASS_ID.
(1) 3ds max标准材质:标准材质的ClassID为DMTL_CLASS_ID,这种材质没有子材质,但是有很多属性参数可以为开发者使用。标准材质的主要类为 StdMat,它提供了一系列方法来存取材质的属性,类如明暗度、漫反射颜色、环境光颜色、高光颜色,反光和透明度等。开发者也可以存取贴图,比如得到漫 反射纹理。(GetSubTexmap()、SetSubTexmap())。
(2) 多材质:多材质的ClassID为MULTI_CLASS_ID,多材质插件可以为不同的面片设置不同的子材质,通过索引匹配。面片的 getMatlID()和setMatlID()即是匹配子材质的方法。多材质可以有多个子材质,子材质的数目可以由NumSubMtls()获 得,GetSubMtl()/SetSubMtl()可是设置子材质。在面片匹配材质的时候MatID注意不要大于子材质的个数,也就是说,不能索引越界。
3DS MAX可以库的方式存储一组材质,称之为材质库,MtlBaseLib类,可以使用Interface类的LoadMaterialLib()、 SaveMaterialLib()方法来装载保存材质库,GetMaterialLibrary()可以返回当前正在使用的一个材质库引用 (MtlBaseLib&)。
创建不同类型的纹理实例的方法见下:
• StdMat *NewDefaultStdMat();
• MultiMtl *NewDefaultMultiMtl();
• BitmapTex *NewDefaultBitmapTex();
• MultiTex *NewDefaultCompositeTex();
• MultiTex *NewDefaultMixTex();
• MultiTex *NewDefaultTintTex();
• GradTex *NewDefaultGradTex();
• StdCubic *NewDefaultStdCubic();
• StdMirror *NewDefaultStdMirror();
• StdFog *NewDefaultStdFog();
//组合两个材质为多材质类型
• Mtl *CombineMaterials(Mtl *mat1, Mtl *mat2, int &mat2Offset);
导入材质代码:
- <SPAN style="FONT-FAMILY: Microsoft YaHei; FONT-SIZE: 14px">bool OnImportTexture(FILE *fp, Interface *gi)
- {
- // Create a material to the node
- StdMat2 *mtl = NewDefaultStdMat();
- float gr, gg, gb, dr, dg, db, rr, rg, rb, a;
- float s;
- fscanf(fp, "g %f %f %f\n", &gr, &gg, &gb); // 全局系数
- Color ambient(gr, gg, gb);
- mtl->SetAmbient(ambient,0);
- fscanf(fp, "d %f %f %f\n", &dr, &dg, &db); // 漫射光系数
- Color diffuse(dr,dg,db);
- mtl->SetDiffuse(diffuse,0);
- fscanf(fp, "r %f %f %f\n", &rr, &rg, &rb); //高光系数
- Color specular(rr,rg,rb);
- mtl->SetSpecular(specular,0);
- fscanf(fp, "a %f\n", &a); //透明度
- /*mtl->Set*/
- fscanf(fp, "s %f\n", &s); //光滑度
- mtl->SetShininess(s,0);
- // Set The Texture
- BitmapTex *bmtex = (BitmapTex*)NewDefaultBitmapTex();
- bmtex->SetName(buf);
- bmtex->SetMapName(buf);
- mtl->SetSubTexmap(ID_DI,bmtex);
- mtl->SetSubTexmap( ID_OP, bmtex);
- mtl->EnableMap( ID_OP, FALSE );
- mtl->SetActiveTexmap( bmtex );
- mtl->SetMtlFlag(MTL_TEX_DISPLAY_ENABLED);
- INode *pNode = gi->GetRootNode()->GetChildNode(1);
- // 给网格对象设置材质
- pNode->SetMtl(mtl);
- // 设置完之后重绘,才能看到材质效果贴图
- gi->RedrawViews(gi->GetTime());
- return true;
- }</SPAN>
bool OnImportTexture(FILE *fp, Interface *gi)
{
// Create a material to the node
StdMat2 *mtl = NewDefaultStdMat();
float gr, gg, gb, dr, dg, db, rr, rg, rb, a;
float s;
fscanf(fp, "g %f %f %f\n", &gr, &gg, &gb); // 全局系数
Color ambient(gr, gg, gb);
mtl->SetAmbient(ambient,0);
fscanf(fp, "d %f %f %f\n", &dr, &dg, &db); // 漫射光系数
Color diffuse(dr,dg,db);
mtl->SetDiffuse(diffuse,0);
fscanf(fp, "r %f %f %f\n", &rr, &rg, &rb); //高光系数
Color specular(rr,rg,rb);
mtl->SetSpecular(specular,0);
fscanf(fp, "a %f\n", &a); //透明度
/*mtl->Set*/
fscanf(fp, "s %f\n", &s); //光滑度
mtl->SetShininess(s,0);
// Set The Texture
BitmapTex *bmtex = (BitmapTex*)NewDefaultBitmapTex();
bmtex->SetName(buf);
bmtex->SetMapName(buf);
mtl->SetSubTexmap(ID_DI,bmtex);
mtl->SetSubTexmap( ID_OP, bmtex);
mtl->EnableMap( ID_OP, FALSE );
mtl->SetActiveTexmap( bmtex );
mtl->SetMtlFlag(MTL_TEX_DISPLAY_ENABLED);
INode *pNode = gi->GetRootNode()->GetChildNode(1);
// 给网格对象设置材质
pNode->SetMtl(mtl);
// 设置完之后重绘,才能看到材质效果贴图
gi->RedrawViews(gi->GetTime());
return true;
}
导入完成:
- <PRE class=cpp name="code" sizcache="2" sizset="14"><PRE class=cpp name="code" sizcache="2" sizset="15"><PRE class=cpp name="code"></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- </PRE></PRE>
- <PRE class=cpp name="code" sizcache="2" sizset="15"><PRE class=cpp name="code"></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- </PRE>
- <PRE class=cpp name="code"></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>
- <PRE></PRE>