[置頂] 萬圣節福利:紅孩兒3D引擎開發課程《3ds max導出插件初步》
來源:程序員人生 發布時間:2014-11-08 08:01:32 閱讀次數:2540次
紅孩兒3D引擎開發課堂 QQ群:275220292
國內最詳實教授如何開發3D引擎的地方!揭開3D引擎開發最不為人知的秘密!
萬圣節福利,國內最詳實的3ds max導出插件編程指南低級篇免費發放!

前言:今天網易的《亂斗西游》上線AppStore ,將繼完善世界《黑暗拂曉》后再次證明自研引擎的實力!如果你想成為引擎研發高手,那末,1切,將從3ds max導出插件起步~
第9章課程《3ds max導出插件初步》
1.3ds max導出插件簡介:
在游戲開發中,我們最多接觸到的資源就是模型,1款游戲的模型量是1個巨大的數字,這么多模型,只能交給美術進行制作。1般的開發流程是:美術使用3ds max或maya等建模軟件對原畫設定進行建模,以后導出相應的數據文件給游戲使用。

在這個流程里,最關鍵的問題是如何能夠將建模軟件中的模型解析到程序中,要解決這個問題,就要了解如何獲得建模轉件中編輯的模型數據并導出為文件。在3ds max的sdk中,提供有導出插件的編程框架與示例,做為1個3D引擎http://www.jyygyx.com,依照引擎的需求編寫3ds max導出插件將3ds max中的模型依照自已的需要格式導出,是非常基本和重要的工作。
比以下圖,這是1個典型的3ds max導出插件:

1般導出插件通過獲得模型數據后,可以導出的信息有:
(1).頂點位置
(2).法線向量
(3).紋理坐標
(4).貼圖名稱
(5).骨骼及蒙皮信息
等等,這些數據都通過3ds max sdk中的接口函數得到相應的頂點數據結構指針及材質結構指針獲得。
下面,我們來學習1下如作甚3ds max 編寫1個導出插件。
2.環境架設:
要為 3ds max編寫相應的導出插件,首先要根據美術需求的3ds max版本安裝3ds max 及 3ds max sdk,然后是跟據3ds max sdk的版本安裝相應的visual studio ,比如 3ds max 8要用vs2005, 3ds max 2010要用到vs2008, 3ds max 2012要用vs2010,這些都有相應的匹配,要注意根據美術的需求進行調劑相應的開發工具。
在安裝好相應的3ds max, 3ds max sdk,visual studio等軟件后,我們就能夠開始為3ds max開發導出插件了。首先是打開3ds max sdk下的howto目錄,依照readme.txt的說明為visual studio增加相應的max導出插件開發向導。
比如:
1. 將3dsmaxPluginWizard.ico, 3dsmaxPluginWizard.vsdir, 3dsmaxPluginWizard.vsz等3個文件拷到VS的VCVCProjects目錄下。
2. 將3dsmaxPluginWizard.vsz文件的只讀屬性去掉,然后修改ABSOLUTE_PATH為3ds max sdk中howto下的3dsmaxPluginWizard目錄。

保存退出后,我們打開VS,找到向導頁:

輸入你想要設定的工程名字后點擊肯定,會彈出1個對話框:

這個頁面列出了很多插件種類,我們只需要開發能進行模型的文件導出功能的插件,所以選擇“FileExport”就能夠了。

點擊“下1步”,會需要設置3ds max目錄,插件目錄和3ds max的可履行程序目錄:

注意:如果你的向導頁如上圖所示,則要求你必須手動選擇相應的路徑.你也能夠在電腦的環境變量中設置相應的路徑值.以后再創建導出插件工程時,這1向導頁會自動顯示出相應的路徑值.
選擇3個輸入框要求的路徑后點擊“Finish”,便可生成1個新的導出插件工程。
解決方案中生成的文件以下:

3.編譯運行調試:
首先編譯1下項目,榮幸的話,當前版本的VS可以順利編譯通過,但有時候也不免不太順利,比以下面這類情況:

平臺工具集要改成V100才可以順利編譯通過。
想要調試導出插件,需要設置工程->屬性->調試->命令設為3ds max的可履行程序路徑:

這樣就能夠將我們調試的導出插件加載到3ds max中,固然,1定1定要肯定當前工程的配置管理器中平臺要與3ds max,操作系統保存1致,如果你的系統是64位的,這里要改成x64,否則啟動程序后3ds max會提示“不是有效的win32程序”之類的對話框。

然后要將輸入文件設為3ds max下的plugins目錄:

以后啟動程序,如果提示“沒法找到3dsmax.exe的調試信息,或調試信息不匹配,是不是繼續調試?”,選擇“是”就能夠繼續調試了。
會發現在程序中收到斷點:

按F5后,我們會發現3ds max也啟動起來了,這樣,我們的導出插件就被3ds max加載了。
在3ds max 中創建1個立方體,然后在主菜單里選擇“導出”,以后在下拉列表中可以看到有1個(*)的奇怪文件格式,那就是我們當前調試中的導出插件所對應的文件格式,由于還沒有為導出插件設置導出文件信息,所以默許為空。

輸入1個文件名并肯定后,會進入到maxProject1::DoExport函數,這個函數即是場景導出插件類maxProject1在3ds max進行文件導出時被調用的函數了,它將是我們3ds max導出插件編程的入口函數。

按F5略過斷點后,我們可以看到彈出了1個對話框:

這個就是我們導出插件的默許導出設置對話框,它對應maxProject1.rc中的IDD_PANEL對話框資源。

通過修改這個對話框資源,我們可以在導出時進行相應的設置。
下面,我們就來嘗試導出1個簡單的模型。
4.導出1個簡單的模型到文件中:
首先,我們先修改1下設置對話框,改成這樣:

1個模型名稱的輸入框,1個顯示信息的列表框和響應“導出”和“退出”的按鈕。
然后我們在場景導出插件類maxProject1中增加1些變量保存DoExport函數傳入的參數指針變量。
private:
ExpInterface* m_pExpInterface; //導出插件接口指針
Interface* m_pInterface; //3ds max接口指針
BOOL m_exportSelected; //是不是只導出選擇項
char m_szExportPath[_MAX_PATH]; //導出目錄名
并增加1個導出場景的處理函數:
//導出模型
int ExportMesh(const char* szMeshName);
對應函數實現:
int maxProject1::ExportMesh(const char* szMeshName)
{
return 0;
}
在構造函數中進行置空設置,并在maxProject1::DoExport中加入
int maxProject1::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
{
#pragma message(TODO("Implement the actual file Export here and"))
//保存變量
strcpy(m_szExportPath,name);
m_pExpInterface = ei;
m_pInterface = i;
m_exportSelected = (options & SCENE_EXPORT_SELECTED);
...
我們可以看到maxProject1::DoExport函數中的實現就是調用創建對話框并設置對話框的消息處理函數為maxProject1OptionsDlgProc(嘿嘿,看名稱就知道是選項設置對話框):
if(!suppressPrompts)
DialogBoxParam(hInstance,
MAKEINTRESOURCE(IDD_PANEL),
GetActiveWindow(),
maxProject1OptionsDlgProc, (LPARAM)this);
我們想做到點1下點擊“肯定”就導出模型,點擊“取消”就退出對話框。首先需要在maxProject1.cpp頭部增加:
#include "resource.h"
//列表框句柄
HWND G_hListBox = NULL;
//輸出字符串到列表框
void AddStrToOutPutListBox(const char* szText)
{
if( G_hListBox )
{
SendMessage(G_hListBox,LB_ADDSTRING,0,(LPARAM)szText);
}
}
然后我們找到
INT_PTR CALLBACK maxProject1OptionsDlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
在這個函數中,為初始化消息WM_INITDIALOG增加:
imp = (maxProject1 *)lParam;
CenterWindow(hWnd,GetParent(hWnd));
G_hListBox = ::GetDlgItem(hWnd,IDC_LIST1);
// 得到文件名
std::string strPathName = imp->GetExportPathName() ;
std::string strFileName;
std::string::size_type pos1 = strPathName.find_last_of('');
std::string strFileName_NoExt;
if (pos1 != std::string::npos)
{
strFileName = strPathName.substr(pos1+1);
}
else
{
strFileName = strPathName;
}
//去掉擴大名
std::string::size_type pos2 = strFileName.find_last_of('.');
if (pos2 != std::string::npos)
{
strFileName_NoExt = strFileName.substr(0, pos2);
}
else
{
strFileName_NoExt = strFileName ;
}
//將字符串設為模型名
HWND hNameEdit = ::GetDlgItem(hWnd,IDC_EDIT1);
SetWindowText(hNameEdit,strFileName_NoExt.c_str());
同時增加WM_COMMAND消息:
case WM_COMMAND:
{
switch(wParam)
{
case IDC_BUTTON1:
{
if(imp)
{
HWND hNameEdit = ::GetDlgItem(hWnd,IDC_EDIT1);
char szMeshName[64];
GetWindowText(hNameEdit,szMeshName,64);
//導出場景
imp->ExportMesh(szMeshName);
}
}
break;
case IDC_BUTTON2:
{
//退出對話框
EndDialog(hWnd, 0);
return 0;
}
break;
}
}
break;
這樣輸入模型名稱后點擊“肯定”,我們將調用 ExportMesh 函數進行相應處理。
點擊“退出”時會退出對話框。
下面,我們來實現1下ExportMesh函數,這個函數將完成獲得模型信息,并導出為2進制文件的功能,首先我們來獲得1下模型的材質信息。
//通過m_pInterface獲得場景中的材質庫
MtlBaseLib * scenemats = m_pInterface->GetSceneMtls();
if (scenemats)
{
char tText[200];
int tCount = scenemats->Count();
sprintf(tText,"共有材質%d個",tCount);
AddStrToOutPutListBox(tText);
if(tCount > 0)
{
m_AllMaterialVec.clear();
m_AllMaterialSize = 0;
//獲得材質數量
for (int i = 0; i < tCount ; i++)
{
MtlBase * vMtl = (*scenemats)[i];
if (IsMtl(vMtl))
{
SParseMaterial* pParseMaterial = new SParseMaterial;
memset(pParseMaterial,0,sizeof(SParseMaterial));
pParseMaterial->m_MaterialID = m_AllMaterialSize++;
strcpy(pParseMaterial->m_MaterialName,vMtl->GetName());
//遍歷材質所用的貼圖
SubTextureEnum(vMtl,pParseMaterial->m_SubTextureVec,m_AllMaterialSize);
m_AllMaterialVec.push_back(pParseMaterial);
}
}
}
}
這里通過m_pInterface->GetSceneMtls()函數獲得場景中的材質庫,以后遍歷每個材質并羅列出這個材質的貼圖。為了方便羅列材質的貼圖,我們創建了1個函數 SubTextureEnum :
//子紋理羅列
BOOL maxProject1::SubTextureEnum(MtlBase * vMtl,vector<SParseTexture>& vTextureVec,int& vMaterialSize)
{
// 獲得紋理數量
int tTextureNum = vMtl->NumSubTexmaps();
//sprintf(tText,"材質%s,共有%d個貼圖",mtl->GetName(),tTextureNum);
for (int j = 0; j < tTextureNum ; j++)
{
Texmap * tmap = vMtl->GetSubTexmap(j);
if (tmap)
{
if (tmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))
{
BitmapTex *bmt = (BitmapTex*) tmap;
//紋理
SParseTexture tParseTexture;
tParseTexture.m_Index = j;
memset(tParseTexture.m_FileName,0,sizeof(tParseTexture.m_FileName));
tParseTexture.m_TexMapPtr = bmt;
std::string strMapName = bmt->GetMapName();
if (false == strMapName.empty())
{
// 得到文件名
std::string strFullName;
std::string::size_type pos = strMapName.find_last_of('');
if (pos != std::string::npos)
{
strFullName = strMapName.substr(pos+1);
}
else
{
strFullName = strMapName;
}
// 得到擴大名
std::string strEx = "png";
std::string strName = strFullName;
pos = strFullName.find_last_of(".");
if (pos != std::string::npos)
{
strEx = strFullName.substr(pos+1);
strName = strFullName.substr(0, pos);
}
// 擴大名轉小寫
transform( strEx.begin(), strEx.end(), strEx.begin(), tolower ) ;
_snprintf( tParseTexture.m_FileName, 60, "%s", strFullName.c_str());
}
vTextureVec.push_back(tParseTexture);
}
}
}
return TRUE;
}
終究我們將材質信息寄存到了m_AllMaterialVec中。
我們接著獲得模型的頂點信息和面索引信息,在3ds max中,渲染對象也是由1套結點系統來組織關系的。我們可以從根節點開始遍歷所有子結點來查詢我們需要的對象:
//獲得根節點的子節點數量
int numChildren = m_pInterface->GetRootNode()->NumberOfChildren();
if(numChildren > 0)
{
for (int idx = 0; idx < numChildren; idx++)
{
//羅列對應節點信息 NodeEnum(m_pInterface->GetRootNode()->GetChildNode(idx),NULL);
}
}
通過NodeEnum對結點進行遍歷:
//羅列結點信息
BOOL maxProject1::NodeEnum(INode* node,SMeshNode* pMeshNode)
{
if (!node)
{
return FALSE;
}
//模型體
SMeshNode tMeshNode;
// 獲得0幀時的物體
TimeValue tTime = 0;
ObjectState os = node->EvalWorldState(tTime);
// 有選擇的導出物體
if (os.obj)
{
//char tText[200];
//sprintf(tText,"導出<%s>----------------------<%d : %d>",node->GetName(),os.obj->SuperClassID(),os.obj->ClassID());
//AddStrToOutPutListBox(tText);
//獲得渲染物體的類型ID
DWORD SuperclassID = os.obj->SuperClassID();
switch(SuperclassID)
{
//基礎圖形
case SHAPE_CLASS_ID:
//網格模型
case GEOMOBJECT_CLASS_ID:
ParseGeomObject(node,&tMeshNode);
break;
default:
break;
}
}
// 遞歸導出子節點
for (int c = 0; c < node->NumberOfChildren(); c++)
{
if (!NodeEnum_Child(node->GetChildNode(c),&tMeshNode))
{
break;
}
}
if(tMeshNode.m_SubMeshVec.size() > 0)
{
//將子模型放入VEC
m_MeshNodeVec.push_back(tMeshNode);
}
return TRUE;
}
//羅列子結點信息
BOOL maxProject1::NodeEnum_Child(INode* node,SMeshNode* pMeshNode)
{
if (!node)
{
return FALSE;
}
// 獲得0幀時的物體
TimeValue tTime = 0;
ObjectState os = node->EvalWorldState(tTime);
// 有選擇的導出物體
if (os.obj)
{
char tText[200];
sprintf(tText,"導出<%s>----------------------<%d : %d>",node->GetName(),os.obj->SuperClassID(),os.obj->ClassID());
AddStrToOutPutListBox(tText);
//獲得渲染物體的類型ID
DWORD SuperclassID = os.obj->SuperClassID();
switch(SuperclassID)
{
//基礎圖形
case SHAPE_CLASS_ID:
//網格模型
case GEOMOBJECT_CLASS_ID:
ParseGeomObject(node,pMeshNode);
break;
default:
break;
}
}
// 遞歸導出子節點
for (int c = 0; c < node->NumberOfChildren(); c++)
{
if (!NodeEnum_Child(node->GetChildNode(c),pMeshNode))
{
break;
}
}
return TRUE;
}
如果我們學過結點系統,對這個子結點遍歷流程是很容易理解的。我們可以看到在3ds max中,通過結點INode調用某1幀時間的EvalWorldState函數可以獲得渲染物體,再通過渲染物體調用SuperClassID函數獲得渲染物體類型,可以判斷是不是是網絡模型。
如果是網絡模型,我們可以創建1個函數來對這個模型的信息進行讀取:
void maxProject1::ParseGeomObject(INode * node,SMeshNode* pMeshNode)
{
char tText[200];
//獲得渲染對象
TimeValue tTime = 0;
ObjectState os = node->EvalWorldState(tTime);
if (!os.obj)
return;
//如果不是有效網格模型格式,則返回。
if (os.obj->ClassID() == Class_ID(TARGET_CLASS_ID, 0))
return;
sprintf(tText,"導出對象<%s>.............",node->GetName());
AddStrToOutPutListBox(tText);
//新建1個子模型信息結構并進行填充
SSubMesh tSubMesh;
tSubMesh.m_pNode = node;
strcpy(tSubMesh.m_SubMeshName,node->GetName());
tSubMesh.m_MaterialID = ⑴;
// 獲得模型對應的材質。
Mtl * nodemtl = node->GetMtl();
if (nodemtl)
{
//獲得材質庫
MtlBaseLib * scenemats = m_pInterface->GetSceneMtls();
//遍歷材質庫,找到本結點所用的材質。
int tCount = scenemats->Count();
for(int i = 0 ; i < tCount ; i++)
{
MtlBase * mtl = (*scenemats)[i];
if(strcmp(mtl->GetName(),nodemtl->GetName()) == 0)
{
tSubMesh.m_MaterialID = i;
break;
}
}
sprintf(tText,"對應材質<%s>",nodemtl->GetName());
AddStrToOutPutListBox(tText);
}
//如果模型是由
bool delMesh = false;
Object *obj = os.obj;
if ( obj )
{
//如果當前渲染物體能轉換為網格模型
if(obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0)))
{
//將當前渲染物體能轉換為網格模型
TriObject * tri = (TriObject *) obj->ConvertToType(0, Class_ID(TRIOBJ_CLASS_ID, 0));
//如果當前渲染物體本身來是網格模型類型,它經過轉換后會生成新的網格模型。所以在處理結束后要進行釋放。
if (obj != tri)
{
delMesh = true;
}
if (tri)
{
//
CMaxNullView maxView;
BOOL bDelete = TRUE;
//通過GetRenderMesh來獲得模型信息結構。
Mesh * mesh = tri->GetRenderMesh(tTime, node, maxView, bDelete);
assert(mesh);
//重建法線
mesh->buildNormals();
//重建法線后要調用1下checkNormals檢查法線。
mesh->checkNormals(TRUE);
sprintf(tText,"模型<%s> 頂點數 :<%d> 面數:<%d>",node->GetName(),mesh->getNumVerts(),mesh->getNumFaces());
AddStrToOutPutListBox(tText);
int tVertexNum = mesh->getNumVerts();
int tFaceNum = mesh->getNumFaces();
//獲得當前結點相對中心點的矩陣信息。
Matrix3 tTMAfterWSMM = node->GetNodeTM(tTime);
//擴大成4X4矩陣
GMatrix tGMeshTM(tTMAfterWSMM);
//保存到模型信息結構的矩陣信息中。
for(int m = 0 ; m < 4 ; m++)
{
for(int n = 0 ; n < 4 ; n++)
{
tSubMesh.m_SubMeshMatrix.m[m*4+n] = tGMeshTM[m][n];
}
}
//開始獲得頂點信息結構并寄存到容器中。
vector<SVertex> tVertexVec;
//頂點信息
for (int i = 0; i < tVertexNum; i++)
{
SVertex tVertex;
//位置,要注意的是在3ds max中z值是朝上的,y值是朝前的,而在我們的游戲中,y值朝上,z值朝前。所以要做下處理。
Point3 vert = mesh->verts[i];
tVertex.m_PosX = vert.x;
tVertex.m_PosY = vert.z;
tVertex.m_PosZ = vert.y;
//法線,一樣Y軸和Z軸要切換下。
Point3 norm = mesh->getNormal(i);
tVertex.m_NPosX = norm.x;
tVertex.m_NPosY = norm.z;
tVertex.m_NPosZ = norm.y;
//頂點色
tVertex.m_Red = 1.0f;
tVertex.m_Green = 1.0f;
tVertex.m_Blue = 1.0f;
//紋理坐標
tVertex.m_U = 0.0f;
tVertex.m_V = 0.0f;
tVertexVec.push_back(tVertex);
}
//獲得頂點色信息
//如果有頂點有色采賦值。
if( mesh->numCVerts > 0)
{
//遍歷每一個3角面
for (int i = 0; i < tFaceNum; i++)
{
//色采信息也以類似頂點的方式寄存在模型的色采信息數組vertCol中,而描寫每一個3角面的3個頂點都對應色采信息數組的哪一個值,也有類似面索引的信息結構TVFace寄存在模型的vcFace數組中。
TVFace tface = mesh->vcFace[i];
//獲得色采數組中對應3角面各頂點色采值的3個索引。
int tSrcColorIndex1 = tface.getTVert(0);
int tSrcColorIndex2 = tface.getTVert(1);
int tSrcColorIndex3 = tface.getTVert(2);
//獲得模型3角面的3個索引。
int tDestColorIndex1 = mesh->faces[i].v[0];
int tDestColorIndex2 = mesh->faces[i].v[1];
int tDestColorIndex3 = mesh->faces[i].v[2];
//將色采數組vertCol中對應3角面各頂點色采的值賦值給相應的頂點。
tVertexVec[tDestColorIndex1].m_Red = mesh->vertCol[tSrcColorIndex1].x;
tVertexVec[tDestColorIndex1].m_Green = mesh->vertCol[tSrcColorIndex1].y;
tVertexVec[tDestColorIndex1].m_Blue = mesh->vertCol[tSrcColorIndex1].z;
tVertexVec[tDestColorIndex2].m_Red = mesh->vertCol[tSrcColorIndex2].x;
tVertexVec[tDestColorIndex2].m_Green = mesh->vertCol[tSrcColorIndex2].y;
tVertexVec[tDestColorIndex2].m_Blue = mesh->vertCol[tSrcColorIndex2].z;
tVertexVec[tDestColorIndex3].m_Red = mesh->vertCol[tSrcColorIndex3].x;
tVertexVec[tDestColorIndex3].m_Green = mesh->vertCol[tSrcColorIndex3].y;
tVertexVec[tDestColorIndex3].m_Blue = mesh->vertCol[tSrcColorIndex3].z;
}
}
//獲得頂點紋理坐標
//如果有頂點有紋理坐標賦值。
if( mesh->numTVerts > 0)
{
//頂點
for (int i = 0; i < tFaceNum; i++)
{
//紋理坐標信息也以類似頂點的方式寄存在模型的色采信息數組tVerts中,而描寫每一個3角面的3個頂點都對應紋理坐標信息數組的哪一個值,也有類似面索引的信息結構TVFace寄存在模型的tvFace數組中。
TVFace tface = mesh->tvFace[i];
//獲得紋理坐標數組中對應3角面各頂點紋理坐標值的3個索引。
int tSrcTexIndex1 = tface.getTVert(0);
int tSrcTexIndex2 = tface.getTVert(1);
int tSrcTexIndex3 = tface.getTVert(2);
//獲得模型3角面的3個索引。
int tDestTexIndex1 = mesh->faces[i].v[0];
int tDestTexIndex2 = mesh->faces[i].v[1];
int tDestTexIndex3 = mesh->faces[i].v[2];
//將紋理坐標數組tVerts中對應3角面各頂點紋理坐標的值賦值給相應的頂點。
SVertex tV1 = tVertexVec[tDestTexIndex1];
SVertex tV2 = tVertexVec[tDestTexIndex2];
SVertex tV3 = tVertexVec[tDestTexIndex3];
//注意:在紋理的縱向上,3ds max與我們游戲中是反的,也需要做下處理。
tV1.m_U = mesh->tVerts[tSrcTexIndex1].x;
tV1.m_V = 1.0 - mesh->tVerts[tSrcTexIndex1].y;
tSubMesh.m_VertexVec.push_back(tV1);
tV2.m_U = mesh->tVerts[tSrcTexIndex2].x;
tV2.m_V = 1.0 - mesh->tVerts[tSrcTexIndex2].y;
tSubMesh.m_VertexVec.push_back(tV2);
tV3.m_U = mesh->tVerts[tSrcTexIndex3].x;
tV3.m_V = 1.0 - mesh->tVerts[tSrcTexIndex3].y;
tSubMesh.m_VertexVec.push_back(tV3);
//將3角面索引信息保存到容器中。
SFace tFace;
tFace.m_VertexIndex1 = i*3;
tFace.m_VertexIndex2 = i*3+1;
tFace.m_VertexIndex3 = i*3+2;
tSubMesh.m_FaceVec.push_back(tFace);
}
}
else
{
//頂點
tSubMesh.m_VertexVec = tVertexVec ;
// 導出面數
for (int i = 0; i < tFaceNum; i++)
{
//將3角面索引信息保存到容器中。
SFace tFace;
tFace.m_VertexIndex1 = mesh->faces[i].v[0];
tFace.m_VertexIndex2 = mesh->faces[i].v[1];
tFace.m_VertexIndex3 = mesh->faces[i].v[2];
tSubMesh.m_FaceVec.push_back(tFace);
}
}
//如果在轉換時有新的渲染模型生成,在這里進行釋放。
if (delMesh)
{
delete tri;
}
}
}
}
//保存信息
pMeshNode->m_SubMeshVec.push_back(tSubMesh);
}
上面的代碼較長,可能不容易理解,我再詳實解釋下:
首先,1個結點的本地矩陣(即相對本身中心點的變換矩陣)通過結點的GetNodeTM可以取得,但取得的是3x3的矩陣,如果要想保存成游戲中用的Mat4這類類型,需要做下擴大。
第2,在3ds max中z值是朝上的,y值是朝前的,而在我們的游戲中,y值朝上,z值朝前。所以要做下處理。
第3,在3ds max中頂點中的信息,是每種類型都寄存在Mesh的各自信息結構容器中,通過對應的面索引結構來指定從容器的哪一個位置取出來賦值給實際的頂點。比如:
(1).頂點位置信息寄存在Mesh的verts數組中,對應的3角面索引信息寄存在Mesh的faces數組中。
(2).頂點色采信息結構寄存在Mesh的vertCol數組中,用來指定3角面的各頂點色采值對應vertCol數組哪一個結構的索引信息是寄存在Mesh的vcFace數組中。
(3).頂點紋理坐標信息結構寄存在Mesh的tVerts數組中,用來指定3角面的各頂點紋理坐標值對應tVerts數組哪一個結構的索引信息是寄存在Mesh的tvFace數組中。
OK,在完成了模型解析后,我們需要的材質,頂點,索引等信息都放在了容器中,準備好了,就開始導出!
//遍歷3ds max中的模型并導出2進制文件。
int nMeshCount = m_MeshNodeVec.size();
for(int m = 0 ; m < nMeshCount ; m++)
{
char szExportFileName[_MAX_PATH];
//如果只有1個模型,就用模型名稱。
if( 1 == nMeshCount )
{
strcpy(m_MeshNodeVec[m].m_MeshName,szMeshName);
strcpy(szExportFileName,m_szExportPath);
}
else
{
//如果有多個模型,就依照“模型名稱_序列號”的命名方式
sprintf(m_MeshNodeVec[m].m_MeshName,"%s_%d",szMeshName,m);
std::string strExportPath = m_szExportPath;
// 得到擴大名
std::string strEx = "";
std::string strName = strExportPath;
std::string::size_type pos = strExportPath.find_last_of(".");
if (pos != std::string::npos)
{
strEx = strExportPath.substr(pos+1);
strName = strExportPath.substr(0, pos);
_snprintf( szExportFileName, _MAX_PATH, "%s_%d.%s", strName.c_str(),m,strEx);
}
else
{
_snprintf( szExportFileName, _MAX_PATH, "%s_%d", strName.c_str(),m);
}
}
//進行2進制文件的寫入。
FILE* hFile = fopen(m_szExportPath,"wb");
fwrite(m_MeshNodeVec[m].m_MeshName,sizeof(m_MeshNodeVec[m].m_MeshName),1,hFile);
int nSubNum = m_MeshNodeVec[m].m_SubMeshVec.size();
fwrite(&nSubNum,sizeof(int),1,hFile);
for( int s = 0 ; s < nSubNum ; s++)
{
SSubMeshHeader tSubMeshHeader;
strcpy(tSubMeshHeader.m_SubMeshName,m_MeshNodeVec[m].m_SubMeshVec[s].m_SubMeshName);
int nMaterialID = m_MeshNodeVec[m].m_SubMeshVec[s].m_MaterialID ;
SParseMaterial* tpParseMaterial = GetMaterial(nMaterialID);
if(tpParseMaterial && false == tpParseMaterial->m_SubTextureVec.empty())
{
strcpy(tSubMeshHeader.m_Texture,tpParseMaterial->m_SubTextureVec[0].m_FileName);
}
else
{
tSubMeshHeader.m_Texture[0]='
主站蜘蛛池模板:
最新日韩在线观看视频
|
国产成人在线一区二区
|
av片在线观看
|
国产精华一区二区三区
|
青青自拍视频
|
国产精选久久
|
国产精品美女久久久久高潮
|
国产亚洲欧美一区
|
综合色婷婷一区二区亚洲欧美国产
|
男人操女人网站
|
日本 国产 欧美
|
精品视频网站在线观看
|
精一区二区
|
精品国产一区二区三区免费
|
高清18麻豆
|
www视频在线观看
|
99热只有精品在线观看
|
一区二区日韩精品
|
中文字幕亚洲欧美
|
精品国产欧美一区二区三区成人
|
国产日产久久高清欧美一区
|
日韩一区二区在线看
|
中文字幕亚洲电影
|
久久国产精|
色又黄又爽18件免费网站
|
国内在线视频
|
午夜在线小视频
|
久久久久久综合
|
小草av|
久久久久国产
|
国产精品久久久久9999
|
国产精品乱码一区二区三区
|
一区不卡
|
优优亚洲精品久久久久久久
|
成人免费国产
|
精品乱人伦一区二区三区
|
国产在线一区二区
|
亚洲乱码国产乱码精品精98午夜
|
成人h精品动漫一区二区三区
|
少妇18xxxx性xxxx片
|
97av免费视频
|