開發3dMax插件的方法和應用


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

0
0
0
3dmax的導出插件是用來把做好的3d模型導出成自己引擎需要的格式的一個dll,它由3dmax加載調用.具體怎樣去寫一個插件,小T不多說,在3dmax的sdk里面有比較詳細的介紹,在google上面也能搜索到不少的源代碼,這里說的只是3dmax的數據組織方式,以及怎么獲取轉換3dmax的數據.
  
  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
0
0

再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM