在三维虚拟互动场景中,存在着由大量的静止物件构成的背景。如室内场景中的桌子、墙壁、壁饰等。室外场景中的楼房、马路、草坪等。这些通常是由建模软件构建好之后,再导入场景中的。常用的建模软件如Max、Maya都提供了SDK以快速开发导出插件,来导出这些物件的位置、材质、光照以及其它信息。在我的应用中,利用Max的SDK开发了两个版本的插件,分别导出不同的内容以供渲染使用。
场景导出dle插件导出的信息包括:相机、光源、几何体、单位、背景等信息。几何体信息包括:位置、法向量、纹理坐标、材质和纹理等信息。支持Max中包括:Stand、BakeShell、Multi/Sub-object、Blend、Matte/Shadow、Double Sided、Composite、Top_Bottom等8种材质和bitmap、Mask、Checker、Marble 3D、Mix、Noise、Gradient、Tint、Reflect/refract、Flat mirror、Composite、RGB
Multiply、Falloff、Output、Plate glass、Vertex Color等16种纹理信息的导出。
动画导出dlu插件导出的信息包括:相机动画、轨迹动画、Bezier/TCB/采样关键帧动画、Bone/Physique骨骼动画、逐帧动画。
使用插件导出场景后,渲染的示意图如下:
场景渲染图一 场景渲染图二
关键帧动画渲染图 骨骼动画渲染图
开发Max插件,通常的做法就是阅读SDK Help和SDK sample的示例代码以及AutoDesk的官方帮助网站。对于导出插件而言,这些就足够了。再根据自己项目的需要,有针对性的做一些研究就够了。这里需要注意的几点是:1,场景导出后,根据Max坐标系和渲染使用的坐标系之间的不同,进行相应的坐标转换。2,最好同时导出相机信息和单位信息,这样,在显示和合并场景时,就不会出现有些物件忽大忽小,或者压根就看不见的情况。3,物件相应的文件,如:几何物件的材质、纹理文件,最好打包后随身携带。以免显示时,因文件丢失而显示错误。
下面给出一些链接和一些资料(文件大小限制,不能提供更多资料,另外,还有一本《如何使用3ds sdk开发程序》的超星格式电子书,有需要的请跟我联系)。希望对大家有所帮助。
开发资料链接:http://kniffo.maxscript.de/tut/3dsmax_plugin/
http://sparks.autodesk.com/
http://sparks.autodesk.com/search/ 搜索 How To Write An Exporter
开发资料下载:http://files.cnblogs.com/dscky/MaxSdkDev.rar
3dmax里面一个比较重要的概念就是INode,3dmax的场景模型都是由一个个的INode组成,这些INode构成一棵体系树,而各个真实的模型都是附着到一个INode上面的,3dmax的sdk提供了怎样获取INode指针,怎样获取INode的几个Matrix的方法,这个能在max的sdk里面找到,也不是小T这次主要谈的东西.获取了相应的Matrix以后,用INode的EvalWorldState等等函数就能获取到附着在这个INode上面的geom object,然后能获取到vertex信息,face信息,material信息,这些都相对容易,随便的一个导出插件的例子都会有提到这些方法,小T也不多少.说了半天,小T究竟想说什么呢?嘿嘿.一个是skin mesh的weight数据获取,一个是keyframe的control数据获取以及3dmax的几种不同的control的keyframe的插值方法.
先说skin mesh的weight table数据.X文件的导出插件里面使用的skin工具属于charactor studio(cs)的一个部分,小T没有找到合适的cs安装,所以小T自己的插件不准备支持cs,小T推荐的也是唯一支持的工具是3dmax5自带的skin工具.下面说的就是skin工具的数据获取.skin这个工作在3dmax里面被称为了modifier,3dmax对于每一个object都维护一个modifier stack(关于这个方面的详细信息可以查看3dmax的sdk,或者使用google),现在首先要作的就是获取到skin这个modifier的接口指针ISkin.--->使用GetModifier函数一一遍历每个modifier,检查它的class id是不是SKIN_CLASSID,然后调用GetInterface获得ISkin的指针,通过这个指针调用GetContextInterface获取ISkinContextData指针,这个指针里面就维护了weight table.首先调用ISkinContextData指针的GetNumAssignedBones,传人vertex的id(从face的数据里面获得这个id),得到了影响这个vertex的bone的数目,然后从0到bone数目减1,一一调用GetAssignedBone,传人vertex的id和bone index,得到bone id,然后使用ISkin的GetBone传人bone id获得bone的INode指针,然后调用ISkinContextData的GetBoneWeight传人vertex的id和bone的index,就能获得weight数据.有点乱,贴代码上来.
// get weights ,CFace is a class that hold face info,i0,i1,i2 is face's vertexes id
void CExporter::GetWeights(CFace* pFace,INode *pNode,Mesh *pMesh,int i0,int i1,int i2)
{
// find skin modifier
Object *pObject = pNode->GetObjectRef();
if (pObject->SuperClassID() == GEN_DERIVOB_CLASS_ID)
{
IDerivedObject *pDerivedObject = (IDerivedObject *)pObject;
int nMod = pDerivedObject->NumModifiers();
for(int i = 0; i < nMod; i++)
{
Modifier *pModifier = pDerivedObject->GetModifier(i);
if (pModifier->ClassID() == SKIN_CLASSID)
{
ISkin *pSkin = (ISkin*)pModifier->GetInterface(I_SKIN); // get ISkin interface
if(pSkin)
{
ISkinContextData* pSkinContext = pSkin->GetContextInterface(pNode); // get context interface
int nBones,j;
// bones
nBones = pSkinContext->GetNumAssignedBones(i0);// param is vertex id,use pmaxMesh->faces[i].v[0]
for(j = 0; j < nBones; j ++)
{
int nBoneIndex = pSkinContext->GetAssignedBone(i0,j);
// FindNode is function that take a INode pointer reture a index id.
pFace->m_vertex[0].m_ltWeights.push_back(std::make_pair(FindNode(pSkin->GetBone(nBoneIndex)),
pSkinContext->GetBoneWeight(i0,j)));
}
nBones = pSkinContext->GetNumAssignedBones(i1);
// ........same for i1 and i2
}
}
}
}
skin mesh 的weight数据就算是获取完成了.接下来的是3dmax的control数据获取.这个部分是整个3dmax里面最为隐讳的一个部分,它的格式只有在3dmax的debug sdk里面才有,而这个debug sdk是要钱的,小T现在可没有那个能力支付多少多少的美圆..嘿嘿.下来的这些资料来自小T从网上收集到的各个open source的3d引擎的源代码,有一小部分是小T自己研究的结果.先列出资料的来源.首先的一个是魔兽的mdl导出插件'DeX.http://republicola.wc3campaigns.com/DeX/,然后的一个是fairy-project,还有一个就是www.nevrax.org.
3dmax里面的control有很多很多,小T只是打算支持主要的3种,linear,bezier和tcb control.下面一个一个的讲.
linear是最简单的,几乎不需要讲,他使用线性插值算法.对于旋转数据使用quat的slerp算法就ok.
void CExporter::GetLinearPosition(CNode *pOurNode,INode *pMaxNode,Control *pControl,IKeyControl *pKeyControl)
{
ILinPoint3Key maxKey;
CAnimationPositionLinearKey ourKey;
for(int i = 0; i < pKeyControl->GetNumKeys(); i ++)
{
// abs position,local system
pKeyControl->GetKey(i,&maxKey);
ourKey.m_fPosition[0] = maxKey.val.x;
ourKey.m_fPosition[1] = maxKey.val.z;
ourKey.m_fPosition[2] = maxKey.val.y;
ourKey.m_nTime = maxKey.time * 1000 / TIME_TICKSPERSEC;
AddAnimationKey(pOurNode,LinearPositionKey,&ourKey);
}
// when do interpolation,key1 is prev key,key2 is next key,t is time,then the position at t is
// pos = key1.pos + (key2.pos - key1.pos)*(t - key1.time)/(key2.time - key1.time)
}
// linear rotation
void CExporter::GetLinearRotation(CNode *pOurNode,INode *pMaxNode,Control *pControl,IKeyControl *pKeyControl)
{
Matrix3 maxMatrix;
ILinRotKey maxKey;
CAnimationRotationLinearKey ourKey;
for(int i = 0; i < pKeyControl->GetNumKeys(); i ++)
{
pKeyControl->GetKey(i,&maxKey);
// this key's quat is an abs value,not a rel value...error in max sdk
// convert to matrix
maxKey.val.MakeMatrix(maxMatrix);
ConvertMaxMat2OurMat(maxMatrix,ourKey.m_matNode);
ourKey.m_nTime = maxKey.time * 1000 / TIME_TICKSPERSEC;
AddAnimationKey(pOurNode,LinearRotationKey,&ourKey);
}
// when do interpolation
// rotation is Quat::Slerp(key1.qRot,key2.qRot,(t - key1.time)/(key2.time - key1.time))
}
接下来说tcb control 这个要比linear复杂一点,tcb control使用的是hermite(埃尔米特)插值,hermite插值是指给定有限个点的值和这些点的一阶导数,构造一个多项式,在那些给定的点的值和一阶导数都和已知值相同.这个在数值分析里面有讲到,给个链接.很明显,一个物体的位置,旋转角度是一个关于时间的函数,给定一个时间,就有一个唯一的位置,一个唯一的旋转,而现在我们不可能记录任何时间的位置和旋转信息,我们只是知道在某些特定的时间点(这些点叫keyframe)的位置和旋转信息,还有这些点的导数信息,现在就要利用这些已知信息计算出任何时间点的值来.这个就叫插值.(呃,这个解释不算是完备,但是我个人觉得还是容易理解的).而利用值和导数,我们已经能用hermite插值方法计算出任何时间点的值来了,但是,实际上,获得单个点的导数信息却并不是已经很容易的事情,所以tcb就应运而生了,他并没有记录单个点的导数,而是记录了3个额外的数据,而单个点的导数信息可以通过这些已知道信息计算出来(具体的方式可以看上面的链接里面的文章),特殊的点是第一个和最后一个点,第一个点只需要计算TD的值,
float tm = 0.5f * (1.0f - firstKey->Tension);
firstKey->TD = tm * ((secondKey->Value - firstKey->Value) * 3.0f - secondKey->TS);
最后一个点计算TS的值
float tm = 0.5f * (1.0f - lastKey->Tension);
lastKey->TS = tm * ((lastKey->Value - previousLastKey->Value) * 3.0f - previousLastKey->TD);
然后,上面那个链接里面给出来的方法里面必须的数据就都差不多了,唯一例外的是那个s.表面上看s就是(t - key1.time)/(key2.time - key1.time),其实不是,在3dmax里面还有一个easeIn和easeOut数据,刚刚得到的结果还得经过一系列的计算才能作为插值参数s.方法列出来:
ease :
first calc
float e0 = Keys[i].m_fEaseOut;
float e1 = Keys[i+1].m_fEaseIn;
float s = e0 + e1;
if (s > 1.0)
{
e0 /= s;
e1 /= s;
}
Keys[i].m_fEase0 = e0;
Keys[i].m_fEase1 = e1;
Keys[i].m_fEaseK = 1.0f / (2.0f - e0 - e1);
if ( e0 != 0.0f )
{
Keys[i].m_fEaseKOverEase0 = Keys[i].m_fEaseK / e0;
}
if ( e1 != 0.0f )
{
Keys[i].m_fEaseKOverEase1 = Keys[i].m_fEaseK / e1;
}
// for the last key
m_fEaseK = 0.5f
when do ease
if(key->m_fEaseK == 0.5f)
{
// keep the same
s = t;
}
else if(t < key->m_fEase0)
{
s = key->m_fEaseKOverEase0 * t * t;
}
els
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow