한번 간단한 GLFW 기본 예제를 뜯어보자.
우선 전체 소스코드는 아래와 같다.
// OpenGL1 glfw.cpp : 이 파일에는 'main' 함수가 포함됩니다. 거기서 프로그램 실행이 시작되고 종료됩니다.
//
#include <glad\glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialized GLAD" << std::endl;
return -1;
}
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
while (!glfwWindowShouldClose(window)) { //if GLFW has been instructed to close
processInput(window);
glClearColor(0.2f, 0.3, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window); // swap the color buffer
glfwPollEvents(); // checks if any events are triggered such as keyboard or mouse input
}
glfwTerminate();
return 0;
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
부분을 나눠서 이해해보자.
#include <glad\glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
GL 함수를 사용하기 위해 glad.h 헤더와 윈도우를 만들고 그 안에 그래픽을 뿌리기 위해서 glfw가 필요하다.
한가지 유의할 점은, 반드시 glad.h 헤더를 glfw 헤더 이전에 선언해야 한다.
GL의 기본 라이브러리 함수들이 gl.h 헤더 파일에 들어있는데, 이 헤더파일은 glad.h 에 있기 때문이다.
각 헤더 파일의 위치를 지정하는 방법은 이 게시글 시리즈의 첫번째 게시글을 참고하자.
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
사용할 함수와 상수를 선언하는 부분이다.
상수는 초기 윈도우창의 가로 세로 크기를 지정하는 변수이다.
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
...
}
이제부터는 main 함수이다.
다른 함수 이야기를 하기전까지 이후 모든 코드는 main 함수에 작성된 코드이다.
처음에 glfwInit()과 마지막에 glfwTerminate() 함수는 프로그램의 시작과 종료에 호출해야 한다.
그리고 GLFW 버전과 모드를 설정해준다.
glfwWindowHint() 함수는 설정 옵션과 해당 옵션에 들어갈 설정값을 인자로 받는다.
우리는 현재 3.3 버전을 사용하고 있으므로 Major 3, Minor 3 버전을 입력해주었다.
지난 글에서 GLFW 3.2 부터는 Immediate 모드 대신 Core-Profile 모드를 사용한다고 했다.
이 부분을 설정하기 위해 OpenGL_Profile 을 GLFW_OPENGL_CORE_PROFILE 로 설정하였다.
그 다음 코드는 이렇게 입력한 설정을 토대로 실제 윈도우 창을 만드는 과정이다.
glfwCreateWindow 함수를 통해 윈도우 창을 생성한다.
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialized GLAD" << std::endl;
return -1;
}
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
그 다음 첫줄은 Context를 만드는 과정이다.
Open GL은 State Machine 이고, OpenGL의 State가 Context로 구현된다고 했었다.
그 Context를 만드는 코드가 첫째줄 코드이다.
우리가 만든 윈도우 창의 OpenGL Context를 현재 호출한 스레드의 Context로 설정해준다.
FrameBuffer 는 간단하게 창 크기라고 보면 된다.
(정확히는 이미지를 화면에 내보내기 직전에 메모리에 담기는 데이터, 말 그대로 '버퍼' 이다.)
프레임 버퍼에 대해 더 자세히 정리하자면,
그래픽 프로세서에서 화면에 그리려고 하는 object의 데이터를 적절히 처리해서 생성한 뒤, 이 데이터를 출력하기 전 프레임 버퍼(컬러 버퍼, depth 버퍼 등의 조합으로 이루어진 버퍼)에 넣어둔다.
화면에 출력할 때는 HDMI, RAMDAC 같은 비디오 컨트롤러에서 프레임 버퍼에 있는 데이터를 읽은 뒤, 모니터라는 아날로그 장치에 출력할 수 있도록 변환한다. 즉, 디지털 데이터를 아날로그로 변환한다.
(RAMDAC = RAM Digital to Analog Convertor, RAM + DAC을 하나로 조합한 칩)
그런데 이렇게 버퍼를 하나만 두면 문제점이 있다.
바로 화면에 새로운 내용을 그리고 싶을 때 프레임 버퍼의 내용을 수정해야 하는데, 버퍼의 내용이 바뀌는 과정에 비디오 컨트롤러가 이를 차례대로 모두 변환하기 때문에 화면이 깜빡깜빡하는 flickering 현상이 발생한다.
(잔상 = artifact 가 남는다고도 한다.)
이 현상을 방지하기 위해 프레임 버퍼 2개를 두는 것을 '더블 버퍼링 (double buffering)' 이라고 한다.
더블 버퍼링은 비디오 컨트롤러에 연결될 '프론트 버퍼' 와, 그래픽 프로세서와 연결될 '백 버퍼' 로 구성되어 있다.
만약 새로운 내용을 화면에 다시 그려야 한다면, 그래픽 프로세서는 다시 그릴 데이터를 백 버퍼에 저장하고, 기존 프론트 버퍼와 백 버퍼를 교체 (swap) 하여 비디오 컨트롤러는 바뀐 데이터를 한번에 읽어 모니터에 출력하도록 한다.
glfw 같은 GL 라이브러리는 double buffering을 위해 glfwSwapBuffers 같은 함수를 제공한다.
두번째 코드는 위에서 만든 윈도우의 창크기(프레임 버퍼 사이즈)가 바뀌어 이벤트가 발생했을 때, 이를 처리할 콜백함수를 등록하는 함수이다.
다음은 GLAD 라이브러리의 함수를 이용해 GL Library를 로드하는 과정을 거친다.
마지막으로 glViewport 는 화면을 그려줄 사이즈 (뷰포트) 를 지정하는 부분이다.
while (!glfwWindowShouldClose(window)) { //if GLFW has been instructed to close
processInput(window);
glClearColor(0.2f, 0.3, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window); // swap the color buffer
glfwPollEvents(); // checks if any events are triggered such as keyboard or mouse input
}
glfwTerminate();
return 0;
마지막은 루프를 돌면서, 윈도우 창이 닫혀야 하는 상태인지 체크하고,
아직 닫혀야 하는 상태가 아니라면 사용자의 입력을 받아 이벤트 처리를 한다. (processInput())
이벤트 처리가 끝났으면 화면을 그려야 한다.
우선 빈 화면의 기본 배경 색상을 지정하고(clear clolor), 기존 color 버퍼를 비운 뒤 (clear) 앞에서 지정한 색상으로 배경색을 새로 지정한다.
glfwPollEvents()는 주석에 나와있는 것처럼 키보드, 마우스 이벤트를 잡는 부분이다.
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
위에서 달아줬던 콜백함수와 input 처리 함수이다.
창 크기 변경 이벤트의 콜백함수는 glViewport() 함수를 호출하여, 윈도우의 가로 세로 길이를 새로 설정한다.
input 처리함수는 esc키를 누르면 프로그램 (윈도우 창)을 종료하도록 하는 코드이다.
'CS > HCI 윈도우즈프로그래밍' 카테고리의 다른 글
[OpenGL] 6. Shader (2) - VAO (1) | 2024.04.07 |
---|---|
[OpenGL] 5. Shader (1) - Shader, VBO, Shader Program (0) | 2024.04.05 |
[OpenGL] 4. Graphics Pipeline (1) | 2024.04.04 |
[OpenGL] 2. Open GL 개념 (0) | 2024.04.03 |
[OpenGL] 1. GLFW, GLAD 환경설정 (2) | 2024.04.03 |