본문 바로가기

Game/Graphics

OpenGL-Tutorial 5 : A Textured Cube

link : http://www.opengl-tutorial.org/kr/beginners-tutorials/tutorial-5-a-textured-cube/


이번 튜토리얼부터는 영어다!


앞으로는 해석과 함께 중요한 개념들을 정리해서 올리도록 하겠다.


[배울 것]

1) UV좌표

2) 직접 텍스처를 입히는 방법

3) OpenGL에서 사용하는 방법

4) 필터링과 맵핑이 무엇이고, 어떻게 사용하는지

5) GLFW로 견고하게 텍스처를 입히는 방법




1) UV Coordinate

- mesh를 Texturing할 때 각 삼각형에 대해 이미지의 어느 부분을 사용해야하는지 OpenGL에 알리는 방법이 필요하다. 

  이 것은 UV 좌표로 수행된다.


  각 정점은, 그 위치의 최상부에 2개의 부동 소수점 U와 V를 가질 수 있다.

  이러한 좌표는 다음의 방법으로 텍스처에 액세스하기 위해서 사용된다.



  삼각형에 텍스처가 왜곡되어 있는지 확인해야한다.




2) BMP images

- 이 튜토리얼에서는 BMP 파일 형식을 아는 것이 중요하지는 않으나, 매우 간단하고 이해하면 도움이 되므로 한 번 보고 간다고 한다.


  이 부분은 딱히 메모할 부분이 없으므로 통과




3) Using the texture in OpenGL

- 먼저 조각 쉐이더를 살펴본다. 대부분이 간단하다.

#version 330 core

// Interpolated values from the vertex shaders
in vec2 UV;

// Ouput data
out vec3 color;

// Values that stay constant for the whole mesh.
uniform sampler2D myTextureSampler;

void main(){

    // Output color = color of the texture at the specified UV
    color = texture( myTextureSampler, UV ).rgb;
}

  Three things :

1) 조각 쉐이더에는 UV 좌표가 필요하다

2) 또한, 액세스 할 텍스쳐를 알기 위해 "sampler2D"가 필요하다. (동일한 쉐이더에서 여러 택스처에 액세스 가능)

3) 마지막으로 Texture에 접근하면 (R,G,B,A) vec4를 돌려준다.



- 버텍스 쉐이더도 살표보자. UV를 프래그먼트 쉐이더에 전달하면 된다.

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec2 vertexUV;

// Output data ; will be interpolated for each fragment.
out vec2 UV;

// Values that stay constant for the whole mesh.
uniform mat4 MVP;

void main(){

    // Output position of the vertex, in clip space : MVP * position
    gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

    // UV of the vertex. No special space for this one.
    UV = vertexUV;
}

   튜토리얼 4의 "layout(location = 1) in vec2 vertexUV"를 기억하고 있습니까? 우리는 여기서 똑같은 작업을 할 것이다.
   버퍼(R, G, B)를 세 쌍으로 제공하는 대신 (U, V)쌍의 버퍼를 제공할 것이다.

// Two UV coordinatesfor each vertex. They were created with Blender. You'll learn shortly how to do this yourself.
static const GLfloat g_uv_buffer_data[] = {
    0.000059f, 1.0f-0.000004f,
    0.000103f, 1.0f-0.336048f,
    0.335973f, 1.0f-0.335903f,
    1.000023f, 1.0f-0.000013f,
    0.667979f, 1.0f-0.335851f,
    0.999958f, 1.0f-0.336064f,
    0.667979f, 1.0f-0.335851f,
    0.336024f, 1.0f-0.671877f,
    0.667969f, 1.0f-0.671889f,
    1.000023f, 1.0f-0.000013f,
    0.668104f, 1.0f-0.000013f,
    0.667979f, 1.0f-0.335851f,
    0.000059f, 1.0f-0.000004f,
    0.335973f, 1.0f-0.335903f,
    0.336098f, 1.0f-0.000071f,
    0.667979f, 1.0f-0.335851f,
    0.335973f, 1.0f-0.335903f,
    0.336024f, 1.0f-0.671877f,
    1.000004f, 1.0f-0.671847f,
    0.999958f, 1.0f-0.336064f,
    0.667979f, 1.0f-0.335851f,
    0.668104f, 1.0f-0.000013f,
    0.335973f, 1.0f-0.335903f,
    0.667979f, 1.0f-0.335851f,
    0.335973f, 1.0f-0.335903f,
    0.668104f, 1.0f-0.000013f,
    0.336098f, 1.0f-0.000071f,
    0.000103f, 1.0f-0.336048f,
    0.000004f, 1.0f-0.671870f,
    0.336024f, 1.0f-0.671877f,
    0.000103f, 1.0f-0.336048f,
    0.336024f, 1.0f-0.671877f,
    0.335973f, 1.0f-0.335903f,
    0.667969f, 1.0f-0.671889f,
    1.000004f, 1.0f-0.671847f,
    0.667979f, 1.0f-0.335851f
};

  위의 UV 좌표는 다음 모델에 해당한다.

   나머지는 명백하다. 버퍼를 생성하고, 바인딩하고, 채우고, 구성하고, 평소와 같인 Vertex Buffer를 그린다.
   3 대신 glVertexAttribPointer의 두 번째 매개 변수로 2를 사용하도록 주의해라.

- 결과


- Zoomed-in version




4) What is filtering and mipmapping, and how to use them
- 위 스크린샷에서 볼 수 있듯이 텍스처 품질이 그다지 좋지 않다. 이는 loadBMP_custom에서 다음과 같이 작성했기 때문이다.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

  이 것은 조각 쉐이더에서 texture()가 (U,V) 좌표에 있는 texel을 가져와서 진행하는 것을 의미한다.

  이를 향상시키기 위해 할 수 있는 일이 몇가지 있다.

1) Linear filtering
- 선형 필터링을 사용하면 texture()는 주변의 다른 texel을 보고 각 중심까지의 거리에 따라 색상을 혼합한다.
  이렇게 하면 위의 딱딱한 가장자리를 피할 수 있다.

  이 것은 훨씬 더 좋고 많이 사용된다. 하지만 매우 높은 품질을 원한다면 약간 더 느린 aniostropic 필터링을 사용할 수 있다.

2) Anisotropic filtering
- 이 것은 조각을 통해 실제로 보인 이미지 부분을 근사한다.
  예를 들어, 다음 텍스쳐가 측면에서 보이고 조금 회전하면 anisotropic 필터링은 주 방향을 따라 고정 된 수의 샘플을
  취해 파란색 직사각형에 포함 된 색상을 계산한다.

3) Mipmaps
- 선형 필터링과 anisotropic 필터링 모두 텍스처가 멀리서 보일 경우 4가지 texel로는 충분하지 않다는 문제가 존재한다.
  사실, 3D 모델이 화면에서 단 하나의 단편을 차지하는 것보다 멀리 떨어져 있다면, 이미지의 모든 texel을 평균해 색상을 만들어야 한다.
  이 것은 성능 상의 이유로 분명히 행해지지는 않는다. 대신에 Mipmaps을 소개한다.

[1] 초기화 시간에 1x1 이미지 (사실상 이미지의 모든 texel의 평균)가 될 때까지 이미지를 2씩 축소시킨다.
[2] 메쉬를 그릴 때 texel이 얼마나 커야 하는지를 고려해 어느 밉맵을 사용하는 것이 더 적합한지 선택한다.
[3] 가장 가까운 선형 또는 anisotropic 필터링을 사용해 이 밉맵을 샘플링한다.
[4] 추가 품질을 위해 두 개의 밉맵을 샘플링하고, 결과를 혼합 할 수도 있다.

  운이 좋게도 이 모든 작업은 OpenGL이 우리에게 제공한다.

// When MAGnifying the image (no bigger mipmap available), use LINEAR filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// When MINifying the image, use a LINEAR blend of two mipmaps, each filtered LINEARLY too
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Generate mipmaps, by the way.
glGenerateMipmap(GL_TEXTURE_2D);



5) How to load texture with GLFW
- loadBMP_custom 함수는 우리가 만들었기 때문에 훌륭하지만 전용 라이브러리를 사용하는 것이 좋다.

GLuint loadTGA_glfw(const char * imagepath){

    // Create one OpenGL texture
    GLuint textureID;
    glGenTextures(1, &textureID);

    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Read the file, call glTexImage2D with the right parameters
    glfwLoadTexture2D(imagepath, 0);

    // Nice trilinear filtering.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Return the ID of the texture we just created
    return textureID;
}

  이미지를 로드하는 방법을 살펴 보겠다. 헤더가 다르게 구성된다는 점을 제외하면 BMP 코드와 매우 유사하다.

GLuint loadDDS(const char * imagepath){

    unsigned char header[124];

    FILE *fp;

    /* try to open the file */
    fp = fopen(imagepath, "rb");
    if (fp == NULL)
        return 0;

    /* verify the type of file */
    char filecode[4];
    fread(filecode, 1, 4, fp);
    if (strncmp(filecode, "DDS ", 4) != 0) {
        fclose(fp);
        return 0;
    }

    /* get the surface desc */
    fread(&header, 124, 1, fp);

    unsigned int height      = *(unsigned int*)&(header[8 ]);
    unsigned int width         = *(unsigned int*)&(header[12]);
    unsigned int linearSize     = *(unsigned int*)&(header[16]);
    unsigned int mipMapCount = *(unsigned int*)&(header[24]);
    unsigned int fourCC      = *(unsigned int*)&(header[80]);

  After the header is the actual data : all the mipmap levels, successively. We can read them all in one batch : (해석불가)

    unsigned char * buffer;
    unsigned int bufsize;
    /* how big is it going to be including all mipmaps? */
    bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
    buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));
    fread(buffer, 1, bufsize, fp);
    /* close the file pointer */
    fclose(fp);

   여기서는 DXT1, DXT3 및 DXT5의 3가지 형식을 다루겠다. 우리는 "fourCC"플래그를 OpenGL이 이해할 수 있는 값으로 변환해야 한다.

    unsigned int components  = (fourCC == FOURCC_DXT1) ? 3 : 4;
    unsigned int format;
    switch(fourCC)
    {
    case FOURCC_DXT1:
        format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
        break;
    case FOURCC_DXT3:
        format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
        break;
    case FOURCC_DXT5:
        format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
        break;
    default:
        free(buffer);
        return 0;
    }

   텍스쳐를 생성하는 것은 평소와 같이 처리된다.

    // Create one OpenGL texture
    GLuint textureID;
    glGenTextures(1, &textureID);

    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, textureID);

   이제 각 밉맵을 하나씩 채워 넣으면 된다.

    unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
    unsigned int offset = 0;

    /* load the mipmaps */
    for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)
    {
        unsigned int size = ((width+3)/4)*((height+3)/4)*blockSize;
        glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,
            0, size, buffer + offset);

        offset += size;
        width  /= 2;
        height /= 2;
    }
    free(buffer);

    return textureID;


[결론]

- OpenGL에서 텍스처를 만들고 입히는 방법을 배웠다.

  일반적으로 압축 된 텍스처는 저장하기에 더 작고 로드하는데 거의 즉각적이며 사용하기가 더 빠르기 때문에 사용해한다.
  Compressonator(또는 유사한 도구)의 주된 단점은 이미지를 변환해야 한다는 점이다.






 위 내용을 공부 후에 코드를 따라 작성하면서 튜토리얼을 진행했다.


 아무리해도 까만 정육면체만 나오길래 튜토리얼 코드와 하나씩 비교하면서 확인해봤더니

 


 위 코드에서 filecode가 "DDS"가 아닌 "DDS " 띄어쓰기를 하나 더 포함한 문자열이었다. (금방 찾아서 다행이지 오래 걸렸으면 허탈했을듯)


 이 문제를 해결하고 나니



 이번에는 숫자들이 뒤엉키는 문제가 발생했다. 실수로 이전에 사용했던 color를 그대로 두고 uv로 바꾸지 않아서 생긴 문제였다.



  uv로 변경해서 성공!


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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <GL/glew.h>
#include <glfw3.h>
GLFWwindow* window;
 
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
using namespace glm;
 
#define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII
#define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII
#define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII
 
GLuint LoadShaders(const char *const char *);
GLuint loadBMP_custom(const char *);
GLuint loadDDS(const char *);
 
int main() {
 
    // Initialise GLFW
    if (!glfwInit())
    {
        fprintf(stderr, "Failed to initialize GLFW\n");
        getchar();
        return -1;
    }
 
    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
 
    // Open a window and create its OpenGL context
    window = glfwCreateWindow(1024768"QBOT_opengl"NULLNULL);
    if (window == NULL) {
        fprintf(stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n");
        getchar();
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
 
    // Initialize GLEW
    glewExperimental = true;
    if (glewInit() != GLEW_OK) {
        fprintf(stderr, "Failed to initialize GLEW\n");
        getchar();
        glfwTerminate();
        return -1;
    }
 
    // Ensure we can capture the escape key being pressed below
    glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
 
    // Dark blue background
    glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
 
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
 
    GLuint VertexArrayID;
    glGenVertexArrays(1&VertexArrayID);
    glBindVertexArray(VertexArrayID);
 
    //Shader를 불러온다.
    GLuint programID = LoadShaders("TransformVertexShader.vertexshader""TextureFragmentShader.fragmentshader");
 
    //매트릭스ID 추가
    GLuint MatrixID = glGetUniformLocation(programID, "MVP");
 
    glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
 
    //카메라 매트릭스
    glm::mat4 View = glm::lookAt(
        glm::vec3(-4-33),    //카메라 위치
        glm::vec3(000), //보는 Look
        glm::vec3(010)  //head-up
    );
 
    //모델
    glm::mat4 Model = glm::mat4(1.0f);
    //우리의 모델뷰로젝션 : 3개의 행렬의 곱
    glm::mat4 MVP = Projection * View * Model;
 
    //어떠한 두 가지의 함수를 사용해서 텍스처를 불러온다
    //GLuint Texture = loadBMP_custom("uvtemplate.bmp");
    GLuint Texture = loadDDS("uvtemplate.DDS");
 
    GLuint TextureID = glGetUniformLocation(programID, "myTextureSampler");
 
    //vertex 데이터
    static const GLfloat g_vertex_buffer_data[] = {
        -1.0f,-1.0f,-1.0f,
        -1.0f,-1.0f, 1.0f,
        -1.0f, 1.0f, 1.0f,
        1.0f, 1.0f,-1.0f,
        -1.0f,-1.0f,-1.0f,
        -1.0f, 1.0f,-1.0f,
        1.0f,-1.0f, 1.0f,
        -1.0f,-1.0f,-1.0f,
        1.0f,-1.0f,-1.0f,
        1.0f, 1.0f,-1.0f,
        1.0f,-1.0f,-1.0f,
        -1.0f,-1.0f,-1.0f,
        -1.0f,-1.0f,-1.0f,
        -1.0f, 1.0f, 1.0f,
        -1.0f, 1.0f,-1.0f,
        1.0f,-1.0f, 1.0f,
        -1.0f,-1.0f, 1.0f,
        -1.0f,-1.0f,-1.0f,
        -1.0f, 1.0f, 1.0f,
        -1.0f,-1.0f, 1.0f,
        1.0f,-1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        1.0f,-1.0f,-1.0f,
        1.0f, 1.0f,-1.0f,
        1.0f,-1.0f,-1.0f,
        1.0f, 1.0f, 1.0f,
        1.0f,-1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        1.0f, 1.0f,-1.0f,
        -1.0f, 1.0f,-1.0f,
        1.0f, 1.0f, 1.0f,
        -1.0f, 1.0f,-1.0f,
        -1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        -1.0f, 1.0f, 1.0f,
        1.0f,-1.0f, 1.0f
    };
 
    // color 데이터
    static const GLfloat g_uv_buffer_data[] = {
        0.000059f, 1.0f - 0.000004f,
        0.000103f, 1.0f - 0.336048f,
        0.335973f, 1.0f - 0.335903f,
        1.000023f, 1.0f - 0.000013f,
        0.667979f, 1.0f - 0.335851f,
        0.999958f, 1.0f - 0.336064f,
        0.667979f, 1.0f - 0.335851f,
        0.336024f, 1.0f - 0.671877f,
        0.667969f, 1.0f - 0.671889f,
        1.000023f, 1.0f - 0.000013f,
        0.668104f, 1.0f - 0.000013f,
        0.667979f, 1.0f - 0.335851f,
        0.000059f, 1.0f - 0.000004f,
        0.335973f, 1.0f - 0.335903f,
        0.336098f, 1.0f - 0.000071f,
        0.667979f, 1.0f - 0.335851f,
        0.335973f, 1.0f - 0.335903f,
        0.336024f, 1.0f - 0.671877f,
        1.000004f, 1.0f - 0.671847f,
        0.999958f, 1.0f - 0.336064f,
        0.667979f, 1.0f - 0.335851f,
        0.668104f, 1.0f - 0.000013f,
        0.335973f, 1.0f - 0.335903f,
        0.667979f, 1.0f - 0.335851f,
        0.335973f, 1.0f - 0.335903f,
        0.668104f, 1.0f - 0.000013f,
        0.336098f, 1.0f - 0.000071f,
        0.000103f, 1.0f - 0.336048f,
        0.000004f, 1.0f - 0.671870f,
        0.336024f, 1.0f - 0.671877f,
        0.000103f, 1.0f - 0.336048f,
        0.336024f, 1.0f - 0.671877f,
        0.335973f, 1.0f - 0.335903f,
        0.667969f, 1.0f - 0.671889f,
        1.000004f, 1.0f - 0.671847f,
        0.667979f, 1.0f - 0.335851f
    };
 
    GLuint vertexbuffer;
    glGenBuffers(1&vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
 
    GLuint uvbuffer;
    glGenBuffers(1&uvbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_uv_buffer_data), g_uv_buffer_data, GL_STATIC_DRAW);
 
    do {
        // Clear the screen. It's not mentioned before Tutorial 02, but it can cause flickering, so it's there nonetheless.
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
        glUseProgram(programID);
 
        //transformation을 현재 쉐이더에 보냄
        glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
 
        //텍스처 유닛0에 있는 텍스처를 바인딩한다.
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, Texture);
 
        //"myTextureSampler" 셈플러를 유저 텍스처 유닛 0에 세팅한다.
        glUniform1i(TextureID, 0);
 
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(
            0,            //0번째 속성. 0이 될 특별한 이유는 없지만 쉐이더의 레이아웃과 반드시 맞춰야함
            3,            //크기(size)
            GL_FLOAT,    //타입(type)
            GL_FALSE,    //정규화(normalized)?
            0,            //다음 요소까지의 간격(stride)
            (void*)0    //배열 버퍼의 오프셋(offset)
        );
 
        glEnableVertexAttribArray(1);
        glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
        glVertexAttribPointer(
            1,
            2,
            GL_FLOAT,
            GL_FALSE,
            0,
            (void*)0
        );
 
        glDrawArrays(GL_TRIANGLES, 012 * 3);
 
        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
 
        // Swap buffers
        glfwSwapBuffers(window);
        glfwPollEvents();
 
    } // Check if the ESC key was pressed or the window was closed
    while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
        glfwWindowShouldClose(window) == 0);
 
    // Cleanup VBO
    glDeleteBuffers(1&vertexbuffer);
    glDeleteVertexArrays(1&VertexArrayID);
    glDeleteProgram(programID);
 
    // Close OpenGL window and terminate GLFW
    glfwTerminate();
 
    return 0;
}
 
GLuint LoadShaders(const char * vertex_file_path, const char * fragment_file_path) {
 
    //쉐이더 생성
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
 
    //버텍스 쉐이더 코드를 파일에서 읽기
    std::string VertexShaderCode;
    std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
    if (VertexShaderStream.is_open()) {
        std::stringstream sstr;
        sstr << VertexShaderStream.rdbuf();
        VertexShaderCode = sstr.str();
        VertexShaderStream.close();
    }
    else {
        printf("파일 %s를 읽을 수 없음. 정확한 디렉토리를 사용 중입니까?\n", vertex_file_path);
        getchar();
        return 0;
    }
 
    //프래그먼트 쉐이더 코드를 파일에서 읽기
    std::string FragmentShaderCode;
    std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
    if (FragmentShaderStream.is_open()) {
        std::stringstream sstr;
        sstr << FragmentShaderStream.rdbuf();
        FragmentShaderCode = sstr.str();
        FragmentShaderStream.close();
    }
 
    GLint Result = GL_FALSE;
    int InfoLogLength;
 
    //버텍스 쉐이더를 컴파일
    printf("Compiling shader : %s\n", vertex_file_path);
    char const * VertexSourcePointer = VertexShaderCode.c_str();
    glShaderSource(VertexShaderID, 1&VertexSourcePointer, NULL);
    glCompileShader(VertexShaderID);
 
    //버텍스 쉐이더를 검사
    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if (InfoLogLength > 0) {
        std::vector<char> VertexShaderErrorMessage(InfoLogLength + 1);
        glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL&VertexShaderErrorMessage[0]);
        printf("%s\n"&VertexShaderErrorMessage[0]);
    }
 
    //프래그먼트 쉐이더를 컴파일
    printf("Compiling shader : %s", fragment_file_path);
    char const * FragmentSourcePointer = FragmentShaderCode.c_str();
    glShaderSource(FragmentShaderID, 1&FragmentSourcePointer, NULL);
    glCompileShader(FragmentShaderID);
 
    //프래그먼트 쉐이더를 검사
    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if (InfoLogLength > 0) {
        std::vector<char> FragmentShaderErrorMessage(InfoLogLength + 1);
        glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL&FragmentShaderErrorMessage[0]);
        printf("%s\n"&FragmentShaderErrorMessage[0]);
    }
 
    //프로그램에 링크
    printf("Linking program\n");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);
 
    //프로그램 검사
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if (InfoLogLength > 0) {
        std::vector<char> ProgramErrorMessage(InfoLogLength + 1);
        glGetProgramInfoLog(ProgramID, InfoLogLength, NULL&ProgramErrorMessage[0]);
        printf("%s\n"&ProgramErrorMessage[0]);
    }
 
    glDetachShader(ProgramID, VertexShaderID);
    glDetachShader(ProgramID, FragmentShaderID);
 
    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);
 
    return ProgramID;
}
 
GLuint loadBMP_custom(const char * imagepath) {
 
    printf("Reading image %s\n", imagepath);
 
    //BMP파일의 헤더에서 데이터를 읽는다
    unsigned char header[54];
    unsigned int dataPos;
    unsigned int imageSize;
    unsigned int width, height;
    //실제 RGB 데이터
    unsigned char * data;
 
    //파일을 연다
    FILE * file = fopen(imagepath, "rb");
    if (!file) {
        printf("%s는 열수 없다. 경로가 맞는지 확인해라.\n", imagepath);
        getchar();
        return 0;
    }
 
    //헤더를 읽는다, i.e. the 54 first bytes
 
    //만약 54 bytes보다 적게 읽혔으면 문제 발생
    if (fread(header, 154, file) != 54) {
        printf("BMP 파일이 아니다\n");
        return 0;
    }
    //A BMP 파일은 항상 "BM"으로 시작한다.
    if (header[0!= 'B' || header[1!= 'M') {
        printf("BMP 파일이 아니다\n");
        return 0;
    }
    //24pp file임을 확인한다.
    if (*(int*)&(header[0x1e]) != 0 || *(int*)&(header[0x1C]) != 24) {
        printf("BMP 파일이 아니다\n");
        return 0;
    }
 
    //이미지에 대한 정보를 읽는다.
    dataPos = *(int*)&(header[0x0A]);
    imageSize = *(int*)&(header[0x22]);
    width = *(int*)&(header[0x12]);
    height = *(int*)&(header[0x16]);
 
    //몇몇 BMP 파일들은 포맷이 놓쳐졌다, 놓쳐진 정보를 추측해라
    if (imageSize == 0) imageSize = width*height * 3// 3 : one byte for each Red-Green-Blue component
    if (dataPos == 0) dataPos = 54//BMP 헤더는 항상 이 형식
 
    //버퍼를 생성한다
    data = new unsigned char[imageSize];
 
    //파일의 버퍼에 있는 실제 데이터를 읽는다
    fread(data, 1, imageSize, file);
 
    //모든 것은 현재 메모리에 있다, 파일을 닫는다
    fclose(file);
 
    //openGL 텍스처를 만든다
    GLuint textureID;
    glGenTextures(1&textureID);
 
    //새로이 만들어진 텍스처를 바인딩한다.
    glBindTexture(GL_TEXTURE_2D, textureID);
 
    //이미지를 OpenGL에게 넘긴다
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);
 
    delete[] data;
 
    // trilinear(삼선형) 필터링
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);
 
    return textureID;
}
 
GLuint loadDDS(const char * imagepath) {
 
    unsigned char header[124];
 
    FILE *fp;
 
    //파일을 연다
    fp = fopen(imagepath, "rb");
    if (fp == NULL) {
        printf("%s는 열 수 없다. 경로를 확인해라\n", imagepath);
        getchar();
        return 0;
    }
 
    //파일의 타입을 확인한다
    char filecode[4];
    fread(filecode, 14, fp);
    if (strncmp(filecode, "DDS "4!= 0) {
        fclose(fp);
        return 0;
    }
 
    //surface desc를 얻는다
    fread(&header, 1241, fp);
 
    unsigned int height = *(unsigned int*)&(header[8]);
    unsigned int width = *(unsigned int*)&(header[12]);
    unsigned int linearSize = *(unsigned int*)&(header[16]);
    unsigned int mipMapCount = *(unsigned int*)&(header[24]);
    unsigned int fourCC = *(unsigned int*)&(header[80]);
 
    unsigned char * buffer;
    unsigned int bufsize;
 
    bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
    buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));
    fread(buffer, 1, bufsize, fp);
    fclose(fp);
 
    unsigned int components = (fourCC == FOURCC_DXT1) ? 3 : 4;
    unsigned int format;
    switch (fourCC)
    {
    case FOURCC_DXT1: 
        format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; 
        break
    case FOURCC_DXT3: 
        format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; 
        break
    case FOURCC_DXT5: 
        format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; 
        break;
    default:
        free(buffer);
        return 0;
    }
 
    //하나의 OpenGL 텍스처를 생성한다
    GLuint textureID;
    glGenTextures(1&textureID);
 
    //새로이 만들어진 텍스처를 바인딩한다
    glBindTexture(GL_TEXTURE_2D, textureID);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
    unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
    unsigned int offset = 0;
 
    //밉맵을 불러온다
    for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)
    {
        unsigned int size = ((width + 3/ 4)*((height + 3/ 4)*blockSize;
        glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,
            0size, buffer + offset);
 
        offset += size;
        width /= 2;
        height /= 2;
 
        //Non-Power-Of-Two 텍스처를 사용합니다.
        //이 코드는 혼란을 줄이기 위해 웹 페이지에는 포함되어 있지 않습니다.
        if (width < 1)width = 1;
        if (height < 1) height = 1;
    }
 
    free(buffer);
 
    return textureID;
}
cs