DirectX的OBJ模型加載與渲染
來源:程序員人生 發布時間:2015-03-11 08:41:20 閱讀次數:3937次
在之前的DirectX例子里我用的模型是.x文件,DirectX有1個方法D3DXLoadMeshFromX可以加載.x模型,但是這里有個問題,.x文件是沒法用文本編輯器打開查看結構的,這里我來演示1下如何解析.obj模型.
首先讓我們看1下.obj模型的組成部份和結構,1個完全的obj模型1共分為3個部份:obj模型文件,mtl材質文件,紋理貼圖;其中obj文件和mtl文件是可以用文本編輯器打開的,先打開obj文件,可以看到這樣的內容:
v ⑶.000767 2.993211 2.014205
v ⑶.000767 -0.006789 2.014205
v ⑵.750767 2.993211 2.014205
v ⑵.750767 -0.006789 2.014205
v ⑵.750767 2.993211 2.014205
v ⑵.750767 -0.006789 2.014205
v ⑵.750767 2.993211 ⑴.985795
v ⑵.750767 -0.006789 ⑴.985795
v ⑵.750767 2.993211 ⑴.985795
vt 0.948633 0.500977
vt 0.948633 0.000977
vt 0.998633 0.500977
vt 0.998633 0.000977
vt 0.000000 0.500000
vt 0.000000 0.000000
vt 1.000000 0.500000
vt 1.000000 0.000000
vt 1.000000 0.501343
vt 0.000000 0.501343
vt 1.000000 0.438843
vn 0.000000 0.000000 1.000000
vn 1.000000 0.000000 0.000000
vn 0.000000 0.000000 ⑴.000000
vn ⑴.000000 0.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 ⑴.000000 0.000000
vn -0.000000 -0.707107 -0.707107
vn 0.000000 0.707107 0.707107
vn -0.000000 0.707107 -0.707106
g Left
usemtl wood
s 1
f 1/1/1 2/2/1 3/3/1
f 2/2/1 4/4/1 3/3/1
s 2
f 5/5/2 6/6/2 7/7/2
f 6/6/2 8/8/2 7/7/2
s 1
f 9/3/3 10/4/3 11/1/3
f 10/4/3 12/2/3 11/1/3
s 2
f 13/7/4 14/8/4 15/5/4
f 14/8/4 16/6/4 15/5/4
s 3
f 17/9/5 18/10/5 19/11/5
f 18/10/5 20/12/5 19/11/5
f 21/10/6 22/9/6 23/12/6
f 22/9/6 24/11/6 23/12/6
讓我來解釋1下字段:
首先v和其后3個值表示1個頂點的xyz坐標值;
vt和其后兩個或3個值表示頂點的紋理坐標uv(w);
vn和其后3個值表示頂點的法向量;
g表示1組面;
usemtl表示這個組用的mtl文件里那個材質的名稱;
f及其后3組值表示1個面的3個 頂點/紋理/法線 在之前v,vt,vn集合里邊的索引值.
打開mtl文件就會看到:
newmtl wood
illum 2
Kd 0.800000 0.800000 0.800000
Ka 0.200000 0.200000 0.200000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ns 0.000000
map_Ka house/house.bmp
map_Kd house/house.bmp
newmtl后面的是材質名對應obj文件usemtl后面跟的值;
為了簡單代碼里只用到了map_Kd,它表示漫反射所使用的紋理名稱;
其他的是光照屬性,代碼里采取默許材質的光照屬性;
為了解析模型,首先要把材質文件給解析出來,把材質名稱與紋理名稱放入數組,這樣解析obj的時候通過材質名稱就可以夠找到對應材質數組的下標就可以找到對應的紋理:
void MtlObj::getLineNum() {
ifstream infile(path.c_str()); //打開指定文件
string sline;//每行
while(getline(infile,sline)) {//從指定文件逐行讀取
if(sline[0]=='n'&&sline[1]=='e')//newmtl
mtlNum++;
}
infile.close();
}
void MtlObj::readfile() {
getLineNum();
names=new string[mtlNum];
textures=new string[mtlNum];
int n=0;
int t=0;
ifstream infile(path.c_str()); //打開指定文件
string sline;//每行
string value,name,texture;
while(getline(infile,sline)) {//從指定文件逐行讀取
if(sline!="") {
istringstream ins(sline);
ins>>value;
if(value=="newmtl") {
ins>>name;
names[n]=name;
n++;
} else if(value=="map_Kd") {
ins>>texture;
textures[t]=texture;
t++;
}
}
}
infile.close();
}
int MtlObj::getIndexByName(string name) {
int index=⑴;
for(int i=0;i<mtlNum;i++) {
if(names[i]==name) {
index=i;
break;
}
}
return index;
}
準備好了材質信息以后開始解析obj文件:
void ModelObj::getLineNum() {
ifstream infile(path.c_str()); //打開指定文件
string sline;//每行
while(getline(infile,sline)) {//從指定文件逐行讀取
if(sline[0]=='v') {
if(sline[1]=='n')
vnNum++;
else if(sline[1]=='t')
vtNum++;
else
vNum++;
}
if(sline[0]=='f')
fNum++;
}
infile.close();
ifstream ifile(path.c_str());
string value,um,group,face;
mtArr=new string[fNum];
groupArr=new int[fNum];
groupNum=0;
int fi=0;
while(getline(ifile,sline)) {
istringstream ins(sline);
ins>>value;
if(value=="usemtl") {
ins>>um;
int mtlId=mtl->getIndexByName(um);
groupMtlMap.insert(pair<int,int>(groupNum,mtlId));
} else if(value=="g") {
ins>>group;
groupNum++;
} else if(value=="f") {
ins>>face;
mtArr[fi]=um;
groupArr[fi]=groupNum;
fi++;
}
}
ifile.close();
}
通過材質名字查找到該材質在之前的材質數組中的id,這邊需要groupMtlMap保存面組id與材質id,那樣在渲染時就能夠通過面組id找到對應的紋理貼圖.
獲得基本信息后讀入文件的詳細內容:
void ModelObj::readfile() {
getLineNum();
vertices=new NormalTexVertex[fNum*3];
indices=new int[fNum*3];
//new2維數組
vArr=new float*[vNum];
for (int i=0;i<vNum;i++)
vArr[i]=new float[3];
vnArr=new float*[vnNum];
for (int i=0;i<vnNum;i++)
vnArr[i]=new float[3];
vtArr=new float*[vtNum];
for (int i=0;i<vtNum;i++)
vtArr[i]=new float[3];
fvArr=new int*[fNum];
ftArr=new int*[fNum];
fnArr=new int*[fNum];
for (int i=0;i<fNum;i++) {
fvArr[i]=new int[3];
ftArr[i]=new int[3];
fnArr[i]=new int[3];
}
ifstream infile(path.c_str());
string sline;//每行
int ii=0,tt=0,jj=0,kk=0;
std::string s1;
float f2,f3,f4;
while(getline(infile,sline)) {
if(sline[0]=='v') {
if(sline[1]=='n') {//vn
istringstream ins(sline);
ins>>s1>>f2>>f3>>f4;
vnArr[ii][0]=f2;
vnArr[ii][1]=f3;
vnArr[ii][2]=f4;
ii++;
} else if(sline[1]=='t') {//vt
istringstream ins(sline);
ins>>s1>>f2>>f3>>f4;
vtArr[tt][0]=f2;
vtArr[tt][1]=1-f3;
vtArr[tt][2]=f4;
tt++;
} else {//v
istringstream ins(sline);
ins>>s1>>f2>>f3>>f4;
vArr[jj][0]=f2;
vArr[jj][1]=f3;
vArr[jj][2]=f4;
jj++;
}
}
if (sline[0]=='f') { //存儲面
istringstream in(sline);
float a;
in>>s1;//去掉f
int i,k;
for(i=0;i<3;i++) {
in>>s1;
//取出第1個頂點和法線索引
a=0;
for(k=0;s1[k]!='/';k++)
a=a*10+(s1[k]⑷8);
fvArr[kk][i]=a;
a=0;
for(k=k+1;s1[k]!='/';k++)
a=a*10+(s1[k]⑷8);
ftArr[kk][i]=a;
a=0;
for(k=k+1;s1[k];k++)
a=a*10+(s1[k]⑷8);
fnArr[kk][i]=a;
}
kk++;
}
}
infile.close();
}
這里需要面數3倍的頂點,由于有n個3角形就有n*3個頂點,由于頂點之間可能不是同享法線和紋理坐標數據,因此不同3角形在同1個位置的頂點要分開來寄存;
由于DirectX的紋理坐標軸v是朝下而模型的v坐標軸朝上,那末讀取的紋理坐標v必須變成1-v才能有DirectX正確渲染,因而就有類似vtArr[tt][1]=1-f3這樣的做法;
渲染的時候需要索引指針,大小是模型面的數量*3;
然后通過各種索引組裝3角形:
void ModelObj::initTriangles() {
for (int i=0;i<fNum;i++) {
int v1Index=i*3;
int v2Index=i*3+1;
int v3Index=i*3+2;
vertices[v1Index].x=vArr[fvArr[i][0]⑴][0];
vertices[v1Index].y=vArr[fvArr[i][0]⑴][1];
vertices[v1Index].z=vArr[fvArr[i][0]⑴][2];
vertices[v1Index].nx=vnArr[fnArr[i][0]⑴][0];
vertices[v1Index].ny=vnArr[fnArr[i][0]⑴][1];
vertices[v1Index].nz=vnArr[fnArr[i][0]⑴][2];
vertices[v1Index].u=vtArr[ftArr[i][0]⑴][0];
vertices[v1Index].v=vtArr[ftArr[i][0]⑴][1];
vertices[v2Index].x=vArr[fvArr[i][1]⑴][0];
vertices[v2Index].y=vArr[fvArr[i][1]⑴][1];
vertices[v2Index].z=vArr[fvArr[i][1]⑴][2];
vertices[v2Index].nx=vnArr[fnArr[i][1]⑴][0];
vertices[v2Index].ny=vnArr[fnArr[i][1]⑴][1];
vertices[v2Index].nz=vnArr[fnArr[i][1]⑴][2];
vertices[v2Index].u=vtArr[ftArr[i][1]⑴][0];
vertices[v2Index].v=vtArr[ftArr[i][1]⑴][1];
vertices[v3Index].x=vArr[fvArr[i][2]⑴][0];
vertices[v3Index].y=vArr[fvArr[i][2]⑴][1];
vertices[v3Index].z=vArr[fvArr[i][2]⑴][2];
vertices[v3Index].nx=vnArr[fnArr[i][2]⑴][0];
vertices[v3Index].ny=vnArr[fnArr[i][2]⑴][1];
vertices[v3Index].nz=vnArr[fnArr[i][2]⑴][2];
vertices[v3Index].u=vtArr[ftArr[i][2]⑴][0];
vertices[v3Index].v=vtArr[ftArr[i][2]⑴][1];
indices[i*3]=v1Index;
indices[i*3+1]=v2Index;
indices[i*3+2]=v3Index;
}
clearTriangles();
}
組裝結束,清除讀取文件用的內存:
void ModelObj::clearTriangles() {
for(int i=0;i<vNum;i++)
delete[] *(vArr+i);
for(int i=0;i<vnNum;i++)
delete[] *(vnArr+i);
for(int i=0;i<vtNum;i++)
delete[] *(vtArr+i);
for(int i=0;i<fNum;i++) {
delete[] *(fvArr+i);
delete[] *(ftArr+i);
delete[] *(fnArr+i);
}
delete[] vArr;
delete[] vnArr;
delete[] vtArr;
delete[] fvArr;
delete[] ftArr;
delete[] fnArr;
delete[] mtArr;
}
然后建立1個網格對象,把這些數據塞進去:
ObjModel::ObjModel(ModelObj* obj) {
objLoader=obj;
D3DXCreateMeshFVF(objLoader->fNum,objLoader->fNum*3,D3DXMESH_MANAGED,normalTexVertFvf,d3d,&mesh);
initVertices();
initTextures();
DWORD* aAdjacency=new DWORD[objLoader->fNum*3];
mesh->GenerateAdjacency(0.001,aAdjacency);
mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT|D3DXMESHOPT_VERTEXCACHE,aAdjacency,NULL,NULL,NULL);
delete[] aAdjacency;
}
void ObjModel::initVertices() {
NormalTexVertex* vertices=NULL;
mesh->LockVertexBuffer(0,(void**)&vertices);
for(int i=0;i<objLoader->fNum*3;i++)
vertices[i]=objLoader->vertices[i];
mesh->UnlockVertexBuffer();
WORD* indices=NULL;
mesh->LockIndexBuffer(0,(void**)&indices);
for(int i=0;i<objLoader->fNum*3;i++)
indices[i]=objLoader->indices[i];
mesh->UnlockIndexBuffer();
DWORD* attributes=NULL;
mesh->LockAttributeBuffer(0,&attributes);
for(int i=0;i<objLoader->fNum;i++)
attributes[i]=objLoader->groupArr[i]⑴;
mesh->UnlockAttributeBuffer();
}
接著通過之前的材質數組創建需要的紋理對象:
void ObjModel::initTextures() {
objLoader->mtl->getLength(mtlNum);
textures=new LPDIRECT3DTEXTURE9[mtlNum];
for(int i=0;i<mtlNum;i++) {
string texFile=TEX_PATH+objLoader->mtl->textures[i];
D3DXCreateTextureFromFile(d3d,texFile.c_str(),&textures[i]);
}
}
這下數據都準備好了,渲染吧:
void ObjModel::render() {
for(DWORD i=0;i<(DWORD)objLoader->groupNum;i++) {
map<int,int>::iterator itor=objLoader->groupMtlMap.find((int)(i+1));
int mtlId=itor->second;
d3d->SetTexture(1,textures[mtlId]);
mesh->DrawSubset(i);
}
}
最后創建對象并渲染之:
void initObjModel() {
objLoader=new ModelObj(MODEL_TANK,MTL_TANK);
objModel=new ObjModel(objLoader);
}
void renderObjModel() {
objModel->render();
}
void releaseObjModel() {
delete objModel;
delete objLoader;
}
我導入了1個坦克模型,終究效果就像這樣:
模型加載器的代碼已寫好,下載地址: 點擊下載
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈