6 minute read

GLFW를 시작하고 실행할 수 있는지 봅시다. 먼저 .cpp 파일을 만들고 새로 만든 파일의 맨 위에 다음 포함을 추가합니다.

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

(주의) GLFW 앞에 GLAD를 포함해야 합니다. GLAD용 포함 파일에는 뒤에서 필요한 OpenGL 헤더(예: GL/gl.h)가 포함되어 있으므로 OpenGL이 필요한 다른 헤더 파일(예: GLFW)보다 먼저 GLAD를 include 해야합니다.

다음으로 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의 첫 번째 인수는 구성하려는 옵션을 알려줍니다. 여기서 GLFW_ 접두사가 붙은 가능한 옵션의 큰 목록에서 옵션을 선택할 수 있습니다. 두 번째 인수는 옵션 값을 설정하는 정수입니다. 가능한 모든 옵션의 목록과 해당 값은 GLFW의 창 처리 문서에서 찾을 수 있습니다. 지금 응용 프로그램을 실행하려고 할 때 정의되지 않은 참조 오류가 많이 발생하면 GLFW 라이브러리를 성공적으로 연결하지 못했다는 의미입니다.

  • 즉, 사용하는 openGL이 3.3 버전이라고 세팅값을 설정해준 것이다.

이 책의 초점은 OpenGL 버전 3.3이기 때문에 GLFW에게 3.3이 우리가 사용하려는 OpenGL 버전이라고 말하고 싶습니다. 이 방법으로 GLFW는 OpenGL 컨텍스트를 생성할 때 적절한 배열을 할 수 있습니다. 이렇게 하면 사용자에게 적절한 OpenGL 버전이 없을 때 GLFW가 실행되지 않습니다. 주 버전과 부 버전을 모두 3으로 설정했습니다. 또한 GLFW에 명시적으로 core-profile을 사용하고 싶다고 알립니다. GLFW에게 core-profile을 사용하기를 원한다는 것은 더 이상 필요하지 않은 이전 버전과 호환되는 기능 없이 OpenGL 기능의 더 작은 하위 집합에 액세스할 수 있음을 의미합니다. Mac OS X에서는 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 작동하도록 초기화 코드에 추가합니다.

시스템/하드웨어에 OpenGL 버전 3.3 이상이 설치되어 있는지 확인하십시오. 그렇지 않으면 응용 프로그램이 충돌하거나 정의되지 않은 동작이 표시됩니다. 컴퓨터에서 OpenGL 버전을 찾으려면 Linux 컴퓨터에서 glxinfo를 호출하거나 Windows용 OpenGL Extension Viewer와 같은 유틸리티를 사용하십시오. 지원되는 버전이 더 낮으면 비디오 카드가 OpenGL 3.3 이상을 지원하는지 확인하고(그렇지 않으면 정말 오래된 버전임) 드라이버를 업데이트하십시오.

다음으로 창 객체를 만들어야 합니다. 이 창 객체는 모든 창 데이터를 보유하고 대부분의 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”이라고 부르지만 원하는 대로 이름을 지정할 수 있습니다. 마지막 2개의 매개변수는 무시해도 됩니다. 이 함수는 나중에 다른 GLFW 작업에 필요한 GLFWwindow 객체를 반환합니다. 그런 다음 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 함수를 통해 이러한 차원을 설정할 수 있습니다.

glViewport(0, 0, 800, 600);

glViewport의 처음 두 매개변수는 창의 왼쪽 아래 모서리 위치를 설정합니다. 세 번째와 네 번째 매개변수는 렌더링 창의 너비와 높이를 픽셀 단위로 설정하며 GLFW의 창 크기와 동일하게 설정합니다.

실제로 GLFW의 차수(dimenstion)보다 작은 값으로 뷰포트 차수를 설정할 수 있습니다. 그러면 모든 OpenGL 렌더링이 더 작은 창에 표시되고 예를 들어 OpenGL 뷰포트 외부에 다른 요소를 표시할 수 있습니다.

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

  • 왜 값이 200, 450이 나오는지에 대한 것은 이해가 안되는데.. 다시 공부해봐야 할 듯 하다.

그러나 사용자가 창 크기를 조정하는 순간 뷰포트도 조정되어야 합니다. 창의 크기가 조정될 때마다 호출되는 창에 콜백 함수를 등록할 수 있습니다. 이 크기 조정 콜백 함수에는 다음 프로토타입이 있습니다.

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

프레임 버퍼 크기 함수는 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이 결과 창 크기와 함께 호출됩니다. 레티나 디스플레이의 경우 너비와 높이는 원래 입력 값보다 훨씬 높아집니다.

자체 함수를 등록하기 위해 설정할 수 있는 많은 콜백 함수가 있습니다. 예를 들어, 조이스틱 입력 변경을 처리하고 오류 메시지를 처리하는 등의 콜백 함수를 만들 수 있습니다. 창을 만든 후 렌더 루프가 시작되기 전에 콜백 함수를 등록합니다.

Ready your engines

우리는 애플리케이션이 단일 이미지를 그린 다음 즉시 종료하고 창을 닫는 것을 원하지 않습니다. 우리는 프로그램이 명시적으로 중지하라는 지시를 받을 때까지 애플리케이션이 이미지를 계속 그리고 사용자 입력을 처리하기를 원합니다. 이러한 이유로 우리는 이제 렌더 루프라고 부르는 while 루프를 생성해야 합니다. 이 루프는 GLFW가 중지할 때까지 계속 실행됩니다. 다음 코드는 매우 간단한 렌더 루프를 보여줍니다.

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

glfwWindowShouldClose 함수는 GLFW가 닫히도록 지시되었는지 여부를 각 루프 반복 시작 시 확인합니다(poll을 통해 확인). 그렇다면 함수는 true를 반환하고 렌더 루프 실행이 중지된 후 응용 프로그램을 닫을 수 있습니다. glfwPollEvents 함수는 이벤트(키보드 입력 또는 마우스 이동 이벤트와 같은)가 트리거되었는지 확인하고 창 상태를 업데이트하고 해당 함수(콜백 메서드를 통해 등록할 수 있음)를 호출합니다. glfwSwapBuffers는 이 렌더 반복 중에 렌더링하는 데 사용되는 색상 버퍼(GLFW 창의 각 픽셀에 대한 색상 값을 포함하는 대형 2D 버퍼)를 교환하고 화면에 출력으로 표시합니다.

Double Buffer
응용 프로그램이 단일 버퍼를 그릴 때 결과 이미지에 깜박임 문제가 표시될 수 있습니다. 그 이유는 결과 출력 이미지가 순간적으로 그려지는 것이 아니라 픽셀 단위로 그리고 일반적으로 왼쪽에서 오른쪽으로, 위에서 아래로 그려지기 때문입니다. 이 이미지는 렌더링되는 동안 사용자에게 즉시 표시되지 않기 때문에 결과에 아티팩트가 포함될 수 있습니다. 이러한 문제를 피하기 위해 윈도우 응용 프로그램은 렌더링을 위해 이중 버퍼를 적용합니다. 전면 버퍼에는 화면에 표시되는 최종 출력 이미지가 포함되며 모든 렌더링 명령은 후면 버퍼에 표시됩니다. 모든 렌더링 명령이 완료되면 백 버퍼를 프런트 버퍼로 교체하여 이미지가 여전히 렌더링되지 않고 표시될 수 있도록 하여 앞서 언급한 모든 아티팩트를 제거합니다.

마지막 한가지

렌더 루프를 종료하자마자 할당된 모든 GLFW 리소스를 적절하게 정리/삭제하고 싶습니다. 메인 함수의 끝에서 호출하는 glfwTerminate 함수를 통해 이를 수행할 수 있습니다.

glfwTerminate();
return 0;

그러면 모든 리소스가 정리되고 응용 프로그램이 올바르게 종료됩니다. 이제 응용 프로그램을 컴파일하려고 시도하고 모든 것이 잘 되었다면 다음 출력이 표시되어야 합니다.

image

매우 볼 것 없는 검은색 이미지라면 제대로 한 것입니다! 올바른 이미지를 얻지 못했거나 모든 것이 어떻게 조화를 이루는지 혼란스럽다면 여기에서 전체 소스 코드를 확인하십시오(다른 색상으로 깜박이기 시작하면 계속 읽으십시오).

응용 프로그램을 컴파일하는 데 문제가 있는 경우 먼저 모든 링커 옵션이 올바르게 설정되어 있고 IDE에 올바른 디렉터리를 올바르게 포함했는지 확인하십시오(이전 장에서 설명한 대로). 또한 코드가 올바른지 확인하십시오. 전체 소스 코드와 비교하여 확인할 수 있습니다.

입력

우리는 또한 GLFW에서 어떤 형태의 입력 제어를 원하고 우리는 GLFW의 여러 입력 기능으로 이것을 달성할 수 있습니다. 키와 함께 창을 입력으로 사용하는 GLFW의 glfwGetKey 함수를 사용할 것입니다. 이 함수는 해당 키가 현재 눌려 있는지 여부를 반환합니다. 모든 입력 코드를 체계적으로 유지하기 위해 processInput 함수를 만들고 있습니다.

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

여기에서 사용자가 이스케이프 키를 눌렀는지 여부를 확인합니다(누르지 않은 경우 glfwGetKey는 GLFW_RELEASE를 반환합니다). 사용자가 이스케이프 키를 누른 경우 glfwSetwindowShouldClose를 사용하여 WindowShouldClose 속성을 true로 설정하여 GLFW를 닫습니다. 그러면 기본 while 루프의 다음 조건 검사가 실패하고 응용 프로그램이 닫힙니다.

그런 다음 렌더 루프가 반복될 때마다 processInput을 호출합니다.

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

    glfwSwapBuffers(window);
    glfwPollEvents();
}  

Rendering 루프의 각 반복 또는 프레임마다 모든 렌더링 명령을 실행하기를 원하기 때문에 모든 렌더링 명령을 렌더 루프에 배치하려고 합니다. 이것은 다음과 같이 보일 것입니다:

// render loop
while(!glfwWindowShouldClose(window))
{
    // input
    processInput(window);

    // rendering commands here
    ...

    // check and call events and swap the buffers
    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 함수는 상태 설정 함수이고 glClear는 현재 상태를 사용하여 클리어링 색상을 검색한다는 점에서 상태 사용 함수입니다.

image

그래서 지금 우리는 많은 렌더링 호출로 렌더 루프를 채울 모든 준비를 마쳤습니다. 하지만 그것은 다음 장을 위한 것입니다. 저는 우리가 여기에서 충분히 거닐었다고 생각합니다.