3ds max sdk导出插件编写心得
2009-06-09 23:19
411 查看
本文来自:http://www.cnblogs.com/billwillman/articles/1283701.html
[转]3dsmaxsdk导出插件编写的心得
3dsmaxsdk导出插件编写的心得
作者:yhchinabest
来自:CG先生-3D图形插件开发网http://www.cgsir.com/
目录
目录
写在前面
为什么要写这个心得?去年11月份的时候我写过一篇3dsMax导出程序的一些尝试,抱着学习的态度把一些心得发到网上供大家参考,不过当时写的还很不完善,很多东西没说清楚。今年6月份开始又做了3dsMax导出导入程序的一些研究,感觉3dsMaxSDK实在是博大精深,初学者入门还是很不方便,所以觉得以前发的心得应该得到补充,因而写了这样一个导出程序介绍,还是抱着学习的态度,不过还是希望能够对大家有所帮助。由于时间精力有限,只写出导出程序的一些体会,以后会写出导入程序的体会。希望大家多批评指教。
环境配置
步骤1.首先你得有VS2005,3dsMax9,如果有就好办了,否则想办法搞到手吧,在中国做到这点应该不难。至于其他相近版本的IDE和MAX,情况基本类似。
步骤2.在3dsMax9SDK"maxsdk"howto"3dsmaxPluginWizard中有个readme.txt,它会向你介绍如何配置3dsMax9plugin的向导。
步骤3.启动vs2005,新建VisualC++项目,如果在右侧的模板组中能够找到”3dsmaxPluginWizard”,并且选择后能够弹出欢迎界面,说面配置已经成功了。
第一个导出程序
这里仅仅是为了让大家更好的了解导出插件是如何工作的,所以什么都不导出,做个测试而已。1.在pluginType中选择FileExport。选择下一步,然后给你的导出类起个名字,比如”MyExport”。选择下一步,再输入你的MAXSDK路径,插件存放的路径,3dsmax.exe存放路径。然后Finish。
2.找到classMyExport的函数constTCHAR*MyExport::Ext(intn)定义。该函数用来显示导出文件的扩展名,改一下,例如return_T(“My3D”)。
3.再找到constTCHAR*MyExport::ShortDesc()的定义,该函数显示插件的描述信息,也改一下,例如return_T(“MyExportPlugin”)
4.为了了解导出程序的入口,在函数DoExport(constTCHAR*name,ExpInterface*ei,Interface*i,BOOLsuppressPrompts,DWORDoptions)内添加:
AllocConsole(); _cprintf("ExportBegin"n");//记得#include<conio.h> |
Mesh,Material,Light,Camera,让我们找到它们
1.首先说说上一章提到的ExpInterface,在3dsMax9的SDK中找到它,可以看到,它继承于MaxHeapOperators,并包含IScene*theScene。按照它的描述,thsScene是用来枚举场景中所有node的。看来这个node就是我们要寻找的对象。先不急着看node,先来看看IScene*theScene。2.IScene有个重要的函数:intEnumTree(ITreeEnumProc*proc)。看看这个函数的描述:
Remarks: ImplementedbytheSystem.. ThismaybecalledtoenumerateeveryINodeinthescene.Thecallbackmayflaganyofthesenodes(usingINode::FlagForeground()). |
Parameters: ITreeEnumProc*proc ThiscallbackobjectiscalledonceforeachINodeinthescene. |
Returns: Nonzeroiftheprocesswasabortedbythecallback(TREE_ABORT);otherwise0. |
3.再看看ITreeEnmuProc的描述:
Description: ThisisthecallbackobjectusedbyIScene::EnumTree().Touseit,deriveaclassfromthisclass,andimplementthecallbackmethod. |
4.看来我们要写些代码了(我估计你也早等不及了),让我们写一个继承ITreeEnumProc的类:
classMyTreeEnum:publicITreeEnumProc { public: MyTreeEnum(void); ~MyTreeEnum(void); public: intcallback(INode*node); }; |
intMyTreeEnum::callback(INode*node) { ObjectStateos=node->EvalWorldState(10); if(os.obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID,0))) { _cprintf("TRIOBJECT%s"n",node->GetName()); Mtl*pMtl=node->GetMtl(); if(pMtl) { _cprintf("MATERIAL%s"n",pMtl->GetName()); } returnTREE_CONTINUE; } if(os.obj) { switch(os.obj->SuperClassID()) { caseCAMERA_CLASS_ID: _cprintf("CAMERA%s"n",node->GetName()); break; caseLIGHT_CLASS_ID: _cprintf("LIGHT%s"n",node->GetName()); break; } } returnTREE_CONTINUE; } |
intMaxExportTest::DoExport(constTCHAR*name,ExpInterface*ei,Interface*i,BOOLsuppressPrompts,DWORDoptions) { MyTreeEnumtempProc; ei->theScene->EnumTree(&tempProc); returnTRUE; } |
5.让我们再来看这些代码,首先来关注INode,根据3dsmaxsdk的说明,INode是场景中每个结点的接口,它可以代表不同类型的物体,如几何体,灯光,摄像机。要访问这些物体,你就得调用INode::EvalWorldState,它会返回一个ObjectState,这个ObjectState又包含一个Object*obj,越说越复杂了,这两个类型都很重要,但我们现在只需要这个obj来帮助我们判断当前传入的node是属于什么类型。这就需要两个函数,canConvertToType()和SuperClassID(),它们是obj的成员函数。在这之前,先看看SuperClassID和ClassID,这是SuperClassID的一段摘要:
GEOMOBJECT_CLASS_ID-Usedbygeometricobjects. CAMERA_CLASS_ID-Usedbyplug-incameras. LIGHT_CLASS_ID-Usedbyplug-inlights. SHAPE_CLASS_ID-Usedbysplineshapes. HELPER_CLASS_ID-Usedbyhelperobjects. SYSTEM_CLASS_ID-Usedbysystemplug-ins. OSM_CLASS_ID-UsedbyObjectSpaceModifiers. WSM_CLASS_ID-UsedbySpaceWarpModifiers(WorldSpaceModifiers). |
SubclassesofGEOMOBJECT_CLASS_ID Builtintocore TRIOBJ_CLASS_ID-TriObject PATCHOBJ_CLASS_ID-PatchObject SubclassesofLIGHT_CLASS_ID: OMNI_LIGHT_CLASS_ID-OmniLight SPOT_LIGHT_CLASS_ID-SpotLight DIR_LIGHT_CLASS_ID-DirectionalLight FSPOT_LIGHT_CLASS_ID-FreeSpotLight TDIR_LIGHT_CLASS_ID-TargetDirectionalLight |
GEOMOBJECT_CLASS_ID
了,我不知道3dsMax为什么要这样设计,所以我只好用canConverToType来判断这个物体是否为三角网物体。
6.好了,我们大概找到了我们需要的东西,下一章,我会示范如何从这些较大的范围中得到我所感兴趣的具体的信息,如灯光的位置和方向,以及最重要的Mesh的顶点信息等。
Mesh,Material,Light,Camera,让我们解析他们
1.首先说说mesh和material吧,这两者结合相当密切。上一章说到如何获得TriObject,通过它可以获得一个mesh:Mesh*pMesh=&tri->GetMesh();
在SDK里查看mesh的描述,发现它可以导出很多信息,而我们一般希望从mesh中获得顶点坐标,法线向量,纹理坐标,顶点颜色等信息,以及顶点的索引值。对于只贴了一个纹理的mesh,我们可以简单的获得这些信息。
Mesh*pMesh=&tri->GetMesh(); intVerticesNum=pMesh->getNumVerts() for(inti=0;i<VerticesNum;i++) { Point3Coord,Normal,TCoord,VColor; if(pMesh->getNumVerts()>0)//导出顶点坐标 { Coord=pMesh->getVert(i); } if(pMesh->faces)//导出法线向量 { Normal=pMesh->getNormal(j); } if(pMesh->getNumTVerts()>0)//导出纹理坐标 { TCoord=pMesh->tVerts[j]; } if(pMesh->vertCol)//导出顶点颜色 { VColor=pMesh->vertCol[i]; } } |
Mtl*pMtl=pNode->GetNode if(pMtl!=NULL) { Texmap*pTexMap=pMtl->GetSubTexmap(ID_DI);//获取漫反射材质的贴图 BitmapTex*pBMPTex=(BitmapTex*)pTexMap; if(pBMPTex) { char*MapName=pBMPTex->GetMapName();//获取漫反射贴图的路径 } } |
先说说具体思想,假设一个立方体每个面都贴了一张纹理,那么可以把这个mesh看作划分了6个子mesh,一个子mesh就是一个面。首先,遍历原来mesh的所有面,计算非重复的材质ID。从而得知这个mesh的子mesh个数。然后再次遍历原来mesh所有的面,将所有具有相同材质ID的面的顶点集合到一个子mesh,这些顶点只存储该顶点在原mesh中的索引。当然,需要重新计算这些顶点在子mesh里的索引值。因此再遍历原mesh的所有面。
structFaceVertex { intm_Index;//表示该顶点在原mesh里的索引值 intm_FaceIndex;//表示该顶点所属的面在原mesh里的索引值 intm_TriIndex;//表示该顶点在所属三角形里的索引值,值为0,1,2 booloperator==(constFaceVertex&refVertex) { if(m_Index==refVertex.m_Index) { returntrue; } else { returnfalse; } } }; classMaxDivideMesh { public: vector<FaceVertex>m_VertexArray; vector<int>m_IndexArray; }; voidMyTreeEnum::CreateMutilMesh(INode*pNode,Mesh*pMesh,Mtl*pMtl) { vector<int>MeshMtls;//该Mesh用到的子材质的数量,用来计算子Mesh的划分 //每个元素表示一个材质ID。 for(inti=0;i<pMesh->getNumFaces();i++) { /*计算子Mesh数量,通过计算所有面使用的非重复材质数量而得*/ intMatID=pMesh->getFaceMtlIndex(i); vector<int>::iteratorMatIndex=find(MeshMtls.begin(),MeshMtls.end(),MatID); if(MatIndex==MeshMtls.end()) { MeshMtls.push_back(MatID);//该材质未在MeshMtls里出现过,说明是个 //新材质 } } //DivideMeshArray,计算Mesh划分的拓扑信息 vector<MaxDivideMesh>DivideMeshArray; DivideMeshArray.resize(MeshMtls.size());//指定划分数量 // //此处有内存的分配 //GMeshD3D是我自己设计的一个类型,用来表示一个子Mesh //GTextureD3D用来表示Texture GMeshD3D*pMeshArray=newGMeshD3D[MeshMtls.size()]; GTextureD3D*pTextureArray=newGTextureD3D[MeshMtls.size()]; GObjectMAXD3DtempObj;//GObjectMAXD3D表示一个模型,有n个mesh和texture组成 tempObj.SetMeshNum(MeshMtls.size()); tempObj.SetTextureNum(MeshMtls.size()); for(inti=0;i<MeshMtls.size();i++) { if(pMtl!=NULL) { Mtl*pSubMtl=pMtl->GetSubMtl(MeshMtls[i]); Texmap*pTexMap=pSubMtl->GetSubTexmap(ID_DI);//获取漫反射材质的贴//图 BitmapTex*pBMPTex=(BitmapTex*)pTexMap; if(pBMPTex) { char*MapName=pBMPTex->GetMapName();//获取漫反射贴图名称 if(MapName!=NULL) { pTextureArray[i].SetMapName(MapName); } } pMeshArray[i].m_MatID=i; tempObj.SetTexture(&pTextureArray[i],i); } } /*这里开始对原有mesh进行重新划分*/ for(inti=0;i<pMesh->getNumFaces();i++) { intMatID=pMesh->getFaceMtlIndex(i);//计算该面的材质ID vector<int>::iteratorMatIndex=find(MeshMtls.begin(),MeshMtls.end(),MatID); intMeshID=MatIndex-MeshMtls.begin();//计算该MatID在TextureArray的纹理索引,使MeshID从0开始编号 for(intj=0;j<3;j++) { intIndex=pMesh->faces[i].v[j];//Index表示在全局顶点数组里的索引 FaceVertextempVertex; tempVertex.m_Index=Index; vector<FaceVertex>::iteratorVertexIter=find(DivideMeshArray[MeshID].m_VertexArray.begin(), DivideMeshArray[MeshID].m_VertexArray.end(),tempVertex); if(VertexIter==DivideMeshArray[MeshID].m_VertexArray.end()) //在DivideMeshArray里寻找顶点索引值相同的顶点,如果没找到该顶点,表示//要添加该顶点 { intVertexIndex=VertexIter-DivideMeshArray[MeshID].m_VertexArray.begin(); FaceVertextempFVertex; tempFVertex.m_Index=Index; tempFVertex.m_FaceIndex=i; tempFVertex.m_TriIndex=j; DivideMeshArray[MeshID].m_VertexArray.push_back(tempFVertex); } } } /*计算顶点在每个子mesh中的索引*/ for(inti=0;i<pMesh->getNumFaces();i++) { intMatID=pMesh->getFaceMtlIndex(i); vector<int>::iteratorMatIndex=find(MeshMtls.begin(),MeshMtls.end(),MatID); intMeshID=MatIndex-MeshMtls.begin();//计算该MatID的纹理索引 for(intj=0;j<3;j++) { intIndex=pMesh->faces[i].v[j]; FaceVertextempVertex; tempVertex.m_Index=Index; //若在子mesh里能找到该点,则计算该点在子mesh的索引 vector<FaceVertex>::iteratorVertexIter=find(DivideMeshArray[MeshID].m_VertexArray.begin(),DivideMeshArray[MeshID].m_VertexArray.end(),tempVertex); if(VertexIter!=DivideMeshArray[MeshID].m_VertexArray.end()) { intVertexIndex=VertexIter-DivideMeshArray[MeshID].m_VertexArray.begin();//VertexIndex表//示该顶点在子Mesh的索引 DivideMeshArray[MeshID].m_IndexArray.push_back(VertexIndex); } } } /*余下部分开始到处顶点信息*/ for(inti=0;i<MeshMtls.size();i++) { pMeshArray[i].SetFVF(GFVF); pMeshArray[i].SetVerticeNum(DivideMeshArray[i].m_VertexArray.size()); intVerticesNum; pMeshArray[i].GetVerticeNum(VerticesNum); for(intj=0;j<VerticesNum;j++) { GVertextempVertex; Point3Coord,Normal,TCoord; if(pMesh->getNumVerts()>0)//导出顶点坐标 { intindex=DivideMeshArray[i].m_VertexArray[j].m_Index; Coord=pMesh->getVert( DivideMeshArray[i].m_VertexArray[j].m_Index); tempVertex.PosCoord=D3DXVECTOR3(Coord.x,Coord.y,-Coord.z); } if(pMesh->faces)//导出法线向量 { Normal=pMesh->getNormal(DivideMeshArray[i].m_VertexArray[j].m_Index); tempVertex.NormalVector=D3DXVECTOR3(Normal.x,Normal.y,Normal.z); } if(pMesh->getNumTVerts()>0)//导出纹理坐标 { FaceVertextempFVertex=DivideMeshArray[i].m_VertexArray[j]; TCoord=pMesh->tVerts[pMesh->tvFace[tempFVertex.m_FaceIndex].getTVert(tempFVertex.m_TriIndex)]; intTCoordIndex=pMesh->tvFace[tempFVertex.m_FaceIndex].getTVert(tempFVertex.m_TriIndex); _cprintf("TextureCoordIndex%d"n",TCoordIndex); tempVertex.TexCoord=D3DXVECTOR2(TCoord.x,TCoord.y); } DWORDVColor=0xffffffff; tempVertex.Color=VColor; pMeshArray[i].SetVertex(tempVertex,j); } pMeshArray[i].SetFaceNum(DivideMeshArray[i].m_IndexArray.size()/3); WORDFaceNum; pMeshArray[i].GetFaceNum(FaceNum); for(intj=0;j<FaceNum;j++) { pMeshArray[i].SetIndex(DivideMeshArray[i].m_IndexArray[j*3],j*3); pMeshArray[i].SetIndex(DivideMeshArray[i].m_IndexArray[j*3+2],j*3+1); pMeshArray[i].SetIndex(DivideMeshArray[i].m_IndexArray[j*3+1],j*3+2); } } tempObj.WritetoFile(); delete[]pMeshArray; delete[]pTextureArray; } |
voidMyTreeEnum::CreateCamera(INode*pNode) { ObjectStateos=pNode->EvalWorldState(10); CameraObject*CameraObj=(CameraObject*)os.obj; structCameraStatecs; Intervalvalid=FOREVER; CameraObj->EvalCameraState(10,valid,&cs); Matrix3SourceMat=pNode->GetNodeTM(10);//获取摄像机源点的变换矩阵 Matrix3destMatrix; pNode->GetTargetTM(0,destMatrix);//获取摄像机目标点的变换矩阵 cs.fov;//获取FOV角 cs.hither;//获取摄像机近平面 cs.yon;//获取摄像机远平面 } |
voidMyTreeEnum::CreateLight(INode*pNode) { ObjectStateos=pNode->EvalWorldState(10); GenLight*light=(GenLight*)os.obj; structLightStatels; Intervalvalid=FOREVER; light->EvalLightState(10,valid,&ls); Matrix3SourceMat=pNode->GetNodeTM(10); Matrix3TargetMat; pNode->GetTargetTM(10,TargetMat); floatTheta,Phi; Theta=ls.hotsize; Phi=ls.fallsize; switch(ls.type)//导出灯光类型 { caseOMNI_LIGHT:_cprintf("%s"n","ID_LIGHT_TYPE_OMNI");break; caseTSPOT_LIGHT:_cprintf("%s"n","ID_LIGHT_TYPE_TARG");break; caseDIR_LIGHT:_cprintf("%s"n","ID_LIGHT_TYPE_DIR");break; caseFSPOT_LIGHT:_cprintf("%s"n","ID_LIGHT_TYPE_FREE");break; } } |
相关文章推荐
- [转]3ds max sdk导出插件编写的心得
- 3ds max sdk导出插件编写的心得
- 3DS Max8 导出插件编写配置经验
- 通过编写3ds max插件导出模型数据的一个demo
- 第一次编写3dMAX 导出插件
- 第一次编写max场景导出插件的经验分享(仅限第一次写max插件的兄弟姐妹)
- 3DS Max 2010简单导出插件开发(实例开发)
- 3ds Max SDK 导入插件的一些技术障碍记录
- C#为IE编写BHO插件心得
- 3ds max 导出插件 中的多重材质问题。
- 导出ECLIPSE中自己编写的插件
- 万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》
- 引擎设计跟踪(九.2) 3DS MAX 导出插件 继续
- 3DsMax导出插件编写
- 3DsMax导出插件编写-(1)环境搭建
- 3DS MAX9 导出插件 pMesh->GetNormal 无法获得正确的法线??
- 引擎设计跟踪(九.3) 3DS MAX 导出插件 小总结
- 3ds Max插件开发(八)编写.Net Plug-ins
- 3D MAX导出插件编写I
- 3ds max 导出插件——md5Exporter中篇