[OpenGL Tutorial] Displaying Points, Lines, Polygons

사용자 삽입 이미지OpenGL에서 화면상에 점과 선, 그리고 면을 그리는 것은 아주 쉽다. 그려주는 핵심적인 코드를 살펴보자. 먼저 코드를 살펴보기 전에 알고 있어야 할 것은 OpenGL의 좌표체계는 3차원 직각좌표를 기본으로 하고 있으며 왼쪽에서 오른쪽으로 그 X좌표의 값이 증가하며 아래에서 위쪽으로 Y좌표의 값이 증가한다. 그리고 모니터 화면의 바깓쪽으로 Z값이 증가한다는 점이다. 또한 폴리곤은 한마디로 안이 채워진 도형이며 면(Face)이라는 것이다


화면에 점을 그려주는 예

glBegin(GL_POINTS);
glVertex3f(0.0f, 0.0f, 0.0f);
glEnd();

화면에 선을 2개 그려주는 예

glBegin(GL_LINES);
glVertex3f(-1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();

폴리곤을 그려주는 예

glBegin(GL_POLYGON);
glVertex3f(-1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();

눈치가 빠른 독자라면 위에서 보인 세가지의 코드가 공통점이 있다는 것을 파악했을 것이다. 바로 glBegin과 glEnd라는 구문 안에 각 점들을 기술해줬다는 것이다. 점들을 기술하는 방법은 우리가 이미 알고 있는 3차원 직교 자표계로 쉽게 생각할 수 있다. 세번째인 폴리곤을 그려주는 예에서 4개의 점 좌표를 기술해 줬는데 종이를 준비해서 각 점을 종이에 순서대로 찍어보고 그 점을 순서 이어보자. 사각형이 그려질 것이다. 폴리곤이므로 처음 좌표와 마지막 좌표가 이어지게 되고 안은 지정된 색으로 채워지게 된다. 바로 이것이 좌표를 기술해 주는 방식이다. 간단하다.

그렇다면 이제 점을 그릴 것인지 선을 그릴것인지, 아니면 폴리곤을 그릴것인지 어떤 식으로 기술할 수 있는가. 역시 눈치 빠른 독자는 이미 그 방법을 위의 코드를 통해서 알것이다. 그렇다. 바로 glBegin이라는 함수를 통해 그 인자로 지시를 해주는 것이다. GL_POINTS는 점, GL_LINES는 선, GL_POLYGON은 폴리곤을 그려라는 의미이다. 이쯤이라면 위에서 제시한 3가지 코드에 대한 이해를 완벽하게 했으리라 믿어 의심치 않는다. 그래도 모르겠다면 필자에게 왜 이렇게 설명을 못하냐는 멜과 함께 뭐가 이해가 가지 않는지 멜을 가볍게 한통 던져주길 바란다.


여기서 끝마치면 너무 아쉽고 서운하다. 이 코드를 실제적으로 실습해봐야 할것이고 glBegin에 오는 인자가 더 있지나 않나하는 호기심을 품어야함이 옳다. 먼저 실습하는 방법에 대해서 알아보자. 필자는 이미 OpenGL을 초기화하는 과정을 튜어리얼 1번을 통해서 아주 길게 설명했다. 이 2번 튜어리얼은 1번에서 했던 코드를 그대로 사용한다. 먼저 1번 튜어리얼에서제작한 소스 코드를 살펴보면 DrawGLScane라는 함수가 있다. 바로 이 함수 안에서 그려주는 코드를 삽입하면 되겠다. 폴리곤을 그려주는 것으로 예를 들면 다음과 같다.

int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
    // Clear Screen And Depth    Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
    glLoadIdentity(); // Reset The Current Modelview Matrix
    glTranslatef(0.0f,0.0f,-10.0f);   // 추가된 코드 1
    glColor3f(1.0f, 1.0f, 1.0f);      // 추가된 코드 2
    glBegin(GL_POLYGON);              // 추가된 코드
    glVertex3f(-1.0f, -1.0f, 0.0f);   // 추가된 코드
    glVertex3f(1.0f, -1.0f, 0.0f);    // 추가된 코드
    glVertex3f(1.0f, 1.0f, 0.0f);     // 추가된 코드
    glVertex3f(-1.0f, 1.0f, 0.0f);    // 추가된 코드
    glEnd();                          // 추가된 코드
    return TRUE; // Everything Went OK
} 

추가된 코드만을 코딩한후에 실행해 보자. 썰렁한 하얀 정사각형이 화면에 그려질 것이다. 하지만 우리가 그리고자 했던 것임은 틀림없다. 아닌가??

사용자 삽입 이미지

위의 코드를 더 살펴보자. 추가된 코드1과 2가 있는데 이것을 그냥 넘겨버리자니 이미 황폐해져 버린 필자의 마지막 남은 양심을 지킬 수가 없고 해서 설명해야겠다. 먼저 OpenGL은 처음에 초기화 과정에서 원점(0, 0, 0)을 화면의 정중앙에 맞추게 된다. 이 원점을 중심으로 OpenGL에서 그려지는 모든 것들을 바라보게 된다. 중요한 것이 바로 여기에 있다. 바로 원점에서 우리가 그린 사각형을 바라봐야 하는데 눈과 사각형이 겹치게 된다. 상상해 보라 독자의 눈과 책이 만날정도로 부착시키면 독자는 무었을 보게 되는가… 그렇다! 아무것도 보이지 않는다. 그래서 추가코드1이 필요한 것이다. 즉 물체를 눈의 위치보다 앞이나 뒤로 옮겨야 볼수 있는 것이다. 추가 코드1인 glTranslatef(0.0f, 0.0f, -10.0f)가 바로 그러한 일을 하는데 물체를 Z축으로 10.0f만큼 뒤로(-) 옮겨 그리라는 것이다. 이제 추가 코드2번에 대해서 살펴보도록 하자. 함수 이름에서 직감할 수 있듯이 그려줄 것에 대한 색을 지정하는 것이다. glColor3f는 세개의 인자를 갖는데 각각의 인자는 Red, Green, Blue 값이며 셋모두 하한값과 상한값이 0~1 사이를 갖는다. 이 정도면 나머지 것들도 실습하는데 별 어려움이 없을 것이다. 하지만 그 어떤 아쉬움이 남지 않는가….. 하지만 일단 그 아쉬움에 대해 후일을 기약하자.



이제 glBegin에 올수 있는 인자에 대해서 알아보도록 하자. 아래 그림으로 한방에 해결하고 마친다.

[OpenGL Tutorial] OpenGL 초기화

사용자 삽입 이미지OpenGL의 초기화와 관련된 내용은 즉, 기본 뼈대가 될 소스는 http://nehe.gamedev.net 에서 얻은 몇 명의 프로그래머들에 의해 다듬어진 초기화 프로그램을 이용하겠습니다. 먼저 잠시 위에서 언급한 사이트는 OpenGL 프로그래머라면 반드시 방문해 봐야할 곳인 듯 싶습니다. 특히 지금까지 제시된 총 24개의 튜어리얼(계속 추가되어지고 있습니다)은 반드시 읽어보고 직접 코딩을 해봐야 할 정도로 많은 것을 배울 수 있습니다. 모든 튜어리얼이 영어로 되어 있고 영어에 서툰 분들을 위해 그 튜어리얼중 1번을 원문 그대로 번역하는 것으로 이 장을 구성하도록 하겠습니다. 아래부터는 번역입니다.

나의 OpenGL 튜어리얼에 온 것을 환영한다. 나는 OpenGL에 대한 열정을 가지고 있는 평범한 사람이다. 내가 처음 OpenGL에 대해서 알게된 때는 3Dfx가 부두1를 위한 OpenGL 가속 드라이버를 배포할 때였다. 바로 나는 OpenGL 꼭 배워야 할 것이라는 것을 느꼈다. 불행이도 OpenGL은 책에서도 또한 인터넷을 통해서도 정보를 얻기 어려웠다. 나는 몇시간 동안 OpenGL에 대한 코드를 작성했고 IRC와 email을 통해서 많은 사람들에게 도움을 요청했다. 그 사람들은 자신이 OpenGL에 대해 엘리트라고 자처했던 사람들이었으나 자신이 알고 있는 지식을 공유하는데는 별 관심이 없었고 나의 수고는 물거품이 되었다.

나는 OpenGL을 배우려는데 관심이 있는 사람들을 위해서 혹 그들이 도움이 필요할 때 방문할 수 있도록 이 웹사이트를 만들었다. 나는 나의 모든 각각의 튜어리얼을 인간적으로 가능한 자세하게 각 라인별로 무엇을 하는지를 설명하려고 노력할 것이다. 나는 VC++의 MFC코드는 사용하지 않고 나의 코드를 이해하기 쉽도록 간단하도록 할 것이다. 나의 사이트는 OpenGL의 튜어리얼을 제공하는 많은 사이트중의 하나일 뿐이다. 만약 당신이 OpenGL의 Hardcore 프로그래머이라면 나의 튜어리얼이 너무 단순하다고 생각할지도 모른다. 하지만 나는 나의 사이트가 많은 것을 제공한다고 생각한다. 이 튜어리얼은 2000년 1월에 완전히 새롭게 다시 작성되었다. 이 튜어리얼은 OpenGL 윈도우를 셉업하는 방법을 제시할 한다. OpenGL 윈도우는 당신이 원하는 해상도와 칼라 비트값을 가진 전체화면으로 실행될 수 있다. 이 코드는 당신의 모든 OpenGL 프로젝트를 적용할 수 있도록 아주 유연하게 다듬어 졌다. 나의 모든 튜어리얼은 이 코드를 토대로 작성되었다. 나는 이 코드를 유연하게 작성했고 동시에 강력하게 작성했다. 모든 에러는 보고되어져 제거되었고 메모리 누수 또한 없다. 그리고 코드는 읽기 쉬우며 수정하기도 쉽다.나는 Fredric Echols에게 이 코드를 수정해 준 것에 대해서 감사의 말을 전한다.

자! 이 튜어리얼을 시작해보자!!

VC++을 시작하고 새로운 Win32 Application을 생성한다. (Console Application이 아니다). OpenGL 라이브러리를 링크시킬 필요가 있다. VC++의 Project 메뉴로 가서 Settings의 서브 메뉴로 다시 클릭한다. 나타나는 윈도우에서 Link Tab을 클릭하고 “Object/Libary Modules”에서 다음 라인을 추가한다.

OpenGL32.lib; GLu32.lib; GLaux.lib;

여기서 다시 OK버튼을 클릭하면 OpenGL 프로그래밍 준비가 된 것이다.

먼저 Include Header에 다음 4개의 라인을 추가한다. 그 라인은 아래와 같다.

#include  // Header File For Windows 
#include  // Header File For The OpenGL32 Library 
#include  // Header File For The GLu32 Library 
#include  // Header File For The GLaux Library

다음에 이 프로그램에서 사용할 변수을 정의하자. 이 프로그램은 아무것도 그리지 않는 검은색의 OpenGL 윈도우를 만들 것이므로 아직은 많은 변수를 정의할 필요는 없다. 우리가 정의할 몇 개의 변수는 아주 중요하며 모든 OpenGL 프로그램의 초기화에서 반드시 사용해야하는 것들이다.

먼저 정의할 변수는 Rendering Context를 위한 것이다. 모든 OpenGL 프로그램은 모두 Rendering Context에 연결되어 있다. OpenGL에서의 랜더링 컨텍스트는 디바이스 컨텍스트라고 불린다. hRC가 랜더링 컨텍스트를 정의하는 변수이다. 우리의 프로그램이 윈도우창에 그림을 그리기 위해 디바이스 컨텍스트를 만들 필요가 있다. 윈도우즈 디바이스 컨텍스트를 정의하기 위한 변수가 hDC이다. DC(Device Context)는 윈도우즈의 GDI(Graphics Deive Interface)와 연결되어있다. RC(Rendering Context)는 다시 DC와 OpenGL 매커니즘을 통해서 연결되어 있다.

새번째 줄에 hWnd라는 변수가 있는데 이것은 윈도우즈(OS)에 의해서 우리의 윈도우즈와 연관된 핸들을 가지고 잇다. 마지막 네 번째 라인의 변수는 프로그램의 인스턴스의 헨들이다.

HGLRC hRC=NULL; // Permanent Rendering Context
HDC hDC=NULL; // Private GDI Device Context
HWND hWnd=NULL; // Holds Our Window Handle
HINSTANCE hInstance; // Holds The Instance Of The Application

다음에 정의할 변수들에 대해서 알아보자. 먼저 첫 번째 것은 키보드의 키입력에 대한 정보를 얻기 위해 정의한 변수이다. 키보드의 정보를 얻는 방법은 많이 있지만 내가 선택한 방법은 이것이다. 이것은 믿을 만하며 동시에 하나의 다중 키 입력 정보도 얻어 올 수 있다. active 변수는 프로그램에게 윈도우가 최소화 되었는지 아닌지를 판별하는데 사용된다. 만약 최소화 되었을 경우 우리의 프로그램은 아무런 일도 하지 않아야 할 목적으로 정의한 변수이다. fullstreen 변수는 아주 분명한데 만약 프로그램이 전체화면이면 True 값을 가지고 윈도우 모드로 실행되면 False 값을 가진다. 이것은 전체적으로도 중요하게 참조되는데 각 프로시져에서 전체화면인지 아닌지를 판별할 때 사용되기 때문이다.

bool keys[256]; // Array Used For The Keyboard Routine
bool active=TRUE; // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default

지금 우리는 WndProc()를 정의해야 한다. 우리가 이 일을 해야하는 이유는 CreateGLWindow()가 WndProc()를 참조하기 때문이다. 그러나 WndProc()는 CreateGLWindow()이후에 나온다. C언어에서 만약 우리가 보다 이후에 나오는 함수를 그 이전의 함수가 사용한다면 프로그램의 맨 윗부분에서 사용할 함수의 원형을 기술해야 한다. 그래서 우리는 다음 라인에 CreateGLWindow()의 WndProc()를 참조할 수 있도록 WndProc()의 선언한다.

LRESULTCALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc 

다음에 우리가 해야할 작업은 OpenGL 윈도우의 크기가 재조정될 경우이다. 물론 전체창 모드가 아닌 윈도우 창 모드의 경우에 해당되는 것이다. 전체창 모드에서 윈도우의 크기는 이미 정해져 있기 때문이다. 그러나 우리가 전체창 모드로 실행될지라도 다음의 루틴은 최소한 한번은 호출된다. OpenGL 창은 그림이 그려질 윈도우의 폭과 너비를 기초로 재조정된다.

GLvoid ReSizeGLScene(GLsizei width, GLsizei height) 
// Resize And Initialize The GL Window
{
    if (height==0) // Prevent A Divide By Zero By
    {
        height=1; // Making Height Equal One
    }
    glViewport(0, 0, width, height); // Reset The Current Viewport

다음 이어지는 코드는 Perspective View를 위해 화면을 설정하는 것이다. 이것의 의미는 거리에 따라 더 작게 나타난다는데 있다. (역자주: 즉 원근감이 나타난다) 퍼스펙티브는 윈도우의 폭과 너비에 기초한 45도 각도의 화각을 가지고 계산된다. 0.1f와 100.0f는 우리가 얼마의 깊이로 물체를 그릴지를 의미하는 시작점과 끝점이다.

glMatrixModel(GL_PROJECTION)은 다음에 이어지는 두라인의 코드와 연관되어 Projection Matrix에 영향을 주게 된다. Perspective Matrix는 Perspective View한 장면을 영향을 미치는 매트릭스 체계이다. (역자주: Matrix는 수학에서 행렬인데 실제로 OpenGL은 대부분의 연상을 행렬로 4X4로 처리한다.) glLoadIdentity()는 Matirx를 초기화시킨다. (역자주: 행렬을 단위행렬로 만든다) 이것은 선택한 매트릭스를 원래의 상태로 돌리는 것이다. (역자주: 원래의 상태가 바로 단위행렬 상태이다) glLoadIdentity()를 호출한 다음 우리는 장면연출을 위해 Perspective View를 설정한다. glMatrixMode(GL_MODELVIEW)는 Modelview Matrix에 영향을 미칠 계수가 있다고 알린다. Modelview matrix는 물체의 정보가 저장될 곳이다. (역자주: 물체의 정보라 함은 3차원적 위치, 3차원적 크기, 그리고 3차원적 회전각을 의미하는데 이는 모두 행렬을 통해 이루어진다. 이 행렬 4X4 행렬인데 이 행렬값이 실제적인 물체의 정보이다) 마지막으로 우리는 Modelview Matirx를 초기화할 것이다. 만약 이 부분을 이해하지 못한다고 해서 걱정하지 말기바란다. 나는 이후 모든 튜어리얼에서 설명할 것이다. 단지 멋진 장면(퍼스펙티브한 장면)을 연출하기 위한 준비과정이라고 생각하기 바란다. (역자주: 수학적인 지식이 필요한 부분이다)

    glMatrixMode(GL_PROJECTION); // Select The Projection Matrix 
    glLoadIdentity(); // Reset The Projection Matrix 
    // Calculate The Aspect Ratio Of The Window
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);  
    glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix 
    glLoadIdentity(); // Reset The Modelview Matrix 
}

다음에 우리는 OpenGL의 초기화의 모든 부분을 다룬다. 화면을 어떤 색으로 지울 것인지 깊이 버퍼를 설정할 것인지 부드러운 Shading으로 할 것인지 등등을 설정한다. 윈도우 창이 생성될 때까지는 다음의 함수는 호출되지 않을 것이다. 이 함수는 반환값을 가진다. 그러니 우리의 초기화가 그렇게 복잡하지 않기 때문에 지금으로써는 그 반환값에 대해 크게 염두해두지 않아도 된다.

int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{

다음 라인은 부드러운(Smooth) shading을 처리하도록 하한다. 부드러운 쉐이딩은 물체의 표면을 부드럽게 처리하여 그려준다. 나는 다른 튜어리얼에서 다시 이 부드러운 쉐이딩에 대해서 설명할 것이다.

glShadeModel(GL_SMOOTH);// Enables Smooth Shading 

다음에 오는 라인은 윈도우 창을 지울 때 사용하는 색을 선택하는 것이다. 색의 값은 0.0에서 1.0의 값을 가진다. 0.0은 가장 어두운 값이고 1.0은 가장 밝은 값이다. glClearColor의 첫 번째 인자는 Red의 값을 그리고 두 번째 인자는 Green의 값을 세 번째 인자는 Blue의 값을 가진다. 가장 높은 값은 1.0이고 가장 밝다는 것을 의미한다. 마지막 인자는 Alpha 값이다. 화면을 지울 때 이 인자의 역할에 대해서 생각할 필요는 없다. 단지 0.0의 값으로 설정하자. 이것에 대한 자세한 내용은 다른 튜어리얼에서 설명한다.

우리는 세 개의 주요 인자(RGB)를 서로 섞어서 여러 가지 색을 만든다. 예를 들어서 glClearColor(0.0f, 0.0f, 1.0f, 0.0f)는 순수한 Blue의 색으로 화면을 지울 색을 선택하는 것이다. 또 예를 들어서 glClearColor(0.5f, 0.0f, 0.0f, 0.0f)일 경우 중간 정도의 Red의 색을 선택하는 것이다. 만약 하얀색의 바탕화면을 얻길 원한다면 세 개의 인자값을 모두 1.0f로 설정해주면 된다. 마찬가지로 검색의 바탕화면을 얻길 원한다면 모든 인자를 0.0f로 설정하면 된다.

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// Black Background

다은 세 개의 라인은 Depth Buffer와 관련된 것이다. (역자주: 깊이 버퍼에 대해서 쉽게 설명해보자. 만약 물체가 두 개 있다고 해보자 하나는 다른 하나의 뒤에 있다. 이때 깊이 버퍼를 설정하지 않는다면 무엇이 앞에 있고 또 뒤에 있는지 모른다. 하지만 깊이 버퍼가 설정되면 분명하게 앞과 뒤가 구분되어 표시된다.) 깊이 버퍼는 OpenGL에서 매우 중요한 부분중의 하나이다.

glClearDepth(1.0f); // Depth Buffer Setup 
glEnable(GL_DEPTH_TEST); // Enables Depth Testing 
glDepthFunc(GL_LEQUAL); // The Type Of Depth Test To Do 

다음에 우리는 OpenGL에게 우리가 가장 멋진 퍼스펙티브한 화면 요청한다. 그러면 OpenGL은 이것에 좀더 많은 초점을 두어 장면을 만들어낸다.

// Really Nice Perspective    Calculations 
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

최종적으로 우리는 True를 반환한다. 만약 우리가 초기화가 정상적인지 아닌지를 알려면 이 반환값을 알아보면 된다. (역자주: 현제로써는 이렇다할 실패할 만한 초기화 과정이 없으므로 반환값은 항상 참이다.)

    return TRUE;// Initialization Went OK
} 

다음 부분은 물체를 그리는 코드의 모든 코드가 위치할 곳이다. 여러분이 표시하기를 원하는 것의 모든 코드가 여기에 위치할 것이다. 이 튜어리얼의 나중에 나오는 모든 튜어리얼들은 이 부분에 코드가 더해질 것이다. 만약 여러분이 이미 OpenGL에 대해서 알고 있다면 이 부분에 간단한 Shape(도형) 정도는 그려넣을 수 있을 것이다. glLoadIdentity()의 아래 코드와 return TRUE 코드 사이에 그려주는 코드가 위치한다. 만약 여러분이 OpenGL에 처음이라면 다음 뉴어리얼을 기다리기 바란다. (역자주: 실제로 역자의 프로젝트들도 이 부분을 눈여겨 보아야될 곳이다) 지금 우리가 여기서 필요한 코드는 화면을 이전에 선택한 색으로 지우고 깊이 버퍼(depth buffer)을 지우는 것이다. 아직 우리는 여기에 아무것도 그리지 않는다.

반환값을 True로 설정함으로써 우리의 프로그램이 아무런 문제 없이 이 함수 루틴을 실행했음을 알린다. 만약 어떤 이유로 프로그램이 멈추기를 바란다면 False값을 리턴함으로써 그리기가 실패했다고 프로그램에게 알린다. 그러면 결과적으로 프로그램은 종료될 것이다.

int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
    // Clear The Screen And The Depth Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity(); // Reset The Current Modelview Matrix
    return TRUE; // Everything Went OK
}

다음은 프로그램이 종료되기 바로 전에 호출되는 함수이다. KillGLWindow()의 역할은 RC(Rendering Context)를 해제하고 DC와 윈도우즈 헨들의 자원을 윈도우즈에게 반환한다. 필자는 여기에 많은 에러 검사 류틴을 사용하였다. 만약 프로그램의 종료에 있어서 어떠한 지장이 있다면 에러 메시지 박스가 나타날 것이며 무엇이 실패했는지를 알려줄 것이다. 문제를 쉽게 찾기 위해 코드를 간단하게 하라.

GLvoid KillGLWindow(GLvoid) // Properly Kill The Window
{

일반 윈도우 창이 아닌 전체화면일 경우 본래의 디스플레이 셋팅으로 복귀시켜야 하는데 아래의 루틴이 그러한 일을 한다. 우리는 ChangeDisplaySettings(NULL, 0)을 원래의 Desktop으로 복귀하기 위해 사용하였다. 몇 번째 NULL인자와 두 번째 0의 값의 인자는 윈도우즈가 레지스트리에 저정되어있는 디스플레이의 해상도와 색상 비트값으로 본래의 Desktop의 값으로 되돌리도록 지시한다. 그리고 다시 감추었던 커서(역자주: 마우스 포인터)를 나타나게 한다.

if (fullscreen) // Are We In Fullscreen Mode? 
{ 
    ChangeDisplaySettings(NULL,0); // If So Switch Back To The Desktop
    ShowCursor(TRUE); // Show Mouse Pointer
}

다음 아래의 코드는 프로그램이 Rendering Context(hRC)를 가지고 있는지를 검사한다. 만약 가지고 있지 않다면 다음 블록의 코드로 점프하게 되는데 다시 이곳에서는 프로그램이 Device Context를 가지고 있는지 검사한다.

if (hRC) // Do We Have A Rendering Context? 
{ 

마약 렌더링 컨테스트를 가지고 있다면 아래의 코드는 이 RC를 해제할 수 있는지를 검사한다. (hDC로부터 hRC가 연결되어져 생성되어졌다) 에러를 검사하는 코드를 주목하기 바란다. 나는 기본적으로 우리의 포로그램이 RC를 해제하도록 지시하였다(wglMakeCurrent(NULL, NULL)을 통해서) 그런후에 나는 이것이 성공적으로 해제되었는지 비성공적이었는지를 검사한다.

if (!wglMakeCurrent(NULL,NULL)) // Are We Able To Release The DC And RC Contexts?
{

혹 DC와 RC가 헤제되지 못한다면 MessageBox()로 해제될 수 없음을 알린다.

    MessageBox(NULL, "Release Of DC And RC Failed.", "SHUTDOWN ERROR",
        MB_OK | MB_ICONINFORMATION);    
} // 

다음 우리는 RC를 완전이 지워 버린다. 만약 그렇게 할 수 없다면 에러메시지를 출력한다.

if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC? 
{ 

만약 우리가 RC를 지울 수 없다면 에러 메시지 박스를 띠워 RC의 삭제가 실패했음을 알린다. 실패했건 성공했건 hRC는 NULL값을 갖게 한다.

    MessageBox(NULL, "Release Rendering Context Failed.", "SHUTDOWN ERROR",
                MB_OK | MB_ICONINFORMATION);     
} 
hRC=NULL; // Set RC To NULL 

이제 프로그램이 DC를 가지고 있는지를 검사하고 그렇다면 이것을 메모리에서 해제할 것이다. 이것을 해제하지 못한다면 에러메세지를 띠우고 무조건 hDC의 값을 NULL로 만든다.

if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC     
{
    MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR", 
        MB_OK | MB_ICONINFORMATION); 
    hDC=NULL; // Set DC To NULL 
} 

다시 프로그램이 윈도우 헨들을 가지고 있는지를 검사하고 그렇다면 DestoryWindow(hWnd)를 사용하여 해제할 것이다. 만약 이것이 실패한다면 에러메세지를 띠우고 모조건 hWnd를 NULL로 만든다.

if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window? 
{
    MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR", 
        MB_OK | MB_ICONINFORMATION); 
    hWnd=NULL; // Set hWnd To NULL     
} 

이 함수의 마지막으로 해야할 일은 윈도우즈 클래스 등록을 해제해야 한다.

if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class 
{
    MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR", 
        MB_OK || MB_ICONINFORMATION); 
    hInstance=NULL; // Set hInstance To NULL 
}

다음에 알아볼 내용은 OpenGL 윈도우를 생성하는 것이다. 필자는 많은 코드가 필요없는 전체화면모드와 많은 코드가 필요한 윈도우창 모드를 생성하는 코드들에 대해 많은 시간을 투자했다. 이 부분은 OpenGL의 전체화면 모드로 실행하는 방법, 윈도우 창 모드로 실행하는 방법, 윈도우의 타이틀을 변경하는 방법, 픽셀 포맷과 해상도를 변경하는 방법등에 대한 많은 것들을 소개할 것이다. 다음에 오는 코드들이 이 모든 것을 설명해줄 것이다.

다음에 설명하는 함수는 Boolean 값을 리턴하며 5개의 인자를 필요로 한다. 그 인자들은 title(윈도우의 제목), width(윈도우의 가로길이), height(윈도우의 세로길이), bits(16/24/32), 그리고 마지막으로 fullscreenflag(전체창 모드인지 아닌지)이다. 반환값이 True일 경우에 성공적으로 OpenGL 창을 생성한 것이고 False인 경우는 OpenGL 창의 생성을 실패한 것이다.

BOOL CreateGLWindow(char* title, int width, int height, int bits, 
    bool fullscreenflag)
{

우리는 윈도우즈에게 우리가 원하는 픽셀 포맷에 가장 근접한 것들 찾아 알려달라고 요청한다. 이 픽셀 포맷은 PixelFormat 변수에 저장되어진다. (역자주: 픽셀 포맷은 DC를 RC로 해석하기 위한 것이다)

GLuintPixelFormat;// Holds The Results After Searching For A Match

wc 변수는 윈도우 클래스 구조체를 위해 사용될 것이다. 윈도우 클래스 구조체는 윈도우 창에 대한 정보를 가지고 있다. 이 클래스의 여러 가지 요소를 변경함으로써 우리는 윈도우가 어떻게 보이고 또 어떤 행동을 할 것인지를 결정할 수 있다.

WNDCLASSwc;// Windows Class Structure 

dwExStyle와 dwStyle는 윈도우 창의 확장 스타일과 일반 스타일 정보가 저장된다.

DWORD dwExStyle; // Window Extended Style
DWORD dwStyle; // Window Style

다음에 오는 다섯 라인의 코드는 사각형의 좌상단과 좌하단의 위치값을 잡는다. 우리는 이러한 변수들을 윈도우즈창을 조정하는데 사용할 것이다.

RECT WindowRect; // Grabs Rectangle Upper Left / Lower Right Values
WindowRect.left=(long)0; // Set Left Value To 0
WindowRect.right=(long)width; // Set Right Value To Requested Width
WindowRect.top=(long)0; // Set Top Value To 0
WindowRect.bottom=(long)height; // Set Bottom Value To Requested Height

우리가 전역변수로 선언한 fullscreen 변수를 fullscreenflag의 값과 같게 만드는 코드가 다음에 온다. 그래서 만약 우리가 전체창 모드로 실행한다면 fullscreenflag는 True값을 가진다. 반명에 윈도우즈창 모드로 실행한다면 False 값을 가진다.

fullscreen=fullscreenflag;// Set The Global Fullscreen Flag

다음에 볼 코드들에서 우리는 윈도우의 인스턴스를 얻어야한다 그래서 우리는 윈도우 클래스를 정의할 것이다. CS_HREDRAW와 CS_VREDRAW의 스타일 옵션은 윈도우창에게 창의 크기가 재조정되었다면 모조건 다시 그리라는 것을 지정한다. CS_OWNDC는 창만의 DC를 생성하라는 것이다. DC의 의미는 어플리케이션간에 공유하는 것이 아니다. WndProc는 우리의 프로그램에서 윈도우 메시지를 처리할 프로시져이다. No Extra Window Data를 위한 2개의 필드를 0으로 만들고 윈도우의 인스턴트를 설정한다. 우리가 틀별이 이 프로그램을 위한 아이콘을 준비하지 않았음으로 hIcon에 NULL값을 갖게 한다. 그리고 우리가 사용할 마우스 포인터는 표준 화살표 포인터를 사용한다. 배경색은 OpenGL에서는 의미가 없다. 또한 우리는 메뉴 필드에 NULL값을 넣음으로써 메뉴가 필요치 않다는 것을 알린다. 윈도우의 클래스 이름은 임의로 정하면 되는데 우리는 단순히 OpenGL이라고 정했다.

hInstance= GetModuleHandle(NULL); // Grab An Instance For Our Window 
// Redraw On Move, And Own DC For Window
wc.style= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  
wc.lpfnWndProc= (WNDPROC) WndProc; // WndProc Handles Messages 
wc.cbClsExtra= 0; // No Extra Window Data 
wc.cbWndExtra= 0;// No Extra Window Data 
wc.hInstance= hInstance;// Set The Instance 
wc.hIcon= LoadIcon(NULL, IDI_WINLOGO);// Load The Default Icon 
wc.hCursor= LoadCursor(NULL, IDC_ARROW);// Load The Arrow Pointer 
wc.hbrBackground= NULL;// No Background Required For GL 
wc.lpszMenuName= NULL;// We Don't Want A Menu 
wc.lpszClassName= "OpenGL";// Set The Class Name 

이제 윈도우 클래스를 등록하자. 만약 뭔가 잘못되었다면 에러메세지를 출력하고 프로그램을 종료시킨다.

if (!RegisterClass(&wc)) // Attempt To Register The Window Class
{
    MessageBox(NULL, "Failed To Register The Window Class.", "ERROR",
        MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // Exit And Return FALSE
}

이제 프로그램이 전체창으로 실행되는지를 봐야한다. 만약 그렇다면 우리는 전체 모드로 실행시켜야 한다.

if (fullscreen) // Attempt Fullscreen Mode? {

다음 코드의 전체모드로 전환하는 것이다. 전체 모드로 전환하는 것에에는 여러분이 꼭 주의깊에 봐야할 부분이 있다. 그것은 바로 화면의 Width와 Height가 System에서 지원하는 해상도이여야 한다는 것이다. (역자주: 640X480, 1024X768, …) 윈도우를 생성하기전에 전체모드로 설정해야 한다. 이 코드에서 여러분은 width와 height에 대해서 걱정하지 않아도 된다. 전체화면 모드와 윈도우의 크기는 모두 인자를 통해 전달되기 때문이다.

DEVMODE dmScreenSettings; // Device Mode   
// Makes Sure Memory's Cleared
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); 
// Size Of The Devmode Structure
dmScreenSettings.dmSize=sizeof(dmScreenSettings); 
dmScreenSettings.dmPelsWidth = width; // Selected Screen Width
dmScreenSettings.dmPelsHeight = height; // Selected Screen Height
dmScreenSettings.dmBitsPerPel = bits; // Selected Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

위의 코드에서 우리는 우리의 비디오 셋팅을 저장할 공간을 0으로 초기화했다. 그리고 원하는 width와 height 그리고 bits로 설정했다. 다음 바로 아래에 나오는 코드에서 우리는 전체화면 모드가 요청되어질 때 그 모드로 실행되게 할 것이다. 우리는 width, height, bits에 대한 모든 정보를 dmScreenSettings 변수에 저장했다. 필자는 모드를 변경할 때 CDS_FULLSCREEN 피라메터를 사용할 것인데 화면 하단의 Start Bar를 감춰야 하기 때문이다.

    // Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN 
    //Gets Rid Of Start Bar.
    if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)
        !=DISP_CHANGE_SUCCESSFUL)
{

만약 전체화면 모드가 지원되지 않거나 원하는 전체화면이 없다면 메시지 화면을 띠우고 윈도우 창 모드로 실행할 것을 묻는다.

// If The Mode Fails, Offer Two Options. Quit Or Run In A Window.
if (MessageBox(NULL, 
    "The Requested Fullscreen Mode Is Not Supported By\n"
    "Your Video Card. Use Windowed Mode nstead?",
    "NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES) {

만약 사용자가 윈도우 창 모드로 실행되기 원한다면 fullscreen값은 False가 되고 프로그램은 계속 실행된다.

    fullscreen=FALSE; // Select Windowed Mode (Fullscreen=FALSE)
} 
else 
{ 

사용자가 윈도우창 모드를 원하지 않는다면 프로그램은 종료한다.

            // Pop Up A Message Box Letting User Know The Program Is Closing.
            MessageBox(NULL,"Program Will Now close.","ERROR",MB_OK|MB_ICONSTOP);
            return FALSE; // Exit And Return FALSE
        }
    }
}

전체화면 모드를 위한 코드가 실패하거나 사용자가 자신의 컴퓨터에서 지원하지 않는 전체화면 대신에 윈도우창 모드로 대신 실행되는, 위에서 살펴본 코드 때문에 우리는 화면을 설정하고 윈도우 타입을 설정하기 전에 다시 한번 fullscreen값이 참인지 거짓인지를 검사해봐야 한다.

if (fullscreen) // Are We Still In Fullscreen Mode?
{

만약 전체화면 모드일 경우 윈도우 확장 스타일을 WS_EX_APPWINDOWS로 지정하는데 이것은 태스크바를 않보이게 한다. 윈도우 스타일은 WS_POPUP 스타일로 지정한다. 이것은 윈도우 창 주변에 보더가 없다. 이것으로써 전체화면 모드의 조건을 모두 만족시킨다.

최종적으로 우리는 마우스 포인터를 않보이게 해야 한다. 만약 프로그램이 사용자와의 상호작용이 없다면 마우스 포인터를 않보이게 하는 것은 괜찬다.

    dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
    dwStyle=WS_POPUP; // Windows Style
    ShowCursor(FALSE); // Hide Mouse Pointer
} else {

전체 화면 모드가 아닌 윈도우창 모드인 경우 우리는 윈도우 확장 스타일을 WS_EX_WINDOWEDGE로 지정한다. 이것은 윈도우를 3D처럼 보이게 한다. 윈도우 스타일로써는 WS_OVERLAPPEDWINDOW로 지정한다. 이것은 타이틀 바와 크기 조정 보더, 윈도우 메뉴, 최소/최대화 버튼을 갖는 윈도우를 만들다.

    dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // WindowExtendedStyle
    dwStyle=WS_OVERLAPPEDWINDOW;// Windows Style 
}

다음에 나오는 코드는 우리가 지정한 윈도우의 스타일에 따라 생성된 윈도우의 크기를 조정한다. 이 조정은 우리의 윈도우를 정확히 우리가 원하는 해상도로 만들어준다. 보통 보더는 우리의 윈도우의 한부분으로 겹쳐있다. AdjustWindowRectEx 함수를 사용함으로써 OpenGL이 생성한 장면이 보더에까지 나타나지 않도록 한다. 하지만 보더가 없는 전체화면 모드에서는 이 함수가 아무런 효과도 없다.

// Adjust Window To True Requested Size 
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); 

다음에 나오는 코드를 보자. 이제 우리는 실제적으로 윈도우를 만들 것이며 성공적으로 만들어졌는지를 검사할 것이다. 우리는 CreateWindowEX() 함수에게 필요한 모든 인자를 넘겨준다. 윈도우 확장 스타일그리고 윈도우 클래스 이름, 윈도우 타이틀. 윈도우의 스타일, 좌상단의 윈도우가 위치할 좌표, 윈도우의 크기가 바로 넘겨줄 인자들이다. 우리는 보무 윈도우를 원하지 않는다. 그리고 우리는 메뉴도 필요없다. 인자중에 NULL로 표시된 것이 바로 이것을 의미한다.

우리가 윈도우 스타일에 WS_CLIPSIBLINGS와 WS_CLIPCHILDREN도 포함하고 있음을 주목하기 바란다. 이 두 스타일 값은 모두 OpenGL에서는 필수적인 것이다. 이 값들은 다른 윈도우가 우리의 OpenGL 윈도위에 그려지는 것을 막는다.

if (!(hWnd=CreateWindowEx( dwExStyle, // Extended Style For The Window
        "OpenGL", // Class Name
        title, // Window Title
        WS_CLIPSIBLINGS | // Required Window Style
        WS_CLIPCHILDREN | // Required Window Style
        dwStyle, // Selected Window Style
        0, 0, // Window Position
        WindowRect.right-WindowRect.left, // Calculate Adjusted Window Width
        WindowRect.bottom-WindowRect.top, // Calculate Adjusted Window Height
        NULL, // No Parent Window
        NULL, // No Menu
        hInstance, // Instance
        NULL))) // Don't Pass Anything To WM_CREATE

다음에 우리는 윈도우가 제대로 생성되었는지 검사한다. 만약 성공적이라면 hWnd 변수에 윈도우 헨들이 넘어오게 된다. 만약 윈도우가 제대로 생성되지 않았다면 에러 메시지를 출력하고 프로그램은 종료된다.

{
    KillGLWindow(); // Reset The Display
    MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // Return FALSE
}

다음의 코드는 Pixel Format를 기술하는 내용이다. 우리는 시스템이 우리가 결정한 것들(역자주: 비트값, RGBA값, 더블버퍼링을 위한 것들 등등)에 맞는 pixel format를 찾는다. 최종적으로 우리는 16Bit Z-Buffer를 설정했다. 나머지 인자들은 사용하지 않거나 중요하지 않은 스텐실 버퍼와 어커물레이션 버퍼이다.

// pfd Tells Windows How We Want Things To Be
static PIXELFORMATDESCRIPTOR pfd= 
{
    sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
    1, // Version Number
    PFD_DRAW_TO_WINDOW | // Format Must Support Window
    PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
    PFD_DOUBLEBUFFER, // Must Support Double Buffering
    PFD_TYPE_RGBA, // Request An RGBA Format
    bits, // Select Our Color Depth
    0, 0, 0, 0, 0, 0, // Color Bits Ignored
    0, // No Alpha Buffer
    0, // Shift Bit Ignored
    0, // No Accumulation Buffer
    0, 0, 0, 0, // Accumulation Bits Ignored
    16, // 16Bit Z-Buffer (Depth Buffer)
    0, // No Stencil Buffer
    0, // No Auxiliary Buffer
    PFD_MAIN_PLANE, // Main Drawing Layer
    0, // Reserved
    0, 0, 0 // Layer Masks Ignored
};

위의 코드에 여러가 없다면 우리는 OpenGL Device Context를 성공적으로 얻은 것이지만 만약 DC를 얻지 못한다면 즉, 실패한다면 에러메세지를 출력하고 프로그램을 종료한다.

if (!(hDC=GetDC(hWnd))) // Did We Get A Device Context?
{
    KillGLWindow(); // Reset The Display
    MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",
        MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // Return FALSE
}

만약 우리가 OpenGL을 위한 DC를 얻었다면 우리는 우리가 위에서 기술 것들과 맞는 Pixel Format 찾기를 시도한다. 만약 윈도우즈가 맞는 Pixel Format이 찾을 수 없다면 에러메세지를 출력하고 프로그램은 종료된다. 이때 이 함수가 실패했음을 말하는 False를 반환한다.

if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Did Windows Find A Matching    Pixel Format?
{
    KillGLWindow(); // Reset The Display
    MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",
        MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // Return FALSE
}

만약 윈도우가 맞는 픽셀 포맷을 찾았다면 우리는 윈도우즈가 찾은 픽셀포멧 값으로 윈도우를 설정해야 한다. 만약 설정될 수 없다면 에러값을 출력하고 프로그램은 종료된다. 마찬가지로 반환값은 False이다.

if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // Are We Able To Set The Pixel    Format?
{
    KillGLWindow();// Reset The Display 
    MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);    
    return FALSE;// Return FALSE 
} 

픽셀 포맷이 적절하게 셋팅되었다면 우리는 이제 Rendering Context를 얻어야 한다. 만약 RC를 얻는데 실패했다면 에러메세지를 출력하고 프로그램은 종료한다. 마찬가지로 반환값은 False이다.

if (!(hRC=wglCreateContext(hDC))) // Are We Able To Get    A Rendering Context?
{
    KillGLWindow(); // Reset The Display
    MessageBox(NULL,"Can't Create A GL Rendering Context.", "ERROR", 
        MB_OK|MB_ICONEXCLAMATION);        return FALSE; // Return FALSE
}

여기까지 에러가 없다면 우리는 RC를 활성화 시켜야 한다. 만약 실패한다면 에러메세지를 출력하고 프로그램은 종료 그리고 반환값은 False이다.

if(!wglMakeCurrent(hDC,hRC)) // Try To Activate The Rendering Context
{
    KillGLWindow(); // Reset The Display
    MessageBox(NULL,"Can't Activate The GL Rendering Context.", "ERROR", 
        MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // Return FALSE
}

모든 것이 순조럽게 이루어졌다면 OpenGL 윈도우는 생성되어졌고 우리는 그 윈도우를 볼 수 있다. 이를 위해서 윈도우를 보이는 함수를 호출하고 이것을 Foreground 윈도우로 설정하고 포커스를 갖게 한다. 앞으로 ReSizeGLScene를 호출할 것인데 이 함수의 인자는 윈오우창의 Width와 Height이다.

ShowWindow(hWnd,SW_SHOW); // Show The Window
SetForegroundWindow(hWnd); // Slightly Higher Priority
SetFocus(hWnd); // Sets Keyboard Focus To The Window
ReSizeGLScene(width, height); // Set Up Our Perspective GL Screen

마지막으로 InitGL() 함수를 호출하는데 이 함수에서는 빛과 텍스처, 그리고 그밖에 필요한 초기화 루틴이 들어있다. 여기서 InitGL()의 반환값을 통해 성공여부를 판단하는데 실패하면 False, 성공하면 True이다. 예를 들어서 텍스처를 로딩하는 로틴이 InitGL()에 있다고 해보자. 무언가 문제가 있어서 텍스쳐 로딩이 실패했다면 False를 반환하게 될 것이다. 그러면 에러 메세지를 출력하고 프로그램은 종료된다.

if (!InitGL()) // Initialize Our Newly Created GL Window
{
    KillGLWindow(); // Reset The Display
    MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
   
    return FALSE; // Return FALSE
}

모든게 성공했다면 윈도우의 생성은 아주 성공적인 것으로 판단할 수 있다. 우리는 True값을 리턴하여 이 함수를 호출하는 WinMain()에게 아무런 에러도 없다는 것을 알린다. 역시 에러가 있다면 에러메세지를 출력하고 프로그램은 종료될 것이다.

    return TRUE; // Success
}

다음은 모든 윈도우 메시지를 처리하는 것이다. 우리가 윈도우 클래스를 등록했을 때 메시지가 발생하면 이 함수를 통해서 메시지를 처리하라고 말해주었다.

LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window
    UINT uMsg, // Message For This Window
    WPARAM wParam, // Additional Message Information
    LPARAM lParam) // Additional Message Information
{{

다음의 uMsg가 메시지의 이름을 가지고 있는데 Case 문을 통해서 처리하게 된다.

switch (uMsg) // Check For Windows Messages

uMsg 변수가 WM_ACTIVE일 때 윈도우가 활성 상태(역자주: 최소화 상태가 아닌 상태)인지를 검사해야 한다. 만약 윈도우가 최소화(역자주: 비활성) 되어었다면 active 변수를 False로 지정할 것이고 우리의 윈도우가 활성 상태라면 active 변수를 True로 지정할 것이다.

{ 
    case WM_ACTIVATE: // Watch For Window Activate Message
    {
        if (!HIWORD(wParam)) // Check Minimization State
        {
            active=TRUE; // Program Is Active
        }
        else
        {
            active=FALSE; // Program Is No Longer Active
        }
   
        return 0; // Return To The Message Loop
}

WM_SYSCOMMAND 메시지(시스템 명령)일 경우 우리는 wParam을 검사한다. 만약 wParam이 SC_SCREENSAVE이거나 SC_MONITORPOWER일 경우 스크린 세이버 프로그램이 실행되기 직전이거나 모니터의 절정 모드로 진입되기 직전이라는 의미이다. 0을 반환함으로써 스크린 세이버와 모니터 절전 모드의 진입을 막는다.

case WM_SYSCOMMAND: // Intercept System Commands
{
    switch (wParam) // Check System Calls
    {
        case SC_SCREENSAVE: // Screensaver Trying To Start?
        case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
            return 0; // Prevent From Happening
    }
    break; // Exit
}

uMsg가 WM_CLOSE이라면 윈도우가 종료되었다는 의미이다. 우리는 quit 메시지를 보냄으로써 메인 루프에서 빠져나와야 한다. done 변수가 True라는 것은 WinMain()의 루프에서 빠져나온다는 의미이고 프로그램은 종료된다. (역자주: done변수는 다음에 나올 WinMain()함수에서 정의한 지역변수이다)

case WM_CLOSE: // Did We Receive A Close Message?
{
    PostQuitMessage(0); // Send A Quit Message
    return 0; // Jump Back
}

만약 키보드 키가 눌러졌을 때 우리는 wParam 값을 읽음으로써 어떤 키가 눌러졌는지 알 수 있다. 필자는 keys[] 배열을 만들어서 해당 키의 배열 원소의 위치에 True값을 설정한다. 이 방법은 나중에 배열에서 어떤 키가 눌러졌는지를 알아내는데 사용된다. 이것은 동시에 여러키가 눌러진 경우도 판별해 낼 수 있다.

case WM_KEYDOWN: // Is A Key Being Held Down?
{
    keys[wParam] = TRUE; // If So, Mark It As TRUE
    return 0; // Jump Back
}

만약 눌러졌던 키가 다시 놓아졌을 때 wParam값을 읽음으로써 어떤 키가 놓아졌는지 알 수 있다. 필자는 keys[] 배열을 만들어서 해당 키의 배열 원소의 위치에 False 값을 설정한다.

case WM_KEYUP: // Has A Key Been Released?
{
    keys[wParam] = FALSE; // If So, Mark It As FALSE
    return 0; // Jump Back
}

윈도우의 크기를 재 조정할 때 uMsg는 WM_SIZE 값을 갖는다. 우리는 lParam의 LOWORD와 HIWORD 값을 읽어서 새로운 Width와 Height을 알아낸다. 우리는 이 새로운 크기를 ReSizeGLScene()에게 넘겨준다. OpenGL의 화면은 이 새로운 화면의 크기로 재설정될 것이다.

    case WM_SIZE: // Resize The OpenGL Window
    { 
        ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width, HiWord=Height
        return 0; // Jump Back
    }
}

더 이상 우리가 처리할 메시지가 없으므로 DefWindowProc를 통해서 그밖의 메시지를 기본 메시지 처리 함수를 통해 처리하도록 지시한다.

    // Pass All Unhandled Messages To DefWindowProc
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

다음 부분은 우리의 윈도우 어플리케이션의 진입(시작) 부분이다. 우리는 여기서 윈도우 생성 루틴을 호출하며 윈도우 메시지 처리 함수를 더루며 사용자와 프로그램간의 상호작용(역자주: 키보드입력)을 다룬다.

int WINAPI WinMain( HINSTANCE hInstance, // Instance
                    HINSTANCE hPrevInstance, // Previous Instance
                    LPSTR lpCmdLine, // Command Line Parameters
                    int nCmdShow) // Window Show State
{

우리는 두 개의 변수를 정의했다. msg는 대기중인 우리가 처리해야할 메시지가 있는지 검사하는데 사용된다. done변수는 시작할 때 False 값을 갖는다. 이것은 아직 우리의 프로그램의 실행이 끝나지 않았음을 의미한다.

done 변수가 False값을 가지고 있는 한 우리의 프로그램은 계속 실행된다. done변수가 True 값을 갖는 순간 프로그램은 바로 종료된다.

MSG msg; // Windows Message Structure
BOOL done=FALSE; // Bool Variable To Exit Loop

다음은 우리가 이 프로그램을 전체화면 모드로 실행시킬 것은지를 묻는 대화 상자를 표시한다. NO 버튼을 클릭하면 fullscreen 변수는 False값을 갖고 그렇지 않으면 True값을 갖는다.

// Ask The User Which Screen Mode They Prefer
if(MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?", 
        MB_YESNO|MB_ICONQUESTION)==IDNO)
{
    fullscreen=FALSE; // Windowed Mode
}

다음은 우리의 OpenGL 윈도우가 어떻게 생성되는가 하는 것이다. 우리는 이것의 제목과 크기와 색상 깊이 인자를 넘겨주고 전체화면 모드로 실행될 것인지를 전해준다. 바로 이것이다! 필자는 이것을 이처럼 간단하게 코드할 수 있어서 얼마나 기쁜지 모른다. 몇가지 이유로 윈도우를 생성하지 못한다면 False값이 넘겨져 오고 프로그램은 즉각 종료된다(return 0)

// Create Our OpenGL Window
If (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
    return 0; // Quit If Window Was Not Created
}

다음은 Loop문의 시작이다. done변수가 False일 동안 이 루프는 계속 반복된다.

while(!done) // Loop That Runs Until done=TRUE
{

우리가 해야할 가장 처음것은 어떤 메시지가 대기중인가 하는 것이다. PeekMessage() 함수를 통해서 이것을 검사한다. 많은 프로그램은 PeekMessage()가 아닌 GetMessage()를 사용한다. 그것은 잘작동하지만 GetMessage()는 Paint 메시지나 다른 윈도우 메시지가 도착할 때까지 아무런 일도 하지 않은체 멈춰있게 된다.

if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
{

다음의 코드는 Quit 메시지가 도착되었는지 검사하는 것이다. 현제의 메시지가 WM_QUIT이라면 PostQuitMessage() 함수를 실행시키고 done 변수를 True로 설정함으로써 프로그램은 종료된다.

if (msg.message==WM_QUIT) // Have We Received A Quit Message?
    {
        done=TRUE; // If So done=TRUE
    }
    else // If Not, Deal With Window Messages
    {

Quit 메시지가 아닌 경우 우리는 WndProc()와 윈도우즈가 처리할 수 있도록 이 메시지를 해석한후 디스패치시켜야 한다.

        TranslateMessage(&msg); // Translate The Message
        DispatchMessage(&msg); // Dispatch The Message
    }
}
else // If There Are No Messages
{

처리해야할 메시지가 없다면 우리는 OpenGL 화면에 무언가를 그린다. 아래의 첫 번째 코드 라인은 윈도우가 활성상태인지를 검사한다. 장면은 만들어지고 반환값이 검사되어진다. DrawGLScene()가 False를 반환하거나 ESC키를 눌렀다면 done 변수는 True값을 가지고 프로그램은 종료된다.

// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Updating View Only If Active
{
    done=TRUE; // ESC or DrawGLScene Signalled A Quit
}  
else // Not Time To Quit, Update Screen
{

모든 것이 잘 그려진다면 우리는 버퍼를 바꿔야 한다. (더블 버퍼링을 사용함으로써 우리는 에니메이션의 깜박거림 현상을 막을 수 있다) 더블 버퍼링을 사용함으로써 우리는 우리가 볼 수 없는 화면에 모든 것을 그린다. 버퍼가 바꿔짐으로써 보이지 않는 화면은 보여지게 되고 그전에 보여지는 화면은 다시 않보여지는 화면이 되다. (역자주: 즉 두 개의 화면이 존제하며 하나는 보여지는 것이고 다른 하나는 보여지지 않는다. 이 화면들은 메리상의 기억 공간 즉, 버퍼이다)

    SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}

다음의 코드는 2000년 5월 1일에 추가된 코드이다. F1키를 누르면 전체화면 모드에서 윈도우창 모드로 바뀐다. 여기서 다시 F1키를 누르면 또 윈도우창 모드에서 전체창 모드로 바뀐다.

        if (keys[VK_F1]) // Is F1 Being Pressed?
        {
            keys[VK_F1]=FALSE; // If So Make Key FALSE
            KillGLWindow(); // Kill Our Current Window
            fullscreen=!fullscreen; // Toggle Fullscreen / Windowed Mode
            // Recreate Our OpenGL Window
            if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
            {
                return 0; // Quit If Window Was Not Created
            }
        }
    }
}

done변수가 더 이상 False가 아니라면 프로그램은 종료된다. 우리는 OpenGL 윈도우를 제거하고 모든 것을 헤제한다. 그리고 프로그램을 종료한다.

    // Shutdown
    KillGLWindow(); // Kill The Window
    return (msg.wParam); // Exit The Program
}

이 튜어리얼에서 나는 아주 많은 것을 설명했다. 여러 가지 변수를 설정하고 OpenGL 프로그램을 생성하고 ESC 키를 누르면 종료한다. 나는 이 코드를 작성하는데 2주가 소요되었는데 그 중 한주는 버그를 수정하고 전문 프로그래머들에게 문의를 하는데 보냈다. 그리고 2일간(총 22시간)은 이 HTML 파이를 쓰는데 보냈다. 혹시 할말이나 질문이 있다면 나에게 메일을 보내기 바란다. 내가 설명한 것중에 잘못된 것이 있다면 부디 나에게 알려주기 바란다. 나는 가장 휼룡한 OpenGL 튜어리얼을 만들고 싶다. 나는 당신의 피드백에 많은 관심이 있다.

여기까지가 변역입니다.

이상으로 튜어리얼의 번역을 끝마칩니다.