728x90

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


Tutorial 8: Loading Maya 2011 Models


이번 강좌는 마야 2011에서 정적 3D 모델을 어떻게 불러올지를 설명할 것입니다. 이번 강좌는 마야에 초점이 맞추어져 있지만 이를 이용하여 다른 많은 3D 모델링 소프트웨어 패키지에 적용할 수 있다는거 아시죠?


이전 강좌에서 우리의 독자 모델 포맷을 만들고 그 포맷을 이요하여 3D 모델을 렌더링해 보았습니다. 이제 다음 목표는 마야 2011 모델을 우리의 독자 포맷으로 변환하여 렌더링하는 것입니다. 이미 인터넷상에 많은 강좌들이 있어서 마야에서 3D 모델을 많드는 것은 설명하지 않습니다. 그래서 텍스쳐가 적용된 삼각형으로 이루어진 3D 모델을 가지고 있는 시점에서 시작할 것입니다.


우리가 이번에 사용할 마야 포맷은 .OBJ 포맷인데 초보자들이 시작하기에 좋고 읽기도 쉽습니다.


여러분의 모델을 .obj 포맷으로 내보내기위해 마야에서 OBJ 익스포터를 활성화해야합니다. "Window"에 "Settings/Preferences"에 "Plug-in Manager"를 누르세요. objExport.mll까지 스크롤을 내리고 "Loaded"와 "Auto load" 둘다 선택하세요. 그리고 이제 모델을 내보내기 위해 "File"에 "Export All"을 누르세요. "Files of type:"에서 밑으로 내리다 보면 "OBJexport"가 있으니 선택하세요. 파일 이름을 입력하고 "Export All"를 누르면 .obj라는 확장자의 텍스트파일이 생성됩니다. 파일을 보기위해 파일에 마우스 오른쪽 버튼을 눌러 "열기"를 누른 뒤 메모장, notepad++, 워드 패드등 아무 에디터로 여세요. 그럼 다음과 같은 텍스트를 보게 될 것입니다.



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개의 텍스쳐 좌표와 법선 벡터를 가지며 총 12개의 삼각형면으로 구성된 6개의 사각형면이 있습니다. 파일을 변환하면서 "V", "VT", "VN", "F"로 시작하는줄 외의 줄은 무시하여도 됩니다. 나머지 정보는 .obj파일을 우리의 포맷으로 변환하는데 필요없을 것입니다. 이제 각 라인이 의미를 살펴보겠습니다.


1. "V"줄은 정점에 대한 줄입니다. 큐브는 8개의 꼭지점 즉 8개의 정점으로 구성됩니다. 각줄은 X, Y, Z 좌표값이 들어갑니다.


2. "VT"줄은 텍스쳐 좌표에 대한 줄입니다. 큐브는 24개의 텍스쳐 좌표를 가지는데 큐브 모델의 모든 삼각형 정점에 대해 기록하였기 때문에 중복이 있습니다. 각줄은 TU, TV 좌표값이 들어갑니다.


3. "VN"줄은 법선 벡터에 대한 줄입니다. 큐브는 24개의 법선 벡터를 가지는데 텍셀좌표랑 동일하게 모든 삼각형 정점에 대해 기록하였기 때문에 중복이 있습니다. 각줄은 NX, NY, NZ 좌표값이 들어갑니다.


4. "F"줄은 큐브 모델의 각 삼각형(면)에 대한 줄입니다. 나열된 값은 정점, 텍스쳐 좌표, 법선 벡터의 인덱스입니다. 각 면의 포맷은 다음과 같습니다.


f Vertex1/Texture1/Normal1 Vertex2/Texture2/Normal2 Vertex3/Texture3/Normal3


그래서 "f 3/13/5 4/14/6 5/15/7"라인을 해석하면 "Vertex3/Texture13/Normal5 Vertex4/Texture14/Normal6 Vertex5/Texture15/Normal7"입니다.


.obj파일에 나열된 데이터의 방식은 매우 중요합니다. 예를들어 파일의 첫번째 정점은 면 목록에서 Vertex1에 대응합니다. 텍스쳐 좌표와 법선 벡터도 동일합니다.


.obj파일에 면 목록을 보면 각 라인의 세 인덱스 그룹이 삼각형 하나를 이룬다는 것을 아실겁니다. 그리고 이번 큐브 모델의 경우 총 12 삼각형면이 면당 2개의 삼각형을 가진 6개의 사각형면을 이룹니다.



오른손 좌표에서 왼손 좌표로 변환


기본적으로 마야 2011은 오른손 좌표계이며 .obj파일도 오른손 좌표로 내보냅니다. 데이터를 DirectX 11이 기본으로 하는 왼손 좌표계로 변환하기위해 다음의 것들을 해야 합니다.


1. 정점의 Z부호를 반전합니다. 이런 코드를 보게 되실 것입니다 : vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f;


2. 텍스쳐 좌표 TV를 반전합니다. 이런 코드를 보게 되실 것입니다 : texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y;

(반전한다고 했지만 부호반전이 아닌 OpenGL에서 텍스쳐 좌표는 좌측하단이 (0,0)이라 이런 코드를 사용한 것 같다. ex) opengl (0.0f, 0.7f) == directx (0.0f, 0.3f) )


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;
}



요약


우리는 이제 마야 2011 .obj 파일을 우리의 독자 모델 포맷으로 변환할 수 있습니다!



연습하기


1. 프로그램을 컴파일하고 위 .obj 모델 파일을 변환해 보세요.


2. 마야 2011 모델을 만들어 보고(가지고 있는 거 있으시면 그거 쓰시면 됩니다.) .obj 포맷으로 내보낸 뒤 변환프로그램으로 변환해 보세요.


3. 코드를 변형해서 다른 모델 포맷을 변환해 보세요.

728x90

+ Recent posts