본문 바로가기

Game/Graphics

OpenGL-Tutorial 6 : Keyboard and Mouse

link : http://www.opengl-tutorial.org/kr/beginners-tutorials/tutorial-6-keyboard-and-mouse/


벌써 6번째 튜토리얼! 이번에는 마우스와 키보드를 사용해서 카메라를 움직이는 방법을 배운다고 한다.




The Interface


이 코드는 튜토리얼 전체에서 다시 사용되므로 common/controls.cpp 라는 별도의 파일에 코드를 추가하고,

controls.hpp를 선언해 사용하라고 한다. (사실 이전에 쓰던 shader와 texture도 따로 선언해야하는데 안 했었다)


나는 튜토리얼만 할 것임으로 한 코드에 넣을 것이다. (이렇게 해야지 초보자 분들도 따라하기 쉬울거 같아서)


do{

    // ...

    // Compute the MVP matrix from keyboard and mouse input
    computeMatricesFromInputs();
    glm::mat4 ProjectionMatrix = getProjectionMatrix();
    glm::mat4 ViewMatrix = getViewMatrix();
    glm::mat4 ModelMatrix = glm::mat4(1.0);
    glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;

    // ...
}

이 코드는 3가지 새로운 기능이 필요하다

1) computerMatricesFromInputs()는 키보드와 마우스를 읽고 Projection과 View 매트릭스들을 계산한다.

2) getProjectionMatrix()는 계산된 Projection행렬을 반환한다.

3) getViewMatrix()는 계산된 View행렬을 반환한다.


이 것은 여러가지 방법 중 하나일 뿐이므로 마음에 들지 않으면 변경해도 된다고 한다.



The actual code


우리는 몇 가지 변수가 필요하다.

// position
glm::vec3 position = glm::vec3( 0, 0, 5 );
// horizontal angle : toward -Z
float horizontalAngle = 3.14f;
// vertical angle : 0, look at the horizon
float verticalAngle = 0.0f;
// Initial Field of View
float initialFoV = 45.0f;

float speed = 3.0f; // 3 units / second
float mouseSpeed = 0.005f;

FoV는 확대/축소 수준을 뜻한다. (80° = 매우 넓은 각도, 거대한 변형,, 60°~45° : 표준,, 20° : 큰 확대)


입력에 따라 position, horizontalAngle, verticalAngle 및 FoV를 다시 계산 한 다음 position, horizontalAngle, verticalAngle 및

FoV에서 View 및 Projection 행렬을 계산한다.



Orientation


마우스 위치 읽기는 쉽다.

// Get mouse position
int xpos, ypos;
glfwGetMousePos(&xpos, &ypos);

 커서를 화면의 중앙으로 되돌려 놓아야한다. 그렇지 않으면 곧 창 밖으로 나가서 더 이상 움직일 수 없게 된다.

// Reset mouse position for next frame
glfwSetMousePos(1024/2, 768/2);

이 코드는 윈도우가 1024*768이라고 가정한다.

// Compute new orientation
horizontalAngle += mouseSpeed * deltaTime * float(1024/2 - xpos );
verticalAngle   += mouseSpeed * deltaTime * float( 768/2 - ypos );

이제 우리의 시야각을 계산할 수 있다.

// Compute new orientation
horizontalAngle += mouseSpeed * deltaTime * float(1024/2 - xpos );
verticalAngle   += mouseSpeed * deltaTime * float( 768/2 - ypos );

1) 1024/2 - xpos는 마우스 중앙에서 마우스까지의 거리를 의미한다. 이 값이 클스록 더 많이 돌린다는 것이다.
2) float(...)은 곱셈이 잘 되도록 부동 소수점 수로 변환한다.
3) mouseSpeed는 회전 속도를 높이거나 낮추기 위한 것이다.
   이 기능을 원하는대로 미세 조정하거나 사용자가 선택할 수 있도록해라.
4) += : 마우스를 움직이지 않으면 1024/2-xpos는 0이 되고 horizontalAngle += 0은 horizontalAngle을 변경하지 않는다.
   대신 "="기호를 사용하면 각 프레임의 원래 방향으로 되돌아 가게 된다. (이는 좋지 않다)


이제 World Space에서 우리가 보고 있는 방향을 나타내는 벡터를 계산할 수 있다.

// Direction : Spherical coordinates to Cartesian coordinates conversion
glm::vec3 direction(
    cos(verticalAngle) * sin(horizontalAngle),
    sin(verticalAngle),
    cos(verticalAngle) * cos(horizontalAngle)
);

이것은 표준 계산이지만 cosine과 sinus에 대해 잘 모르는 경우 간단한 설명을 하겠다.

이제 "up" vector에 대해 안정적인 계산을 하려고 한다. 그 전에 "up" vector가 항상 +Y 방향으로 향하는 것이 아님을 알아야한다.
만약 너가 아래쪽을 본다면, "up" vector는 수평이 될 것이다. 다음 사진을 참조해라.

우리의 경우에는 유일한 constant는 카메라의 오른쪽으로가는 벡터가 항상 수평이라는 것이다.
팔을 수평으로 놓고 어떤 방향으로든 아래를 올려다 보면서 이것을 확인할 수 있다.
"right" vector를 정의해보자. Y 좌표는 수평이므로 0이고, X 및 Z 좌표는 위의 그림과 같지만 각도는 90° 또는 Pi/2 라디안으로 회전한다.

// Right vector
glm::vec3 right = glm::vec3(
    sin(horizontalAngle - 3.14f/2.0f),
    0,
    cos(horizontalAngle - 3.14f/2.0f)
);

"right"vector와 "direction", 또는 "front" vector를 가지고 있다. "up" vector는 이 둘에 수직인 벡터이다.
유용한 수학 도구를 사용하면 이 작업을 매우 간단하게 처리할 수 있다.

// Up vector : perpendicular to both direction and right
glm::vec3 up = glm::cross( right, direction );



Position

// Move forward
if (glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS){
    position += direction * deltaTime * speed;
}
// Move backward
if (glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS){
    position -= direction * deltaTime * speed;
}
// Strafe right
if (glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS){
    position += right * deltaTime * speed;
}
// Strafe left
if (glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS){
    position -= right * deltaTime * speed;
}

여기서 잘 봐야할 것은 deltaTime이다. 컴퓨터 속도에 따라 달라지지 않도록 하기 위해 deltaTime을 곱해준다.

double currentTime = glfwGetTime();
float deltaTime = float(currentTime - lastTime);


Field Of View

마우스 휠을 Field of View에 바인딩해 확대/축소할 수 있다.

float FoV = initialFoV - 5 * glfwGetMouseWheel();


Computing the matrices

// Projection matrix : 45&deg; Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
ProjectionMatrix = glm::perspective(glm::radians(FoV), 4.0f / 3.0f, 0.1f, 100.0f);
// Camera matrix
ViewMatrix       = glm::lookAt(
    position,           // Camera is here
    position+direction, // and looks here : at the same position, plus "direction"
    up                  // Head is up (set to 0,-1,0 to look upside-down)
);


마우스와 키보드로 움직이는거 뿐인데도 많은 과정이 필요했다. 어렵지는 않았으니 나중에 프로젝트를 진행할 때 한 번 더 보도록 하자.

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
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
#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 *);
 
 
//mouse-keyboard input
void computeMatricesFromInputs();
glm::mat4 getViewMatrix();
glm::mat4 getProjectionMatrix();
 
glm::mat4 ViewMatrix;
glm::mat4 ProjectionMatrix;
 
glm::mat4 getViewMatrix() {
    return ViewMatrix;
}
glm::mat4 getProjectionMatrix() {
    return ProjectionMatrix;
}
 
//포지션 초기화
glm::vec3 position = glm::vec3(005);
float horizontalAngle = 3.14f;
float verticalAngle = 0.0f;
float initialFoV = 45.0f;
 
float speed = 3.0f;
float mouseSpeed = 0.005f;
 
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);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
 
    // Set the mouse at the center of the screen
    glfwPollEvents();
    glfwSetCursorPos(window, 1024 / 2768 / 2);
 
    // Dark blue background
    glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
 
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_CULL_FACE);
 
    GLuint VertexArrayID;
    glGenVertexArrays(1&VertexArrayID);
    glBindVertexArray(VertexArrayID);
 
    //Shader를 불러온다.
    GLuint programID = LoadShaders("TransformVertexShader.vertexshader""TextureFragmentShader.fragmentshader");
 
    //매트릭스ID 추가
    GLuint MatrixID = glGetUniformLocation(programID, "MVP");
    
    //어떠한 두 가지의 함수를 사용해서 텍스처를 불러온다
    //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, 0.000004f,
        0.000103f, 0.336048f,
        0.335973f, 0.335903f,
        1.000023f, 0.000013f,
        0.667979f, 0.335851f,
        0.999958f, 0.336064f,
        0.667979f, 0.335851f,
        0.336024f, 0.671877f,
        0.667969f, 0.671889f,
        1.000023f, 0.000013f,
        0.668104f, 0.000013f,
        0.667979f, 0.335851f,
        0.000059f, 0.000004f,
        0.335973f, 0.335903f,
        0.336098f, 0.000071f,
        0.667979f, 0.335851f,
        0.335973f, 0.335903f,
        0.336024f, 0.671877f,
        1.000004f, 0.671847f,
        0.999958f, 0.336064f,
        0.667979f, 0.335851f,
        0.668104f, 0.000013f,
        0.335973f, 0.335903f,
        0.667979f, 0.335851f,
        0.335973f, 0.335903f,
        0.668104f, 0.000013f,
        0.336098f, 0.000071f,
        0.000103f, 0.336048f,
        0.000004f, 0.671870f,
        0.336024f, 0.671877f,
        0.000103f, 0.336048f,
        0.336024f, 0.671877f,
        0.335973f, 0.335903f,
        0.667969f, 0.671889f,
        1.000004f, 0.671847f,
        0.667979f, 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);
 
        //키보드와 마우스 인풋으로부터의 MVP 매트릭스를 계산한다
        computeMatricesFromInputs();
        glm::mat4 ProjectionMatrix = getProjectionMatrix();
        glm::mat4 ViewMatrix = getViewMatrix();
        glm::mat4 ModelMatrix = glm::mat4(1.0);
        glm::mat4 MVP = ProjectionMatrix*ViewMatrix*ModelMatrix;
 
        //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);
    glDeleteBuffers(1&uvbuffer);
    glDeleteProgram(programID);
    glDeleteTextures(1&TextureID);
    glDeleteVertexArrays(1&VertexArrayID);
 
    // 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;
}
 
void computeMatricesFromInputs() {
 
    //glfwGetTime은 한번만 호출된다.
    static double lastTime = glfwGetTime();
 
    //현재와 마지막 프레임의 시간 차를 계산한다.
    double currentTime = glfwGetTime();
    float deltaTime = float(currentTime - lastTime);
 
    //마우스의 위치를 얻는다.
    double xpos, ypos;
    glfwGetCursorPos(window, &xpos, &ypos);
 
    //다음 프레임의 마우스 위치를 리셋한다.
    glfwSetCursorPos(window, 1024 / 2768 / 2);
 
    horizontalAngle += mouseSpeed * float(1024 / 2 - xpos);
    verticalAngle += mouseSpeed * float(768 / 2 - ypos);
 
    //Direction : Spherical 좌표 to Cartesian 좌표 변환
    glm::vec3 direction(
        cos(verticalAngle)*sin(horizontalAngle),
        sin(verticalAngle),
        cos(verticalAngle)*cos(horizontalAngle)
    );
 
    //Right vector
    glm::vec3 right = glm::vec3(
        sin(horizontalAngle - 3.14f / 2.0f),
        0,
        cos(horizontalAngle - 3.14f / 2.0f)
    );
 
    //Up vector
    glm::vec3 up = glm::cross(right, direction);
 
    //앞으로 이동
    if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS) {
        position += direction*deltaTime*speed;
    }
    //뒤로 이동
    if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS) {
        position -= direction*deltaTime*speed;
    }
    //오른쪽로 Strafe
    if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) {
        position += right*deltaTime*speed;
    }
    //왼쪽으로 Strafe
    if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) {
        position -= right*deltaTime*speed;
    }
 
    float FoV = initialFoV;
 
    ProjectionMatrix = glm::perspective(FoV, 4.0f / 3.0f, 0.1f, 100.0f);
 
    ViewMatrix = glm::lookAt(
        position,                //camera here
        position + direction,        //and looks here
        up                        // Head is up
    );
 
    //다음 프레임을 위해
    lastTime = currentTime;
}
cs