GLFW (Graphics Library Framework)
GLFW는 OpenGL과 같은 그래픽 API에서 사용할 수 있는 창 관리 및 입력 처리 라이브러리이다. OpenGL 컨텍스트를 생성하고, 창을 띄우거나, 키보드 및 마우스 입력을 처리하는 데 매우 유용하다. 또한, 다중 모니터 지원, Vulkan API 지원 등 다양한 기능을 제공한다. 이 라이브러리를 사용하면 복잡한 플랫폼 종속 코드를 작성하지 않아도 쉽게 창을 생성하고 이벤트를 처리할 수 있다.
- 주요 기능: 창 생성, OpenGL 컨텍스트 관리, 입력 처리 (키보드, 마우스), 다중 모니터 지원
- 사용 예시: OpenGL 컨텍스트를 생성하고, 창 크기를 변경하거나 키보드 입력 이벤트를 처리하는 콜백 함수 등록
GLAD (OpenGL Loader Generator)
GLAD는 OpenGL 함수 포인터를 로드하고, OpenGL 확장을 관리하는 로더 라이브러리이다. OpenGL은 플랫폼에 따라 드라이버에서 함수 포인터를 런타임에 로드해야 하기 때문에, GLAD를 통해 이 과정을 쉽게 처리할 수 있다. GLAD는 OpenGL 함수들을 호출할 수 있게 해주는 역할을 하며, 다양한 OpenGL 버전과 확장도 지원한다.
- 주요 기능: OpenGL 함수 로딩, 확장 관리
- 사용 예시: OpenGL 함수 호출 전에 gladLoadGLLoader로 모든 OpenGL 함수를 로드하고, 이 함수들이 제대로 로드되었는지 확인
예시 코드가 주어졌는데 가장 기본적인 코드라 하나하나 뜯어봐야 나중에 고생 덜 한다.
#include <spdlog/spdlog.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
void OnFramebufferSizeChange(GLFWwindow* window, int width, int height) {
SPDLOG_INFO("framebuffer size changed: ({} x {})", width, height);
glViewport(0, 0, width, height);
}
void OnKeyEvent(GLFWwindow* window,
int key, int scancode, int action, int mods) {
SPDLOG_INFO("key: {}, scancode: {}, action: {}, mods: {}{}{}",
key, scancode,
action == GLFW_PRESS ? "Pressed" :
action == GLFW_RELEASE ? "Released" :
action == GLFW_REPEAT ? "Repeat" : "Unknown",
mods & GLFW_MOD_CONTROL ? "C" : "-",
mods & GLFW_MOD_SHIFT ? "S" : "-",
mods & GLFW_MOD_ALT ? "A" : "-");
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
}
int main() {
SPDLOG_INFO("Start program");
// glfw 라이브러리 초기화, 실패하면 에러 출력후 종료
SPDLOG_INFO("Initialize glfw");
if (!glfwInit()) {
const char* description = nullptr;
glfwGetError(&description);
SPDLOG_ERROR("failed to initialize glfw: {}", description);
return -1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// glfw 윈도우 생성, 실패하면 에러 출력후 종료
SPDLOG_INFO("Create glfw window");
auto window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_NAME,
nullptr, nullptr);
if (!window) {
SPDLOG_ERROR("failed to create glfw window");
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// glad를 활용한 OpenGL 함수 로딩
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
SPDLOG_ERROR("failed to initialize glad");
glfwTerminate();
return -1;
}
auto glVersion = glGetString(GL_VERSION);
SPDLOG_INFO("OpenGL context version: {}", reinterpret_cast<const char*>(glVersion));
// callback 함수 등록
OnFramebufferSizeChange(window, WINDOW_WIDTH, WINDOW_HEIGHT);
glfwSetFramebufferSizeCallback(window, OnFramebufferSizeChange);
glfwSetKeyCallback(window, OnKeyEvent);
// glfw 루프 실행, 윈도우 close 버튼을 누르면 정상 종료
SPDLOG_INFO("Start main loop");
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
glClearColor(0.0f, 0.1f, 0.2f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
1. #include 순서가 중요한 이유
#include <spdlog/spdlog.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
여기서 중요한 점은 glad가 항상 glfw보다 먼저 포함되어야 한다는 것이다. 그 이유는 glad가 OpenGL의 모든 함수들을 로드해주는 역할을 하기 때문이다. glfw는 이 함수들을 사용하여 창을 제어하고, 이벤트를 처리하는 라이브러리이므로, glad가 먼저 로드되어 있어야만 OpenGL 함수들을 사용할 수 있다.
2. GLFW 초기화 및 OpenGL 버전 설정
if (!glfwInit()) {
const char* description = nullptr;
glfwGetError(&description);
SPDLOG_ERROR("failed to initialize glfw: {}", description);
return -1;
}
이 부분은 GLFW를 초기화하는 코드이다. glfwInit() 함수가 성공하면 true를 반환하고, 실패하면 false를 반환한다.
3. GLFW 창 생성 및 GLAD 초기화
auto window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_NAME, nullptr, nullptr);
if (!window) {
SPDLOG_ERROR("failed to create glfw window");
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwCreateWindow는 지정된 크기와 이름으로 OpenGL 창을 생성하는 함수이다. 실패할 경우 에러 메시지를 출력하고 프로그램을 종료하도록 설정되어 있다. 창이 성공적으로 생성되면, glfwMakeContextCurrent로 해당 창을 OpenGL의 현재 컨텍스트로 설정한다.
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
SPDLOG_ERROR("failed to initialize glad");
glfwTerminate();
return -1;
}
이 코드는 GLAD를 초기화하는 부분이다. gladLoadGLLoader를 통해 OpenGL 함수 포인터들을 로드하며, glfwGetProcAddress를 통해 GLAD가 올바른 함수 포인터들을 찾을 수 있게 도와준다. 초기화에 실패하면 프로그램을 종료한다.
4. 콜백 함수 등록
OnFramebufferSizeChange(window, WINDOW_WIDTH, WINDOW_HEIGHT);
glfwSetFramebufferSizeCallback(window, OnFramebufferSizeChange);
glfwSetKeyCallback(window, OnKeyEvent);
GLFW에서는 이벤트 처리(예: 창 크기 변경, 키 입력)를 위해 콜백 함수를 등록할 수 있다. 위 코드에서는 두 가지 콜백을 등록했다.
- glfwSetFramebufferSizeCallback: 창의 크기가 변경될 때 호출되는 콜백을 등록한다. 창 크기가 변경되면 OpenGL 뷰포트도 변경되어야 하기 때문에, glViewport를 호출하여 화면에 렌더링할 영역을 새롭게 설정한다.
- glfwSetKeyCallback: 키보드 입력 이벤트를 처리하는 콜백을 등록한다. 이 콜백에서는 키가 눌리거나 떼어졌을 때 해당 정보를 로그로 출력하며, ESC 키를 누르면 창을 닫도록 설정되어 있다.
5. GLFW 메인 루프
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
glClearColor(0.0f, 0.1f, 0.2f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
}
이 코드는 메인 루프이다. glfwWindowShouldClose 함수는 창이 닫혀야 하는지 확인하는 함수로, 창이 닫히지 않은 동안 계속해서 루프를 돌게 된다. glfwPollEvents는 입력 이벤트를 처리하고, glClearColor와 glClear는 화면을 특정 색으로 초기화하는 역할을 한다. 마지막으로 glfwSwapBuffers를 통해 렌더링된 화면을 화면에 표시한다.
glfwSwapBuffers와 더블 버퍼링
이번에 학습한 glfwSwapBuffers 함수는 화면을 부드럽게 전환하는 중요한 역할을 한다. 이 함수가 어떻게 동작하는지 이해하기 위해서는 더블 버퍼링(Double Buffering) 개념을 알아야 한다.
더블 버퍼링이란?
더블 버퍼링은 프론트 버퍼와 백 버퍼라는 두 개의 버퍼를 교대로 사용하여 화면을 부드럽게 업데이트하는 기술이다.
- 프론트 버퍼: 현재 화면에 표시되고 있는 내용이 저장된 버퍼.
- 백 버퍼: 다음 프레임을 그리기 위해 준비하는 버퍼.
더블 버퍼링이 없다면 GPU가 프론트 버퍼에 직접 렌더링을 하게 되어 화면이 깜빡이거나 화면 찢김(Tearing) 현상이 발생할 수 있다. 더블 버퍼링을 사용하면 백 버퍼에 새로운 프레임을 먼저 그린 후, 준비가 완료되면 백 버퍼와 프론트 버퍼를 교체하는 방식으로 이러한 문제를 해결할 수 있다.
glfwSwapBuffers의 역할
glfwSwapBuffers 함수는 백 버퍼와 프론트 버퍼를 교체하는 역할을 한다. 백 버퍼에 그려진 이미지가 준비되면, 이 함수를 호출하여 백 버퍼를 프론트 버퍼로 바꾸고, 화면에 표시되는 내용을 업데이트한다.
- 프로그램이 백 버퍼에 새로운 프레임을 렌더링한다.
- glfwSwapBuffers를 호출하면 백 버퍼와 프론트 버퍼가 교체되어, 백 버퍼에 그려진 이미지가 화면에 표시된다.
- 백 버퍼는 다음 프레임을 그리기 위해 비워지고 다시 사용된다.
이 과정을 통해 화면 전환이 부드럽고 자연스럽게 이루어지며, 깜빡임이나 화면 찢김 현상을 방지할 수 있다.
'Computer Graphics > OpenGL' 카테고리의 다른 글
OpenGL 정리 - 5. context 클래스 추가 (OpenGL 초기화 및 렌더링 과정) (0) | 2024.09.10 |
---|---|
OpenGL 정리 - 4. shader, program 클래스 추가 (0) | 2024.09.10 |
OpenGL 정리 - 3. 프로그래머블 셰이더(Programmable Shader) (0) | 2024.09.10 |
OpenGL 정리 - 1. CMake 사용 (0) | 2024.09.09 |
OpenGL 정리 - 0. (1) | 2024.09.09 |