[DirectX11] Tutorial 8 - 마야 2011 모델 불러오기
Tutorial 8 - 마야 2011 모델 불러오기
원문 : http://www.rastertek.com/dx11tut08.html
이 튜토리얼에서는 Maya 2011에서 제작한 3D 모델을 불러오는 방법에 대해 다룹니다. 이 튜토리얼은 Maya에 중점을 두고 있지만 소스코드를 변경하여 다른 3D 모델링 소프트웨어 프로그램에서도 적용할 수 있습니다.
이전 튜토리얼에서는 우리들만의 포맷을 만들고 이를 이용하여 3D 모델을 그려보았습니다. 이번 듀토리얼의 목표는 Maya 2011 모델을 우리의 포멧으로 변환하여 렌더링하는 것입니다. 이미 Maya에서 3D 객체를 모델링하는 방법은 수많은 듀토리얼이 있기때문에 따로 다루지는 않을 것입니다. 그 대신에 텍스처와 삼각형을 가진 3D 모델을 출력하는 부분부터 시작하겠습니다.
Maya 내보내기 형식의 경우 .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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | # 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 | cs |
위의 .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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | //////////////////////////////////////////////////////////////////////////////// // 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; // 모델 파일의 이름을 읽습니다. GetModelFilename(filename); // 필요한 정확한 크기로 데이터 구조를 초기화 할 수 있도록 꼭지점 수, 텍셀 좌표, 법선 및면 수를 읽습니다. result = ReadFileCounts(filename, vertexCount, textureCount, normalCount, faceCount); if(!result) { return -1; } // 정보를 얻기 위해 화면에 카운트를 표시합니다. cout << endl; cout << "Vertices: " << vertexCount << endl; cout << "UVs: " << textureCount << endl; cout << "Normals: " << normalCount << endl; cout << "Faces: " << faceCount << endl; // 이제 파일의 데이터를 데이터 구조로 읽어 들인 다음 모델 형식으로 출력합니다. result = LoadDataStructures(filename, vertexCount, textureCount, normalCount, faceCount); if(!result) { return -1; } // 모델이 변환 된 것을 사용자에게 알립니다. 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; // 파일 이름이 될 때까지 반복합니다. done = false; while(!done) { // 사용자에게 파일 이름을 물어 봅니다. cout << "Enter model filename: "; // 파일 이름을 읽습니다. cin >> filename; // 파일을 열려고 시도합니다. fin.open(filename); if(fin.good()) { // 파일이 존재하고 아무런 문제가 없다면 우리는 파일 이름을 가지고 있기 때문에 종료합니다. done = true; } else { // 파일이 없거나 열 때 문제가 발생하면 사용자에게 알리고 프로세스를 반복합니다. 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; // 카운트를 초기화합니다. vertexCount = 0; textureCount = 0; normalCount = 0; faceCount = 0; // 파일을 엽니 다. fin.open(filename); // 파일 열기에 성공했는지 확인합니다. if(fin.fail() == true) { return false; } // 파일에서 읽은 다음 파일의 끝에 도달 할 때까지 계속 읽습니다. fin.get(input); while(!fin.eof()) { // 라인이 'v'로 시작하면 꼭지점, 텍스처 좌표 또는 법선 벡터를 계산합니다. if(input == 'v') { fin.get(input); if(input == ' ') { vertexCount++; } if(input == 't') { textureCount++; } if(input == 'n') { normalCount++; } } // 라인이 'f'로 시작하면면 수를 증가시킵니다. if(input == 'f') { fin.get(input); if(input == ' ') { faceCount++; } } // 그렇지 않으면 나머지 행을 읽습니다. while(input != '\n') { fin.get(input); } // 다음 줄의 시작 부분을 읽기 시작한다. fin.get(input); } // 파일을 닫습니다. 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; // 네 개의 데이터 구조를 초기화합니다. 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; } // 인덱스를 초기화합니다. vertexIndex = 0; texcoordIndex = 0; normalIndex = 0; faceIndex = 0; // 파일을 엽니 다. fin.open(filename); // 파일 열기에 성공했는지 확인하십시오. if(fin.fail() == true) { return false; } // 정점, 텍스처 좌표 및 법선을 데이터 구조로 읽어들입니다. // 중요 : Maya는 오른손 좌표계를 사용하기 때문에 왼손 좌표계로 변환한다. fin.get(input); while(!fin.eof()) { if(input == 'v') { fin.get(input); // 정점을 읽습니다. if(input == ' ') { fin >> vertices[vertexIndex].x >> vertices[vertexIndex].y >> vertices[vertexIndex].z; // Z 정점을 뒤집어 왼손 시스템으로 바꾼다. vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f; vertexIndex++; } // 텍스처 uv 좌표를 읽습니다. if(input == 't') { fin >> texcoords[texcoordIndex].x >> texcoords[texcoordIndex].y; // V 텍스처 좌표를 왼손 시스템으로 반전시킵니다. texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y; texcoordIndex++; } // 법선을 읽습니다. if(input == 'n') { fin >> normals[normalIndex].x >> normals[normalIndex].y >> normals[normalIndex].z; // Z 법선을 반전시켜 왼손 시스템으로 변경합니다. normals[normalIndex].z = normals[normalIndex].z * -1.0f; normalIndex++; } } // 표면을 읽는다. if(input == 'f') { fin.get(input); if(input == ' ') { // 오른손 시스템에서 왼손 시스템으로 변환하기 위해 표면 데이터를 거꾸로 읽습니다. 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++; } } // 나머지 행을 읽습니다. while(input != '\n') { fin.get(input); } // 다음 줄의 시작 부분을 읽기 시작한다. fin.get(input); } // 파일을 닫습니다. fin.close(); // 출력 파일을 엽니다. fout.open("model.txt"); // 모델 형식에서 사용하는 파일 헤더를 작성합니다. fout << "Vertex Count: " << (faceCount * 3) << endl; fout << endl; fout << "Data:" << endl; fout << endl; // 이제 모든면을 반복하고 각면의 세 꼭지점을 출력합니다. 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; } // 출력 파일을 닫는다. fout.close(); // 네 개의 데이터 변수를 해제한다. 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; } | cs |
출력 화면
마치면서
이제 Maya 2011의 .obj파일을 우리의 간단한 모델 포맷으로 바꿀 수 있습니다.
연습문제
1. 소스를 다시 컴파일하고 제공된 .obj 모델 파일을 바꿔보십시오.
3. Maya 2011 모델을 만들고(이미 있는 것을 써도 좋습니다) .obj 포맷으로 내보낸 뒤 이 프로그램을 이용하여 바꿔보십시오.
3. 이 프로그램을 수정하여 여러분이 원하는 다른 모델 포맷을 변환할 수 있게 해 보십시오.
소스코드
소스코드 : Dx11Demo_08.zip
'DirectX 11 > Basic' 카테고리의 다른 글
[DirectX11] Tutorial 10 - 정반사광 (0) | 2017.12.06 |
---|---|
[DirectX11] Tutorial 9 - 주변광 (0) | 2017.12.04 |
[DirectX11] Tutorial 7 - 3D 모델 렌더링 (0) | 2017.12.02 |
[DirectX11] Tutorial 6 - 조명 (5) | 2017.12.01 |
fatal error RC1004: unexpected end of file found (0) | 2017.11.30 |