들어가기 앞서서
이전 시간에 GLFW, glad 관련 환경 설정을 해주었다면, 솔루션 탐색기 > 모든 항목 표시 를 누르면, 빨간색으로 포함이 안된것을 볼수 있습니다.
이경우 디렉토리 우클릭 > 프로젝트에 포함 버튼을 누르면 해결 가능합니다.
창 만들기 (Hellow Window)
GLFW를 실행해 봅시다. 먼저, .cpp 파일을 생성하고 다음 include 문을 파일 상단에 추가하세요.
#include <glad/glad.h>
#include <GLFW/glfw3.h>
GLAD를 GLFW보다 먼저 포함해야 합니다. GLAD의 include 파일은 필요한 OpenGL 헤더(GL/gl.h와 같은)를 내부적으로 포함하므로, GLAD를 OpenGL이 필요한 다른 헤더 파일(GLFW 같은) 전에 포함해야 합니다.
다음으로, 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's window handling에서 찾을 수 있습니다. 빌드했을대 애플리케이션을 실행하고 정의되지 않은 참조 오류가 많이 발생한다면 GLFW 라이브러리를 성공적으로 연결하지 못한 것입니다. 오류가 발생한다면 이전 포스팅 개발환경설정을 참고해보세요.
이 책은 OpenGL 버전 3.3에 초점을 맞추고 있으므로 GLFW에 사용하려는 OpenGL 버전이 3.3임을 알려주고 싶습니다. 이렇게 하면 OpenGL 컨텍스트를 생성할 때 GLFW가 적절한 설정을 할 수 있습니다. 사용자가 적절한 OpenGL 버전을 가지고 있지 않으면 GLFW가 실행되지 않도록 합니다. 주 버전과 부 버전을 모두 3으로 설정합니다. 또한 GLFW에 코어 프로필을 명시적으로 사용하고 싶다고 알려줍니다. GLFW에 코어 프로필을 사용하고 싶다고 알려주면 더 이상 필요하지 않은 이전 버전과 호환되는 기능 없이 OpenGL 기능의 더 작은 하위 집합에 액세스할 수 있습니다. Mac OS X에서는 초기화 코드에 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);를 추가해야 작동합니다.
다음으로 Window Object를 생성해야 합니다. 이 Window Object는 모든 창 데이터를 보유하며 대부분의 다른 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에 현재 스레드에서 Window Context를 Main Context로 만들도록 지시합니다.
GLAD
이전 장에서 GLAD가 OpenGL의 함수 포인터를 관리한다고 언급했으므로 OpenGL 함수를 호출하기 전에 GLAD를 초기화합니다.:
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
우리는 GLAD에 OpenGL 함수 포인터의 주소를 로드하는 함수를 전달하며, 이는 OS에 따라 다릅니다. GLFW는 컴파일 중인 OS에 따라 올바른 함수를 정의하는 glfwGetProcAddress를 제공합니다.
Viewport
렌더링을 시작하기 전에 마지막으로 한 가지 작업을 수행해야 합니다. OpenGL에 렌더링 창의 크기를 알려주어 OpenGL이 데이터와 좌표를 창과 관련하여 어떻게 표시할지 알 수 있도록 해야 합니다. glViewport 함수를 통해 해당 치수를 설정할 수 있습니다:
glViewport(0, 0, 800, 600);
glViewport의 처음 두 매개변수는 창의 왼쪽 하단 모서리 위치를 설정합니다. 세 번째와 네 번째 매개변수는 렌더링 창의 너비와 높이를, 픽셀 단위로 설정하며 GLFW의 창 크기와 동일하게 설정합니다.
실제로 뷰포트 치수를 GLFW의 치수보다 작은 값으로 설정할 수도 있습니다. 그러면 모든 OpenGL 렌더링이 더 작은 창에 표시되고 예를 들어 OpenGL 뷰포트 외부에 다른 요소를 표시할 수 있습니다.
그러나 사용자가 창 크기를 조정하는 순간 뷰포트도 조정해야 합니다. 창이 크기 조정될 때마다 호출되는 창에 콜백 함수를 등록할 수 있습니다. 이 크기 조정 콜백 함수에는 다음과 같은 프로토타입이 있습니다:
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이 결과 창 치수와 함께 호출됩니다. 레티나 디스플레이의 경우 Width와 Height는 원래 입력 값보다 훨씬 높아집니다.
자체 함수를 등록할 수 있는 많은 콜백 함수가 있습니다. 예를 들어, 조이스틱 입력 변경, 오류 메시지 처리 등을 처리하는 콜백 함수를 만들 수 있습니다. 창을 만든 후 렌더 루프가 시작되기 전에 콜백 함수를 등록합니다.
준비하세요 (Ready your engines)
애플리케이션이 단일 이미지를 그린 다음 즉시 종료하고 창을 닫기를 원하지 않습니다. 우리는 프로그램이 명시적으로 중지하라는 지시를 받을 때까지 애플리케이션이 계속 이미지를 그리고 사용자 입력을 처리하기를 원합니다. 이를 위해 render loop라고 부르는 while 루프를 만들어야 합니다. 이 루프는 GLFW에 중지하라고 지시할 때까지 계속 실행됩니다. 다음 코드는 매우 간단한 렌더 루프를 보여줍니다:
while(!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwWindowShouldClose 함수는 각 루프 반복의 시작 부분에서 GLFW가 닫히도록 지시받았는지 확인합니다. 그렇다면 함수는 true를 반환하고 렌더 루프는 실행을 중지하며, 그 후 애플리케이션을 닫을 수 있습니다.
glfwPollEvents 함수는 이벤트(예: 키보드 입력 또는 마우스 움직임 이벤트)가 트리거되었는지 확인하고, 창 상태를 업데이트하고, 해당 함수(콜백 메서드를 통해 등록할 수 있음)를 호출합니다. glfwSwapBuffers는 이 렌더 반복 중에 렌더링하는 데 사용되는 색상 버퍼(GLFW 창의 각 픽셀에 대한 색상 값을 포함하는 큰 2D 버퍼)를 교체하고 화면에 출력으로 표시합니다.
Double buffer
애플리케이션이 단일 버퍼에 그릴 때 결과 이미지에 깜박임 문제가 표시될 수 있습니다. 이는 결과 출력 이미지가 즉시 그려지지 않고 픽셀별로 그려지고 일반적으로 왼쪽에서 오른쪽으로, 위에서 아래로 그려지기 때문입니다. 이 이미지는 여전히 렌더링되는 동안 사용자에게 즉시 표시되지 않기 때문에 결과에 아티팩트가 포함될 수 있습니다. 이러한 문제를 해결하기 위해 창 애플리케이션은 렌더링에 이중 버퍼를 적용합니다. 프론트 버퍼에는 화면에 표시되는 최종 출력 이미지가, 모든 렌더링 명령은 백 버퍼에 그려집니다. 모든 렌더링 명령이 완료되면 백 버퍼를 프론트 버퍼로 교체하여 이미지가 여전히 렌더링되는 동안 표시되지 않도록 하여 앞서 언급한 모든 아티팩트를 제거합니다.

마지막 한 가지
render loop를 종료하는 즉시 할당된 GLFW의 모든 리소스를 적절하게 정리/삭제하고 싶습니다. 메인 함수의 끝에서 호출하는 glfwTerminate 함수를 통해 이 작업을 수행할 수 있습니다.
glfwTerminate();
return 0;
이렇게 하면 모든 리소스가 정리되고 애플리케이션이 올바르게 종료됩니다. 이제 애플리케이션을 컴파일해 보고 모든 것이 잘 진행되면 다음과 같은 출력이 표시됩니다:
매우 둔하고 지루한 검은색 이미지라면 제대로 한 것입니다! 올바른 이미지를 얻지 못했거나 모든 것이 어떻게 맞는지 혼란스러운 경우 전체 소스 코드를 확인하세요(다른 색상이 깜박이기 시작하면 계속 읽으세요).
애플리케이션을 컴파일하는 데 문제가 있는 경우 먼저 모든 링커 옵션이 올바르게 설정되어 있고 IDE에서 올바른 디렉터리를 적절하게 포함했는지 확인하세요(이전 장에서 설명한 대로). 또한 코드가 올바른지 확인하세요. 전체 소스 코드와 비교하여 확인할 수 있습니다.
입력
또한 GLFW에서 어떤 형태의 입력 제어를 원하며 GLFW의 여러 입력 함수로 이를 달성할 수 있습니다. 창과 키를 입력으로 사용하는 GLFW의 glfwGetKey 함수를 사용할 것입니다. 이 함수는 이 키가 현재 눌려져 있는지 여부를 반환합니다. 모든 입력 코드를 구성하기 위해 processInput 함수를 만들고 있습니다:
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
여기서 사용자가 Escape 키를 눌렀는지 확인합니다(눌리지 않은 경우 glfwGetKey는 GLFW_RELEASE를 반환합니다). 사용자가 Escape 키를 눌렀다면 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 함수는 상태 설정 함수이고 glClear는 상태 사용 함수로, 현재 상태를 사용하여 지우기 색상을 검색합니다.
애플리케이션의 전체 소스 코드는 여기에서 찾을 수 있습니다.
다음장은 렌더 루프를 많은 렌더링 호출을 다룹니다.
생각하기
Window창을 만들기 위해서는 첫번째, glfw로 Window의 초기값인 OpenGL 버전, profile를 설정한다.
두번째, glfwCreateWindow(width, height, title, null, null)를 사용하여 Create 해준다.
셋째, glad로 OpenGL 함수 pointer를 load해준다.
넷째, while문 내에서 render loop를 설정한다. render loop내에서 input, window 컬러설정, 이벤트 작용 polling, window graphic swap를 한다.
마지막으로, window가 종료되면 glfwTerminate()로 남은 리소스를 제거하므로서 메모리를 관리한다.
어찌보면 단순하게 색칠된 창화면을 띄우는거지만, 내부에서 특정 절차대로 세밀하게 동작되는것을 배웠네요 !
댓글과 관심은 글쓰는데 큰 응원이됩니다 :)
그럼 좋은 하루되세요.
'컴퓨터 공학 > OpenGL' 카테고리의 다른 글
[최신 OpenGL 시작하기 #1_5] 셰이더 기초 (Shader) (0) | 2025.03.06 |
---|---|
[최신 OpenGL 시작하기 #1_4] 삼각형 만들기 (Hellow Triangle) (2) | 2025.03.04 |
[최신OpenGL 시작하기 #1_2] Window 창 만들기 (개발 환경 설정) (0) | 2025.02.26 |
[최신OpenGL 시작하기#1_1] OpenGL 이란 ? (4) | 2025.02.26 |
[최신OpenGL 소개#0] 소개(Introduction) (2) | 2025.02.25 |