// 버퍼 가로, 버퍼 세로 선언
int bufferWidth, bufferHeight;
// mainWindow로부터 버퍼 가로 크기와 버퍼 세로 크기를 받아온다.
glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);
// OpenGL Context 설정
glfwMakeContextCurrent(mainWindow);
return (0);
기본 시작 코드
자, 이번엔 실제로 GLFW 코드를 작성해보자.
기본 시작 코드는 아래와 같다.
▼ 기본 시작 코드
#include <cstdio>
#include <clocale>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
int main()
{
_wsetlocale(LC_ALL, "Korean");
return (0);
}
가장 먼저 해야 하는 것은 GLFW를 초기화하는 것이다.
GLFW의 초기화는 int glfwinit()으로 가능한데 함수 자료형이 int인 것에 집중하자.
그래서 초기화하면서 동시에 성공과 실패 유무를 반환해주기 때문에 if문 안에서 사용한다.
GLEW 초기화
▼ GLFW 초기화
int main()
{
_wsetloacle(LC_ALL, L"Korean");
// GLFW 초기화
if (glfwInit() == GLFW_FALSE)
{
wprintf(L"GLFW 초기화 실패\n");
glfwTerminate();
return (1);
}
return (0);
}
코드를 보면 알겠지만 GLFW 초기화에 실패하면 glfwTerminate()를 호출한다.
C와 CPP는 메모리를 직접 제어하기 때문에 메모리 할당이 이루어지면 메모리 해제가 반드시 일어나야 한다.
GLFW가 정상적으로 초기화가 되었다면 이제 OpenGL 설정을 진행해준다.
OpenGL 버전 설정
▼ OpenGL 버전 설정
if (glfwInit() == GLFW_FALSE)
{
//.. CODE ..
}
// OpenGL MAJOR.MINOR
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
return (0);
OpenGL 설정에서 가장 먼저 하는 것은 OpenGL 버전을 설정하는 것이다.
OpenGL의 버전은 MAJOR.MINOR로 표현하고 필요에 따라서 다양하게 적용할 수 있다.
OpenGL의 버전을 설정하는 것에서 알 수 있듯이 OpenGL은 많은 버전을 가지고 있다.
그만큼 버전이 변하면서 더 이상 사용되지 않는 기능들이거나 추가된 기능들이 있기 마련이다.
이제 OpenGL Profile과 호환성을 설정해보자.
OpenGL Profile과 호환성 설정
▼ OpenGL Profile 설정과 호환성 설정
if (glfwInit() == GLFW_FALSE)
{
//.. CODE ..
}
// OpenGL MAJOR.MINOR
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// OpenGL 코어 프로필 설정
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// OpenGL 상위호환 설정
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
return (0);
GLFW_OPENGL_CORE_PROFILE은 변수명에서도 알 수 있듯이 OpenGL 코어 프로필을 사용한다는 의미다.
코어 프로필은 OpenGL에서 deprecated(폐지 예정) 된 기능들을 사용하지 않는 것을 의미한다.
코어 프로필을 사용하면 하위 호환성은 버리게 되지만 OpenGL 기능들에 겹치거나 불필요한 기능들을 줄여서 가벼워진다.
반대로 하위 호환성을 활성화하고 싶을 때는 GLFW_OPENGL_COMPAT_PROFILE을 사용하면 된다.
하지만 현세대의 그래픽 카드들은 기본적으로 모던 OpenGL(3.3+)을 지원하기 때문에 굳이 하위 호환을 허용할 이유는 없다.
하지만 OpenGL 새 버전이 나온다면 상위 호환을 해줘야 할 필요가 있기 때문에 GLFW_OPENGL_FORWARD를 설정해준다. 지금은 Vulkan이 나옴으로써 새 OpenGL이 나올 가능성이 거의 없어졌지만 Vulkan을 공부하려면 일단 OpenGL을 살펴보는 것이 좋아 보인다.
이제 OpenGL을 그릴 실제 윈도를 생성해보자.
GLFW 윈도 생성
▼ GLFW 윈도우 생성
#include <cstdio>
#include <clocale>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
const GLint WIDTH = 720, HEIGHT = 480;
int main()
{
/*
... CODE ...
*/
// OpenGL 코어 프로필 설정
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// OpenGL 상위호환 설정
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
// GLFW Window 생성
GLFWwindow *mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL INIT", NULL, NULL);
if (mainWindow == NULL)
{
wprintf(L"GLFW 윈도우 생성이 실패했습니다.");
glfwTerminate();
return (1);
}
return (0);
}
Main 함수 위에 WIDTH와 HEIGHT를 추가로 선언했다.
GLint라는 낯선 자료형이 등장했는데 별로 특별한 건 없고 기본 int 정수형을 재정의 해둔 것뿐이다.
GLFWwindow라는 윈도 객체를 하나 만드는데 GLFW는 glfwCreateWindow 함수로 직관적으로 생성할 수 있다.
이미 함수명부터 윈도 생성이라고 하니 따로 API 문서를 찾아볼 필요도 없었다.
glfwCreateWindow 함수에 필요한 인자가 적진 않은데 기본적으로는 (가로, 세로, "창 이름", 윈도 모드, 공유 여부)로 정의되어 있다.
다른 인자들은 낯설지 않지만 윈도 모드와 공유 여부는 잘 들어보지 못했던 용어다.
윈도 모드는 우리가 게임에서 흔히 설정하는 풀 스크린 모드, 윈도 모드를 설정하는 옵션이다.
공유 여부는 말 그대로 이 화면 리소스를 어딘가에 공유할 때 사용하는 것으로 보인다.
우리는 기본적으로 하나의 창과 윈도 모드에서만 작업할 것이기 때문에 둘 다 NULL로 정의한다.
윈도가 만들어졌으니 이제 버퍼를 생성해보자.
버퍼 생성
▼ 버퍼 생성
// GLFW 윈도우 생성
GLFWwindow *mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL INIT", NULL, NULL);
if (mainWindow == NULL)
{
wprintf(L"GLFW 윈도우 생성이 실패했습니다.\n");
glfwTerminate();
return (1);
}
// 버퍼 가로, 버퍼 세로 선언
int bufferWidth, bufferHeight;
// mainWindow로부터 버퍼 가로 크기와 버퍼 세로 크기를 받아온다.
glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);
return (0);
버퍼(buffer)는 많은 프로그래밍 글에서 대충 간이 저장소 느낌으로 많이 설명한다.
대부분의 프로그래밍 용어가 현실에서 가져온 만큼 버퍼 역시 현실에서 가져온 단어고 경우에 따라 익숙할 수 있다.
현실에서는 기차 머리 앞에 있는 충돌 방지 장치를 버퍼라고 부른다.
즉 CPP에서도 버퍼는 같은 메모리를 사용해서 충돌하는 것을 막기 위한 장치를 뜻한다.
이 코드에서는 화면을 그리기 위한 버퍼를 선언한다.
기본적으로 GLFW는 더블 버퍼링을 사용하기 때문에 훨씬 부드럽게 화면을 그릴 수 있다.
더블 버퍼링에 대한 구현은 언젠가 해볼 WinAPI에서 살펴보도록 하자.
이제 버퍼도 설정했으니 OpenGL Context를 설정한다.
OpenGL Context 설정하기
▼ OpenGL Context 설정
// 버퍼 가로, 버퍼 세로 선언
int bufferWidth, bufferHeight;
// mainWindow로부터 버퍼 가로 크기와 버퍼 세로 크기를 받아온다.
glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);
// OpenGL Context 설정
glfwMakeContextCurrent(mainWindow);
return (0);
OpenGL은 기본적으로 State Machine(상태 기계)로 구현되어 있고 렌더링에 필요한 모든 상태를 저장하고 있다.
이 저장소를 Context라고 명한다고 한다.
그래서 우리가 만든 mainWindow에 OpenGL Context를 연결해준다.
GLEW 초기화
▼ GLEW 초기화
// OpenGL Context 설정
glfwMakeContextCurrent(mainWindow);
// GLEW의 모든 기능 활성화
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
wprintf(L"GLEW 초기화가 실패했습니다.\n");
// mainWindow 삭제
glfwDestroyWindow(mainWindow);
glfwTerminate();
return (1);
}
return (0);
GLEW에 있는 기능들을 모두 사용하기 위해서 glewExperimental을 GL_TRUE 해준다.
마찬가지로 glewInit() 역시 확인해주는데 GLFW와 GLEW는 다른 라이브러리기 때문에 조건 체크가 약간 다르다.
GLFW는 조건 체크를 위해서 GLFW_TRUE와 GLFW_FALSE 둘 다 있지만 GLEW는 GLEW_OK만 존재한다.
나의 코딩 스타일은 되도록이면 == 체크만 하는 것을 선호한다. 위의 예시들은 대부분 == 로 확인 해왔었다.
그리고 위의 경우들과 다르게 이번엔 glfwDestroyWindow(mainWindow)가 추가되었다.
이전까지는 윈도를 생성하지 않았기 때문에 glfwTerminate()만 호출했다.
하지만 이제 윈도 역시 메모리를 할당받았기 때문에 윈도도 확실히 해제해주어야 한다.
OpenGL Viewport 생성
▼ OpenGL Viewport 생성
if (glewInit() != GLEW_OK)
{
wprintf(L"GLEW 초기화가 실패했습니다.\n");
// mainWindow 삭제
glfwDestroyWindow(mainWindow);
glfwTerminate();
return (1);
}
// OpenGL Viewport 생성
glViewport(0, 0, bufferWidth, bufferHeight);
return (0);
OpenGL을 그릴 Viewport를 생성한다.
위에서 생성한 bufferWidth와 bufferHeight가 화면의 크기를 가지고 있기 때문에 그 크기대로 Viewport를 생성한다.
GLFW 기본 구동 순환문 생성
▼ GLFW 기본 구동 순환문 생성
// OpenGL Viewport 생성
glViewport(0, 0, bufferWidth, bufferHeight);
// GLFW가 종료되지 않는 한 계속 도는 순환문
while (glfwWindowShouldClose(mainWindow) == GLFW_FALSE)
{
// GLFW 이벤트 입력
glfwPollEvents();
// GLFW 더블 버퍼링
glfwSwapBuffers(mainWindow);
}
return (0);
GLFW의 기본 구동 순환문은 게임과 크게 다르지 않다.
어떤 프로그램도 결국엔 절차 지향적으로 컴파일되기 때문에 우리가 만든 mainWindow도 종료되지 않는 한 계속 작동해야 한다.
이름부터 명백한 glfwWindowShouldClose(mainWindow)가 거짓인 한 이 while문이 계속해서 돈다.
게임 루프 역시 이 형태에서 크게 벗어나지 않는다.
루프가 계속 도는 동안 GLFW는 두 가지 기능을 행한다.
하나는 마우스, 키보드와 같은 입력을 포함한 이벤트를 전달받는다.
하나는 더블 버퍼링을 진행한다.
OpenGL 연두색 화면 그리기
▼ GLFW 기본 구동 순환문 생성
// GLFW가 종료되지 않는 한 계속 도는 순환문
while (glfwWindowShouldClose(mainWindow) == GLFW_FALSE)
{
// GLFW 이벤트 입력
glfwPollEvents();
// 연두색 화면 그리기
glClearColor(0.4f, 0.6f, 0.2f, 1.0f);
// OpenGL 배경색상 초기화
glClear(GL_COLOR_BUFFER_BIT);
// GLFW 더블 버퍼링
glfwSwapBuffers(mainWindow);
}
이제 거의 다 왔다.
glClearColor는 말 그대로 OpenGL 배경 색상 초기화 색을 지정한다.
그런 다음 glClear 명령어를 통해서 배경 색상을 초기화한다.
while문을 돌면서 계속해서 초기화하기 때문에 컴퓨터에 따라서는 엄청난 프레임으로 화면을 초기화하고 있을 것이다.
전체 코드
▼ 전체 코드
#include <cstdio>
#include <clocale>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
const GLint WIDTH = 720, HEIGHT = 480;
int main()
{
// 로케일 국가 한국 지정
_wsetlocale(LC_ALL, L"Korean");
// GLFW 초기화
if (glfwInit() == GLFW_FALSE)
{
wprintf(L"GLFW 초기화 실패\n");
glfwTerminate();
return (1);
}
// OpenGL 버전 지정
// OpenGL MAJOR.MINOR 방식으로 표현
// 이번엔 3.3을 사용한다.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// OpenGL 코어 프로필 설정
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// OpenGL 상위호환 활성화
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
// GLFW 윈도우 생성
GLFWwindow *mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL INIT", NULL, NULL);
if (mainWindow == NULL)
{
wprintf(L"GLFW 윈도우 생성이 실패했습니다.\n");
glfwTerminate();
return (1);
}
// 버퍼 가로, 버퍼 세로 선언
int bufferWidth, bufferHeight;
// mainWindow로부터 버퍼 가로 크기와 버퍼 세로 크기를 받아온다.
glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);
glfwMakeContextCurrent(mainWindow);
// GLEW의 모든 기능 활성화
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
wprintf(L"GLEW 초기화가 실패했습니다.\n");
// mainWindow 삭제
glfwDestroyWindow(mainWindow);
glfwTerminate();
return (1);
}
// OpenGL Viewport 생성
glViewport(0, 0, bufferWidth, bufferHeight);
// GLFW가 종료되지 않는 한 계속 도는 순환문
while (glfwWindowShouldClose(mainWindow) == GLFW_FALSE)
{
// GLFW 이벤트 입력
glfwPollEvents();
// 연두색 화면 그리기
glClearColor(0.4f, 0.6f, 0.2f, 1.0f);
// 배경 색상 초기화
glClear(GL_COLOR_BUFFER_BIT);
// GLFW 더블 버퍼링
glfwSwapBuffers(mainWindow);
}
return (0);
}
'<LIBRARY> > OPENGL' 카테고리의 다른 글
5. GLM 라이브러리 적용하기 (0) | 2021.01.19 |
---|---|
4. 삼각형 움직이기 (0) | 2021.01.17 |
3. 삼각형 그리기(2) (0) | 2021.01.17 |
2. 삼각형 그리기 (1) (0) | 2021.01.14 |
0. OpenGL 개발환경 설정하기 (0) | 2021.01.09 |
댓글