詳細講解3DMAX匯出外掛-tiamo(轉)

post0發表於2007-08-12
詳細講解3DMAX匯出外掛-tiamo(轉)[@more@]

  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.---&gt使用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.然後的一個是fairy-project,還有一個就是

  

  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;

  }

  el

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-951948/,如需轉載,請註明出處,否則將追究法律責任。

相關文章