앞선 지식들을 구현하며 공부하느라 많이 정리를 하지 못했습니다.
구현했던 순서대로 작성을 해보겠습니다!
윈도우를 만들기 위한 전체 코드
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <OpenGL/gl.h>
#include <iostream>
int main(void)
{
if (!glfwInit())
{
std::cerr << "GFLW Init Failed" << std::endl;
return (-1);
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
GLFWwindow* window = glfwCreateWindow(500, 500, "scop", NULL, NULL);
glfwMakeContextCurrent(window);
int framebuffer_w, framebuffer_h;
glfwGetFramebufferSize(window, &framebuffer_w, &framebuffer_h);
glViewport(0, 0, framebuffer_w , framebuffer_h);
if (glewInit() != GLEW_OK)
{
std::cerr << "GLEW Init Failed" << std::endl;
glfwTerminate();
return (-1);
}
while (!glfwWindowShouldClose(window))
{
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return (0);
}
1. OpenGL 프로젝트의 시작은 항상 창 만들기부터!
OpenGL은 "그래픽"만 담당합니다. 운영체제 창은 만들어주지 않죠.
그래서 우리는 GLFW 같은 라이브러리를 써야 해요. GLFW는 "운영체제에 독립적인 윈도우 생성 및 입력 처리"를 담당합니다.
#include <GLFW/glfw3.h>헤더를 포함한 뒤 glfw 함수를 사용하기 위해 초기화해야 합니다.
if (!glfwInit()) {
std::cerr << "GFLW Init Failed" << std::endl;
return (-1);
}
glfwInit() 은 GLFW를 사용하기 위해 라이브러리 초기화 함수입니다.
초기화가 성공하면 GLFW_TRUE(1)를 오류가 발생하면 GLFW_FALSE(0)가 반환됩니다.
실패하면 OpenGL을 시작도 못 하게 됩니다. 그래서 항상 체크해 주는 게 기본 중 기본입니다.
🧠 참고로 glfwInit은 내부적으로 OS에 맞는 초기화 루틴을 실행합니다.
예: 윈도우에서는 WinAPI 초기화, 맥에서는 Cocoa 세팅 등!
GLFW 사용이 끝나면, 일반적으로 프로그램이 종료되기 직전에 GLFW를 종료해야 합니다.
glfwTerminate ();
이렇게 하면 남아 있는 모든 윈도우가 삭제되고 GLFW가 할당한 다른 리소스도 해제됩니다.
이 함수를 호출한 후에는 GLFW를 다시 초기화해야 해당 리소스를 필요로 하는 GLFW함수를 사용할 수 있습니다.
2. OpenGL 3.3 Core Profile로 세팅하기
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
이건 뭐냐고요? 간단히 말하면,
"나는 2000년대 OpenGL 말고, 최신 방식만 쓸 거야!" 선언입니다.
- GLFW_CONTEXT_VERSION_MAJOR, MINOR -> OpenGL 버전 지정(3.3은 안정적 + 최소 셰이더 기반 버전)
- GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE -> 구형 API 제거, VAO/VBO 안 쓰면 에러 발생!
- GLFW_OPENGL_FORWARD_COMPAT -> 더 이상 안 쓰는 기능 무시 (Mac에선 필수!)
이걸 안 하면 VAO 없이도 그려지는 이상한(?) 상태가 발생될 수 있어요.
왜 GLFW_OPENGL_CORE_PROFILE이 중요한가?
VAO 없이도 그려지는 이상한(?) 상태의 정체
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
위 설정을 하지 않으면 기본적으로 Compatibility Profile로 실행될 수 있습니다.
그러면 OpenGL의 예전 방식, 예를 들면 glBegin, glVertex, glEnd와 같은 함수들이 여전히 살아있죠. 그뿐만 아니라, VAO (Vertex Array Object) 없이도 버퍼를 쓰는 게 “되는 것처럼 보이는” 상황이 발생합니다.
이게 왜 문제냐고요?
\Core Profile에서는 VAO 없이 그리면 명백하게 에러가 납니다. VAO가 없으면 GPU는 “어떤 버퍼에서, 어떤 방식으로 정점 데이터를 읽을지”를 알 수 없기 때문이죠.
하지만 Compatibility Profile에선 자동으로 기본 VAO 상태가 세팅되어 있는 것처럼 작동합니다. 그래서 실수로 VAO를 안 만들었는데도 삼각형이 그려지거나, 버퍼가 먹히는 일이 생깁니다.
이 상태가 바로 "이상한(?) 상태"입니다. 코드가 돌아가긴 하는데, 실제로는 명확하지도, 이식성도 없고, 최신 OpenGL 문법에도 어긋납니다.
3. GLFW 윈도우 생성과 컨텍스트 설정
GLFWwindow* window = glfwCreateWindow(500, 500, "scop", NULL, NULL);
glfwMakeContextCurrent(window);
드디어 창을 생성했습니다! 🎉🎉🎉
glfwCreateWindow()는 너비, 높이, 창 이름을 받고 윈도우를 만들어줘요
근데 만든다고 바로 쓰는 건 아닙니다.
OpenGL은 렌더링 컨텍스트라는 걸 만들어야만 함수들이 제대로 작동해요.
그래서 glfwMakeContextCurrent(window)로 이 창을 "현재 쓰는 컨텍스트"로 등록합니다.
참고: "컨텍스트"는 OpenGL 상태 저장소라고 보면 됩니다, OpenGL 함수들은 모두 이 상태에 종속되며.
프레임버퍼 객체에 그리지 않을 때 렌더링 명령이 그릴 (잠재적으로 보이는) 기본 프레임 버퍼를 나타냅니다.
4. Viewport 설정 - 너의 픽셀 범위는 어디니?
int framebuffer_w, framebuffer_h;
glfwGetFramebufferSize(window, &framebuffer_w, &framebuffer_h);
glViewport(0, 0, framebuffer_w , framebuffer_h);
여기서 창이 생겼다고 GPU가 그릴 공간이 자동으로 지정되는 건 아닙니다.
glViewport는 말 그래도 이 화면에서 실제로 그릴 픽셀 영역을 정해줘요.
- (0, 0)은 좌측 하단의 시작점
- (w, h)는 그릴 영역 크기
💡 고해상도 모니터 (예: Retina)에서는 실제 프레임버퍼 크기가 창 사이즈보다 클 수 있어서
glfwGetFramebufferSize로 정확하게 받아와야 합니다.
5. GLEW로 OpenGL 함수 포인터 초기화
if (glewInit() != GLEW_OK) {
std::cerr << "GLEW Init Failed" << std::endl;
glfwTerminate();
return (-1);
}
OpenGL 함수는 마법처럼 다 쓸 수 있는 게 아닙니다.
OS는 기본적으로 OpenGL의 정의만 알려주고, 실제 포인터 주소는 우리가 직접 가져와야 해요.
그래서 GLEW를 써서 모든 OpenGL 확장 함수 포인터를 초기화합니다.
- GLEW는 Extension Wrangler의 약자
- glGenBuffers, glCreateShader 등등.. 다 여기서 초기화됨
실패했을 때 위에서 glfw를 생성했기 때문에 glfwTerminate를 호출한 뒤 나가는 게 중요 포인트!
GLEW를 쓰지 않는다면? glX, wgl처럼 OS별 API로 직접 불러와야 하는 복잡한 세상에 빠집니다.
6. 루프: 그리기, 버퍼 교체, 이벤트 처리
while (!glfwWindowShouldClose(window)) {
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
드디어 메인 루프!
창을 열고 계속해서 프레임을 갱신합니다.
- glClearColor와 glClear로 매 프레임 배경색을 초기화
- glfwSwapBuffers: 백 버퍼를 프론트로 교체 (더블 버퍼링)
- glfwPollEvents: 마우스, 키도브, 창 크기 등 이벤트 확인
이 루프 안에서 드로잉, 물리 연산, 애니메이션 등 모든 게 돌아갑니다.

7. 종료 처리 - 메모리는 우리가 책임져야죠
glfwTerminate();
return (0);
마지막엔 GLFW가 사용한 자원을 해제해줘야 해요.
안 그러면 OS에 리소스가 남아 이상한 버그의 원인이 되기도 하죠.
다음 글 예고
지금까지는 정말 "창만 띄운" 코드였죠.
하지만 여기서부터가 진짜 시작입니다. 앞으로 우리는
- VAO, VBO를 만들고
- 셰이더를 로딩하고
- 3D 모델을 로딩하고
- 마우스로 회전까지!
Scop 구현기 2편: VAO와 VBO의 세계
'Dev > Grapics' 카테고리의 다른 글
| Scop 구현기 3편: 셰이더 클래스를 만들기로 했다 (0) | 2025.04.11 |
|---|---|
| Scop 구현기 2편: VAO와 VBO의 세계 (0) | 2025.04.09 |
| Rasterization vs Ray Tracing (0) | 2025.04.09 |
| 셰이더는 그낭 함수일까? (0) | 2025.04.08 |
| OpenGL의 좌표계, 진짜로 이해하기 (1) | 2025.04.08 |
