티스토리 뷰

이 글에서는 GLFW와 GLEW를 이용하여 창을 띄워볼 것이다.
환경 셋업에 대한 것은 아래의 글을 참고.

https://hezma.tistory.com/120

완성된 코드를 바로 보고 싶다면 아래 repo를 참고하면 된다.

https://github.com/9ru9ru/open-gl-course/blob/ch0-ready-fixed/runtime/section01/Section01Runner.cpp

커버하는 지식 범위는 다음과 같다.

  1. 간단한 OpenGL의 work-flow
  2. 기본적인 API들

이 글을 통해 달성할 목표는 다음과 같다.

  1. GLFW와 GLEW의 초기화 및 환경 셋업.
  2. 800x600으로 빨간색 만을 출력하는 윈도우 띄우기.

1. Window를 띄우기 위한 work-flow 개략

간단하게라도 어떤 work-flow로 작업할 것인지 알아보자.
특히, 윈도우를 띄우는데 있어서 비즈니스 로직 외적으로 부가적인 기능들이 사용되기 때문에 잘 익혀두면 좋다.
윈도우 하나를 띄우기 위해 해야 하는 일들은 다음과 같다.

  1. GLFW 초기화 및 셋업
  2. GLEW 초기화 및 셋업
  3. 창 관련 설정
  4. 빨간 색 창을 띄우는 메인 루프 실행

실제 코드에서 다음과 같은 일들을 해보자.

2. 코드 작성

우선 전체 완성된 코드는 다음과 같다.

#include "Section01Runner.h"
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdio.h>

bool Section01Runner::Run()
{
    // init GLFW.
    if(!glfwInit())
    {
        printf("GLFW init fail");
        glfwTerminate();
        return false;
    }

    // Setup GLFW window properties.
    // OpenGL Version.
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

    // Core profile = No Backwards Compatible.
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // Allow forward compatible.
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    GLFWwindow* mainWindow = glfwCreateWindow(windowWidth, windowHeight, "Hello Window", NULL, NULL);
    if (!mainWindow)
    {
        printf("glfwCreateWindow failed!");
        glfwTerminate();
        return false;
    }

    // Get Buffer Size Info.
    int bufferWidth, bufferHeight;
    glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);

    // Set Context for GLEW to use.
    glfwMakeContextCurrent(mainWindow);

    // Allow modern extension feats.
    glewExperimental = GL_TRUE;

    if (glewInit() != GLEW_OK)
    {
        printf("glew init failed");
        glfwDestroyWindow(mainWindow); // 이 시점에서 window가 생성되었으니 지워야 한다.
        glfwTerminate();
        return false;
    }

    // Setup Viewport size.
    glViewport(0, 0, bufferWidth, bufferHeight);

    // loop til window closed.
    while (!glfwWindowShouldClose(mainWindow))
    {
        // Get + handle user input events.
        glfwPollEvents();

        // Clear window.
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glfwSwapBuffers(mainWindow);
    }

    return true;
}

하나하나 알아볼 것이다.

2.1. GLFW 초기화 및 셋업

GLFW를 초기화 해주는 부분

    // init GLFW.
    if(!glfwInit())
    {
        printf("GLFW init fail");
        glfwTerminate();
        return false;
    }
  • glfwInit() 함수를 통해 glfw를 초기화
  • 제대로 초기화 되지 않은 경우에는 glfwTerminate()을 통해 종료해준다.

GLFW의 셋업 - 버전 설정 및 호환성 설정

    // Setup GLFW window properties.
    // OpenGL Version.
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

    // Core profile = No Backwards Compatible.
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // Allow forward compatible.
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

glfwWindowHint(int hint, int value)

glfw의 다음 초기화에 대한 힌트를 셋업. hint에 해당하는 값에 value 할당하는 간단한 역할.
실제 한 작업들은 다음과 같다.

  • GLFW_CONTEXT_VERSION을 3.3으로 설정: 사용할 버전에 알아서 맞춰주면 된다.
  • GLFW_OPENGL_PROFILE을 GLFW_OPENGL_CORE_PROFILE으로 설정: 오래된 기능인 Immediate 모드를 사용 X.
  • GLFW_OPENGL_FORWARD_COMPAT를 TRUE로 설정: OpenGL context의 상위 호환성을 true로 설정하는 거다. 나중에 나올 버전과 호환 가능하도록 하는 것. 즉 다음 버전에서도 잘 작동할 수 있도록 설정하는 동작이다.

GLFW를 이용한 윈도우 생성과 프레임 버퍼 설정, Context 생성

이제 GLFW의 초기화와 설정이 끝난 단계이니 실제 작업들을 해 볼 시간이다.

    GLFWwindow* mainWindow = glfwCreateWindow(windowWidth, windowHeight, "Hello Window", NULL, NULL);
    if (!mainWindow)
    {
        printf("glfwCreateWindow failed!");
        glfwTerminate();
        return false;
    }

    // Get Buffer Size Info.
    int bufferWidth, bufferHeight;
    glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);

    // Set Context for GLEW to use.
    glfwMakeContextCurrent(mainWindow);

    // Allow modern extension feats.
    glewExperimental = GL_TRUE;

glfwCreateWindow(windowWidth, windowHeight, "Hello Window", NULL, NULL);

window, window와 연관된 context를 생성해주는 함수. 패러미터 목록은 아래.

  • int width: 생성할 창의 width
  • int height: 생성할 창의 height
  • const char *title: 생성할 창의 제목
  • GLFWmonitor *monitor: fullscreen 모드로 사용할 monitor. NULL로 두면 그냥 창 모드로 뜬다.
  • GLFWwindow *share: context를 share할 window. NULL로 두면 그 무엇과도 share 하지 않는다.

그리고 이렇게 생성하게 되는 window의 경우 생성만 되고 current context로 설정되지 않는다.
따라서 뒷쪽에서 currrent context로 설정하게 되는 과정이 필요하다.

glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight): window의 frame buffer size를 pixel 단위로 받아오는 함수.
프레임 버퍼에 대한 개념을 간단히 설명하자면 ‘화면에 그려질 전체에 대한 정보를 담는 공간’이라고 볼 수 있다. 해상도가 800600인 창인 경우 800600이 프레임 크기가 될 것이다.
실제로 여기서 800과 600으로 mainWindow를 만들고 프레임 버퍼의 크기를 찍어보면 다음과 같이 뜬다.

뒷쪽에서 해당 사이즈를 쓸 일이 생기니 우선 넘어가자.

glfwMakeContextCurrent(mainWindow)

window의 context를 current context로 지정한다.

보충 - Current ‘Context’란 무엇인가?

윗 쪽 설명에 계속 ‘context’에 대한 설명이 등장한다.
생성한 window를 current context로 지정하는 등 다양한 작업들이 이루어진다.
그렇다면 ‘context’는 무엇을 의미하는 것인가?

Context는 OpenGL API를 사용하여 그래픽을 렌더링하는 데에 필요한 모든 정보를 가지는 데이터 구조이다.
활성 셰이더 / 현재 정점 배열 / 현재 프레임 버퍼 / 현재 텍스쳐 등이다.
특징으로는 하나의 스레드는 여러 context들을 가지고 있을 수 있지만, 단 하나의 context만이 current로 지정될 수 있다는 것이다. 또한 하나의 context도 하나의 스레드에만 할당될 수 있다.

특히 위에서 만들어진 window object와 같은 경우 window와 context가 분리할 수 없이 연결되어있다. 따라서 창 오브젝트가 context handle 역할을 함께 수행하고 있다.
또한, 이런 context는 sharing할 수 있는데, 예를 들어 다음과 같이 두 번째 생성되는 창을 첫번째 창의 context에 sharing할 수 있다.

GLFWwindow* second_window = glfwCreateWindow(640, 480, "Second Window", NULL, first_window);

2.2. GLEW 초기화 및 셋업

GLEW를 초기화 해주는 부분

    if (glewInit() != GLEW_OK)
    {
        printf("glew init failed");
        glfwDestroyWindow(mainWindow); // 이 시점에서 window가 생성되었으니 지워야 한다.
        glfwTerminate();
        return false;
    }

glewInit(): GLEW 초기화 관련 작업을 진행.

glfwDestroyWindow(mainWindow): window가 생성된 시점이므로 추가적으로 이 일을 해줘야 한다.

ViewPort의 Setup

    // Setup Viewport size.
    glViewport(0, 0, bufferWidth, bufferHeight);

glViewPort: VIewPort의 위치와 크기를 정하는 커맨드이다. 처음 두개가 시작점,

  • 처음 두 파라미터는 viewport의 시작좌표 x,y(왼쪽아래)
  • 뒤의 두 파라미터는 뷰포트의 크기 w,h다.

따라서 위의 커맨드는 viewport를 0,0에서 시작해서 화면 꽉 차게 채운다는 의미다.
window 내의 viewport는 다음과 같이 표시된다고 보면 된다.

또한 viewport의 scale을 줄이면 렌더링 객체도 똑같은 scale로 줄어든다.
예를 들어 viewport를 window와 동일한 크기로 설정하여 다음과 같이 삼각형이 뜰 때

glViewport(0, 0, bufferWidth, bufferHeight);

viewport를 절반으로 줄여보면 다음과 같이 표시된다.

glViewport(0, 0, bufferWidth/2, bufferHeight/2);

또한 이 상황에서 viewport가 화면 중앙에서 시작하도록 설정하면 다음과 같이 표시된다.

glViewport(bufferWidth/2, bufferWidth/2, bufferWidth/2, bufferWidth/2);

2.3. MainLoop

이제 창도 띄우고 viewport 설정을 끝냈으니 마침내 원하는 화면을 렌더링 해야 할 차례다.
메인 루프를 보자.
우선 기본적으로 매 프레임 꺼지면 안 되므로 while 안에 넣어 루프화 해야한다.

    while (!glfwWindowShouldClose(mainWindow))
    {
        // Get + handle user input events.
        glfwPollEvents();

        // Clear window.
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glfwSwapBuffers(mainWindow);
    }

glfwWindowShouldClose(mainWindow)

지정된 window의 close flag가 활성화 됐는지 확인한다.
x단추 등을 누르면 close flag가 활성화된다.

glfwPollEvents()

입력 관련 이벤트 프로세싱을 가능하게 해 주는 함수다.

glClearColor(1.0f, 0.0f, 0.0f, 1.0f)

color buffer가 clear 되었을 때 기본 색 값을 정한다.
간단히 생각하면 아무것도 안 뜨는 상태에서 윈도우의 배경 색을 결정하는 함수로 볼 수 있다.

glClear(GL_COLOR_BUFFER_BIT)

buffer내의 값을 지워버리는 함수다.
파라미터는 무슨 버퍼를 지울 것인지에 대해 지정한다.
color / depth / stencil 세 가지 종류가 있다.

간단하게 생각하면 화면에 띄워질 정보를 지우는 것이라 생각할 수 있겠다.
이 경우 배경색 지정 후 배경색을 보기 위해 color buffer를 날려야 하므로 이 작업을 수행한다.

glfwSwapBuffers(mainWindow)

window에서 이전 프레임에 그려졌던 Buffer와 현재 Buffer를 스왑함으로서 현재 Buffer에 있는 값을 보여지게 한다.
사실 이 루프 자체에서는 Buffer에 아무 값도 안 넣었기 때문에 큰 의미 없는 명령이 된다.
단, 그리는 값을 계속 변경해주기 위해 필요한 하나의 과정이므로 미래를 위해 일단 사용해본다.

Refs

사실 GLFW와 GLEW는 API설명이 매우 잘 되어있는 편이다.
나도 API 문서 읽는 거 싫어하긴 하는데 꽤 읽어볼 만 하다.

화이팅.

https://www.glfw.org/docs/3.3/window_guide.html

https://www.glfw.org/docs/3.3/group__context.html

https://www.glfw.org/docs/3.3/context_guide.html

https://www.glfw.org/docs/3.3/group__window.html

https://www.khronos.org/opengl/wiki/OpenGL_Context

https://www.reddit.com/r/opengl/comments/lb3ptw/what_is_context_in_opengl/

https://kanteloper.tistory.com/4

댓글