在三維虛擬互動場景中,存在着由大量的靜止物件構成的背景。如室內場景中的桌子、牆壁、壁飾等。室外場景中的樓房、馬路、草坪等。這些一般是由建模軟件構建好以後,再導入場景中的。經常使用的建模軟件如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.rarhtml
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/jiangjunshowgit