OpenGL 시리즈 링크
OPENGL 소개: OPENGL, GLFW, GLEW란? [OPENGL E02]
OPENGL 설치: GLEW, GLFW 다운로드법과 VISUAL STUDIO에서 OPENGL 사용하기 [OPENGL E03]
OPENGL 창 만들기: OPENGL 기초 세팅[OPENGL E04]
OPENGL 삼각형 그리기 (1)VBO, VAO [OPENGL E05]
OPENGL 삼각형 그리기 (1)-2 코드 중간 정리 [OPENGL E06]
OPENGL 삼각형 그리기 (2)VERTEX SHADER, FRAGMENT SHADER의 기초 [OPENGL E07]
OPENGL 삼각형 그리기 (3)SHADER PROGRAM [OPENGL E08]
창(Window) 생성
GLFW
우선, GLFW와 GLEW를 include 해야합니다.
#include <GL/glew.h>
#include <GLFW/glfw3.h>
main 함수를 만들고 다음 코드들을 순서대로 작성합니다.
GLFW Init
Window를 만드는 역할은 GLFW가 합니다. 따라서 GLFW를 init합니다.
if (!glfwInit())
exit(EXIT_FAILURE);
glfwInit 함수는 GLFW 라이브러리를 초기화합니다.
glfwInit이 초기화에 성공할 시 1로 정의된 GLFW_TRUE를 반환하고, 실패 시 glfwTerminate 함수를 호출하고 0으로 정의된 GLFW_FALSE를 반환합니다.
glfwTerminate 함수는 glfwInit으로 할당된 메모리들을 해제하기 때문에 glfwInit을 사용했다면 프로그램을 종료하기 전 꼭 호출해야하는 함수입니다.
다만, glfwInit 함수가 초기화를 실패할 때에는 알아서 glfwTerminate 함수를 호출하기 때문에 또 호출할 필요는 없습니다.
참고로 exit 함수와 EXIT_FAILURE는 stdlib.h 헤더 파일을 추가하셔야 호출, 사용할 수 있습니다.
GLFW Window Hint 설정
Window를 만들기 전에 설정할 옵션들이 있습니다. 이 옵션들은 Window를 만들거나 OpenGL context를 생성하기 전에 설정하며 glfwWindowHint 함수를 호출하여 설정합니다.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // OpenGL 버전(x.y 중 x)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); // 4.6에 맞춰 실행(x.y 중 y)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 더 이상 쓰이지 않는 하위 호환 기능들 에러 처리
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 상위 호환성 지원
glfwWindowHint 함수의 첫 번째 파라미터는 설정하고자 하는 옵션입니다. 두 번째 파라미터는 옵션의 값입니다.
우선, 자신의 그래픽 카드에 탑재된 OpenGL의 버전에 따라 GLFW_CONTEXT_VERSION_MAJOR과 GLFW_CONTEXT_VERSION_MINOR을 설정해줍니다. 자신의 그래픽 카드가 어느 버전의 OpenGL을 제공하는지 모르신다면 OpenGL 버전 체크법 글을 참고해주세요!
이어서, GLFW_OPENGL_PROFILE을 GLFW_OPENGL_CORE_PROFILE로 설정합니다. OpenGL은 3.0 버전이 나오면서 이전의 Fixed Function Pipeline을 사용하지 않기로 합니다. Fixed Function Pipeline에서 쓰던 glVertex3f, glColor3f 등과 같은 Fixed Function을 낡은 함수로 취급하고 더 이상 지원하지 않는 것입니다. GLFW_OPENGL_PROFILE을 GLFW_OPENGL_CORE_PROFILE로 설정한다는 것은 이 낡은 함수들, 즉 하위 호환 함수들을 사용하지 않겠다는 의미입니다. 만약 하위 호환 함수들을 사용하고자 한다면 옵션값을 GLFW_OPENGL_COMPAT_PROFILE로 설정하시면 됩니다. 하지만 삭제된 함수들을 쓸 필요는 없겠죠. (만약 본인의 OpenGL 버전이 3.2 이하라면 GLFW_OPENGL_ANY_PROFILE로 설정해주세요!)
마지막으로, GLFW_OPENGL_FORWARD_COMPAT을 GL_TRUE로 설정하여 상위 호환성 지원 채택합니다. GL_TRUE란 OpenGL에서 사용하는 매크로값으로 glew.h에 1로 정의되어있습니다. (GL_FALSE는 0입니다.)
GLFW Windows Hint에 대해 더 자세히 알고 싶으시다면 GLFW Windows Hint를 참고해주세요!
Window 만들기
GLFW를 이용해 창을 만들어 보겠습니다.
GLFWwindow* window = glfwCreateWindow(1080, 720, "title", NULL, NULL);
if (!window)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
glfwCreateWindow 함수는 창을 만들고 OpenGL context를 생성합니다.
첫 번째, 두 번째 파라미터는 각각 만들고자 하는 창의 가로, 세로 길이이며 0보다 커야합니다.
세 번째 파라미터는 창의 이름이 들어가야 합니다.
네 번째 파라미터는 창을 전체화면 모드로 사용하고자 하는 경우 사용할 모니터입니다. 전체화면 모드를 사용하지 않는 경우 NULL을 넣어주면 됩니다. 만약 창이 전체화면이기를 원할 경우 위 코드의 첫 두 줄을 아래 코드로 바꿔주시면 됩니다.
GLFWmonitor* primary = glfwGetPrimaryMonitor();
GLFWwindow* window = glfwCreateWindow(1080, 720, "title", primary, NULL);
마지막 파라미터는 리소스를 공유할 OpenGL context를 가진 창이며 리소스를 공유할 창이 없다면 NULL로 두시면 됩니다.
glfwCreateWindow는 창을 생성 시 생성된 창의 핸들을 반환하고, 이에 실패 시 NULL을 반환합니다. 창을 만들지 못했다면 프로그램을 종료해야 하므로 glfwTerminate를 호출해야하고 종료합니다.
창을 만들었다면 glfwMakeContextCurrent 함수를 호출하여 창과 함께 생성된 OpenGL context를 current context로 만들어줍니다. 이 때, 인자는 생성된 창의 핸들입니다. OpenGL은 finite state machine(유한 상태 기계)로 current context를 바꾸지 않는 이상 지정된 OpenGL context를 current context로 간주합니다. finite state machine(유한 상태 기계)는 가벼운 개념이 아니므로 나중에 따로 설명하도록 하겠습니다. 일단은 우리가 생성한 OpenGL context를 current context로 만들어주어야 한다고 알고 넘어가도록 하겠습니다.
Viewport 만들기
창은 우리가 무언가를 그릴 수 있는 도화지입니다. 그렇다면 이 도화지 위의 어느 부분에 그림을 그릴 것인지 설정해주어야합니다. 이를 viewport(뷰포트)라고 합니다.
int framebuf_width, framebuf_height;
glfwGetFramebufferSize(window, &framebuf_width, &framebuf_height);
glViewport(0, 0, framebuf_width, framebuf_height);
뷰포트는 glViewport 함수를 호출하여 생성합니다.
첫 번째, 두 번째 파라미터는 뷰포트의 좌하단 픽셀의 x, y값을 넣어주어야 하며 0, 0이 디폴트값입니다.
세 번째, 네 번째 파라미터는 뷰포트의 가로, 세로 크기입니다. 우리는 생성한 창 전체를 쓰고자 하므로 창의 가로, 세로 크기를 인자로 넣도록 하겠습니다. 창의 가로, 세로 크기는 창의 프레임버퍼의 가로, 세로 크기와 동일합니다. 따라서, glfwGetFramebufferSize 함수에 우리가 생성한 창의 핸들, 가로, 세로 크기를 담을 int 변수의 주소를 인자로 넣고 가로, 세로 크기를 가져옵니다. 그리고 그 값들을 glViewport의 세 번째, 네 번째 인자로 전달합니다.
glViewport가 내부적으로 화면 좌표를 계산하는 방식은 수학적으로 간단하나, 이를 이해하기 위해서는 Normalized Device Coordinates(NDC)의 개념을 알아야 하므로 여기서는 생략하도록 하겠습니다. glViewport 함수를 통해서 뷰포트를 설정한다고만 알고 넘어가도 좋습니다.
GLEW Init
GLFW로 창을 만들고 뷰포트를 설정하였습니다. 이제 GLEW 라이브러리를 초기화해야합니다. 이는 GLFW 라이브러리를 초기화한 방식과 똑같습니다.
if (glewInit() != GLEW_OK)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
GLEW 라이브러리의 초기화는 glewInit 함수를 호출하여 실행합니다. GLEW의 초기화는 꼭 OpenGL context를 생성하고 current context로 설정한 이후에 진행해야 합니다. 따라서, glewInit 함수를 창 만들기 전에 호출하면 우리가 원하는 창이 뜨지 않거나 에러가 날 수 있습니다.
glewInit 함수는 초기화에 성공 시 GLEW_OK를 반환합니다. 이제 OpenGL의 주요 기능과 확장 기능을 모두 사용할 수 있게 됩니다. 초기화 실패 시 glfwTerminate 함수로 위에서 초기화한 GLFW를 정리한 후 프로그램을 종료해야합니다.
빨간 화면 그리기
자, 우리는 그림을 그릴 준비를 마쳤습니다. 이제 프로그램이 종료되기 전까지 계속 반복하여 돌 while 반복문을 만들어주어야 합니다. 이 반복문 안에서 모든 그래픽 그리기 작업이 진행된다고 보면 됩니다. 일단은 빨간 화면을 그려보도록 하겠습니다.
while (!glfwWindowShouldClose(window))
{
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwWindowShouldClose 함수는 생성된 창의 핸들을 인자로 받아서 해당 창의 close flag를 리턴합니다. 현재는 창 닫기 버튼(windows 창 기준 오른쪽 상단 X 버튼)을 누를 경우 해당 창의 close flag가 참이 되어 반복문을 벗어나게 됩니다. 프로그램을 종료할 수 있는 다른 조건을 설정하고 싶다면 glfwSetWindowShouldClose 함수를 사용하여 창의 close flag를 설정하면 됩니다. 나중에 esc 키를 눌러서 프로그램을 종료하는 콜백 함수를 작성할 때 더 자세히 살펴보겠습니다.
color buffer(컬러 버퍼)를 조정하여 창의 배경색을 설정하겠습니다. glClearColor 함수는 창의 컬러 버퍼를 초기화할 때 사용할 색을 지정합니다. 인자는 앞에서부터 순서대로 빨강, 초록, 파랑, 알파값을 전달받으며 0 ~ 1 사이의 float값이어야 합니다. 색을 지정했다면, glClear 함수에 GL_COLOR_BUFFER_BIT을 인자로 넣고 호출하여 컬러 버퍼를 우리가 설정한 색으로 초기화합니다.
glfwSwapBuffers 함수는 인자로 받은 창의 버퍼를 교체합니다. 컴퓨터 그래픽스에 대한 지식이 전무하다면 버퍼를 교체한다는 말이 이해가 안 될 것입니다. 기본적으로 OpenGL은 더블 버퍼링으로 작동합니다. 그림을 그리는 메모리 공간, 즉 버퍼가 두 개입니다. 버퍼를 하나만 사용할 경우 버퍼에 데이터를 저장하는 동안 다음 그림의 데이터를 화면에 전달할 수 없고, 따라서 화면이 깜빡이거나 끊어지는 현상이 나타납니다. 이를 해결하기 위해, 버퍼를 두 개 보유하여 하나에는 데이터를 저장하고 하나는 저장된 데이터를 화면에 전달하는 방식을 취합니다.
예컨대, 버퍼 A와 버퍼 B가 있다고 가정해봅시다. OpenGL은 A에 그래픽 데이터를 저장하는 동안, B에 담긴 그래픽 데이터를 화면에 송출합니다. 그리고 glfwSwapBuffer 함수로 두 버퍼를 교체합니다. 이번엔 이전에 A에 저장한 그래픽 데이터를 화면에 송출하는 동안 B에 다음 그래픽 데이터를 저장합니다. 그리고 glfwSwapBuffer로 다시 두 버퍼를 교체합니다. 이 과정이 반복됩니다.
따라서, glfwSwapBuffers 함수는 그래픽 데이터를 버퍼에 모두 저장한 후 while 반복문의 말미에 호출합니다.
마지막으로 glfwPollEvents 함수를 호출하여 이벤트 큐에 있는 이벤트들을 실행합니다. 추후 콜백 함수를 활용하여 키보드 입력, 마우스 입력 등을 받는 이벤트를 생성할 때 더 자세히 다루도록 하겠습니다. 지금은 그냥 while 반복문 어디엔가 한 번 호출해주시면 됩니다.
프로그램 종료
어떤 이유에서 glfwWindowShouldClose 함수가 참을 반환하여 반복문이 종료된다면 프로그램을 종료시켜야 합니다. 프로그램을 종료하기 전 glfwTerminate 함수를 호출하여 창을 포함해서 GLFW 라이브러리에서 할당된 메모리를 해제해주고 정리합니다.
glfwTerminate();
return 0;
전체 코드
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdlib.h>
int main(void)
{
if (!glfwInit())
exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
GLFWwindow* window = glfwCreateWindow(1080, 720, "title", NULL, NULL);
if (!window)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
int framebuf_width, framebuf_height;
glfwGetFramebufferSize(window, &framebuf_width, &framebuf_height);
glViewport(0, 0, framebuf_width, framebuf_height);
if (glewInit() != GLEW_OK)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
while (!glfwWindowShouldClose(window))
{
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}