Modern OpenGL로 그래픽스 프로그래밍 환경 구현하기 - 1. 개발 환경 세팅하기 & 창 띄우기
1. 개발 환경 세팅하기 & 창 띄우기
이번 글에서는 윈도우 환경에서 개발 환경을 세팅하는 방법에 대해서 알아보도록 하겠습니다. 이 글에서는 윈도우 10에서 Visual Studio 2019를 사용하여 개발할 예정입니다.
Visual Studio 2019를 설치한 후에, 새로운 프로젝트를 생성합니다. 새 프로젝트 이름은 GraphicsTutorial 이라고 하겠습니다.
![]() |
| 빈 프로젝트 생성 |
![]() |
| GraphicsTutorial 프로젝트 생성 |
생성된 프로젝트를 연 후, main 함수가 들어갈 파일을 main.cpp란 이름으로 생성하겠습니다.
![]() |
main.cpp 파일 생성 |
이제 이 파일에서 코딩을 시작하여 창을 띄워보도록 하겠습니다. 우리가 띄울 창은 OpenGL이 그림을 그릴 창으로, 일반적으로 다른 프로그램을 만들어서 띄우는 cmd 창과는 다릅니다. 그리고 그러한 창을 띄우기 위해서는 다른 라이브러리가 필요합니다.
1-1. 윈도우(window) 라이브러리
여기서 윈도우라 함은, 운영체제 윈도우(Windows)를 말하는 것이 아니라 OpenGL이 결과물을 출력할 창을 일컫습니다. 그리고 윈도우 라이브러리는 그러한 창을 생성할 뿐만 아니라 창에서 발생하는 입출력(I/O) 등을 모두 관리해주는 역할을 합니다. 예를 들어, 우리가 만든 프로그램 창의 어떤 지점을 클릭했을 때, 그 사실을 프로그램에 전달해주는 역할을 해주는 것이 이 윈도우 라이브러리입니다.이 부분이 많이 혼동되는 부분이라, 부연설명을 조금 더 하도록 하겠습니다. OpenGL은 단지 우리가 그릴 그림을 화면에 그려주는 역할만을 합니다. 해당 그림이 그려지는 창은 우리가 다른 라이브러리를 이용하여 직접 생성해야 합니다. 즉, OpenGL이 우리가 명령한 대로 그림을 그려주는 화가라면, 그 화가가 그림을 그릴 캔버스는 우리가 따로 준비해줘야 하는 거죠. 이와 같이 기능을 분리해 놓은 것은 운영체제 별로 모두 창을 다루는 방식이 다르게 구현되어 있기 때문입니다.
이와 같은 목적으로 사용할 수 있는 윈도우 라이브러리는 여러가지가 있습니다. 여기서는 그 중 세 라이브러리를 소개하고자 합니다.
1) GLUT(OpenGL Utility Toolkit) : GLUT은 OpenGL을 위한 윈도우 라이브러리 중 아마(?) 가장 오래된 라이브러리일 것입니다. GLUT은 사용하기가 정말 간단하고 구, 육면체 등 간단한 도형을 쉽게 그릴 수 있게 도와줘서 초심자들이 사용하기에 좋은 라이브러리입니다. 단, GLUT은 개발이 중단된지 굉장히 오랜 시간이 지났으므로 관심있는 분들은 GLUT을 계승한 Freeglut (http://freeglut.sourceforge.net/)을 사용하시면 되겠습니다.
2) GLFW : GLFW는 GLUT과 마찬가지로 OpenGL(+ OpenGL ES, Vulkan)을 위한 윈도우 라이브러리지만, 지금도 계속 업데이트되고 있습니다. 이런 강력한 장점과 더불어 GLFW는 GLUT만큼 사용하기 편리하므로 좋은 선택이 될 것입니다.
3) SDL2(Simple DirectMedia Layer) : SDL은 위의 GLUT과 GLFW 보다는 규모가 큰 라이브러리입니다. 이름에서도 알 수 있듯이, SDL은 OpenGL만을 위한 라이브러리가 아니라 다양한 멀티미디어 환경을 제어하기 위해 만들어졌습니다. 따라서 SDL은 Direct3D 또한 지원하며, 화면뿐만 아니라 사운드 등의 다른 멀티미디어 기능 또한 지원합니다.
본 글에서는 SDL2를 사용할 것입니다. 단, 다른 윈도우 라이브러리를 사용하고자 하는 분들은 창을 다루는 부분의 코드만 본인의 라이브러리에 맞게 작성해주시면 됩니다. 실제 그림을 그리는 OpenGL 코드는 모두 똑같으니까요.
1-2. SDL2.0 임포트 하기
먼저, SDL의 다운로드 페이지(https://www.libsdl.org/download-2.0.php)로 가서 SDL2.0을 다운로드 받도록 합니다.
![]() |
| 소스 코드를 다운받아서 직접 빌드하는 것보다, 이미 빌드된 버전을 다운받도록 합니다. |
이제 GraphicsTutorial 폴더 내에 Dependencies 폴더를 만듭니다. 앞으로 우리가 사용할 외부 라이브러리들을 모두 이 폴더 내에서 관리할 것입니다. 이 폴더에 방금 다운 받은 파일의 압축을 풀어서 가져다 놓습니다.
![]() |
Dependencies 폴더 생성 |
![]() |
SDL 라이브러리를 Dependencies 폴더에 복사합니다. |
이제 다시 프로젝트로 돌아가서 SDL을 프로젝트에 임포트합니다.
1-2-1. 헤더 파일
SDL 라이브러리의 헤더 파일(*.h)은 include 폴더 내에 있습니다. 따라서 다음과 같이 프로젝트 속성에서 C/C++→일반→추가 포함 디렉터리를 수정해줍니다.
![]() |
| SDL 라이브러리 - 헤더 추가 |
1-2-2. 라이브러리 파일
SDL 라이브러리의 정적 라이브러리 파일(*.lib)은 lib 폴더 내에 있습니다. 그런데 정적 라이브러리 파일은 32bit(x86)와 64bit(x64) 별로 따로 컴파일 되어 있으므로, 현재 환경에 맞도록 추가해주도록 합니다. 프로젝트 속성에서 링커→명령줄에서 다음과 같이 명령줄을 추가합니다.
![]() |
| SDL 라이브러리 - 정적 라이브러리 파일 추가 : 32bit(x86) |
![]() |
| SDL 라이브러리 - 정적 라이브러리 파일 추가 : 64bit(x64) |
1-2-3. DLL 파일
SDL 라이브러리의 동적 라이브러리 파일(*.dll)은 정적 라이브러리 파일과 같은 곳에 있습니다. dll 파일은 앞으로 프로젝트가 있는 폴더에 bin이라는 폴더를 만들어서 그 곳에서 관리하도록 하겠습니다. 그러기 위해서 bin 폴더와 32bit, 64bit을 위한 폴더(x86, x64)를 각각 만들도록 하겠습니다. 그리고 SDL 라이브러리의 dll 파일을 32bit, 64bit 별로 맞는 폴더에 복사합니다.
![]() |
| DLL 파일들을 보관할 bin 폴더 생성 |
![]() |
| bin 폴더에 DLL 파일 추가 |
자, 그리고 프로젝트에 동적 라이브러리 파일이 위의 폴더에 있다는 것을 알려주도록 합니다. 이 작업은 다음과 같이 프로젝트 속성의 디버깅→환경 탭에서 설정할 수 있습니다. 32bit는 x86 폴더로, 64bit는 x64 폴더로 PATH를 설정합니다.
![]() | |
|
![]() |
| SDL 라이브러리 - 동적 라이브러리 파일 추가 : 64bit(x64) |
1-3. OpenGL 임포트 하기
코드를 작성하기 전 마지막 관문이 남았습니다. SDL을 임포트했으니, OpenGL을 임포트하여 SDL이 만드는 창 위에 그림을 그려야 합니다. 비주얼 스튜디오를 정상적으로 설치하셨다면, OpenGL을 따로 다운로드 받을 필요는 없습니다. 대신, 이미 설치된 OpenGL 라이브러리를 링크시켜줘야 합니다. 이는 간단하게 다음과 같이 프로젝트 속성에서 링커→입력→추가 종속성에 opengl32.lib를 추가시켜주면 됩니다.![]() |
| OpenGL 라이브러리 링크 |
1-4. 창 띄우기
자, 이제 main.cpp에 코드를 작성하여 창을 띄워봅시다. 다음과 같이 코드를 작성합니다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | #include <SDL.h> #undef main #include <SDL_opengl.h> // SDL 헤더를 include 합니다. #include <functional> #include <iostream> std::function<void()> mainLoop; // 프로그램이 끝날 때까지 매 프레임마다 호출하는 함수입니다. // 이 함수 안에서 이번 프레임에 그릴 화면을 결정합니다. int wndWidth = 640, wndHeight = 480; // 우리가 만들 창(Window)의 너비(wndWidth)와 높이(wndHeight) 입니다. void resizeCallback(int width, int height); // 창의 크기가 변할 때마다 호출되는 함수입니다. void windowCallback(const SDL_Event& event, bool& done); // 창에 어떤 이벤트(event)가 생길 때 호출되는 함수입니다. // @done : 이벤트에 따라 프로그램의 종료 여부를 결정하고, 종료한다면 true로 설정합니다. int main() { // SDL을 초기화합니다. 우리는 SDL을 VIDEO만을 위해서 사용하므로 SDL_INIT_VIDEO만 인수로 전달합니다. // 자세한 사항은 https://wiki.libsdl.org/SDL_Init 를 참조하세요. if (SDL_Init(SDL_INIT_VIDEO) < 0) { // 만약 SDL이 정상적으로 초기화되지 않았다면 오류 메시지를 출력합니다. std::cerr << "[ SDL_Init ] failed : %s" << SDL_GetError() << std::endl; exit(-1); } // 사용할 OpenGL 버전을 선택합니다. 우리는 OpenGL 3.3을 사용하므로 그에 맞게 지정합니다. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); // SDL을 통해 창을 실제로 생성합니다. // 이 때, 인자로 창의 위치와 크기 등을 설정해줄 수 있습니다. // 자세한 사항은 https://wiki.libsdl.org/SDL_CreateWindow 를 참조하세요. SDL_Window* window = SDL_CreateWindow( "Viewer", // 창의 이름입니다. SDL_WINDOWPOS_CENTERED, // 창의 X 좌표입니다. 스크린의 중앙에 위치하도록 합니다. SDL_WINDOWPOS_CENTERED, // 창의 Y 좌표입니다. 스크린의 중앙에 위치하도록 합니다. wndWidth, wndHeight, // 창의 크기입니다. SDL_WINDOW_OPENGL); // 옵션들을 지정합니다. OpenGL을 사용하여 그릴 수 있도록 SDL_WINDOW_OPENGL 옵션을 줍니다. SDL_SetWindowResizable(window, SDL_TRUE); // 창이 생성된 이후에 창의 크기를 재조정할 수 있도록 합니다. // 방금 생성한 창(window)에 OpenGL을 사용하여 그리기 위해 OpenGL Context를 생성합니다. SDL_GLContext glc = SDL_GL_CreateContext(window); if (glc == nullptr) { // 만약 OpenGL Context가 생성되지 않았다면, 오류 메시지를 출력합니다. std::cerr << "[ SDL_GL_CreateContext ] failed : %s" << SDL_GetError() << std::endl; exit(-1); } // 방금 생성한 창(window)에 2D 화면을 그리기 위해 렌더러(Renderer)를 생성합니다. SDL_Renderer* rdr = SDL_CreateRenderer(window, -1, 0); if (rdr == nullptr) { // 만약 렌더러가 생성되지 않았다면, 오류 메시지를 출력합니다. std::cerr << "[ SDL_CreateRenderer ] failed : %s" << SDL_GetError() << std::endl; exit(-1); } bool done = false; // 이 변수가 false인 한, 프로그램을 계속합니다. 만약 true가 된다면, 프로그램을 종료합니다. // 위에서 선언한 mainLoop 함수를 정의합니다. mainLoop = [&] { SDL_Event event; // SDL에서 관리하는 이벤트(ex. 마우스 클릭, 키보드 입력)에 관한 정보를 담고 있습니다. // SDL에서 관리하는 이벤트 중 이번 프레임에 발생한 이벤트 정보를 가져옵니다. // 각 이벤트마다 이벤트의 종류에 따라 어떻게 처리할 지 결정해줘야 합니다. while (SDL_PollEvent(&event)) { // 창과 관련된 이벤트는 [ windowCallback ] 함수에서 처리합니다. windowCallback(event, done); } // OpenGL을 이용하여 화면을 그립니다. // 지금은 아무것도 그리지 말고 화면 전체를 까맣게 색칠하도록 합니다. glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 화면 전체를 어떤 색으로 칠할 것인지 결정합니다. glClear(GL_COLOR_BUFFER_BIT); // 화면의 색 버퍼(Color buffer)를 모두 비우고, 위에서 정한 색으로 다시 칠합니다. // OpenGL의 버퍼를 교체합니다. SDL은 디폴트로 OpenGL의 더블 버퍼링을 지원합니다. SDL_GL_SwapWindow(window); }; // [ done ] 변수가 true로 변할 때까지 mainLoop 함수를 호출합니다. while (!done) mainLoop(); // 프로그램이 끝나서 루프에서 빠져나왔으므로, OpenGL Context와 SDL 창을 제거합니다. SDL_GL_DeleteContext(glc); SDL_DestroyWindow(window); // SDL을 종료합니다. SDL_Quit(); return 0; } void resizeCallback(int width, int height) { // 새로운 창의 크기로 [ wndWidth ] 와 [ wndHeight ] 를 업데이트합니다. wndWidth = width; wndHeight = height; // OpenGL의 뷰포트를 새로운 창의 크기로 재조정합니다. glViewport(0, 0, wndWidth, wndHeight); } void windowCallback(const SDL_Event& e, bool& done) { // 이벤트가 창과 관련된 이벤트일 경우 처리합니다. if (e.type == SDL_WINDOWEVENT) { // 만약 창을 닫을 경우, [ done ] 변수를 true로 만들어줍니다. if (e.window.event == SDL_WINDOWEVENT_CLOSE) done = true; // 만약 창의 크기를 변경할 경우, [ resizeCallback ] 함수를 호출합니다. else if (e.window.event == SDL_WINDOWEVENT_RESIZED) resizeCallback(e.window.data1, e.window.data2); } } | cs |
위와 같이 코드를 작성하고 프로젝트를 빌드하여 실행하면, 다음과 같은 창이 뜨는 것을 확인할 수 있습니다.
![]() |
| 실행 결과 |
















댓글
댓글 쓰기