DirectX11 Tutorial 8 - 마야 2011 모델 불러오기

강좌번역/DirectX 11 2013. 2. 9. 13:46 by 빠재

원문: http://www.rastertek.com/dx11tut08.html



이 튜토리얼에서는 마야 2011(Maya 2011)에서 만든 3D 모델을 불러오는 방법을 다룹니다. 여기서는 마야에 초점을 맞추었지만, 조금 변형하여 다른 많은 3D 모델링 패키지 프로그램에도 적용할 수 있습니다.


이전 튜토리얼에서는 우리들만의 포맷을 만들고 이를 이용하여 3D 모델을 그려보았습니다. 이번의 목표는 바로 마야 2011의 모델을 우리 포맷으로 바꾸고 그것을 렌더링하는 것입니다. 마야에서 3D 모델을 만드는 방법은 이미 그에 관해 수백개가 넘는 튜토리얼들이 있기 때문에 따로 다루지 않고 이미 텍스쳐를 붙인 삼각형화된 3D 모델을 만들어서 내보낼 준비가 되었다는


여러 마야 포맷 중에서 초심자에게 좋고 읽기 쉬운 .OBJ 포맷을 사용할 것입니다.


여러분의 모델을 .obj 포맷으로 내보내기 위해서는 반드시 마야의 .obj 익스포터(exporter)을 활성화해야 합니다. 메뉴에서 Window를 선택하고, Settings/Preferences로 간 뒤에 Plug-in Manager로 이동합니다. objExport.mll까지 스크롤 하고 Loaded와 Auto load를 둘 다 선택합니다. 이제 모델을 내보내기 위해서 메뉴의 File을 선택하고 Export All을 선택합니다. 그리고 아랫쪽의 "Files of type: "에서 OBJexport를 스크롤하여 선택합니다. 파일 이름을 입력하고 Export All 버튼을 클릭하면 .obj 확장자를 가진 텍스트 파일로 모델 데이터가 내보내집니다. 파일 내용을 보고 싶다면 파일에 오른쪽 클릭을 한 뒤 "연결 프로그램"에서 워드패드를 골라 열어볼 수 있습니다. 아마 아래 내용과 비슷한 것을 보게 될 겁니다.





Cube.obj

# This file uses centimeters as units for non-parametric coordinates.

mtllib cube.mtl
g default
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.998008 0.998008
vt 0.001992 0.998008
vt 0.998008 0.001992
vt 0.001992 0.001992
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
s 1
g pCube1
usemtl file1SG
f 1/1/1 2/2/2 3/3/3
f 3/3/3 2/2/2 4/4/4
s 2
f 3/13/5 4/14/6 5/15/7
f 5/15/7 4/14/6 6/16/8
s 3
f 5/21/9 6/22/10 7/23/11
f 7/23/11 6/22/10 8/24/12
s 4
f 7/17/13 8/18/14 1/19/15
f 1/19/15 8/18/14 2/20/16
s 5
f 2/5/17 8/6/18 4/7/19
f 4/7/19 8/6/18 6/8/20
s 6
f 7/9/21 1/10/22 5/11/23
f 5/11/23 1/10/22 3/12/24






위의 .obj 모델 파일은 3D상의 육면체를 표현합니다. 8개의 정점, 24개의 텍스쳐 좌표와 법선 벡터, 그리고 6개의 면이 총 12개의 폴리곤을 구성합니다. 파일을 읽을 때 "V", "VT", "VN" 또는 "F"로 시작하지 않는 줄은 무시해도 됩니다. 파일의 추가적인 정보들은 우리 포맷으로 바꿀 때 필요하지 않습니다. 이제 각 중요한 줄들이 어떤 의미들인지 살펴보도록 하겠습니다.

1. "V"는 정점을 의미합니다. 육면체는 꼭지점이 8개이므로 8개의 정점을 가집니다. 각각의 정점은 X, Y, Z의 float형 포맷으로 나열되어 있습니다.

2. "VT"는 텍스쳐 좌표를 의미합니다. 육면체에는 24개의 텍스쳐 좌표가 있고 대부분 육면체 모델의 모든 삼각형의 모든 정점에 대한 기록을 하기 때문에 중복되는 내용이 많습니다. 각 좌표는 TU, TV로 float형 포맷입니다.

3. "VN"은 법선 벡터입니다. 육면체는 24개의 법선 벡터가 있고 역시 모든 삼각형의 모든 정점에 대한 기록을 하기 때문에 중복되는 내용이 많습니다. NX, NY, NZ의 float형 포맷으로 나열되어 있습니다.

4. "F"줄은 육면체 모델의 삼각형(표면)을 의미합니다. 나열된 값들은 정점의 순서(인덱스), 텍스쳐 좌표들, 그리고 법선 벡터들입니다. 각 면의 포맷은 다음과 같습니다.

f 정점1/텍스쳐1/법선1 정점2/텍스쳐2/법선2 정점3/텍스쳐3/법선3

따라서 "f 3/13/5 4/14/6 5/15/7" 이라고 하는 라인은 "Vertex3/Texture13/Normal5 Vertex4/Texture14/Normal6 Vertex5/Texture15/Normal7" 로 해석할 수 있습니다.

.obj 파일에서의 데이터의 배치 순서는 매우 중요합니다. 예를 들어 파일의 첫번째 정점은 삼각형 리스트에서의 정점1에 해당합니다. 텍스쳐 좌표나 법선 벡터도 역시 동일합니다.

삼각형 라인을 보고 있다보면 라인마다 세 개의 인덱스 그룹들이 하나의 삼각형을 구성한다는 것을 알게 될 것입니다. 그리고 이 육면체의 경우 각 면마다 2개의 삼각형이 있어 총 12개의 삼각형이 육면체를 만들게 됩니다.





오른손 좌표계에서 왼손 좌표계로

마야 2011에서는 기본적으로 오른손 좌표계를 사용하며 .obj파일도 오른손 좌표계 체제로 내보냅니다. 이 데이터들을 DirectX 11의 기본인 왼손 좌표계로 바꾸기 위해서는 다음과 같이 해야 합니다.

1. 정점의 Z좌표를 뒤집습니다. 소스에서 다음과 같은 것을 볼 것입니다.
vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f;

2. 텍스쳐 좌표의 TV를 뒤집습니다. 소스에서 다음과 같은 것을 볼 것입니다.
texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y;

3. 법선의 NZ를 뒤집습니다. 소스에서 다음과 같은 것을 볼 것입니다.
normals[normalIndex].z = normals[normalIndex].z * -1.0f;

4. 그리기 방향을 반시계방향에서 시계방향으로 바꿉니다. 소스에서는 순서를 재구성하기보다는 단순히 읽을 때 저장을 반대되는 순서로 하였습니다.

fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3;
fin >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2;
fin >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1;

위 4단계를 거치면 DirectX 11에서 올바로 그려지는 모델 데이터가 완성됩니다.





Main.cpp

마야 2011의 .obj파일들을 우리의 DirectX 11 포맷으로 바꾸는 프로그램은 꽤 간단해서 main.cpp 파일 하나에 다 담을 수 있습니다. 이 프로그램은 커맨드 창을 열고 변환할 .obj 파일의 이름을 묻습니다. 유저가 그 파일명을 입력하면 파일을 열고 데이터를 읽어 구조체들을 채워넣습니다. 전부 읽은 뒤에는 이를 왼손 좌표계로 바꿉니다. 그 뒤에 이 정보들을 model.txt 파일로 저장합니다. 파일 이름은 바꿀 수 있고 지난 튜토리얼의 3D 모델 렌더링 프로그램을 사용하여 DirectX 11로 그릴 수 있습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: main.cpp
////////////////////////////////////////////////////////////////////////////////


//////////////
// INCLUDES //
//////////////
#include <iostream>
#include <fstream>
using namespace std;


//////////////
// TYPEDEFS //
//////////////
typedef struct
{
	float x, y, z;
}VertexType;

typedef struct
{
	int vIndex1, vIndex2, vIndex3;
	int tIndex1, tIndex2, tIndex3;
	int nIndex1, nIndex2, nIndex3;
}FaceType;


/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
void GetModelFilename(char*);
bool ReadFileCounts(char*, int&, int&, int&, int&);
bool LoadDataStructures(char*, int, int, int, int);


//////////////////
// MAIN PROGRAM //
//////////////////
int main()
{
	bool result;
	char filename[256];
	int vertexCount, textureCount, normalCount, faceCount;
	char garbage;


	// Read in the name of the model file.
	GetModelFilename(filename);

	// Read in the number of vertices, tex coords, normals, and faces so that the data structures can be initialized with the exact sizes needed.
	result = ReadFileCounts(filename, vertexCount, textureCount, normalCount, faceCount);
	if(!result)
	{
		return -1;
	}

	// Display the counts to the screen for information purposes.
	cout << endl;
	cout << "Vertices: " << vertexCount << endl;
	cout << "UVs:      " << textureCount << endl;
	cout << "Normals:  " << normalCount << endl;
	cout << "Faces:    " << faceCount << endl;

	// Now read the data from the file into the data structures and then output it in our model format.
	result = LoadDataStructures(filename, vertexCount, textureCount, normalCount, faceCount);
	if(!result)
	{
		return -1;
	}

	// Notify the user the model has been converted.
	cout << "\nFile has been converted." << endl;
	cout << "\nDo you wish to exit (y/n)? ";
	cin >> garbage;

	return 0;
}


void GetModelFilename(char* filename)
{
	bool done;
	ifstream fin;


	// Loop until we have a file name.
	done = false;
	while(!done)
	{
		// Ask the user for the filename.
		cout << "Enter model filename: ";

		// Read in the filename.
		cin >> filename;

		// Attempt to open the file.
		fin.open(filename);

		if(fin.good())
		{
			// If the file exists and there are no problems then exit since we have the file name.
			done = true;
		}
		else
		{
			// If the file does not exist or there was an issue opening it then notify the user and repeat the process.
			fin.clear();
			cout << endl;
			cout << "File " << filename << " could not be opened." << endl << endl;
		}
	}

	return;
}


bool ReadFileCounts(char* filename, int& vertexCount, int& textureCount, int& normalCount, int& faceCount)
{
	ifstream fin;
	char input;


	// Initialize the counts.
	vertexCount = 0;
	textureCount = 0;
	normalCount = 0;
	faceCount = 0;

	// Open the file.
	fin.open(filename);

	// Check if it was successful in opening the file.
	if(fin.fail() == true)
	{
		return false;
	}

	// Read from the file and continue to read until the end of the file is reached.
	fin.get(input);
	while(!fin.eof())
	{
		// If the line starts with 'v' then count either the vertex, the texture coordinates, or the normal vector.
		if(input == 'v')
		{
			fin.get(input);
			if(input == ' ') { vertexCount++; }
			if(input == 't') { textureCount++; }
			if(input == 'n') { normalCount++; }
		}

		// If the line starts with 'f' then increment the face count.
		if(input == 'f')
		{
			fin.get(input);
			if(input == ' ') { faceCount++; }
		}
		
		// Otherwise read in the remainder of the line.
		while(input != '\n')
		{
			fin.get(input);
		}

		// Start reading the beginning of the next line.
		fin.get(input);
	}

	// Close the file.
	fin.close();

	return true;
}


bool LoadDataStructures(char* filename, int vertexCount, int textureCount, int normalCount, int faceCount)
{
	VertexType *vertices, *texcoords, *normals;
	FaceType *faces;
	ifstream fin;
	int vertexIndex, texcoordIndex, normalIndex, faceIndex, vIndex, tIndex, nIndex;
	char input, input2;
	ofstream fout;


	// Initialize the four data structures.
	vertices = new VertexType[vertexCount];
	if(!vertices)
	{
		return false;
	}

	texcoords = new VertexType[textureCount];
	if(!texcoords)
	{
		return false;
	}

	normals = new VertexType[normalCount];
	if(!normals)
	{
		return false;
	}

	faces = new FaceType[faceCount];
	if(!faces)
	{
		return false;
	}

	// Initialize the indexes.
	vertexIndex = 0;
	texcoordIndex = 0;
	normalIndex = 0;
	faceIndex = 0;

	// Open the file.
	fin.open(filename);

	// Check if it was successful in opening the file.
	if(fin.fail() == true)
	{
		return false;
	}

	// Read in the vertices, texture coordinates, and normals into the data structures.
	// Important: Also convert to left hand coordinate system since Maya uses right hand coordinate system.
	fin.get(input);
	while(!fin.eof())
	{
		if(input == 'v')
		{
			fin.get(input);

			// Read in the vertices.
			if(input == ' ') 
			{ 
				fin >> vertices[vertexIndex].x >> vertices[vertexIndex].y >> vertices[vertexIndex].z;

				// Invert the Z vertex to change to left hand system.
				vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f;
				vertexIndex++; 
			}

			// Read in the texture uv coordinates.
			if(input == 't') 
			{ 
				fin >> texcoords[texcoordIndex].x >> texcoords[texcoordIndex].y;

				// Invert the V texture coordinates to left hand system.
				texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y;
				texcoordIndex++; 
			}

			// Read in the normals.
			if(input == 'n') 
			{ 
				fin >> normals[normalIndex].x >> normals[normalIndex].y >> normals[normalIndex].z;

				// Invert the Z normal to change to left hand system.
				normals[normalIndex].z = normals[normalIndex].z * -1.0f;
				normalIndex++; 
			}
		}

		// Read in the faces.
		if(input == 'f') 
		{
			fin.get(input);
			if(input == ' ')
			{
				// Read the face data in backwards to convert it to a left hand system from right hand system.
				fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3
				    >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2
				    >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1;
				faceIndex++;
			}
		}

		// Read in the remainder of the line.
		while(input != '\n')
		{
			fin.get(input);
		}

		// Start reading the beginning of the next line.
		fin.get(input);
	}

	// Close the file.
	fin.close();

	// Open the output file.
	fout.open("model.txt");
	
	// Write out the file header that our model format uses.
	fout << "Vertex Count: " << (faceCount * 3) << endl;
	fout << endl;
	fout << "Data:" << endl;
	fout << endl;

	// Now loop through all the faces and output the three vertices for each face.
	for(int i=0; i<faceIndex; i++)
	{
		vIndex = faces[i].vIndex1 - 1;
		tIndex = faces[i].tIndex1 - 1;
		nIndex = faces[i].nIndex1 - 1;

		fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
		     << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
		     << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;

		vIndex = faces[i].vIndex2 - 1;
		tIndex = faces[i].tIndex2 - 1;
		nIndex = faces[i].nIndex2 - 1;

		fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
		     << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
		     << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;

		vIndex = faces[i].vIndex3 - 1;
		tIndex = faces[i].tIndex3 - 1;
		nIndex = faces[i].nIndex3 - 1;

		fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
		     << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
		     << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;
	}

	// Close the output file.
	fout.close();

	// Release the four data structures.
	if(vertices)
	{
		delete [] vertices;
		vertices = 0;
	}
	if(texcoords)
	{
		delete [] texcoords;
		texcoords = 0;
	}
	if(normals)
	{
		delete [] normals;
		normals = 0;
	}
	if(faces)
	{
		delete [] faces;
		faces = 0;
	}

	return true;
}





마치면서

이제 Maya 2011의 .obj파일을 우리의 간단한 모델 포맷으로 바꿀 수 있습니다.







연습 문제

1. 소스를 다시 컴파일하고 제공된 .obj 모델 파일을 바꿔보십시오.

3. Maya 2011 모델을 만들고(아니면 이미 있는 것을 써도 좋습니다) .obj 포맷으로 내보낸 뒤 이 프로그램을 이용하여 바꿔보십시오.

3. 이 프로그램을 수정하여 여러분이 원하는 다른 모델 포맷을 변환할 수 있게 해 보십시오.





소스 코드

Visual Studio 2010 프로젝트: dx11tut08.zip

소스 코드: dx11src08.zip

실행 파일: dx11exe08.zip


'강좌번역 > DirectX 11' 카테고리의 다른 글

DirectX11 Tutorial 10 - 정반사광  (1) 2013.03.11
DirectX11 Tutorial 9 - 주변광  (0) 2013.02.10
DirectX11 Tutorial 7 - 3D 모델 렌더링  (1) 2013.02.02
DirectX11 Tutorial 6 - 조명  (1) 2013.01.22
DirectX11 Tutorial 5 - 텍스쳐  (14) 2013.01.16
Nav