【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 2565|回复: 0

[转帖]詳細講解3DMAX導出插件-tiamo (3D)

[复制链接]
发表于 2006-10-13 13:35:48 | 显示全部楼层 |阅读模式
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.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.m_fEaseOut;
  float e1 = Keys[i+1].m_fEaseIn;
  float s = e0 + e1;
  if (s > 1.0)
  {
  e0 /= s;
  e1 /= s;
  }
  Keys.m_fEase0 = e0;
  Keys.m_fEase1 = e1;
  Keys.m_fEaseK = 1.0f / (2.0f - e0 - e1);
  if ( e0 != 0.0f )
  {
  Keys.m_fEaseKOverEase0 = Keys.m_fEaseK / e0;
  }
  if ( e1 != 0.0f )
  {
  Keys.m_fEaseKOverEase1 = Keys.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
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 06:56

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表