TOP

ABOUT

PROGRAM

DIALY







メッシュを読み込む!!(1) 〜OBJファイル〜

 1.はじめに…
※<注1> 2009/03/01 一部記事とプログラムの修正を行いました。以前の記事はこちらから閲覧できます。
※<注2> 2009/03/04 プログラムを修正。四角形ポリゴンにも対応しました。

さすがにサンプル作るのに,いつも箱とか平面だとへぼいので,メッシュファイルを読み込んでみます。
使うファイルはAlias Wavefront OBJ File(*.obj)です。




 2.ファイルフォーマット
 いろいろなファイルがありますけども,そのなかでもOBJファイルはシンプルでわかりやすいほうだと思います。
 まずはファイルの読み込みをつくる前にざっとファイルフォーマットの説明をします。
 上のようなメッシュファイルを表示するためにはOBJファイルとMTLファイルの2つを使います。
OBJファイルの方は幾何形状のデータを取り扱います。要するに頂点座標とか法線ベクトルとかテクスチャ座標とか,面を構成するのに必要なデータとかそういうのが書かれています。
一方MTLファイルの方は,材質データを取り扱います。環境色とか拡散反射光とかテクスチャのファイル名とかそういうのが書かれています。
とりあえず,まずOBJファイルのほうから説明します。
ファイルフォーマットで色々と扱えるように決まっているのですが…
今回の読み込みプログラムは,三角形・四角形メッシュのみ取り扱うことにします。
使用するMTLファイル → mtllib name
頂点座標 → v x y z (w)
法線ベクトル → vn x y z
面情報 → パターン@ f v0/t0/n0 v1/t1/n1 …
      パターンA f v0//n0 v1//n1 …
      パターンB f v0/t0 v1/t1 …
      パターンC f v0 v1 …
使用する材質 → usemtl name
一番上の「使用するMTLファイル」ですが,OBJファイル側に面データがあってMTL側には面データがありません。つまり「面にこの色つかうぜぇ〜!」っていうのはOBJファイルに書かれているんですね。「じゃ,その色は具体的にどういう色なの?RGBAの値いくつぐらいなの?」と思うのですが,RGBAの値がいくつになっているのかというはMTLファイルの方に書かれているんですね。で,どうなっているかわかるようにするために,OBJファイル内にある「この色はMTLファイルの方にのっているからそっち見てけろ。」というのが
mtllib name
で定義されます。mtllibっていうのが「このMTLファイルを使うよ!」にあたり,nameにMTLファルの名がきます。

つづいて,頂点座標です。これは…
v x y z (w)
で定義されます。vが頂点座標の定義の開始を表します。x, y, z, wにはx座標の値,y座標の値,z座標の値, w座標の値がはいります。OBJファイル出力するソフトによってw座標が定義されていたり,されていなかったりします。ちなみにメタセコイアは定義されてないほうです。今回の読み込みでは,このw成分は無視することにします。

次は法線ベクトルですが,上の頂点座標とほぼ同じです。
vn x y z
で定義されます。x,y,zは法線ベクトルのx成分,y成分,z成分です。

つぎは面データです。面データは…
@ f v0/t0/n0 v1/t1/n1 v2/t2/n2 …
A f v0//n0 v1//n1 v2//n2 …
B f v0/t0 v1/t1 v2/t2 …
C f v0 v1 v2 …
という風に定義されます。面データは定義の仕方が若干複雑で,faceを表すfで定義を開始します。つぎにv0/t0/n0という風にデータが来て,これは使用する頂点の番号がv0にはいります。スラッシュをはさんで次にくるt0は使用するテクスチャ座標の番号を示し,またスラッシュをはさんでn0ときて,このn0は使用する法線ベクトルの番号を示します。
テクスチャ座標の番号と法線ベクトルの番号は使用しない場合,省略が可能となっているために上のように4パターンの定義が存在します。@はすべて使用,Aはテクスチャ座標を使わないタイプ,Bは法線を使わないタイプ,Cはテクスチャ座標と法線ベクトルを使わないタイプです。
つぎにv2/t2/n2などの後に…となっているのは面構成する要素によってこの長さが決まるためです。
たとえば,三角形の場合は f v0/t0/n0 v1/t1/n1 v2/t2/n2 という風に3つになり。
四角形の場合は f v0/t0/n0 v1/t1/n1 v2/t2/n2 v3/t3/n3 です。
五角形の場合は f v0/t0/n0 v1/t1/n1 v2/t2/n2 v3/t3/n3 v4/t4/n4 という風に5つになります。

最後は面に使用する色です。これは…
usemtl name
で定義します。usemtlはuse materialのおそらく略でしょう。nameは使用するマテリアルの名前が入ります。

実際にOBJファイルをメモ帳で開いてみると下のような感じで書いてあります。

ちなみに行の先頭に書いてある#はコメント行を示しています。


続いてMTLファイルですが,同じようにメモ帳で開いてみると下のような感じです。

上の画像を例に説明していきます。
まず新しいマテリアルは…
newmtl name
で定義します。
次にアンビエントカラー・ディフューズカラー・スペキュラーカラーは,Ka, Kd, Ksを使って定義します。
Ka r g b
Kd r g b
Ks r g b
Ka, Kd, Ksの後につづく数値はR, G, B成分を意味します。
Ns shininess
Nsは反射の強さです。プログラムで言うと所のGL_SHININESSを指定したときに使う値になります。
上のKa, Kd, Ksを見るとわかるように色の成分はR, G, Bしかありません。「アルファ値はどうやって指定するんだい?」と思われるかもしれません。
アルファ値は,キーワードd,あるいはTrを使って定義します。
d alpha
Tr alpha

今回は,テクスチャの読み込みプログラムは付属させていないのですが,テクスチャを取扱いしたい場合は,map_Kd(ディフューズマップ)のキーワードの後にテクスチャファイル名が格納されるようになっています。
map_Kd texturefilename
なので,テクスチャ名をあらかじめ読みとっておき,あとで自前のテクスチャ読み込み関数などで,保存されているテクスチャファイル名からロードしてやればいいと思います。
ざっとですが,ファイルフォーマットはこんな感じです。見てわかるように1行に頂点座標や色のみという風に1種類のデータが定義されているため,比較的ファイルの読み込みはつくりやすいです。


 3.ファイルの読み込み
 ファイルの読み込みの仕方ですが,ファイルを開いて,1行ずつ読み取って読み取った内容をバッファに格納します。あとは,行の先頭にvやらnvやらnewmtlやらのキーワードがくるので,バッファの先頭の1文字で判別して,Ka, Kd, Ksのように1文字目が同じ場合はバッファの2文字目で判別してやります。
前は,sscanf_s関数を使って,解析を行っていたんですが…
DirectXのSample Browser見てたら,OBJの読み込みのサンプルがあったので,自分で作ったやつよりもいいなぁと思ったので,参考にさせてもらうことにしました。
まずは,strcpy関数などを使うので,ヘッダーの最初でC4996警告メッセージを無効にするように下のようにdefineしておきます。
00015:  #pragma region Disable Waring C4996
00016:  //
00017:  // Disable Warning C4996
00018:  //
00019:  #ifndef _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES
00020:  #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
00021:  #endif
00022:  #ifndef _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES
00023:  #define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 1
00024:  #endif
00025:  #ifndef _CRT_SECURE_NO_WARNINGS 
00026:  #define _CRT_SECURE_NO_WARNINGS 1
00027:  #endif
00028:  #pragma endregion
00029:  
上のdefineを入れておけば,自動的にC++のオーバーロードの機能を使って,strcpy関数などをstrcpy_s関数などに置き換えてくれるそうです。詳しいことはMSDNライブラリに載っているので,確認してみてください。C++の機能を使うので,当たり前ですが,C言語では使えないので注意して下さいね。
次に,テクスチャ座標と法線ベクトル,頂点座標,色などを格納するためのベクトル構造体を定義しておきます。
00056:  /////////////////////////////////////////////////////////////////////////
00057:  // OBJVEC2
00058:  /////////////////////////////////////////////////////////////////////////
00059:  struct OBJVEC2
00060:  {
00061:  	float x;
00062:  	float y;
00063:  	OBJVEC2( float nx=0.0f, float ny=0.0f );
00064:  	operator float* ();
00065:  	operator const float* () const;
00066:  };
00067:  
00068:  /////////////////////////////////////////////////////////////////////////
00069:  // OBJVEC3
00070:  /////////////////////////////////////////////////////////////////////////
00071:  struct OBJVEC3
00072:  {
00073:  	float x;
00074:  	float y;
00075:  	float z;
00076:  	OBJVEC3( float nx=0.0f, float ny=0.0f, float nz=0.0f );
00077:  	operator float* ();
00078:  	operator const float* () const;
00079:  };
00080:  
glVertex3fv()関数やglTexCoord2fv()関数とかでも使えるように,operator float*()とoperator const float*()を定義してあります。
それと,glBegin()関数とglEnd()関数ではさんで描画するのは,パフォーマンス的に考えていかがなものなのかぁ〜と前々から思っていて,頂点バッファやらインデックスバッファやらを使える形式に直したいなぁ〜と思っていたので,今回使える形式に変更しました。そんなわけで,四角形と三角形が混じっていると,取扱いに困るので,描画は三角形メッシュしか取り扱わない仕様にしました。DirectXなんかは三角形メッシュだけしか取り扱いしないし,ゲーム機なんかでもほとんど三角形ポリゴンですよね?glBegin(GL_QUADS)で描画しても,結局GPU内部で三角形にされるんじゃないでしたっけ?…とか適当な言い訳をしておくw
 で,前まではテクスチャ座標とか法線ベクトルとか別々の配列にして格納していたんですが,下のように頂点データ構造にして定義する形にしました。
00081:  /////////////////////////////////////////////////////////////////////////
00082:  // OBJVERTEX
00083:  /////////////////////////////////////////////////////////////////////////
00084:  struct OBJVERTEX
00085:  {
00086:  	OBJVEC2 texcoord;
00087:  	OBJVEC3 normal;
00088:  	OBJVEC3 position;
00089:  	OBJVERTEX(){}
00090:  };
00091:  
 あと,今回からサブセット用の構造体を作りました。
マテリアルを切り替えるには,結構処理コストがかかったりします。できるだけ処理効率を上げたければ,マテリアルの切り替えの回数は減らした方がいいです。昔は,そんな知識無くて面一つ一つ全部にglColor4fv()やらglMaterialfv()やら,いちいちマテリアル切り替えをしていたんですが…愚の骨頂ですね。
で,そこで出てくるのがサブセットです。


上の図のように同じマテリアルを適用する面はマテリアルを切り替えずにまとめて描画してしまった方が面一つ一つに対してマテリアルを切り替えるよりは効率的になります。このように同じマテリアルを適用する面のセットをサブセットと呼ぶそうで,これを使います。
構造体は下のように定義しました。
00092:  /////////////////////////////////////////////////////////////////////////
00093:  // OBJSUBSET
00094:  /////////////////////////////////////////////////////////////////////////
00095:  struct OBJSUBSET
00096:  {
00097:  	unsigned int materialIndex;
00098:  	unsigned int faceStart;
00099:  	unsigned int faceCount;
00100:  	OBJSUBSET(){}
00101:  };
00102:  
肝心な,解析の部分なんですが,一応掲載しておきますが… まぁ,まんまって感じですけど,大丈夫かな?
まず,OBJMESHクラスの定義なんですが…下のようにしてあります。
00145:  /////////////////////////////////////////////////////////////////////////
00146:  // OBJMESH
00147:  /////////////////////////////////////////////////////////////////////////
00148:  class OBJMESH
00149:  {
00150:  private:
00151:  	OBJVERTEX* m_Vertices;
00152:  	OBJSUBSET* m_Subsets;
00153:  	OBJMATERIAL* m_Materials;
00154:  	unsigned int* m_Indices;
00155:  	unsigned int m_NumVertices;
00156:  	unsigned int m_NumSubsets;
00157:  	unsigned int m_NumMaterials;
00158:  	unsigned int m_NumIndices;
00159:  	OBJBOUNDINGBOX m_Box;
00160:  	OBJBOUNDINGSPHERE m_Sphere;
00161:  	char m_directoryPath[OBJ_NAME_LENGTH];
00162:  
00163:  	bool LoadMTLFile( const char* filename );
00164:  	bool LoadOBJFile( const char* filename );
00165:  
00166:  public:
00167:  	OBJMESH();
00168:  	~OBJMESH();
00169:  
00170:  	bool LoadFile( const char* filename );
00171:  	void Release();
00172:  	void Draw();
00173:  	unsigned int GetNumVertices();
00174:  	unsigned int GetNumSubsets();
00175:  	unsigned int GetNumMaterials();
00176:  	unsigned int GetNumIndices();
00177:  	unsigned int  GetIndexData( unsigned int index );
00178:  	unsigned int* GetIndices();
00179:  	OBJVERTEX  GetVertex( unsigned int index );
00180:  	OBJVERTEX* GetVertices();
00181:  	OBJSUBSET  GetSubset( unsigned int index );
00182:  	OBJSUBSET* GetSubsets();
00183:  	OBJMATERIAL  GetMaterial( unsigned int index );
00184:  	OBJMATERIAL* GetMaterials();
00185:  	OBJBOUNDINGBOX GetBox();
00186:  	OBJBOUNDINGSPHERE GetSphere();
00187:  };
00188:  
まぁ,データ格納する配列と読み込み用の関数と描画関数を宣言してあるだけです。
で,核となるOBJファイルの読み込みですが,LoadOBJFile()関数にて行います。定義は下のようになっとります。
00322:  //-----------------------------------------------------------------------
00323:  // Name : LoadOBJFile()
00324:  // Desc : OBJファイルの読み込み
00325:  //-----------------------------------------------------------------------
00326:  bool OBJMESH::LoadOBJFile(const char *filename)
00327:  {
00328:  	ifstream file;
00329:  
00330:  	char mtlFileName[OBJ_NAME_LENGTH] = {0};
00331:  	char buf[OBJ_BUFFER_LENGTH] = {0};
00332:  	vector<OBJVEC3> positions;
00333:  	vector<OBJVEC3> normals;
00334:  	vector<OBJVEC2> texcoords;
00335:  	vector<OBJVERTEX> t_vertices;
00336:  	vector<OBJSUBSET> t_subsets;
00337:  	vector<unsigned int> t_indices;
00338:  	bool initBox = false;
00339:  	int prevSize = 0;
00340:  
00341:  	unsigned long total = 0;
00342:  
00343:  	OBJMATERIAL material;
00344:  	unsigned int dwFaceIndex = 0;
00345:  	unsigned int dwFaceCount = 0;
00346:  	unsigned int dwCurSubset = 0;
00347:  
00348:  	// ディレクトリを切り取り
00349:  	strcpy( m_directoryPath, GetDirectoryPath( filename ) );
00350:  
00351:  	// ファイルを開く
00352:  	file.open( filename, ios::in );
00353:  
00354:  	// チェック
00355:  	if ( !file.is_open() )
00356:  	{
00357:  		cerr << "Error : ファイルオープンに失敗\n";
00358:  		cerr << "File Name : " << filename << endl;
00359:  		return false;
00360:  	}
00361:  
00362:  	// ループ
00363:  	for( ;; )
00364:  	{
00365:  		file >> buf;
00366:  		if ( !file )
00367:  			break;
00368:  
00369:  		// コメント
00370:  		if ( 0 == strcmp( buf, "#" ) )
00371:  		{
00372:  		}
00373:  
00374:  		// 頂点座標
00375:  		else if ( 0 == strcmp( buf, "v" ) )
00376:  		{
00377:  			float x, y, z;
00378:  			file >> x >> y >> z;
00379:  			OBJVEC3 v( x, y, z );
00380:  			positions.push_back( v );
00381:  
00382:  			// バウンディングボックスの初期化
00383:  			if ( !initBox )
00384:  			{
00385:  				m_Box = OBJBOUNDINGBOX( v );
00386:  				initBox = true;
00387:  			}
00388:  
00389:  			// バウンディングボックスの算出
00390:  			m_Box.Merge( v );
00391:  		}
00392:  
00393:  		// テクスチャ座標
00394:  		else if ( 0 == strcmp( buf, "vt" ) )
00395:  		{
00396:  			float u, v;
00397:  			file >> u >> v;
00398:  			texcoords.push_back( OBJVEC2( u, v ) );
00399:  		}
00400:  
00401:  		// 法線ベクトル
00402:  		else if ( 0 == strcmp( buf, "vn" ) )
00403:  		{
00404:  			float x, y, z;
00405:  			file >> x >> y >> z;
00406:  			normals.push_back( OBJVEC3( x, y, z) );
00407:  		}
00408:  
00409:  		// 面
00410:  		else if ( 0 == strcmp( buf, "f" ) )
00411:  		{
00412:  			unsigned int iPosition, iTexCoord, iNormal;
00413:  			unsigned int p[4] = {-1}, t[4] = {-1}, n[4] = {-1};	
00414:  			OBJVERTEX vertex;
00415:  			dwFaceIndex++;
00416:  			dwFaceCount++;
00417:  			int count = 0;
00418:  			unsigned int index = 0;
00419:  		
00420:  			// 三角形・四角形のみ対応
00421:  			for ( int iFace = 0; iFace < 4; iFace++ )
00422:  			{
00423:  				count++;	// 頂点数を数える
00424:  				ZeroMemory( &vertex, sizeof( OBJVERTEX ) );
00425:  
00426:  				file >> iPosition;
00427:  				vertex.position = positions[ iPosition - 1 ];
00428:  				p[iFace] = iPosition -1;
00429:  				
00430:  				if ( '/' == file.peek() )
00431:  				{
00432:  					file.ignore();
00433:  
00434:  					// テクスチャ座標インデックス
00435:  					if ( '/' != file.peek() )
00436:  					{
00437:  						file >> iTexCoord;
00438:  						vertex.texcoord = texcoords[ iTexCoord - 1 ];
00439:  						t[iFace] = iTexCoord -1;
00440:  					}
00441:  
00442:  					// 法線ベクトルインデックス
00443:  					if ( '/' == file.peek() )
00444:  					{
00445:  						file.ignore();
00446:  
00447:  						file >> iNormal;
00448:  						vertex.normal = normals[ iNormal - 1 ];
00449:  						n[iFace] = iNormal -1;
00450:  					}
00451:  				}
00452:  
00453:  				// カウントが3未満
00454:  				if ( iFace < 3 )
00455:  				{
00456:  					t_vertices.push_back( vertex );
00457:  					index = t_vertices.size()-1;
00458:  					t_indices.push_back( index );
00459:  				}
00460:  
00461:  				// 次が改行だったら終了
00462:  				if ( '\n' == file.peek() )
00463:  				{
00464:  					break;
00465:  				}
00466:  
00467:  			}
00468:  
00469:  			// 四角形ポリゴンの場合,三角形を追加する
00470:  			if ( count > 3 )
00471:  			{
00472:  				// カウント
00473:  				dwFaceIndex++;
00474:  				dwFaceCount++;
00475:  
00476:  				// 頂点とインデックスを追加
00477:  				for ( int iFace = 1; iFace < 4; iFace++ )
00478:  				{
00479:  					int j = (iFace+1)%4;
00480:  					ZeroMemory( &vertex, sizeof( OBJVERTEX ) );
00481:  
00482:  					if ( p[j] != -1 ) vertex.position = positions[ p[j] ];
00483:  					if ( t[j] != -1 ) vertex.texcoord = texcoords[ t[j] ];
00484:  					if ( n[j] != -1 ) vertex.normal = normals[ n[j] ];
00485:  
00486:  					t_vertices.push_back( vertex );
00487:  					index = t_vertices.size() - 1;
00488:  					t_indices.push_back( index );
00489:  				}
00490:  
00491:  			}
00492:  		}
00493:  
続いて,MTLファイルの読み込みですがLoadMTLFile()関数にて行います。
00541:  //-----------------------------------------------------------------------
00542:  // Name : LoadMTLFile()
00543:  // Desc : MTLファイルの読み込み
00544:  //-----------------------------------------------------------------------
00545:  bool OBJMESH::LoadMTLFile( const char* filename )
00546:  {
00547:  	char buf[OBJ_BUFFER_LENGTH] = {0};
00548:  	int iMtlCount = -1;
00549:  	ifstream file;
00550:  	vector<OBJMATERIAL> t_materials;
00551:  	OBJMATERIAL material;
00552:  	InitMaterial( &material );
00553:  
00554:  	// ファイルを開く
00555:  	file.open( filename, ios::in );
00556:  
00557:  	// チェック
00558:  	if ( !file.is_open() )
00559:  	{
00560:  		cerr << "Error : ファイルオープンに失敗しました\n";
00561:  		cerr << "File Name : " << filename << endl;
00562:  		return false;
00563:  	}
00564:  
00565:  	// ループ
00566:  	for( ;; )
00567:  	{
00568:  		file >> buf;
00569:  		if ( !file )
00570:  			break;
00571:  
00572:  		// New Material
00573:  		if ( 0 == strcmp( buf, "newmtl" ) )
00574:  		{
00575:  			iMtlCount++;
00576:  			t_materials.push_back( material );
00577:  			char strName[OBJ_NAME_LENGTH] = {0};
00578:  			file >> strName;
00579:  			strcpy( t_materials[iMtlCount].name, strName );
00580:  		}
00581:  		// Ambient Color
00582:  		else if ( 0 == strcmp( buf, "Ka" ) )
00583:  		{
00584:  			float r, g, b;
00585:  			file >> r >> g >> b;
00586:  			t_materials[iMtlCount].ambient = OBJVEC3( r, g, b );
00587:  		}
00588:  		// Diffuse Color
00589:  		else if ( 0 == strcmp( buf, "Kd" ) )
00590:  		{
00591:  			float r, g, b;
00592:  			file >> r >> g >> b;
00593:  			t_materials[iMtlCount].diffuse = OBJVEC3( r, g, b );
00594:  		}
00595:  		// Specular Color
00596:  		else if ( 0 == strcmp( buf, "Ks" ) )
00597:  		{
00598:  			float r, g, b;
00599:  			file >> r >> g >> b;
00600:  			t_materials[iMtlCount].specular = OBJVEC3( r, g, b );
00601:  		}
00602:  		// Alpha
00603:  		else if ( 0 == strcmp( buf, "d" ) ||
00604:  				  0 == strcmp( buf, "Tr" ) )
00605:  		{
00606:  			file >> t_materials[iMtlCount].alpha;
00607:  		}
00608:  		// Shininess
00609:  		else if ( 0 == strcmp( buf, "Ns" ) )
00610:  		{
00611:  			file >> t_materials[iMtlCount].shininess;
00612:  		}
00613:  		// Ambient Map
00614:  		else if ( 0 == strcmp( buf, "map_Ka" ) )
00615:  		{
00616:  			char mapKaName[OBJ_NAME_LENGTH];
00617:  			file >> mapKaName;
00618:  			SetDirectoryPath( mapKaName, m_directoryPath );
00619:  			strcpy( t_materials[iMtlCount].ambientMapName, mapKaName );
00620:  		}
00621:  		// Diffuse Map
00622:  		else if ( 0 == strcmp( buf, "map_Kd" ) )
00623:  		{
00624:  			char mapKdName[OBJ_NAME_LENGTH];
00625:  			file >> mapKdName;
00626:  			SetDirectoryPath( mapKdName, m_directoryPath );
00627:  			strcpy( t_materials[iMtlCount].diffuseMapName, mapKdName );
00628:  		}
00629:  		// Specular Map
00630:  		else if ( 0 == strcmp( buf, "map_Ks" ) )
00631:  		{
00632:  			char mapKsName[OBJ_NAME_LENGTH];
00633:  			file >> mapKsName;
00634:  			SetDirectoryPath( mapKsName, m_directoryPath );
00635:  			strcpy( t_materials[iMtlCount].specularMapName, mapKsName );
00636:  		}
00637:  		// Bump Map
00638:  		else if ( 0 == strcmp( buf, "map_Bump" ) )
00639:  		{
00640:  			char mapBumpName[OBJ_NAME_LENGTH];
00641:  			file >> mapBumpName;
00642:  			SetDirectoryPath( mapBumpName, m_directoryPath );
00643:  			strcpy( t_materials[iMtlCount].bumpMapName, mapBumpName );
00644:  		}
00645:  
00646:  		file.ignore( OBJ_BUFFER_LENGTH, '\n' );
00647:  	}
00648:  
00649:  	// ファイルを閉じる
00650:  	file.close();
00651:  
00652:  	// マテリアルデータをコピー
00653:  	m_NumMaterials = t_materials.size();
00654:  	m_Materials = new OBJMATERIAL[ m_NumMaterials ];
00655:  	for ( unsigned int i = 0; i<m_NumMaterials; i++ )
00656:  		m_Materials[i] = t_materials[i];
00657:  
00658:  	// 正常終了
00659:  	return true;
00660:  }
00661:  
上で示した,内部関数であるLoadOBJFile()関数とLoadMTLFile()関数は,publicで宣言されているLoadFile()関数内で呼び出しされるようになっています。
00662:  //-----------------------------------------------------------------------
00663:  // Name : LoadFile()
00664:  // Desc : メッシュファイルの読み込み
00665:  //-----------------------------------------------------------------------
00666:  bool OBJMESH::LoadFile( const char* filename )
00667:  {
00668:  	// OBJ, MTLファイルを読み込み
00669:  	if ( !LoadOBJFile( filename ) )
00670:  	{
00671:  		cerr << "Error : メッシュファイルの読み込みに失敗しました\n";
00672:  		return false;
00673:  	}
00674:  
00675:  	// 正常終了
00676:  	return true;
00677:  }
00678:  
00679:  	
次は描画関数なんですが,インデックスバッファを使って描画するようになっています。パフォーマンスが気になる方は頂点バッファオブジェクト(VBO)とか使うとよろしいかと思います。
00680:  //-----------------------------------------------------------------------
00681:  // Name : Draw()
00682:  // Desc : 描画処理
00683:  //-----------------------------------------------------------------------
00684:  void OBJMESH::Draw()
00685:  {
00686:  	for ( unsigned int i = 0; i<m_NumSubsets; i++ )
00687:  	{
00688:  		// マテリアル
00689:  		OBJMATERIAL* pMat = &m_Materials[m_Subsets[i].materialIndex];
00690:  		SetMaterial( pMat );	
00691:  
00692:  		// 三角形描画
00693:  		glInterleavedArrays( GL_T2F_N3F_V3F, 0, m_Vertices );
00694:  		glDrawElements( GL_TRIANGLES, m_Subsets[i].faceCount, GL_UNSIGNED_INT, &m_Indices[m_Subsets[i].faceStart] );
00695:  	}
00696:  }
00697:  


 4.表示してみる
作成したローダーを使って,表示してみます。
まずは,ヘッダーファイルをインクルードしておきます。
00011:  #pragma region Includes
00012:  //
00013:  // Includes
00014:  //
00015:  #include <iostream>
00016:  #include <cmath>
00017:  #include <GL/glut.h>
00018:  #include "Mouse.h"
00019:  #include "OBJLoader.h"
00020:  #pragma endregion
00021:  
続いて,OBJMESHクラスを宣言しておきます。
00029:  #pragma endregion Global Variables
00030:  //
00031:  // Global Variables
00032:  //

…(省略)…

00040:  OBJMESH mesh;

LoadFile()関数を呼び出して,ファイルのロードを行います。引数には,ファイル名を指定しておきます。
00109:  //----------------------------------------------------------------------------------------------------
00110:  // Name : Initialize()
00111:  // Desc : 初期化処理
00112:  //----------------------------------------------------------------------------------------------------
00113:  void Initialize()
00114:  {

… (省略) …

00127:  	// メッシュをロード
00128:  	if ( !mesh.LoadFile( "Mesh/test2.obj" ) )
00129:  		Shutdown();
00131:  }
00132:  
描画は,普通にDisplay()関数内でDraw()関数を呼び出すだけです。
00155:  //---------------------------------------------------------------------------------------------------
00156:  // Name : Display()
00157:  // Desc : ウィンドウへの描画
00158:  //---------------------------------------------------------------------------------------------------
00159:  void Display()
00160:  {

… (省略) …

00182:  	// メッシュを描画
00183:  	mesh.Draw();

… (省略) …

00195:  	// ダブルバッファ
00196:  	glutSwapBuffers();
00197:  
00198:  #if defined(DEBUG) || defined(_DEBUG)
00199:  	glutReportErrors();
00200:  #endif
00201:  }
00202:  
とりあえず,これでメッシュが表示できるようになりました。
表示速度が気になる方はディスプレイリストとかVBO(Vertexbuffer Object)とかで効率よくなるようにいじってみてください。


 Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。
プログラムの作成にはVisual Studio 2008 Professionalを用いています。