OpenGL Shader – 3

Vertex Processor

버텍스 프로세서는 버텍스 쉐이더를 실행한다. 버텍스 쉐이더의 입력은 버텍스 데이터로써, 주로 버텍스의 위치, 색, 법선벡터 등으로써 OpenGL 어플리케이션이 보내준다.

다음 OpenGL 코드는 버텍스 프로세서에게 각 버텍스에 대한 색상, 위치 값을 전달해준다.

glBegin(...);
    glColor3f(0.2,0.4,0.6);
    glVertex3f(-1.0,1.0,2.0);

    glColor3f(0.2,0.4,0.8);
    glVertex3f(1.0,-1.0,2.0);
glEnd();

버텍스 쉐이더에서 다음과 같은 일을 할 수 있다.

  • 모델뷰 행렬과 프로젝션 행렬을 이용해서 버텍스의 위치를 이동
  • 노말 벡터의 변환, 그리고 필요하다면 노말 벡터의 정규화
  • 텍스쳐 좌표 생성과 변환
  • 버텍스에 대한 빛 또는 픽셀에 대한 빛 계산
  • 색상 계산

위의 모든 연산을 수행할 필요는 없다. 그러나 일단 버텍스 쉐이더를 만들었다면 버텍스 프로세서의 전체 기능을 버텍스 쉐이더가 대신하며, 따라서 법선 변환을 수행할 수 없고 고정 기능이 텍스쳐 좌표 생성을 수행하도록 할 수 없다. 버텍스 쉐이더가 사용될때 파이프 라인의 이 단계의 필요한 모든 기능을 버텍스 쉐이더가 대신하게 된다.

이전에서 설명했던 것처럼, 버텍스 쉐이더는 버텍스간 연결성 고려에 대한 정보를 가지고 있지 않다. 그러므로 위상 데이터를 필요로 하는 연산은 버텍스 쉐이더에서 수행할 수 없다. 예를들어서, 버텍스 쉐이더가 뒷면 제외를 할 수 없는데, 버텍스 쉐이더는 버텍스 단위 연산이지 버텍스가 이루고 있는 면에 대한 연산을 할 수 없기 때문이다. 면을 알려면 버텍스간의 연결 정보를 알아야 한다. 이러한 연결정보가 바로 위상 데이터의 하나이다. 버텍스 프로세서는 개별적인 버텍스 하나 하나를 처리 하는 순간에 그 하나 이외의 나머지 버텍스를 알지 못한다.

버텍스 쉐이더는 최소한 gl_Position 이라는 변수를 변경해야만 한는데, 이 변수는 보통 모델뷰 행렬과 프로젝션 행렬을 이용한 변환이다.

버텍스 프로세서는 OpenGL 상태에 접근할 수 있어서, 예를 들어 빛을 포함하고 재질을 사용하는 연산을 수행할 수 있다. 또한 텍스쳐에 대한 정보도 접근할 수 있는데 이것은 최신 그래픽 카드에서만 허용한다. 하지만 프레임 버퍼에는 접근할 수 없다.

OpenGL Shader – 2

다음 그림은 매우 단순화된 파이프라인 다이어그램이며 데이터가 어떤 식으로 파이프 라인을 타는지를 보여준다. 매우 단순화시켰지만 쉐이더 프로그래밍에 대한 중요한 개념을 제공하고 있다. 이 부분에서는 파이프라인의 고정 기능을 제공한다. 파이프라인은 추상적이며 모든 각 단계에서 어떤 특정한 구현과 만날 필요가 없다는 점을 주의하기 바란다.
Vertex Transformation

여기에서 버텍스는 공간상의 위치, 색상, 노말벡터, 텍스쳐 좌표 등과 같은 속성들의 집합이다. 이 단계에서의 입력값들은 하나 하나의 버텍스 속성들이다. 이 고정 기능에 의해 수행되는 연산은 다음과 같다.

  • 버텍스의 위치 변환
  • 버텍스에 대한 광원 계산
  • 텍스쳐 좌표의 생성 및 변환

Primitive Assembly and Rasterization

이 단계에서의 입력값은 변환된 버텍스와 연결 정보이다. 연결정보라는 것은 버텍스들이 Primitive(삼각형 등과같은 기본 요소)를 이루기 위해 어떻게 연결되느냐이다. 이 단계에서 Primitive가 생성된다. 또한 이 단계는 뒷면제거나 뷰 절두체에 대한 클리핑 연산을 담당할 수 있다.

라스터라이징은 Fragment를 결정하고 Primitive의 픽셀 위치를 결정한다. 이 문맥에서 Fragment는 특정한 위치에서 프레임 버퍼의 픽셀을 변경하기 위해 사용될 데이터의 조각이다. 쉽게 말하자면 Primitive가 화면에 실제 렌더링될때 그려질 Pixel 값이다. Fragment는 색 뿐만이 아니라 수직벡터값과 텍스쳐 좌표 등과 같은 값들인데, 이러한 값들을 이용해서 실제로 화면상에 찍힐 픽셀의 새로운 색상을 계산하는데 쓰인다.

이 단계에서의 출력값은 다음과 같다.

  • 프레임버퍼 안의 Fragment들의 위치
  • 버텍스 변환 단계에서 계산된 속성에 대한 각 Framgment에 대한 보간된 값

버텍스 변환 단계에서 계산된 값은 버텍스 연결정보와 함께 조합되어 Fragment를 위한 알맞은 속성을 계산하는데 사용된다. 예를들어서 각 버텍스는 변환된 위치를 가지고 있다. 버텍스가 Primitive로 만들어지는데 사용될때 Primitive의 Fragment의 위치를 계산하는것이 가능하다. 또 다른 예는 색상의 사용이다. 만약 삼각형을 구성하는 각 버텍스가 각기 다른 색상을 가지고 있다면, 삼각형 안의 Fragment 색상은 각 버텍스의 상대적인 거리에의한 가중치를 받아 색상값들이 보간되어져 얻어진다.

Fragment Texturing and Coloring

이 단계에서의 입력값은 보간되어진 Fragment의 정보이다. 색상은 이미 이전 단계에서 보간을 통해 계산되어졌고, 이 단계에서는 텍셀(Texel, Texture element)값 결합과 같은 연산이 수행된다. 텍스쳐 좌표는 이전단계에서 보건되어진다. 안개도 역시 이 단계에서 적용된다. 고통적인 최종 출력값은 Fragment의 색상값과 깊이 값이다.

Raster Operations

이 단계에서의 입력값은 다음과 같다.

  • 픽셀의 위치
  • Fragment의 색상값과 깊이값

Fragment에 대해 수행되는 파이프라인의 마지막 연속 단계는 주로 다음과 같다.

  • Scissor Test
  • Alpha Test
  • Stencil Test
  • Depth Test

만약 테스트가 성공한다면, Fragment 정보는 현재의 블렌딩 모드에 따라 픽셀의 값을 변경하는데 사용된다.  블렌딩은 이 단계에서만 수행되는데, Fragment 텍스쳐링과 컬링 단계에서는 프레임 버퍼에 접근하지 못하기 때문이다. 프레임 버퍼는 오직 이 단계에서만 접근할 수 있다.

고정 기능에 대한 간단한 그림 설명

다음 그림은 위에서 설명한 각 단계에 대한 설명을 그림으로 다시 풀어높은 것이다.


Replacing Fixed Functionality

현재의 그래픽 카드는 프로그래머가 위에서 기술된 단계 중에 2개를 새롭게 정의할 수 있게 해준다.

  • 버텍스 쉐이더는 Vertex Transformation 단계를 작성하는데 사용된다.
  • 프레그먼트(Fragment) 쉐이더는 프레그먼트 텍스쳐링과 컬러링 단계의 고정 기능을 교체하는데 사용된다.

다음 섹션에서는 프로그래밍 가능한 단계, 즉 버텍스 처리기와 프레그먼트 처리기에 대해 설명할 것이다.

OpenGL Shader – 1

GLSL을 사용하는 쉐이더 프로그래밍을 알아보자. 쉐이더는 최신 기술이며 3D 게임에서 경이로운 효과를 내는데 사용된다. 이 글을 통해 쉐이더의 세계에 발을 들여놓자.

이 글과 앞으로 진행될 내용을 이해하기 위해서는 OpenGL 프로그래밍에 익숙해야 한다. 또한 쉐이더를 위한 스펙에 대해 알고 싶다면, OpenGL 2.0과 GLSL에 대한 공식문서를 반드시 읽어보길 바란다.

GLSL은 OpenGL 쉐이딩 언어를 의미하며 종종 “glslang”으로 불리며 ARB(Architectural Review Board) OpenGL에 의해 정의된다.

OpenGL과 경쟁하는 Cg, 즉 Nvidia가 나름대로 만든 또 다른 쉐이더 언어와 GLSL을 비교하거나 누가 더 우수한지 따지지는 않겠다. 그 이유는 지금 여기서는 Cg가 아닌 GLSL에 대해서만 살펴볼 것이기 때문이다.

어떤 언어로든 쉐이더를 작성하기 전에, 그래픽 파이프라인에 대한 기본을 이해하는 것이 매우 중요하다. 그래픽 파이프라인은 쉐이더가 무엇을 하는지, 쉐이더의 종류에는 뭐가 있는지와 같은 쉐이더의 핵심을 제공한다. 또한 쉐이더로 할 수 없는것이 무엇인지에 대한 힌트도 제공한다.

파이프라인에 대한 소개 다음에 GLSL을 위한 OpenGL의 설정에 대해서 알아보겠다. OpenGL 어플리케이션에서 쉐이더를 사용하데 필요한 단계에 대해 자세하게 살펴볼 것이다. 최종적으로 OpenGL 어플리케이션이 매우 유연하고 강력한 효과를 낼 수 있는 방법에 대해 살펴볼 것이다.

몇가지 기본적인 개념으로 데이터 타입(data types), 변수(variables), 문장(statements), 함수(function)의 정의에 대해서 알아본다.

이 글은 ARB 확장과 OpenGL 2.0 버전에 대해 다룬다. 후자는 이미 표준이지만 전자는 향후 표준으로 자리잡을 가능성이 있는 것이다. 차이는 작지만 함수의 이름이나 상수 이름에 약간의 차이를 가지고 있다. 이것들을 구분을 돕기위해 코드에 색상을 넣었다. ARB는 회색으로 OpenGL 2.0은 오렌지 색으로 말이다.

이 적용범위는 실수하기 쉽다. 그러니 혹시 이런 표기에 문제가 있다면 언제든지 내게 알려주길 바란다.

이 글은 점진적으로 작성될 것이므로 글과 데모 프로그램에서 문제가 있을 수 있는데, 이것에 대해서 좀 참아주길 바란다. 비록 사소할지라도 문제를 발견하면 알려주길 바란다. 또한 많은 제안을 해주길 바란다. 부디 이 글을 즐겁게 읽어주길 희망하며 첫번째 GLSL, OpenGL Shader의 첫글을 마치도록 하겠다.

[OpenGL Tutorial] Display List

사용자 삽입 이미지이번장에서는 오랬동안 길게 끌어온 OpenGL의 렌더링 성능을 향상 시켜주는 Display List에 대해 알아보고자 한다. 사실 이 부분을 16장에 위치시키기에는 그 내용이 기본적이긴 하지만 필자 스스로의 혼란을 줄이기 위해 그냥 순서대로 번호를 매겼다. 이 강좌는 1장에 이여 두번째로 NeHe의 Display List를 그대로 번역한다. 이제 시작해 보자.

이 강좌에서는 Display List에 대한 설명이다. 간단한 장면을 연출하고자 할때 Display List를 사용함으로써 속도의 개선 뿐만이 아니라 코드 줄수 또한 줄일 수 있다.

예를 들어서, 소행성 게임을 만들고 있다고 해 보자. 각 레벨은 최소한 2개 이상의 소행성으로 시작된다. 그래서 당신은 3차원의 소행성을 어떻게 만들 것인지 계산한다. 일단 계산이 끝난후 폴리곤이나 Quad를 이용하여 소행성을 만든다. 이 소행성이 여덜개의 면으로 이루어졌다고 해보자. 이렇게 만든 하나의 소행성을 여러개 만들기 위해, 간결하게 반복문을 이용하여 여러개의 소행성들을 그리게 될 것이다. 소행성을 만들기 위해 18줄 이상의 코드를 쳐 넣을 것이다. 소행성 하나 하나를 시스템 상에서 계산하여 매번 소행성을 그려내는 것은 비효율적이다. 소행성이 복잡하면 복잡할 수록 그 효율은 극적으로 떨어지게 된다.

그렇다면 그 해결책은 무엇인가? 바로 Display Lists이다. Display List를 사용함으로써, 소행성을 단 한번만 만들기만 하면 된다. 여기에 텍스쳐 맵핑을 할 수도 있고 색을 입힌다거나 원하는 모든 것들을 적용할 수 있다. 이 Display List에 이름을 부여할 수 있는데, 예를 들어 소행성의 Display List로 생성을 하고 이름을 ‘Asteroid’라고 한후에 나중에 소행성이 필요할때 마다 단지 이미 정해둔 이름인 ‘Asteroid’로써 Display List를 호출하기만 하면 된다. 즉, 앞으로 소행성을 그리고자 한다면 단지 단 한줄의 코드인 glCallList(asteroid)를 호출하기만 하면 된다. 이미 만들어진 소행성은 즉시 화면에 나타나게 된다. 소행성은 이미 Display List에서 모든 계산이 이루어져 생성되었기 때문에 OpenGL은 다시 소행성을 그리기 위해 계산할 필요가 없어진다. 이것은 메모리 상에 이미 계산의 결과를 저장해 놓았기 때문이다. 이것이 바로 왜 Dislpay List를 사용함으로써 속도가 극적으로 향상되는지하는지에 대한 해답니다.

이제 Display List에 대해 배워볼 마음이 생기는가? 🙂 우리는 앞으로 Q-Bert Display List 데모 프로그램을 만들어 볼 것이다. 화면상에 15개의 큐브를 올려 놓아 보는 데모이다. 각각의 큐브는 BOX와 TOP으로 구성되져 있다. TOP은 별도의 Display List로서 만들어질 것인데 이것은 이 것에 좀더 어두운 색으로 연출하고자 함이다. BOX는 윗면이 없는 큐브이다.

이 코드는 강좌 6의 것을 사용한다. 무엇을 전달하고자 하는지를 쉽게 하기 위해 프로그램을 거의 전체적으로 다시 작성할 것이다. 다음의 코드 라인은 대부분의 강좌에서의 표준 코드들이다. (역자주: 강좌 6의 소스 코드를 일단 다운 로드 받아 살펴보기 바란다. 단지 텍스쳐 맵핑에 대한 내용을 다룬 것이다.)

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

HDC hDC=NULL; // Private GDI Device Context 
HGLRC hRC=NULL; // Permanent Rendering Context
HWND hWnd=NULL; // Holds Our Window Handle 
HINSTANCE hInstance; // Holds The Instance Of The Application
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 

이제, 앞으로 사용할 변수를 선언해 보자. 먼저 하나의 텍스쳐를 저장하기 위한 변수를 선언하고 앞에서 언급한 2개의 Display List를 위한 변수 2개를 선언하자. 이러한 변수는 Display List가 램에서 어디에 저장되어 있는가 하는 지시자로써의 행동한다. 이 2개의 Display List를 BOX와 TOP이라 부를 것이다.

2개의 변수를 선언한 후에 xloop와 yloop라는 변수를 선언하는데 이것은 화면상의 큐브를 위치시키는데 사용된다. 또 xrot와 yrot라는 2개의 변수를 선언하는데 이것은 x와 y축으로 큐브들을 회전시키는데 사용되는 변수들이다.

GLuint texture[1]; // Storage For One Texture 
GLuint box; // Storage For The Display List 
GLuint top; // Storage For The Second Display List 
GLuint xloop; // Loop For X Axis 
GLuint yloop; // Loop For Y Axis 
GLfloat xrot; // Rotates Cube On The X Axis 
GLfloat yrot; // Rotates Cube On The Y Axis 

다음에 우리는 2개의 색상 배열을 생성한다. 첫번째의 boxcol은 밝은 계열의 색상들로 빨간색, 오랜지색, 초록색 그리고 파란색의 색상값을 저장한다. {}안에 지정된 각각의 값은 Red, Green Blue 값을 나타낸다. 각각의 {}의 구룹은 지정된 색이다.
두번째 색상의 배열은 어두운 계열의 색상들로 빨간색, 오랜지색, 초록색 그리고 파란색을 위한 것이다. 어두운 색들은 박스의 윗면을 나타내는데 사용된다. 우리는 박스의 나머지 부분보다 박스의 뚜껑을 좀더 어둡게 하고자 한다.

static GLfloat boxcol[5][3]= // Array For Box Colors 
 {// Bright: Red, Orange, Yellow, Green, Blue 
 {1.0f,0.0f,0.0f},
 {1.0f,0.5f,0.0f},
 {1.0f,1.0f,0.0f},
 {0.0f,1.0f,0.0f},
 {0.0f,1.0f,1.0f}}; 
   
static GLfloat topcol[5][3]= // Array For Top Colors 
 { // Dark: Red, Orange, Yellow, Green, Blue 
 {.5f,0.0f,0.0f},
 {0.5f,0.25f,0.0f},
 {0.5f,0.5f,0.0f},
 {0.0f,0.5f,0.0f},
 {0.0f,0.5f,0.5f}}; 
  // Declaration For WndProc
 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);   


이제 실제 Display List를 만들어 본다. 이미 짐작했겠지만, 박스를 만들기 위한 모든 코드는 첫번째 List로 하고 뚜껑으로 사용할 박스의 Top은 다른 List에 들어가게 된다. 다음 절에서 더 자세한 내용을 설명하도록 하겠다.

GLvoid BuildLists() // Build Box Display List 
{ 

우리는 OpenGL에게 우리가 2개의 List를 필요로 한다고 알리는 것으로 시작한다. glGenLists(2)는 두개의 List를 위한 공간을 생성하고 첫번재 List를 가르키는 포인터를 반환한다. box가 첫번째 List의 위치를 가지게 된다. 언제든지 box를 호출한다면 첫번째 list에 담긴 물체가 그려지게 된다.

    box=glGenLists(2); // Building Two Lists 

List를 위한 공간을 마련했으므로 실제 List를 생성해 보자. 우리는 이미 두개의 List를 위한 메모리 공간을 마련했고 box가 첫번째 list를 저장하는 공간을 가르킨다는 것을 알고 있다. 이제 OpenGL에게 알려할 모든 것은 리스드가 어디로 가야하며 만들어질 리스트가 어떤 타입인가 하는 것들이다.

작업을 하기 위해 glNewList() 명령을 사용한다. 우리는 첫번째 인자가 box라는 것을 알게 될 것이다. 이것은 box가 가르키고 있는 메모리의 위치에 리스트가 저장되어져 있다고 openGL에게 알리는 것이다. 두번째 인자인 GL_COMPILE는 OpenGL에게 우리가 메모리 상에 모든 계산을 미리 해 놓으라는 지시인데 이렇게 함으로써 나중에 다시 불피요한 재계산을 하지 않아도 되도록 하는 것이다.


GL_COMPILE은 프로그래밍과 유사하다. 프로그램을 작성한다면 컴파일러에 적재해야 한다. 코드를 실행하기 위해서 매번 컴파일을 해야 한다. 코드가 이미 실행파일 형태로 컴파일 되어있다면 이후부터 컴파일 할 필요없이 실행파일만을 클릭하여 실행하기만 하면 된다. 컴파일이 필요없는 것이다. 일단 GL이 display list를 컴파일했다면 이미 렌더링 준비가 되어진 것이고 나중에 또 컴파일이 요구되지 않는다. 이것이 바로 display list를 사용함으로써 속도를 향상시키는 이유이다.

    glNewList(box,GL_COMPILE); // New Compiled box Display List


코드의 다음부는 뚜껑(top)이 없는 박스를 그리는 것이다. 이것은 화면상에 나타나지 않고 단지 display list상에 저장된다.

glNewList()와 glEndList()사이에 당신이 원하는 코드들을 추가할 수 있다. 색상을 설정하거나 텍스쳐 맵핑을 바꾼다거나 하는 것등. 추가할 수 없는 유일한 코드는 list에 지정된 객체의 형태를 변경시키는 코드이다. 일단 list로 만들어지면 변경할 수 없다.

만약 glColor3ub(rand()%255, rand()%255, rand()%255)의 코드를 아래에 제시한 코드에 추가했다면, 아마도 당신은 화면상에 물체가 그려지는 때마다 이것의 색이 바뀐다고 생각할 것이다. 그러나 이미 리스트를 생성할때 색상이 지정되졌기 때문에 색은 변경되지 않는다. (역자주: 만약 변경하고자 한다면 list를 생성할때 색상을 지정하는 코드를 사용해서는 않된다)

    glBegin(GL_QUADS); // Start Drawing Quads 
    // Bottom Face 
    // - Top Right Of The Texture and Quad
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); 
    // - Top Left Of The Texture and Quad 
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    // - Bottom Left Of The Texture and Quad 
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); 
    // - Bottom Right Of The Texture and Quad    
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); 
   
    // Front Face 
    // - Bottom Left Of The Texture and Quad
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); 
    // - Bottom Right Of The Texture and Quad 
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
    // - Top Right Of The Texture and Quad 
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);  
    // - Top Left Of The Texture and Quad    
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); 
    
    // Back Face 
    // - Bottom Right Of The Texture and Quad
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    // - Top Right Of The Texture and Quad  
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); 
    // - Top Left Of The Texture and Quad 
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); 
    // - Bottom Left Of The Texture and Quad
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  
   
    // Right face 
    // - Bottom Right Of The Texture and Quad
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    // - Top Right Of The Texture and Quad   
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
    // - Top Left Of The Texture and Quad  
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); 
    // - Bottom Left Of The Texture and Quad 
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); 
   
    // Left Face 
    // - Bottom Left Of The Texture and Quad 
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    // - Bottom Right Of The Texture and Quad 
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
    // - Top Right Of The Texture and Quad   
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); 
    // - Top Left Of The Texture and Quad
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);  
    glEnd(); // Done Drawing Quads 

우리는 List를 다 만들었다는 것을 OpenGL에게 알리기 위해 glEndList() 명령을 내린다. glNewList()와 glEndList() 사이의 부분은 Display list의 부분이다. glNewList() 앞의 부분이나 glEndList()의 뒷부분은 현재 display list의 부분과는 상관없는 부분이다.

    glEndList(); // Done Building The box List 

이제, 두번째 Display list를 만들 것이다. 두번째 display list가 메모리에 저장될 곳을 찾기 위해 우리는 예전의 display list(box)에 1을 추가한다. 아래의 코드는 두번째 display list의 위치와 같은 ‘top’을 만들 것이다.

    top=box+1; // top List Value Is box List Value +1 

이제 두번째 display list을 저장할 곳을 알았으므로 뚜껑에 대한 list를 만들 수 있다. 첫번재 list를 만드는 같은 방법을 사용하지만 이번에는 ‘box’대신 ‘top’에 list를 저장한다고 OpenGL에게 알린다.

    glNewList(top,GL_COMPILE); // New Compiled top Display List

다음 코드는 단지 박스의 꼭대기를 그려주는 것이다. Z 평면 상의 간단한 사각형이다.

    glBegin(GL_QUADS); // Start Drawing Quad 
    // Top Face 
    // - Top Left Of The Texture and Quad 
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
    // - Bottom Left Of The    Texture and Quad 
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); 
    // - Bottom Right Of    The Texture and Quad 
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);  
    // - Top Right Of The    Texture and Quad 
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); 
    glEnd(); // Done Drawing Quad 

다시 우리는 OpenGL에게 두번째 list를 생성했다고 glEndList() 명령을 이용해 알린다. 바로 아래처럼. 우리는 성공적으로 2개의 display list를 생성했다.

    glEndList(); // Done Building The top Display List 
}

비트맵/텍스쳐를 생성하는 코드는 이전에 강좌의 비트맵을 로딩하고 텍스쳐를 생성하는 코드와 같다. (역자주: Dip2K의 튜터리얼도 NeHe의 텍스쳐 맵핑 방법과 동일하다. 우리는 각각의 큐브의 모든 6면 상에 맵핑할 텍스쳐를 원한다.좀더 부드러운 텍스쳐 맵핑을 위해 Mipmapping을 사용하고자 한다. 필자는 픽셀을 보는 것을 무척 싫어한다. (역자주: 픽셀을 본다는 것은 알리아싱 현상을 의미한다) ‘cube.bmp’라는 비트맵을 로딩한다. data 디렉토리에 이 비트맵 파일이 위치한다. LoadBMP 함수를 찾아서 다음과 같이 수정한다.

if (TextureImage[0]=LoadBMP("Data/Cube.bmp")) // Load The Bitmap 

폼의 크기가 재조정될때 발생하는 이벤트의 코드는 강좌 6의 코드와 동일하다.

init 코드는 약간의 수정만이 필요하다. BuildList() 코드를 추가한다. 이것은 앞에서 display list를 생성하는 코드를 담고 있다. LoadGLTextures()함수 이후에 BuildList() 를 호출한다는 것에 주의해야 한다. 이것은 BuildList() 함수에서 텍스처를 필요로 하므로 먼저 텍스쳐를 생성해야 한다는 것이다.

int InitGL(GLvoid) // All Setup For OpenGL Goes Here 
{ 
    if (!LoadGLTextures()) // Jump To Texture Loading Routine 
    { 
        return FALSE; // If Texture Didn't Load Return FALSE 
    } 

    BuildLists(); // Jump To The Code That Creates Our Display Lists 
    glEnable(GL_TEXTURE_2D); // Enable Texture Mapping 
    glShadeModel(GL_SMOOTH); // Enable Smooth Shading 
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background 
    glClearDepth(1.0f); // Depth Buffer Setup 
    glEnable(GL_DEPTH_TEST); // Enables Depth Testing 
    glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do 

다음의 세줄의 코드는 quick & dirty 빛을 활성화 하는 것이다. (역자주: quick & dirty 광원은 비디오 카드에 기본적으로 설정된 광원이다) Light0는 대부분의 비디오 카드에 미리 정의된 것이다. light0을 활성화 함으로써 빛을 사용한다. 만약 독자의 비디오 카드에서 light0가 작동하지 않는다면(화면에 마냥 검다면) 그냥 광원을 꺼라.마지막 라인인 GL_COLOR_MATRIAL은 텍스쳐 맵핑에 색을 추가하도록 해 준다. 이 재질의 컬러링을 가능하지 않게 한다면, 텍스쳐는 항상 원래의 색상으로 나타난다. 즉 glColor3f(r,g,b)가 아무런 효과를 내지 못한다. 그래서 이것을 가능하게 해주는 것이 중요하다.

    glEnable(GL_LIGHT0); // Quick And Dirty Lighting (Assumes Light0 Is Set Up)    
    glEnable(GL_LIGHTING); // Enable Lighting 
    glEnable(GL_COLOR_MATERIAL); // Enable Material Coloring 

최종적으로 멋지게 보이도록 하기 위해서 투영에 대한 힌트를 GL에게 제공하고 초기화가 잘되었음을 알리기 위해 TRUE를 반환한다.

    // Nice Perspective Correction 
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 
   
    return TRUE; // Initialization Went OK 


이제 그려주는 코드에 대한 것이다. 일단 화면과 Depth Buffer를 청소하는 일부터 시작한다.

그 다음에 큐브에 텍스쳐 맵핑을 바인드한다. Display list 안에 이 텍스쳐 맵핑을 추가할 수 있었으나 그 밖으로 빼 놓았다. 이것으로써 원한다면 언제든 텍스쳐 맵핑을 변경할 수 있는 것이다. (역자주: 이미 앞에서 언급한대로 List를 만들때 색상이라든지 텍스쳐 맵핑을 지정하면 나중에 그 List의 객체의 색상, 텍스쳐 맵핑을 변경할 수 없다)

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);      
    glBindTexture(GL_TEXTURE_2D, texture[0]); // Select The Texture   

이제 재미있는 것에 대해 살펴보자. yloop라 불리는 반복문이 있다. 이 반복문은 큐브들의 Y 축상(위에서 아래)에 위치시키는데 사용된다. 위에서 아래로 5개의 큐브를 원하므로 6보다 하나 작은 반복을 한다(즉, 다섯번)

for (yloop=1;yloop<6;yloop++) // Loop Through The Y Plane 
{ 

xloop라는 다른 반복문이 있다. 이것은 X 축상(왼쪽에서 오른쪽)에 큐브들을 위치시키는데 사용된다. 왼쪽에서 오른쪽으로 그리고자 하는 큐브는 정해져 있지 않다. 만약 가장 위쪽의 줄이라면 xloop는 0에서 1까지의 xloop 반복이면 된다. 그리고 그 다음 줄의 xloop는 0에서 2가 될것이다(즉, 2개의 큐브가 그려져야 한다) (역자주: 계속 하나씩 증가한다, 실제 결과 프로그램을 실행하여 살펴보면 확실히 알수있다)

    // Loop Through The X Plane
    for (xloop=0;xloop    { 

glLoadIdentity()를 호출함으로써 View를 초기화한다.

            glLoadIdentity(); // Reset The View  

다음은 화면상의 지정된 지점으로 이동시키는 코드이다. 이것은 다소 헤깔리지만 실제로는 그렇지 않다. X축 상에서, 다음의 내용이 이뤄진다 :

큐브들이 모여 이뤄진 피라미드가 화면상의 중심에 놓여지도록 오른쪽으로 1.4 유닛만큼 이동한다. 그 다음에 xloop에 2.8을 곱한후 다시 여기에 1.4를 더한다. 마지막으로 yloop*1.4를 뺀다. 이것은 큐브들을 그들이 위치한 줄에 따라 왼쪽으로 이동하게 된다.

Y축상에서 6에서 yloop를 뺌으로써 아래 방향으로 피라미드가 만들어진다. 다음에 그 결과에 2.4를 곱한다. 큐브들은 y축 상의 각각의 꼭대기 상에 놓여진다. 다음에 7을 빼는데 이것은 피라미드가 화면의 아래쪽에서 시작하고 윈쪽으로 만들어지게 한다.

최종적으로 Z축 상에서 화면 안쪽으로 20 유닛 이동한다. 이것은 피라미드를 멋지게 만든다.

    // Position The Cubes On The Screen 
    glTranslatef(1.4f+(float(xloop)*2.8f)-(float(yloop)*1.4f),
        ((6.0f-float(yloop))*2.4f)-7.0f,-20.0f);  

이제 x축 상으로 회전시켜 보자. 우리는 큐브를 뷰쪽으로 45도에서 yloop*2를 뺀 상태로 기울인다.. 퍼스펙티브 모드는 자동으로 큐브들을 기울이므로 이 기울어짐을 제거 했다. 이것은 최선은 아니지만 원하는대로 작동한다. 🙂

최종적으로 xrot를 더한다. 이것은 키보드를 통해 각을 조정할 수있도록 한다.

x축으로 회전한후에 y축상으로 45도 회전하고 yrot을 더하는데 이것은 키보드를 통해서 yrot값을 조정할 수 있다.

glRotatef(45.0f-(2.0f*yloop)+xrot,1.0f,0.0f,0.0f); // Tilt The Cubes Up And Down 
glRotatef(45.0f+yrot,0.0f,1.0f,0.0f); // Spin Cubes Left And Right 

다음에 큐브들이 위치한 줄에 따라 달리 색상을 지정한다.

            glColor3fv(boxcol[yloop-1]); // Select A Box Color

이제 박스에 대한 List를 호출한다.

            glCallList(box); // Draw The Box

큐브의 꼭대기에 놓여질 뚜껑의 색상을 지정한다.

            glColor3fv(topcol[yloop-1]); // Select The Top Color

뚜껑에 대한 List를 호출하고 모든것이 잘되었으므로 TRUE를 호출한다.

            glCallList(top); // Draw The Top 
        } 
    } 
    return TRUE; // Jump Back 
} 

이제 남은 코드들은 WinMain 함수에서 이뤄지는데 다음은 키보드에 대해 xrot와 yrot의 값을 변경해 주는 키보드 입력 처리 코드이다. SwapBuffer(hDC) 코드 다음에 놓여진다.

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

if (keys[VK_LEFT]) // Left Arrow Being Pressed? 
{ 
    yrot-=0.2f; // If So Spin Cubes Left 
} 

if (keys[VK_RIGHT]) // Right Arrow Being Pressed? 
{ 
    yrot+=0.2f; // If So Spin Cubes Right 
}

if (keys[VK_UP]) // Up Arrow Being Pressed? 
{ 
    xrot-=0.2f; // If So Tilt Cubes Up 
} 

if (keys[VK_DOWN]) // Down Arrow Being Pressed? 
{ 
    xrot+=0.2f; // If So Tilt Cubes Down 
} 

<변역 끝>

변역을 여기서 끝마친다. 사실 이 강좌는 Display List에 대한 장이다. Display List에 대한 사용법이 쉽다는 것을 알 수 있다. 필자는 이 강좌를 번역할때 List를 제작하고 사용하는 방법에 대해서는 최대한 모든내용을 번역했지만 후반부의 큐브를 화면상에 피라미드 모양으로 위치시키는 세세한 내용은 그냥 술~ 술~ 넘기며 번역했음을 고백한다.