Post Lists

2018년 6월 24일 일요일

Getting Started - Hello Window(4) 번역

https://learnopengl.com/Getting-started/Hello-Window
위의 자료를 번역한 내용입니다.
-------------------------------------------------------------------------------------------------------------------------
Hello Window
 우리가 GLFW를 가지고 구동시킬 수 있는지를 봐보자. 처음에, .cpp 파일을 만들고 다음 include들을 너의 새로이 만들어진 파일의 상단에 추가해라

#include <glad/glad.h>
#include <GLFW/glfw3.h> (나의 경우엔 glfw3.h가 있는 include폴더에 바로 연결해서 GLFW를 안해도된다.)

다음으로 main 함수를 만들고 거기에서 우리는 GLFW window를 초기화 할 것이다.

int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

return 0;
}

메인 함수에서, 우리는 GLFW를 glfwInit으로 초기화한다.

glfwInit은 GLFW 라이브러리를 초기화한다. GLFW 함수가 사용되기 이전에, GLFW은 반드시 초기화 되어야만 한다.

그리고나서 우리는 glfwWindowHint를 사용하여 GLFW를 설정한다.

glfwWindowHint는 다음 호출 함수인 glfwCreateWindow에 대해 hint를 설정한다. 일단 정해진 힌트들은 
그 힌트들은 glfwWindowHint라는 다른 호출에의해서 변경되어질 때 까지 그들의 값을 보유하고 있는다.
glfwWindowHint(int target, int hint)의 인자들은 다음과 같다.

target : 우리가 바꾸고자 하는 타겟과 옵션을 정한다. 타겟들은 GLFW_라고 접두사를 가진 GLFW의 열거형 중의 하나를 사용하여 설정된다.
hint : 우리가 설정하고자 하는 타겟들의 값 또는 hint

example : glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

만약 당신이 프로그램을 지금 구동시키고자 하고 그 프로그램이 많은 정의되지 않은 참조에러를 준다면, 너가 성공적으로 GLFW 라이브러리를 link하지 않았다는 것을 의미한다.

이 웹사이트의 focus는 OpenGL Ver 3.3이기 때문에, 우리는 GLFW에게 우리가 사용할 OpenGL version은 3.3이라고 말하길 원한다. 이러한 방식으로 GLFW은 OpenGL context를 만들 때 적절한 준비를 할 수 있다. 이것은 사용자가 적절한 OpenGL version을 가지고 있지 않았 을 때, GLFW은 작동하는 것을 실패한다는 것이다. 우리는 major와 minor version 둘 다 3으로 설정한다. 우리는 또한 GLFW에게 우리는 명백히 core-profire를 사용하고 싶다고 말한다. GLFW에게 명백히 우리가 core-profile을 사용하고 싶다고 말하는 은 우리가 OpenGL 특징들의 더욱 작은 셋트에 접근할 것이라는 것을 의미한다. (우리가 더이상 필요없는 backwards-compatible feature들이 없이) 

Green Box
너가 OpenGL 3.3이나 그 이상의 버전이 너의 시스템이나 하드웨어에 설치되어있도록 해라. 만약 그렇지 않다면, 프로그램은 충돌되고 정의되지않은 행동을 보일 것이다. 너의 기계에서 OpenGL version을 알기위해서, 리눅스 머신에서 glxinfo를 호출하거나 윈도우에서는 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는 윈도우를 만들고 그것과 연관된 context를 만든다. 윈도우와 그것의 context가 어떻게 만들어져야 하는지를 통제하는 대부분의 옵션들은
glfwWindowHint를 통해 명시되어진다. 성공적인 creation은 context를 바꾸지 않는다. 너가 새롭게 만들어진 context를 사용할 수 있기 이전에,
너는 glfwMakeContextCurrent를 사용하여 그것을 통용되게 만들필요가 있다. 
glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share)의 인자들은 다음과 같다.

width : 스크린 좌표에서 윈도우의 요구되는 너비
height : 스크린 좌표에서 윈도우의 요구되는 높이
title : 초기 윈도우 제목
monitor : full screen mode를 위해 사용할 monitor, windowed mode를 사용하려면 NULL
share : resource를 공유할 context의 window, NULL이면 resource를 공유하지 않음
이 함수는 다른 GLFW 연산을 위해 요구되는 GLFWwindow 객체의 포인터를 반환

GLAD
 이전 튜토리얼에서 우리는 GLAD가 OpenGL을 위한 함수 포인터들을 관리한다고 언급했었다. 그래서 우리는 어떤 OpenGL 함수들을 호출하기전에 GLAD를 초기화를 하고 싶다.
if(!gladLoadGLLoader( (GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initializae GLAD"" << std::endl;
return -1;
}

우리는 GLAD에 OS-특수한 OpenGL 함수 포인터의 주소를 부를 함수를 넘겨준다. GLFW은 우리에게 glfwGetProcAddress를 준다. 이것은 우리가 컴파일 하고있는 OS에 기반으로 하여 정확한 함수를 정의한다.

Viewport
 우리가 렌더링을 시작하기전에, 우리는 마지막 한 가지 것을 해야한다. 우리는 OpenGL에게 렌더링 윈도우의 크기를 말해야만 한다. 그래야 OpenGL은 어떻게 우리가 데이터를 보이고 싶은지를 알고 그 윈도우에 따라 구성한다. 우리는 그러한 크기들을 glViewport 함수를 통해서 설정할 수 있다.

glViewport는 너의 렌더링을 위한 실제 윈도우 사각형을 설정한다. 이 함수는 너의 viewport 사각형의 left, bottom, right 그리고 top 좌표를 요구한다. 설정된 좌표는
OpenGL에게 어떻게 그것이 그것의 표준화된 장치 좌표를 (-1에서 1의 범위에서) 윈도우 좌표로 (주어진 좌표에서 명시된 범위로) 사상시킬지를 말해준다. 
Y좌표가 뷰포트의 밑바닥에서 시작하는 것을 주목해라. 만약 Y가 0이라면 뷰포트의 밑마닥이다.
glViewport(GLint x, GLint y, GLsizei width, GLsizei height)의 인자는 다음과 같다.

x : 뷰포트 사각형의 왼쪽 x좌표
y : 뷰포트 사각형의 bottom y 좌표
width : 뷰포트의 너비
height : 뷰포트의 높이

우리는 실제로 GLFW의 치수보다 더 작은 값에서 뷰포트의 크기를 설정할 수 있다. 그러고나서 모든 OpenGL 렌더링은 더 작은 윈도우에서 보일것이다. 그리고 예를들어 우리는 OpenGL 뷰포트 밖에서 다른 요소들을 보여줄 수 있다.

Green Box
scenes들 뒤에서, OpenGL은 당신의 스크린에서 좌표들에 대해 처리하는 2D 좌표 변환을 하는 glViewport를 통해 명시된 data를 사용한다. 예를들어
위치 (-0.5, 0.5)의 처리되어진 지점은 (그것의 최종 변형으로서) 스크린 좌표에서 (200, 450)으로 사상되어 질 것이다. OpenGL에서 처리된 좌표가 -1과 1사이라는 것에 주목해라. 그래서 우리는 효과적으로 -1~1의 범위를 (0, 800) 과 (0, 600)으로 사상한다.

그러나, 사용자가 윈도우의 크기를 재설정할 때 마다, 뷰포트는 또한 조정되어야 한다. 우리는 윈도우가 재조정 될 때마다 윈도우에서 호출되는 callback 함수를 등록할 수 있다. 이 resize callback 함수는 다음 prototype을 가진다.

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은 결과로 나타나는 윈도우 크기와 함꼐 호출된다. retina display에 대해서는, 너비와 높이는 원래 인풋 값보다 더 높게 잡혀진다.

우리 자신의 함수를 등록하기위해 설정할 수 있는 많은 callback 함수들이 있다. 예를들어, joystick input 변화를 처리하는, 에러 메세지를 처리하는 등의 callback 함수들을 만들 수 있다. 우리는 우리가 윈도우를 만들고난 후에, 그리고 게임 루프가 초기화 되기 이전에 callback 함수를 등록한다.

Ready your engines
 우리는 프로그램이 하나의 이미지를 그리고 그리고나서 즉시 윈도우를 중단하고 끄는 것을 원하지 않는다. 우리는 프로그램이 명백히 중단되라고 말을 들을 때 까지 프로그램이 계속해서 이미지를 그리고 사용자의 입력을 처리하기를 원한다. 이러한 이유 때문에, 우리는 while loop를 만들어야만 한다. 그 while loop는 우리가 render loop라고 지금은 부르고, 우리가 GLFW에게 그만하라고 말할 때 까지 계쏙해서 작동한다. 다음 코드는 간단한 렌더 루프를 보여준다.

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

glfwWindowShouldClose 함수는 각각의 loop 반복의 시작점에서 GLFW가 종료되도록 지시를 받았는지를 체크한다. 만약 그렇다면, 그 함수는 TRUE를 반환하고, 게임 루프는 작동되는 것을 멈춘다. 그리고 그 후에, 우리는 프로그램을 닫을 수 있다. 
glfwPollEvents 함수는 어떤 event들이 유발되었는지를 확인한다. (키보드 입력 또는 마우스 움직임 이벤트 같은) 그리고 윈도우 상태를 업데이트하고 그에 상응한흔 함수를 호출한다. (우리는 callback method를 통해 설정할 수 있다.) 
glfwSwapBuffers는 반복동안에 draw하도록사용될 color buffer를 swap한다. 그리고 그것을 스크린에 output으로서 보여준다.

Green Box
Double buffer (이중 버퍼)
프로그램이 single buffer에 draw할 때, 결과로 나오는 이미지는 깜빡이는 문제를 보여준다. 이것은 왜냐하면 output image가 한 순간에 그려지고 있지 않고 픽셀마다 그리고 보통 왼쪽에서 오른쪽, 위에서 아래로 그려지고 있기 때문이다. 이러한 이미지들은 사용자에게 한 순간에 보여지지 않기 때문에, 오히려 단계를 거친 생성을 통해 결과는 거의 몇가지 artifact만 포함할지도 모른다. 이러한 문제를 피하기 위해, windowing 프로그램들은 렌더링을 위한 이중 버퍼를 적용한다. front buffer는 스크린에 나타나지는 최종 output image를 가지고 있다. 반면에 모든 렌더링 명령들은 back buffer에다가 draw한다. 모든 rendering command들이 끝나자마자, 우리는 back buffer와 front buffer를 교체한다. 그래서 이미지는 끊임없이 사용자에게 보여진다. 그리고 모든 앞에서 언급한 artifact들을 제거한다.

One last thing
 우리가 렌더 루프를 종료하자마자, 우리는 적절히 할당된 모든 자원들을 해제하고 싶어한다. 우리는 이것을 glfwTerminate 함수를 통해서 할 수 있다. 우리는 이것을 main 함수 마지막에 호출한다.

glfwTerminate();
return 0;

이것은 모든 자원을 해제하고 적절히 프로그램을 종료하다. 지금 너의 프로그램을 컴파일해보고 만약 모든것이 잘 되었다면 결과물을 봐야만 한다.
                                      

나는 바로 window를 볼 수 없었는데, GLAD를 호출하는 부분을 main함수의 맨 처음에 두었더니 그랬다. 아직 정확히 이유는 모르겠다.
glad를 호출하는 부분을 loop 전에 호출하면 된다.

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

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

int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

GLFWwindow* window = glfwCreateWindow(800, 600, "Chan Practice", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << '\n';
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initializae GLAD" << '\n';
return -1;
}

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

glfwSwapBuffers(window);
glfwPollEvents();
}

glfwTerminate();
return 0;
}

GLAD가 openGL를 OS에 맞게 설정하는 걸로 알고 있는데, 그래서 맨 처음에 두면 된다고 생각했다.
실험을 해보니, GLFW를 초기화하고 윈도우 오브젝트 context설정이 끝난 부분에 두면은 widnow창이 출력된다.

Input
우리는 GLFW에서 입력 제어의 form을 갖기를 원한다. 그리고 우리는 GLFW의 입력함수의 몇가지를 성취하고 싶다. 우리는 GLFW의 glfwGetKey 함수를 사용할 것이다. 이 함수는 키와 함께 input으로서 window를 취한다. 그 함수는 이것의 키가 현재 눌러지고 있는지를 return한다. glfwgetKey는 눌러지지 않았다면 GLFW_RELEASE를 return한다.

Rendering
우리는 모든 렌더링 명령어들을 렌더 loop에 넣길 원한다. 왜냐하면 우리는 모든 렌더링 명령어들이 루프의 매 반복마다 실행되길 원하기 때문이다. 이것은 다음 처럼 보일 것이다.

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

// rendering commands here
...

// check and call events and swap the buffers
glfwPollEvents();
glfwSwapBuffers(window);
}

실제로 잘 작동하는지를 테스트 하기 위해서, 우리는 스크린을 우리가 원하는 색으로 하고 싶어한다. 각각의 랜더 iteration의 시작에서, 우리는 항상 screen을 clear하기를 원한다. 만약 그렇지 않는다면 우리는 여전히 이전의 반복의 결과를 볼 것이다. (이것이 너가 찾고있는 효괄일 것이다. 그러나 보통 그렇지 않다.) 우리는 glClear 함수를 사용하여 스크린의 color buffer를 clear할 수 있다.  glClear 함수에서 우리가 clear하고자 하는 buffer가 어떤 것인지 명시하기 위해 우리는 버퍼에서 bit를 넘긴다. 우리가 설정할 수 있는 가능한 bit들은 GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, 그리고 GL_STENCIL_BUFFER_BIT이다. 지금 당장 우리는 color value들을 신경쓴다. 그래서 우리는 오직 color buffer를 clear한다. 

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

우리가 또한 스크린을 clear하는 glClearColor를 통해 색을 지정한다는 것에 주목해라. 우리가 glClear를 호출할 떄마다 그리고 color buffer를 clear할 때마다, 전체 colorbuffer는 glClearColor에서 설정한 색으로 채워질 것이다. 이것은 dark green-blueish color를 만들 것이다.

glClearColor는 colorbuffer를 reset하기위해 OpenGL이 사용할 color value를 설정한다. glClear 또는 glClearBuffer가 호출되자마자, color value를 reset하기 위해 glClearColor의 value값을 사용한다.

glClear는 현재 framebuffer의 전체 buffer를 clear한다. GL_COLOR_BUFFER_BIT로 명시할 때, 그 버퍼는 glClearColor에 명시되어있는 색으로 cleared 된다.
GL_DEPTH_BUFFER_BIT는 depth buffer를 clear하고, GL_STENCIL_BUFFER_BIT는 stencil buffer를 clear한다.

Green Box
OpenGL 튜토리얼에서 상기할지도 모르듯이, glClearColor 함수는 state-setting function 이다. (상태설정 함수) 그리고 glClear는 state-using function이다. (상태 사용 함수) 그리고 그것은 clearing color를 가져오기위해 current state를 사용한다는 점에서 상태사용함수이다.

댓글 없음:

댓글 쓰기