본문 바로가기
잡(job)기술/opengl

OpenGL #01 - 시리즈 소개: 처음부터 만드는 모던 OpenGL 렌더러

by 무니이구나 2026. 8. 17.

이 시리즈는 glBegin, glEnd 같은 레거시 고정 파이프라인을 사용하지 않고, OpenGL 3.3 Core Profile과 C++17을 사용해 작은 렌더러를 처음부터 만들어 가는 글입니다. 이번 편에서는 시리즈의 목표와 독자 대상, 최종 결과물, 필요한 사전 지식, 전체 로드맵을 정리하고, 이후 실습을 위한 최소 프로젝트 골격과 OpenGL 오류 확인 습관을 잡습니다.

🎯 이 글에서 얻을 수 있는 것

  • 이 시리즈가 무엇을 만들고, 왜 OpenGL 3.3 Core Profile과 C++17을 기준으로 하는지 이해합니다.
  • GLFW, GLAD, GLM, stb_image, CMake가 렌더러 프로젝트에서 각각 어떤 역할을 하는지 정리합니다.
  • 그래픽스 프로그래밍이 처음인 독자를 위한 전체 학습 로드맵을 확인합니다.
  • 이후 실습에 바로 사용할 수 있는 최소 CMake 프로젝트 구조와 창 생성 코드를 얻습니다.
  • OpenGL 오류 확인과 셰이더 컴파일 로그 출력의 기본 습관을 미리 만들 수 있습니다.

📸 결과물 미리보기

 

이 편의 실행 결과는 800x600 창이 열리고 어두운 배경색으로 채워지는 것입니다. 아직 삼각형이나 3D 모델이 나오지는 않지만, OpenGL 컨텍스트가 정상적으로 생성되었고 GLAD 로딩과 기본 OpenGL 상태 확인이 가능한지를 검증하는 화면입니다. 시리즈를 진행하면서 이 창에는 버퍼, 셰이더, 카메라, 텍스처, 조명이 적용된 간단한 3D 장면이 그려지게 됩니다.

🧠 개념 설명

왜 "모던 OpenGL"인가?

OpenGL은 렌더링 엔진이 아니라 GPU와 소통하기 위한 API입니다. 즉, 화면에 무엇을 그릴지 직접 버퍼를 만들고, 셰이더를 작성하고, 상태를 설정한 뒤 드로우 콜을 날려야 합니다.

과거 OpenGL에는 glBegin, glEnd, glVertex3f, glColor3f 같은 고정 파이프라인 함수들이 있었습니다. 하지만 모던 OpenGL에서는 이러한 방식 대신 다음 요소들을 직접 다룹니다.

  • GPU에 올릴 정점 데이터를 담는 버퍼
  • 정점을 어떻게 해석할지 알려주는 VAO
  • GPU에서 실행되는 프로그램인 셰이더
  • 변환, 카메라, 재질, 조명 같은 데이터를 넘기는 유니폼
  • 상태 머신으로서의 OpenGL 객체 바인딩

이 시리즈에서는 OpenGL 3.3 Core Profile을 기준으로 합니다. Core Profile은 고정 파이프라인 기능을 제거한 현대적인 프로필이며, 이후 학습할 Vulkan이나 다른 그래픽 API의 개념과도 더 잘 연결됩니다.

OpenGL 파이프라인 한눈에 보기

모던 OpenGL 렌더링은 대략 다음 흐름으로 이해할 수 있습니다.

 

이번 편은 파이프라인 전체를 자세히 구현하기보다는, 이 파이프라인을 실행할 OpenGL 컨텍스트를 만들고 오류를 확인할 준비를 하는 단계입니다. 이후 편부터 VBO, VAO, EBO, 셰이더, 텍스처, 카메라, 조명 순서로 파이프라인의 각 단계를 실제로 채워 넣습니다.

시리즈 목표

이 시리즈의 목표는 단순히 OpenGL 함수 사용법을 나열하는 것이 아니라, 작은 렌더러를 직접 설계하고 확장할 수 있는 감각을 만드는 것입니다.

시리즈를 마치면 다음을 할 수 있는 것을 목표로 합니다.

  • OpenGL 상태를 직접 관리하면서 화면에 장면을 그릴 수 있다.
  • VBO, VAO, EBO, 셰이더, 텍스처의 생성과 바인딩 순서를 설명할 수 있다.
  • 모델, 뷰, 프로젝션 행렬을 이용해 3D 장면을 카메라로 볼 수 있다.
  • 기본적인 조명과 재질을 적용할 수 있다.
  • 렌더러 코드를 재사용 가능한 단위로 조금씩 정리할 수 있다.

독자 대상

이 시리즈는 다음 독자를 가정합니다.

  • C/C++ 기본 문법을 읽을 수 있는 사람
  • 포인터, 참조, 구조체, 클래스의 기본 개념을 아는 사람
  • 그래픽스 프로그래밍은 처음인 사람
  • 삼각형이 화면에 그려지는 원리를 처음부터 이해하고 싶은 사람
  • 게임을 만들거나 엔진을 공부하기 전에 렌더링 기초를 다지고 싶은 사람

반대로 이미 OpenGL 렌더러를 어느 정도 만들어 본 사람에게는 로드맵과 코드 구조를 점검하는 용도로 유용할 수 있습니다.

최종 결과물

최종 결과물은 매우 거창한 게임 엔진이 아니라, 다음 기능을 가진 미니 렌더러입니다.

  • GLFW 윈도우와 입력 처리
  • GLAD를 이용한 OpenGL 함수 로딩
  • 셰이더 컴파일과 오류 로그 출력
  • VBO, VAO, EBO 기반 메쉬 렌더링
  • GLM 기반 모델, 뷰, 프로젝션 변환
  • 마우스/키보드 입력으로 움직이는 간단한 카메라
  • stb_image를 이용한 텍스처 로딩
  • Ambient, Diffuse, Specular 개념을 포함한 기본 조명
  • 상태 변경과 드로우 콜을 의식하는 간단한 렌더링 루프

즉, "텍스처와 조명이 적용된 간단한 3D 장면을 카메라로 바라보는 렌더러"를 만드는 것이 목표입니다.

필요한 사전 지식

필수로 필요한 지식은 다음과 같습니다.

  • C++ 코드 컴파일 방법
  • 함수, 클래스, 포인터/참조 기본 개념
  • 터미널 또는 명령 프롬프트에서 CMake를 실행할 수 있는 정도
  • 텍스트 편집기와 빌드 도구 사용 경험

수학 지식은 처음부터 완벽할 필요 없습니다. 벡터, 내적, 정규화, 행렬 곱셈 같은 개념은 필요한 시점에 렌더링 맥락과 함께 정리합니다.

전체 학습 로드맵

시리즈는 다음 순서로 진행될 예정입니다.

회차 주제 핵심 개념
01 시리즈 소개와 개발 환경 검증 GLFW 창, GLAD 로딩, OpenGL 오류 확인, 프로젝트 구조
02 첫 삼각형 VBO, VAO, 셰이더, glDrawArrays
03 인덱스 버퍼와 사각형 EBO, glDrawElements, 정점 재사용
04 변환과 행렬 GLM, Model/View/Projection, 행렬 곱셈 순서
05 카메라 yaw/pitch, LookAt 행렬, 투영 행렬
06 텍스처 stb_image, UV 좌표, 샘플러, y축 뒤집힘
07 조명 법선, ambient/diffuse/specular, 광원
08 메쉬와 재질 추상화 Vertex, Mesh, Material, 드로우 순서
09 프레임버퍼 오프스크린 렌더링, 후처리 가능성
10 최적화 관점 상태 변경 최소화, 드로우 콜 배치, 인스턴싱

 

최적화 편에서는 단순히 "빨리 그리는 법"보다는, OpenGL에서 성능에 영향을 주는 상태 변경, 드로우 콜, 인스턴싱 관점을 중심으로 다룹니다.

🛠️ 구현 준비

이 시리즈에서 사용할 기본 라이브러리는 다음과 같습니다.

라이브러리 역할
GLFW 윈도우 생성, 입력 이벤트, OpenGL 컨텍스트 관리
GLAD OpenGL 함수 포인터 로딩
GLM 벡터, 행렬 수학
stb_image 이미지 파일 로딩
CMake 크로스 플랫폼 빌드 설정

 

GLFW는 창과 입력을 담당합니다. OpenGL 함수 자체는 운영체제와 드라이버에 따라 동적으로 로딩해야 하므로 GLAD 같은 로더가 필요합니다. GLM은 좌표 변환에 필요한 벡터와 행렬을 제공합니다. stb_image는 텍스처로 사용할 이미지 파일을 메모리로 읽어 주는 단일 헤더 라이브러리입니다.

운영체제별 차이

개발 환경 세팅은 운영체제마다 약간씩 다릅니다.

OS 설치/링크 특징 주의할 점
Windows vcpkg, 직접 빌드, 또는 NuGet 사용 가능. OpenGL32 라이브러리 링크 필요. GLFW/GLAD 포함 경로와 CMake 링크 설정을 정확히 맞춥니다.
macOS Homebrew로 GLFW 설치 가능. OpenGL은 deprecated 상태지만 3.3 Core Profile 학습은 가능합니다. GLFW_OPENGL_FORWARD_COMPAT 힌트를 주는 것이 안전합니다.
Linux Mesa 또는 GPU 드라이버와 GLFW 개발 패키지 설치 필요. 배포판에 따라 libglfw3-dev, libgl1-mesa-dev, X11 관련 패키지 설치가 필요할 수 있습니다.

 

이 글의 코드는 특정 패키지 매니저에 강하게 의존하지 않도록 CMake 기준으로 작성합니다.

파일 구조

이 시리즈에서 사용할 초기 폴더 구조는 다음과 같습니다.

modern_opengl_renderer/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   └── shader_util.hpp
├── third_party/
│   └── glad/
│       ├── include/
│       │   └── glad/
│       │       └── glad.h
│       └── src/
│           └── glad.c
└── shaders/
    ├── check.vert
    └── check.frag

 

 

glad.h glad.c는 GLAD 생성기에서 OpenGL 3.3 Core Profile용으로 생성한 파일을 넣는다는 가정입니다.

💻 코드 구현

1. CMake 프로젝트 골격

CMakeLists.txt를 다음처럼 작성합니다. GLFW와 OpenGL을 찾고, GLAD 소스를 함께 빌드하는 구조입니다.

cmake_minimum_required(VERSION 3.16)
project(modern_opengl_renderer C CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(glfw3 REQUIRED)
find_package(OpenGL REQUIRED)

add_executable(renderer
    src/main.cpp
    third_party/glad/src/glad.c
)

target_include_directories(renderer PRIVATE
    third_party/glad/include
    third_party
)

target_link_libraries(renderer PRIVATE
    glfw
    OpenGL::GL
)

 

third_party/glad/include를 include 경로에 추가하는 이유는 #include <glad/glad.h> 형태로 GLAD 헤더를 찾기 위해서입니다. glad.c는 C 파일이므로 project(... C CXX)처럼 C 언어도 함께 활성화합니다.

2. GLFW 창과 OpenGL 컨텍스트 만들기

src/main.cpp를 다음처럼 작성합니다. 이 코드는 창을 열고, OpenGL 3.3 Core Profile 컨텍스트를 만들고, GLAD로 함수를 로딩한 뒤 배경색을 지우는 최소 실행 코드입니다.

#include <glad/glad.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#include <iostream>

static void glfw_error_callback(int error, const char* description) {
    std::cerr << "[GLFW error] " << error << ": " << description << '\n';
}

static void check_gl_error(const char* command, const char* file, int line) {
    GLenum error = glGetError();

    while (error != GL_NO_ERROR) {
        std::cerr << "[GL error] code=" << error
                  << " after " << command
                  << " at " << file << ":" << line << '\n';
        error = glGetError();
    }
}

#define GL_CALL(x)                 \
    do {                           \
        x;                         \
        check_gl_error(#x, __FILE__, __LINE__); \
    } while (false)

static void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    // 창 크기가 바뀌었을 때 뷰포트를 다시 맞춰야 화면이 찌그러지거나 잘리지 않습니다.
    if (width > 0 && height > 0) {
        GL_CALL(glViewport(0, 0, width, height));
    }
}

int main() {
    glfwSetErrorCallback(glfw_error_callback);

    if (!glfwInit()) {
        std::cerr << "Failed to initialize GLFW\n";
        return -1;
    }

    // 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);

#ifdef __APPLE__
    // macOS에서는 Core Profile 사용 시 이 힌트가 필요할 수 있습니다.
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    GLFWwindow* window = glfwCreateWindow(800, 600, "Modern OpenGL Renderer", nullptr, nullptr);
    if (!window) {
        std::cerr << "Failed to create GLFW window\n";
        glfwTerminate();
        return -1;
    }

    // OpenGL 컨텍스트를 현재 스레드에 바인딩합니다.
    glfwMakeContextCurrent(window);

    // 프레임버퍼 크기 변경 콜백을 등록합니다.
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // GLAD는 OpenGL 컨텍스트가 만들어진 뒤에 로딩해야 합니다.
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cerr << "Failed to initialize GLAD\n";
        glfwTerminate();
        return -1;
    }

    const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
    const char* renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));

    std::cout << "OpenGL version: " << (version ? version : "unknown") << '\n';
    std::cout << "Renderer: " << (renderer ? renderer : "unknown") << '\n';

    // 배경색을 설정합니다. RGBA 순서이며 값은 0.0f ~ 1.0f 범위입니다.
    GL_CALL(glClearColor(0.08f, 0.09f, 0.12f, 1.0f));

    while (!glfwWindowShouldClose(window)) {
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
            glfwSetWindowShouldClose(window, GLFW_TRUE);
        }

        // 현재 색상 버퍼를 glClearColor로 설정한 색으로 지웁니다.
        GL_CALL(glClear(GL_COLOR_BUFFER_BIT));

        // 백 버퍼를 화면으로 스왑합니다.
        glfwSwapBuffers(window);

        // 키보드/마우스 이벤트를 처리합니다.
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

 

이 코드에서 특히 중요한 부분은 다음입니다.

  • glfwMakeContextCurrent(window) 이후에 GLAD를 로딩해야 합니다.
  • OpenGL 3.3 Core Profile을 요청했으므로 이후 코드에서는 고정 파이프라인 함수를 사용하지 않습니다.
  • GL_CALL 매크로는 OpenGL 호출 직후 glGetError()를 확인해 오류를 즉시 로그로 남깁니다.
  • framebuffer_size_callback에서 glViewport를 다시 설정해 창 크기 변경에 대응합니다.

3. OpenGL 오류 확인 습관

OpenGL은 대부분의 오류를 예외로 던지지 않습니다. 대신 내부 오류 플래그를 기록하고, glGetError()를 호출했을 때 확인할 수 있습니다. 그래서 잘못된 호출을 빨리 발견하려면 다음 습관이 중요합니다.

  • OpenGL 호출 직후 오류를 확인합니다.
  • 오류가 발생했을 때 어떤 호출에서 발생했는지 파일과 라인 정보를 함께 출력합니다.
  • 셰이더 컴파일과 링크는 별도의 로그 확인 함수를 사용해야 합니다.

GL_CALL 매크로는 개발 단계에서 원인을 좁히는 데 유용합니다. 성능이 중요한 릴리스 빌드에서는 오류 확인 방식을 선택적으로 끄거나 줄일 수 있습니다.

4. 셰이더 컴파일 로그를 출력하는 유틸 준비

다음 편부터 셰이더를 실제로 사용하므로, 미리 셰이더 컴파일과 링크 오류 로그를 출력하는 유틸을 만들어 둡니다. src/shader_util.hpp 파일을 만들고 다음 코드를 추가합니다.

#pragma once

#include <glad/glad.h>

#include <iostream>
#include <string>

inline GLuint compile_shader(GLenum type, const char* source) {
    GLuint shader = glCreateShader(type);

    glShaderSource(shader, 1, &source, nullptr);
    glCompileShader(shader);

    GLint success = GL_FALSE;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

    if (success == GL_FALSE) {
        GLint length = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);

        if (length > 0) {
            std::string log(length, '\0');
            glGetShaderInfoLog(shader, length, nullptr, log.data());
            std::cerr << "Shader compile error:\n" << log << '\n';
        }

        glDeleteShader(shader);
        return 0;
    }

    return shader;
}

inline GLuint link_program(GLuint vertex_shader, GLuint fragment_shader) {
    if (vertex_shader == 0 || fragment_shader == 0) {
        return 0;
    }

    GLuint program = glCreateProgram();

    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);

    GLint success = GL_FALSE;
    glGetProgramiv(program, GL_LINK_STATUS, &success);

    if (success == GL_FALSE) {
        GLint length = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);

        if (length > 0) {
            std::string log(length, '\0');
            glGetProgramInfoLog(program, length, nullptr, log.data());
            std::cerr << "Program link error:\n" << log << '\n';
        }

        glDeleteProgram(program);
        return 0;
    }

    return program;
}


셰이더 오류는 C++ 컴파일 오류와 다르게 런타임에 발생합니다. 따라서 glGetShaderInfoLogglGetProgramInfoLog를 통해 로그를 확인하는 습관이 필수입니다.

5. 검증용 최소 셰이더

아직 화면에 무언가를 그리지는 않지만, 셰이더 컴파일 로그가 정상적으로 동작하는지 확인하기 위한 최소 셰이더를 준비할 수 있습니다. shaders/check.vert에는 다음을 저장합니다.

#version 330 core

void main() {
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}

 

shaders/check.frag에는 다음을 저장합니다.

#version 330 core

out vec4 FragColor;

void main() {
    FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}

 

이 셰이더는 이 시리즈에서 사용할 실제 렌더링 셰이더의 최소 형태입니다. #version 330 core는 셰이더 파일의 첫 줄에 있어야 하며, 앞에 공백이나 다른 문자가 있어도 문제가 생길 수 있습니다.

6. 앞으로 지켜야 할 생성/바인딩/사용 순서

이번 편에서는 아직 버퍼와 텍스처를 만들지 않지만, 이후 편에서 반복해서 사용할 핵심 순서를 먼저 정리합니다.

셰이더는 다음 순서입니다.

 

VBO와 VAO는 다음 순서를 기본으로 합니다.

 

EBO는 VAO가 바인딩된 상태에서 바인딩해야 인덱스 버퍼 정보가 VAO 상태에 기록됩니다. 텍스처는 다음 순서입니다.

 

OpenGL은 상태 머신이기 때문에 "지금 어떤 객체가 바인딩되어 있는지"를 항상 의식해야 합니다. 이 감각이 잡히면 OpenGL 디버깅이 훨씬 수월해집니다.

7. GLM 행렬 곱셈 순서 미리 보기

아직 GLM을 사용하지 않지만, 이후 변환 행렬을 다룰 때는 다음 순서를 기억해 두세요.

glm::mat4 mvp = projection * view * model;

 

정점 위치에는 일반적으로 다음처럼 곱합니다.

gl_Position = projection * view * model * vec4(localPos, 1.0);

 

GLM은 수학적으로 익숙한 행렬 곱셈 순서를 그대로 사용한다고 이해하면 됩니다. 즉, 로컬 좌표에 model을 먼저 곱하고, view, projection 순서로 곱합니다.

✅ 실행 방법

프로젝트 루트에서 다음 명령으로 빌드 파일을 생성하고 빌드합니다.

cmake -S . -B build
cmake --build build

 

실행 파일 위치는 환경에 따라 다릅니다.

# Linux / macOS
./build/renderer
# Windows Visual Studio 기본 생성 위치 예시
build\Debug\renderer.exe

 

GLFW를 패키지 매니저로 설치한 경우, CMake가 glfw3 설정을 찾지 못하면 해당 패키지 매니저의 툴체인 파일을 지정해야 할 수 있습니다. 예를 들어 vcpkg를 사용한다면 다음처럼 CMake 툴체인 파일을 넘길 수 있습니다.

cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build

 

정상적으로 실행되면 콘솔에 OpenGL 버전과 렌더러 이름이 출력되고, 어두운 배경의 창이 열립니다. ESC 키를 누르면 창이 닫힙니다.

⚠️ 흔한 실수와 디버깅 체크리스트

이 단계에서 자주 발생하는 문제는 대부분 창 생성, OpenGL 로딩, 뷰포트, 셰이더 로그 확인과 관련됩니다.

흔한 실수

  1. GLFW 초기화 또는 창 생성 실패
    • OpenGL 3.3 Core Profile을 지원하지 않는 드라이버나 환경일 수 있습니다.
    • macOS에서는 GLFW_OPENGL_FORWARD_COMPAT 힌트를 추가했는지 확인합니다.
  2. GLAD 로딩 실패
    • gladLoadGLLoaderglfwMakeContextCurrent(window) 이후에 호출해야 합니다.
    • GLAD 생성 결과가 OpenGL 3.3 Core Profile용인지 확인합니다.
  3. 검은 화면 또는 빈 화면
    • glClearColorglClear가 호출되었는지 확인합니다.
    • glfwSwapBuffers가 렌더링 루프 안에 있는지 확인합니다.
    • 창 크기가 0이 아닌지 확인합니다.
    • 아직 드로우 콜이 없으므로 배경색만 보이는 것은 정상입니다.
  4. 셰이더 컴파일 오류를 콘솔에서 볼 수 없음
    • glGetShaderInfoLog 또는 glGetProgramInfoLog를 사용하지 않으면 원인을 알기 어렵습니다.
    • #version 330 core가 첫 줄에 있는지 확인합니다.
  5. CMake에서 GLAD 소스를 빌드 대상에 포함하지 않음
    • third_party/glad/src/glad.cadd_executable에 추가했는지 확인합니다.
    • include 경로에 third_party/glad/include가 들어 있는지 확인합니다.

디버깅 체크리스트

  • glfwInit()이 성공했는가?
  • glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)을 설정했는가?
  • glfwMakeContextCurrent(window) 이후에 GLAD를 로딩했는가?
  • gladLoadGLLoader가 성공했는가?
  • OpenGL 버전 문자열이 콘솔에 출력되는가?
  • glClearColorglClear를 호출했는가?
  • glfwSwapBuffers를 호출했는가?
  • 창 크기 변경 시 glViewport를 다시 설정했는가?
  • 이후 셰이더를 사용할 때 glGetShaderInfoLogglGetProgramInfoLog를 출력하도록 준비했는가?
  • VAO/VBO/EBO를 사용할 때 생성 → 바인딩 → 데이터 업로드 → 속성 설정 순서를 지킬 준비가 되어 있는가?

🧪 연습 문제

연습 1: 필수 개념 확인

framebuffer_size_callback 함수 안에서 glViewport 호출을 주석 처리한 뒤, 창 크기를 변경해 보세요. 배경색이 새 창 크기에 맞춰 다시 채워지지 않거나 이전 프레임이 남아 보이는 형태를 관찰할 수 있습니다. 왜 glViewport가 필요한지 설명할 수 있어야 합니다.

연습 2: 응용 확장

glfwGetTime()을 이용해 배경색이 천천히 변하도록 만들어 보세요. 예를 들어 sin 함수를 사용해 빨강, 초록, 파랑 성분 중 하나를 시간에 따라 변화시킬 수 있습니다.

double time = glfwGetTime();
float r = static_cast<float>(0.5 + 0.5 * sin(time));
GL_CALL(glClearColor(r, 0.09f, 0.12f, 1.0f));

이 연습을 통해 OpenGL 상태가 매 프레임 어떻게 갱신되는지 체감할 수 있습니다.

연습 3: 셰이더 로그 확인

shader_util.hppcompile_shader 함수를 사용해 check.vert 또는 check.frag의 내용을 일부러 망가뜨린 뒤 로그를 확인해 보세요. 예를 들어 세미콜론을 하나 제거하거나 존재하지 않는 변수를 사용해 보면, 셰이더 컴파일 로그가 어떤 형태로 출력되는지 확인할 수 있습니다.

🔭 다음 편 예고

다음 편에서는 이번 편에서 준비한 창과 OpenGL 상태 확인 코드 위에 첫 삼각형을 그립니다. VBO와 VAO를 만들고, 셰이더를 실제로 컴파일해 GPU로 데이터를 보내는 과정을 다룹니다.

📚 참고 자료