Thinking Different




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
default
-0.500000 -0.500000 0.500000
0.500000 -0.500000 0.500000
-0.500000 0.500000 0.500000
0.500000 0.500000 0.500000
-0.500000 0.500000 -0.500000
0.500000 0.500000 -0.500000
-0.500000 -0.500000 -0.500000
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
1
g pCube1
usemtl file1SG
1/1/1 2/2/2 3/3/3
3/3/3 2/2/2 4/4/4
2
3/13/5 4/14/6 5/15/7
5/15/7 4/14/6 6/16/8
3
5/21/9 6/22/10 7/23/11
7/23/11 6/22/10 8/24/12
4
7/17/13 8/18/14 1/19/15
1/19/15 8/18/14 2/20/16
5
2/5/17 8/6/18 4/7/19
4/7/19 8/6/18 6/8/20
6
7/9/21 1/10/22 5/11/23
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*intintintint);
 
 
//////////////////
// 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