OpenGL

[Learn OpenGL 번역] 2-3. 시작하기 - Hello Window

짱승_ 2018. 7. 15. 19:56

Hello Window

시작하기/Hello Window

  우리가 GLFW를 가동시킬 수 있는지 봅시다. 먼저 .cpp 파일을 만들고 새로 만든 파일의 맨 위에 다음 include를 추가합니다.


#include <glad/glad.h>
#include <GLFW/glfw3.h>
  GLFW 전에 먼저 GLAD를 포함시켜야 합니다. GLAD 용 헤더파일에는 (GL/gl.h)와 같은 OpenGL 헤더파일이 포함되어 있으므로 OpenGL을 필요로 하는 다른 헤더파일보다 먼저 GLAD를 포함시켜야 합니다.

  다음으로 GLFW 창을 인스턴스화 할 main 함수를 만듭니다.


int main()
{
    glfwInit();
    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);
  
    return 0;
}

  main 함수에서 먼저 glfwInit 을 이용하여 GLFW를 초기화합니다. 그런 다음 glfwWindowHint를 사용하여 GLFW를 설정할 수 있습니다. glfwWindowHint의 첫 번째 파라미터는 우리가 설정하고자 하는 옵션을 알려주며 enum 클래스에 존재하는 GLFW_ 접두어가 붙은 설정 가능한 옵션들을 선택할 수 있습니다. 두 번째 파라미터는 옵션의 값을 설정하는 정수입니다. 모든 설정 가능한 옵션과 해당 값들의 목록은 GLFW's window handling 문서에서 찾아볼 수 있습니다. 지금 으용 프로그램을 실행했는데 많은 undefined reference 오류가 발생했다면 GLFW 라이브러리를 성공적으로 연결하지 않았음을 의미합니다.


  이 강좌의 초점은 OpenGL 3.3 버전이므로 우리는 GLFW에게 우리가 3.3버전의 OpenGL을 사용하려고 한다는 사실을 알려주어야 합니다. OpenGL 컨텍스트를 생성할 때 GLFW는 위 방법으로 적절한 조치를 취할 수 있습니다. 이렇게 하면 사용자가 적절한 OpenGL 버전을 가지고 있지 않을 때 GLFW가 실행되지 않습니다. 우리는 major version, minor version 모두 3으로 설정했습니다. 또한 우리는 GLFW에게 명시적으로 core-profile을 사용하기를 원한다고 알려주었습니다. 우리가 GLFW에게 core-profile을 사용한다고 알려줌으로써 우리는 OpenGL 기능의 작은 집합에 접근할 수 있습니다(더이상 필요하지 않는 하위 호환 기능들을 제외합니다). Mac OS X 에서는 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);를 추가해야 코드가 성공적으로 작동할 것입니다.

  OpenGL 3.3 버전 이상이 시스템/하드웨어에 설치되어 있는지 확인하세요. 그렇지 않다면 응용 프로그램이 충돌하거나 예기치 못한 동작을 야기할 수 있습니다. OpenGL 버전을 확인하려면 리눅스에서 glxinfo를 호출하거나 윈도우에서 OpenGL Extension Viewer와 같은 유틸리티를 사용하세요. 지원되는 버전이 낮다면 그래픽 카드가 OpenGL 3.3 버전 이상을 지원하는지 확인(그렇지 않다면 정말 오래된 그래픽 카드입니다)한 후 드라이버를 업데이트하세요.

  다음으로 window 객체를 생성해야 합니다. 이 window 객체는 모든 window 데이터를 보유하고 있으며 GLFW의 다른 기능에 의해 자주 사용됩니다.


GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

  glfwCreateWindow 함수는 처음 두개의 파라미터로 창의 너비와 높이를 받습니다. 세 번째 파라미터는 창의 이름을 생성할 수 있게 합니다. 지금 우리는 "LearnOpenGL"로 설정했지만 여러분이 좋아하는 이름으로 설정할 수 있습니다. 마지막 두개의 파라미터는 무시하셔도 됩니다. 이 함수의 리턴값은 GLFWwindow 객체이고 이 객체는 나중에 다른 GLFW 기능을 사용하기 위해 필요합니다. 그런 다음 우리 window의 컨텍스트를 현재 스레드의 주 컨텍스트로 지정하겠다고 GLFW에게 알려줍니다.

GLAD

  앞의 강좌에서 우리는 GLAD가 OpenGL용 함수 포인터를 관리한다고 했습니다. 그래서 우리는 OpenGL 함수들을 호출하기 전에 GLAD를 초기화해야 합니다.


if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
}    

  우리는 OS마다 다른 OpenGL 함수 포인터의 주소를 로드하기 위해 GLAD 함수를 거칩니다. GLFW는 우리가 컴파일 할 환경인 OS에 따라 올바른 함수를 정의하는 glfwGetProcAddress를 제공합니다.

Viewport

  렌더링을 시작하기전에 해야할 일이 하나 남았습니다. 우리는 OpenGL에게 렌더링 윈도우의 사이즈를 알려주어야 합니다. 그렇게 해야 OpenGL이 윈도우와 관련하여 데이터와 좌표를 어떻게 표시할지 알 수 있기 때문입니다. glViewport 함수를 통해 이러한 차원(dimensions)을 설정할 수 있습니다.


glViewport(0, 0, 800, 600);

  glViewport 함수의 처음 두 개의 파라미터는 윈도우의 왼쪽 아래 모서리의 위치를 설정합니다. 세 번째, 네 번째 파라미터는 렌더링 윈도우의 너비와 높이를 픽셀로 설정합니다. 여기서는 GLFW 자체에서 가져옵니다.


  실제로 뷰포트의 차원을 GLFW의 차원보다 작은 값으로 설정할 수 있습니다. 그렇게 하면 모든 OpenGL 렌더링이 더 작은 창에 표시됩니다. 그리고 예를 들어 OpenGL 뷰포트 밖의 요소들을 출력할 수 있습니다.

  내부에서 OpenGL은 glViewport 함수를 통해 지정된 데이터를 사용하여 2D 좌표를 여러분의 스크린 좌표로 변환합니다. 예를 들어 처리된 지점 (-0.5,0.5)은 스크린 좌표에서 (200,450)에 매핑됩니다. OpenGL에서 처리된 좌표는 -1과 1 사이에 있으므로 범위(-1 에서 1)에서 (0, 800) 및 (0, 600)까지 효과적으로 매핑됩니다.

  하지만 사용자가 창의 크기를 조정하는 순간의 뷰포트도 조정해야 합니다. 창의 크기를 조정할 때마다 호출되는 콜백 함수를 등록할 수 있습니다. 이 resize 콜백 함수는 다음과 같습니다.


void framebuffer_size_callback(GLFWwindow* window, int width, int height);  

  framebuffer size 함수는 첫 번째 파라미터로 GLFWwindow 객체를 받고 창의 새로운 크기를 나타내는 두 개의 정수를 나머지 파라미터로 받습니다. 창의 크기가 변경될 때마다 GLFW는 이 함수를 호출하고 파라미터에 적절한 데이터를 채우게 됩니다.


void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}  

  모든 창의 크기가 변경될 때마다 이 함수를 호출하겠다고 GLFW에게 등록하여 알려주어야 합니다.


glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  

  창을 처음 표시할 때 초기의 창 크기로 framebuffer_size_callback 함수가 호출됩니다. 레티나 디스플레이의 경우 widthheight의 값이 원래 입력 값보다 훨씬 높아집니다.


  함수들을 등록하기 위해 설정할 수 있는 콜백 함수들은 많이 존재합니다. 예를 들어 조이스틱 입력 변경, 프로세스 오류 메시지 등을 처리하는 콜백 함수를 만들 수 있습니다. 윈도우를 생성하고 게임 루프가 시작되기 전에 콜백 함수를 등록할 수 있습니다.

Engine 준비

  우리는 응용 프로그램이 하나의 이미지를 그리고 난 후 바로 종료되는 것을 원하지 않습니다. 프로그램이 명시적으로 중지하라는 메시지를 받기 전까지 계속해서 이미지들을 그리고 사용자 입력을 처리하도록 해야합니다. 이러한 이유 때문에 우리는 render loop라고 불리는 while 루프를 만들어야 합니다, 이 루프는 우리가 GLFW에게 그만하라고 할때까지 계속 실행됩니다. 다음 코드는 아주 간단한 render loop를 보여줍니다.


while(!glfwWindowShouldClose(window))
{
    glfwSwapBuffers(window);
    glfwPollEvents();    
}

  glfwWindowShouldClose 함수는 각 루프가 시작될 때마다 GLFW가 종료하도록 지시되었는지 확인합니다. 만약 그렇다면 이 함수는 true를 반환하여 게임 루프를 중지합니다. 그 후 우리는 응용 프로그램을 닫을 수 있습니다.
  glfwPollEvents 함수는 이벤트(키보드 입력이나 마우스 이동 이벤트)가 발생하였는지 확인하고 윈도우 상태를 업데이트 하며 정해진 함수(콜백 함수를 통해 등록할 수 있는)를 호출합니다.
  glfwSwapBuffers 함수는 컬러 버퍼(GLFW 창 안의 각 픽셀들에 대한 컬러 값을 가지고 있는 큰 버퍼)를 교체합니다. 컬러 버퍼는 반복중에 이미지를 그리고 화면에 출력하는 기능을 합니다.

Double buffer
  응용 프로그램이 single buffer로 이미지를 그렸을 때 이미지가 깜박이는 문제가 발생될 수 있습니다. 이는 이미지가 순식간에 그려지는 것이 아니라 픽셀 하나 하나 그려지기 때문입니다. 일반적으로 왼쪽 픽셀에서 오른쪽 픽셀순으로 그리고 위쪽 픽셀에서 아랫쪽 픽셀 순으로 그려집니다. 이러한 이미지들은 사용자에게 순간적으로 동시에 표시되지 않고 단계별로 보여지기 때문에 결함이 보일 수 있는 것입니다. 이러한 문제를 피하기 위해 윈도우 응용 프로그램은 double buffer 렌더링을 적용합니다. 앞(front) 버퍼에는 최종 출력 이미지를 담고 모든 렌더링 명령은 뒤(back) 버퍼에 그려집니다. 모든 렌더링 명령이 완료되자마자 back 버퍼를 front 버퍼로 교체(swap)합니다. 이렇게 함으로써 사용자에게 이미지를 즉시 표시하여 앞에 설명한 모든 결함을 제거할 수 있습니다.

마지막 하나

  렌더링 루프가 종료되자마자 할당되었던 모든 자원들을 정리 / 삭제해야 합니다. main 함수의 맨 밑에 glfwTerminate 함수를 호출하여 이를 수행할 수 있습니다.


glfwTerminate();
return 0;

  이렇게하면 모든 자원들이 정리되고 응용 프로그램이 올바르게 종료됩니다. 이제 응용 프로그램을 컴파일 해보세요. 모든 것이 잘 되었다면 다음과 같은 결과를 볼 수 있을 것입니다.


Image of GLFW window output as most basic example

  화면이 보잘 것 없는 검은 화면이라면, 여러분은 아주 잘 하고 있는 것입니다. 올바른 화면이 나오지 않거나 모든 것들이 어떻게 작동하는지 혼란스럽다면 여기에서 전체 소스 코드를 확인하세요.


  컴파일하는 데 문제가 있다면, 먼저 모든 링커 설정이 올바르게 설정되어 있고 IDE에 올바른 디렉터리를 설정하였는지 확인하세요(이전 강좌에서 설명한대로). 또한 코드가 올바른지 확인하세요. 소스 코드를 보고 쉽게 확인할 수 있을 것입니다. 여전히 문제가 있따면, 댓글을 달아주세요.

입력

  우리는 GLFW에서의 입력을 관리하는 방법에 대해 알아야 합니다. GLFW의 입력에 관한 함수 중 몇가지를 알아볼 수 있습니다. glfwGetKey 함수를 사용하여 키보드 키와 함께 윈도우의 입력을 받을 수 있습니다. 이 함수는 해당 키가 현재 눌려져 있는지 여부를 리턴합니다. processInput 함수를 만들어 모든 입력 코드를 체계적으로 유지할 수 있습니다.


void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

  여기에서 사용자가 esc 키를 눌렀는지 확인합니다(눌려지지 않았다면, glfwGetKey 함수는 GLFW_RELEASE 를 리턴합니다). 사용자가 esc 키를 누를 경우 glfwSetwindowShouldClose 함수를 사용하여 WindowShouldClose 속성을 true 로 세팅함으로써 GLFW를 닫습니다 . 그러면 while 루프의 다음 조건이 거짓이 되기 때문에 응용 프로그램이 닫히게 됩니다.


  우리는 렌더링 루프가 반복될 때마다 processInput 함수를 호출합니다.


while (!glfwWindowShouldClose(window))
{
    processInput(window);

    glfwSwapBuffers(window);
    glfwPollEvents();
}  

  이를 통해 모든 프레임에서 특정 키가 눌려져 있음을 확인한 후 그에 대한 반응을 할 수 있습니다.

렌더링

  우리는 모든 렌더링 명령들이 각 루픞마다 실행되기 원하기 때문에 모든 렌더링 명령을 렌더링 루프 안에 넣어야 합니다. 이는 다음과 같이 보일 것입니다.


// 렌더링 루프
while(!glfwWindowShouldClose(window))
{
    // 입력
    processInput(window);

    // 여기에 렌더링 명령
    ...

    // 이벤트를 확인하고 버퍼를 교체
    glfwPollEvents();
    glfwSwapBuffers(window);
}

  실제로 작동하는지 테스트하기 위해 우리가 선택한 색상으로 화면을 지우도록 하겠습니다. 렌더링 루프 돌때마다 처음에 우리는 항상 화면을 지울 것입니다. 그렇게 하지 않으면 이전 루프의 결과 이미지들이 화면에 계속 남아 있게 되기 때문입니다(이것은 여러분이 사용하고 싶은 효과일 수도 있지만 대개는 그렇지 않습니다). glClear 함수를 사용하여 화면의 컬러 버퍼를 지울 수 있습니다. 이 함수는 우리가 지우려 하는 버퍼의 비트를 전달하게 됩니다. 사용가능한 비트는 GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT 가 있습니다. 현재 우리는 컬러 값만 생각하므로 컬러 버퍼만 지웁니다.


glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

  glClearColor 함수를 통해 어떠한 색으로 화면을 지울것인지에 대해 설정할 수 있습니다. glClear 함수를 호출하여 컬러 버퍼를 지울 때마다 전체 컬러버퍼는 glClearColor 함수에서 설정한 색으로 지워지게 됩니다. 이 경우 짙은 청녹색으로 지워지게 됩니다.

  OpenGL 강좌에서 알 수 있듯이, glClearColor 함수는 상태 설정(state-setting) 함수이고 glClear 함수는 어떠한 색으로 지울 것이지 검색하는 데에 현재 상태를 사용한다는 점에서 상태 사용(state-using) 함수입니다. Image of GLFW's window creation with <function id='13'><function id='10'>glClear</function>Color</function> defined

  응용 프로그램의 전체 소스 코드는 여기에서 볼 수 있습니다.


  우리는 이제 게임 루프를 많은 렌더링 명령으로 채울 준비가 되어있습니다. 하지만 이는 다음 강좌에서 다루도록 하겠습니다. 이 강좌는 충분히 길었다가 생각합니다.



출처 : https://learnopengl.com, Joey de Vries

반응형