09. 3D 공간에서 비트맵을 움직여 보자


소개


이 강좌에서는 여러분들이 요청하신 몇몇 주제들을 다루겠습니다. 3D 공간에서 물체들을 움직이는 방법을 알고 싶어하시는 분들이 계셨죠? 투명한 비트맵을 그리는 법을 알고 싶어하셨던 분들도 계셨습니다. 간단한 애니메이션과 블렌딩의 다른 용도를 사용하는 법을 싶어하셨던 분들도 계셨더군요. 이 강좌에서 이 모두를 가르쳐 드리겠습니다. 더 이상 제자리에서 뱅글뱅글 회전하는 상자를 보지 않으셔도 됩니다! 여지까지의 강좌들은 OpenGL의 기초를 다뤘습니다. 여태까지 모든 강좌들이 바로 전의 강좌를 확장한 것처럼 이번 강좌에서도 여태까지 배우신 모든 내용에 3차원에서 물체를 움직이는 방법을 추가해봅시다. 이 강좌는 이전 강좌들에 비해 조금 더 어려우니 지난 강좌들을 잘 숙지하도록 하세요.


본문

제9강에 오신것을 환영합니다. 지금쯤이면 모두들 OpenGL을 매우 잘 이해하고 계시겠군요. OpenGL 창을 설정하는 법부터 텍스처매핑, 조명, 블렌딩에 이르기까지 모든 것을 배우셨죠? 이번 강좌가 첫 중급 강좌가 되겠군요. 이 강좌에서는 3D 공간 속에서 비트맵을 움직이는 법과 비트맵 주변의 검정 픽셀들을 제거하는 법(블렌딩을 사용), 그레이스케일 텍스처에 색깔을 더하는 법과 여러색의 텍스처들을 혼합하여 그럴듯한 색상과 간단한 애니메이션을 만드는 법을 배울 것입니다.

이 강좌에서는 제1강에서 사용했던 코드를 수정할 겁니다. 우선 프로그램의 앞부분에 몇 개의 새로운 변수를 추가합시다. 이 부분의 코드를 모두 다시 보여 드리겠습니다. 이러면 어디를 바꿨는지 알아보기에 더 편하겠죠?


#include    <windows.h>                    // 윈도우즈용 헤더파일
#include    <stdio.h>                      // 표준 입축력용 헤더파일
#include    <gl\gl.h>                      // OpenGL32 라이브러리용 헤더파일
#include    <gl\glu.h>                     // GLu32 라이브러리용 헤더파일
#include    <gl\glaux.h>                   // GLaux 라이브러리용 헤더파일

HDC         hDC=NULL;                      // 전용 GDI 장치 컨텍스트
HGLRC       hRC=NULL;                      // 렌더링 컨텍스트
HWND        hWnd=NULL;                     // 이 프로그램 창의 핸들
HINSTANCE   hInstance;                     // 이 프로그램의 인스턴스

bool        keys[256];                     // 키보드 루틴에 사용하는 배열
bool        active=TRUE;                   // 창 활성화 플래그. 기본 값은 TRUE
bool        fullscreen=TRUE;               // 전체화면 플래그. 기본값은 전체화면


이제 새로 추가된 코드들을 살펴보지요. twinkle과 tp는 불리언 변수로서 TRUE(참)나 FALSE(거짓)의 값을 가집니다. twinkle은 반짝이는 효과를 사용하는지를 나타내며, tp는 'T'키가 현재 눌리고 있는지를 확인합니다. (tp=TRUE이면 눌린 것이고 tp=FALSE이면 더 이상 안눌리고 있는 것입니다.)

BOOL    twinkle;                           // 반짝 반짝 작은 별
BOOL    tp;                                // 'T' 키가 눌렸는가?

num은 화면에 그릴 별의 갯수를 기억합니다. 이것은 상수(const)로 정의되어 있으므로 코드 안에서 값을 바꾸는 것이 불가능합니다. 이것을 상수로 정의한 이유는 이걸 이용해서 정적 배열을 만들기 때문입니다. 따라서 코드 어디에선가 num을 51로 증가시켜도 배열을 51로 자라나게 할 수 없거든요. num을 상수로 정의했으므로 이 라인에서만 이 값을 변경할수 있습니다. 코드의 다른 부분에서 이 값을 변경하려고 하지 마세요. 만약 그러면 불행한 일(?)이 일어날 겁니다.

const    num=50;                           // 화면에 그릴 별의 수


이제 구조체를 하나 만들겠습니다. 구조체라는 말이 어렵게 들린다구요? 전혀 그렇지 않습니다. 간단히 말해 구조체는 단순한 데이터들(변수 등)을 한데 모아놓은 그룹에 지나지 않습니다. 별들의 정보를 구조체로 저장해보죠. 코드를 7줄 더 아래로 내려보면 stars 구조체가 보일 겁니다. 각 별은 r, g, b 색상 값을 가집니다. (각 값은 정수형입니다.)  당연히 r = 빨강색, g = 녹색, b = 파랑색이겠지요. 각 별들은 화면 중앙으로부터 어느 정도 떨어진 거리에 위치할 것이며, 화면중앙과 별이 이루는 각도도 다양할 겁니다. 4번째 라인을 보시면 dist라는 부동소수점 변수가 있죠? 이것이 거리를 기억합니다. 5번째 라인인 angle은 별의 각도를 기억하는 부동소수점 변수입니다..

이제 별 하나의 색상, 거리, 각도를 묘사하는 데이터 그룹을 만들었습니다. 불행히도 기억해야 할 별의 수는 하나가 아니지요? 50개의 빨강색 값, 50개의 녹색 값, 50개의 파랑색 값, 50개의 거리 값, 50개의 각도 값을 생성하는 대신 star라고 불리는 배열을 만듭시다. star 배열의 각 색인은 이 모든 정보를 담고 있는 stars 구조체를 가집니다. 8번째 줄 코드에서 star 배열을 만듭니다. 8번째줄을 자세히 살펴볼까요? Stars star[num]이라고 되어 있군요. 배열의 형(type)은 stars로, 구조체입니다. 각 배열의 요소가 이 구조체란 말이지요. 이 배열의 이름은 star고, 배열이 가지는 요소의 수는 [num]입니다. num = 50 이므로 이제 모든 별들의 정보가 star라고 이름에 들어가겠습니다. 이러는 것이 여러 개의 변수들을 사용해 각 별의 정보를 기억하는 것보다 훨씬 쉽습니다. 그리고 별을 추가 및 제거하려고 할 땐, 간단히 상수값인 num을 변경하면 되지요.
        
typedef struct                            // 별 구조체
{
    int r, g, b;                          // 별 색상
    GLfloat dist;                         // 중점으로부터 별까지의 거리
    GLfloat angle;                        // 별의 현재 각도
}
stars;                                    // 구조체 이름이 stars임
stars star[num];                          // 'stars'구조체에 담겨있는 정보로부터 'num' 개수 만큼의 'star' 배열을 만듬

이제 화면으로부터 별까지의 거리를 기억할 변수(zoom)와 별의 각도를 기억할 변수(tilt)를 선언합니다. 또한 z축 상에서 별들을 회전시키는 spin이라는 변수를 만듭니다. 이러면 현재 위치에서 별들이 회전하는 것처럼 보일 것입니다.

loop는 50개의 별들을 그릴 때 사용할 변수이며, texture[1]은 좀 있다 읽어올 흑백 텍스처를 저장할 것입니다. 다른 텍스쳐도 사용하시려면 사용할 텍스처의 수만큼 배열을 선언해주세요.

        
GLfloat    zoom=-15.0f;                   // 별의 가시거리
GLfloat tilt=90.0f;                       // 뷰를 기울인다
GLfloat    spin;                          // 반짝이는 별들을 회전시킨다

GLuint    loop;                           // 일반 루프 변수
GLuint    texture[1];                     // 텍스처 하나를 저장할 공간

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);        // WndProc 선언


위의 라인 바로 다음에 텍스처를 로딩하는 코드를 추가하겠습니다. 이 코드를 자세히 설명할 필요는 없겠죠? 이것은 6, 7, 8강에서 텍스처를 로딩할 때 사용했던 것과 동일한 코드거든요. 이번에 읽어올 비트맵은 star.bmp입니다. glGenTextures(1, &texture[0])을 이용하여 텍스처를 하나 만듭니다. 이 텍스처는 선형(linear) 필터링을 사용할 것입니다.
        
        
AUX_RGBImageRec *LoadBMP(char *Filename)  // 비트맵 이미지를 로딩함
{
    FILE *File=NULL;                      // 파일 핸들

    if (!Filename)                        // 파일명을 매개변수로 받았는지 확인
    {
        return NULL;                      // 그렇지 않다면 NULL을 반환
    }

    File=fopen(Filename,"r");             // 파일이 존재하는지 확인

    if (File)                             // 파일이 존재하면?
    {
        fclose(File);                     // 파일 핸들을 닫음
        return auxDIBImageLoad(Filename); // 비트맵을 로딩하고 포인터를 반환
    }
    return NULL;                          // 로딩에 실패한 경우 NULL을 반환
}


이제 위의 코드를 호출하여 비트맵을 로딩하고 이것을 텍스처로 변환해 봅시다. Status 변수를 사용하여 텍스처를 로딩하고 생성했는지를 기억합니다.
   
This is the section of code that loads the bitmap (calling the code above) and converts it into a textures. Status is used to keep track of whether or not the texture was loaded and created.     
        
int LoadGLTextures()                        // 비트맵을 로딩한 뒤 텍스처로 변환합니다.
{
    int Status=FALSE;                    // 현재 상태

    AUX_RGBImageRec *TextureImage[1];            // 텍스처를 저장할 공간을 만듭니다.

    memset(TextureImage,0,sizeof(void *)*1);        // 포인터를 NULL로 지정합니다.

    // 비트맵을 로딩한 뒤, 오류를 검사합니다. 만약 비트맵을 찾지 못했다면 리턴합니다.
    if (TextureImage[0]=LoadBMP("Data/Star.bmp"))
    {
        Status=TRUE;                    // 상태를 TRUE로 바꿉니다.

        glGenTextures(1, &texture[0]);            // 텍스처 하나를 만듭니다.

        // 선형 필터링을 사용하는 텍스처를 만듭니다.
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
    }

    if (TextureImage[0])                    // 텍스처가 존재한다면
    {
        if (TextureImage[0]->data)            // 그리고 텍스처 이미지가 존제한다면
        {
            free(TextureImage[0]->data);        // 텍스처 이미지 메모리를 반환(free)하고
        }

        free(TextureImage[0]);                // 이미지 구조체도 반환(free)합니다.
    }

    return Status;                        // 그리고 상태를 리턴합니다.
}


이제 OpenGL의 렌더링 방식을 바꿔어봅시다. 이 프로젝트에서는 깊이 테스트를 사용하지 않을 것이므로 제1강의 코드에서 glDepthFunc(GL_LEQUAL); 과 glEnable(GL_DEPTH_TEST); 를 삭제하도록 합시다. 이러지 않으면 잘못된 결과가 발생할 것입니다. 하지만 여기서 텍스처 맵핑을 사용할 것이므로 다음의 라인들을 추가하도록 합지요. 텍스처 매핑과 블렌딩을 모두 사용하는거 보이시죠?

Now we set up OpenGL to render the way we want. We're not going to be using Depth Testing in this project, so make sure if you're using the code from lesson one that you remove glDepthFunc(GL_LEQUAL); and glEnable(GL_DEPTH_TEST); otherwise you'll see some very bad results. We're using texture mapping in this code however so you'll want to make sure you add any lines that are not in lesson 1. You'll notice we're enabling texture mapping, along with blending.     
        

int InitGL(GLvoid)                        // 모든 OpenGL 설정을 여기에 정의합니다.
{
    if (!LoadGLTextures())                    // 텍스처 로딩 루틴을 호출합니다
    {
        return FALSE;                    // 텍스쳐를 읽어올 수 없었다면 FALSE를 리턴합니다.
    }

    glEnable(GL_TEXTURE_2D);                // 텍스쳐 매핑을 켭니다.
    glShadeModel(GL_SMOOTH);                // 부드러운(Smooth) 쉐이딩을 켭니다.
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);            // 바탕색은 검정입니다.
    glClearDepth(1.0f);                    // 깊이버퍼 설정
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // 매우 뛰어난 원근 계산을 사용합니다.
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);            // 투명 블렌딩 모드를 사용합니다.
    glEnable(GL_BLEND);                    // 블렌딩을 켭니다.


        
아래의 코드는 새로운 코드입니다. 각 별의 시작 각도와, 거리를 정의합니다. 이 구조체 안에 정보를 바꾸는 것이 매우 쉽다는 거 보이시죠? 이 루프는 50개 별 전부를 훑습니다. star[1]의 각도를 바꾸려면 간단히 star[1].angle={숫자}라는 코드를 쓰면 됩니다. 매우 간단하죠?

The following code is new. It sets up the starting angle, distance, and color of each star. Notice how easy it is to change the information in the structure. The loop will go through all 50 stars. To change the angle of star[1] all we have to do is say star[1].angle={some number} . It's that simple!     
        

    for (loop=0; loop<num; loop++)                // 모든 별들을 훑는루프를 만듭니다.
    {
        star[loop].angle=0.0f;                // 모든 별들의 시작가도를 0으로 설정합니다.


        
현재 별의 색인 값을 총 별의 수로 나눈 뒤 5를 곱하는 방법으로 별의 거리를 계산하도록 하죠. 이러면 각 별을 바로 전의 별보다 조금씩 멀어지게 할 수 있습니다. 루프의 색인이 50(마지막 별)이 되면 이것의 총 별의 수로 나누면 1.0f이 되고 여기에 5.0f을 곱하면 5.0이 됩니다. 5.0은 화면의 테두리입니다. 별이 화면 밖으로 나가는 것을 원치 않으니 5.0f은 완벽한 값입니다. 만약 카레라를 뒤로 좀더 잡아당긴다면 5.0f보다 큰 수를 사용할 수 도 있습니다. 하지만 원근법 때문에 별이 훨씬 작아 보이겠지요?

I calculate the distance by taking the current star (which is the value of loop) and dividing it by the maximum amount of stars there can be. Then I multiply the result by 5.0f. Basically what this does is moves each star a little bit farther than the previous star. When loop is 50 (the last star), loop divided by num will be 1.0f. The reason I multiply by 5.0f is because 1.0f*5.0f is 5.0f. 5.0f is the very edge of the screen. I don't want stars going off the screen so 5.0f is perfect. If you set the zoom further into the screen you could use a higher number than 5.0f, but your stars would be alot smaller (because of perspective).

각 별의 색상은 0~255 사이의 값을 무작위로 골라보기로 합시다. 보통 색상값은 0.0f ~ 1.0f인데 왜 이렇게 큰 수를 사용하냐구요? glColor4f대신 glColor4ub 함수를 사용하면 부호없는 바이트 값을 사용할 수 있습니다. ub가 바로 부호없는 바이트(Unsigned Byte)를 나타내거든요. 바이트는 0~255 사이의 값을 가지는 거 다들 아시죠? 랜덤한 부동소숫점 값을 만드는 것 보다 랜덤한 바이트를 만드는 것이 더 쉬우니 바이트를 사용하도록 합시다.

You'll notice that the colors for each star are made up of random values from 0 to 255. You might be wondering how we can use such large values when normally the colors are from 0.0f to 1.0f. When we set the color we'll use glColor4ub instead of glColor4f. ub means Unsigned Byte. A byte can be any value from 0 to 255. In this program it's easier to use bytes than to come up with a random floating point value.     
        

        star[loop].dist=(float(loop)/num)*5.0f;        // 중심으로부터의 거리를 계산합니다.
        star[loop].r=rand()%256;            // star[loop]에 랜덤한 빨강 명도를 대입합니다.
        star[loop].g=rand()%256;            // star[loop]에 랜덤한 연두 명도를 대입합니다.
        star[loop].b=rand()%256;            // star[loop]에 랜덤한 파랑 명도를 대입합니다.
    }
    return TRUE;                        // 초기화를 훌륭히 마쳤습니다.
}


        
Resize 코드는 변한게 없으니 곧바로 렌더링 코드를 살펴보도록 합시다. 강좌 1의 코드를 사용하신다면 DrawGLScene 코드를 삭제하시고 아래의 코드를 대신 복사해 넣으세요. 제1강의 코드는 2줄밖에 안되니 그다지 지울게 많지도 않을 겁니다.

The Resize code is the same, so we'll jump to the drawing code. If you're using the code from lesson one, delete the DrawGLScene code, and just copy what I have below. There's only 2 lines of code in lesson one anyways, so there's not a lot to delete.     
        

int DrawGLScene(GLvoid)                        // 모든 그리기를 여기에서 합니다.
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // 화면과 깊이버퍼를 지웁니다.
    glBindTexture(GL_TEXTURE_2D, texture[0]);        // 텍스처를 선택합니다.

    for (loop=0; loop<num; loop++)                // 모든 텍스처를 루프로 훑습니다.
    {
        glLoadIdentity();                // 각 별을 그리기 전에 뷰(view)를 리셋합니다.
        glTranslatef(0.0f,0.0f,zoom);            // 'zoom' 값을 사용하여 화면을 확대합니다.
        glRotatef(tilt,1.0f,0.0f,0.0f);            // 'tilt' 값을 사용하여 뷰를 기울입니다.


        
이제 별들을 움직여 봅시다. 별들은 화면의 중앙에서 시작합니다. 우선 x축 상에서 화면을 회전시킵니다. 90도만큼 회전시키면 x축이 더이상 오른쪽을 향하지 않을 것입니다. 그 대신 화면 바깥쪽을 가르킬 것입니다. 설명을 돕기위해 예를 들어보도록 하죠. 여러분이 방의 중앙에 서계시다고 생각해보세요. 왼쪽 벽에 -x라는 글자가 써져있고 앞쪽 벽에는 -z, 오른쪽벽에서 +x, 뒤쪽벽에는 +z가 써져있습니다. 이제 이 방을 오른쪽으로 90도 회전시켜봅시다. 하지만 본인 자신은 회전하지 않았다고 하고요. 그러면 앞쪽벽은 더이상 -z가 아니라 -x일 것입니다. 그리고 오른쪽벽은 -z, 왼쪽벽은 +z, 앞쪽은 -x, 그리고 뒤쪽벽은 +x 이겠죠? 이해가 되시나요? 장면(scene)을 회전시키면 x와 z 평면의 방향을 바꿀 수 있습니다.

Now we move the star. The star starts off in the middle of the screen. The first thing we do is spin the scene on the x axis. If we spin 90 degrees, the x axis will no longer run left to right, it will run into and out of the screen. As an example to help clarify. Imagine you were in the center of a room. Now imagine that the left wall had -x written on it, the front wall had -z written on it, the right wall had +x written on it, and the wall behind you had +z written on it. If the room spun 90 degrees to the right, but you did not move, the wall in front of you would no longer say -z it would say -x. All of the walls would have moved. -z would be on the right, +z would be on the left, -x would be in front, and +x would be behind you. Make sense? By rotating the scene, we change the direction of the x and z planes.

두번째 코드라인은 x 평면상에서 양수값만큼 이동시킵니다. x에 양수값을 더하면 화면의 오른쪽(보통 +x가 향하는 방향입니다)으로 이동을 할것이지만 이미 y 평면상에서 회전을 했으니 +x는 어디에도 있을 수 있습니다. 180도만큼 회전을 시켰다면 오른쪽 대신의 화면의 왼쪽이 +x겠지요. 따라서 양의 x평면을 따라 앞쪽으로 움직이는 것은 전후좌우 어느방향도 될 수 있습니다.

The second line of code moves to a positive value on the x plane. Normally a positive value on x would move us to the right side of the screen (where +x usually is), but because we've rotated on the y plane, the +x could be anywhere. If we rotated by 180 degrees, it would be on the left side of the screen instead of the right. So when we move forward on the positive x plane, we could be moving left, right, forward or backward.     
        

        glRotatef(star[loop].angle,0.0f,1.0f,0.0f);    // 현재 별의 각도만큼 회전시킵니다.
        glTranslatef(star[loop].dist,0.0f,0.0f);       // X평면의 앞쪽으로 이동시킵니다.


        
이제 조금 난이도가 있는 코드군요. 별은 사실 평평한 텍스처입니다. 이제 이 평평한 사각형(quad)를 화면 중간에 그리고 텍스처릴 씌우면 괜찮아 보이겠지요. 하지만 이것을 y 축 상에서 90도만큼 회전시키면 텍스처가 화면의 왼쪽과 오른쪽 측면을 향할 것입니다. 따라서 실선 하나만 보이지요. 당연히 이러면 안되겠지요? 회전과 기울기 값에 상관없이 별들이 언제나 화면을 향해야 합니다.

Now for some tricky code. The star is actually a flat texture. Now if you drew a flat quad in the middle of the screen and texture mapped it, it would look fine. It would be facing you like it should. But if you rotated on the y axis by 90 degrees, the texture would be facing the right and left sides of the screen. All you'd see is a thin line. We don't want that to happen. We want the stars to face the screen all the time, no matter how much we rotate and tilt the screen.

그럼 별을 그리기 바로 전에 앞서 더해줬던 회전을 되돌리는 방법을 사용해보죠. 반대방향으로 회전을 되돌리면 회전을 취소시키는 것과 동일한 효과겠지요? 앞에서는 화면을 기울인 후에 별들을 회전시켰으니 여기서는 우선 별들을 반대로 회전시킵니다. 전에 사용했던 각의 부호를 바꾸면 되지요. 만약 10도만큼 회전시켰다면 -10도로 회전시키면 별들이 화면을 다시 향하게 됩니다. 따라서 아래 첫번째 코드라인이 y 축 상에서의 회전을 취소시킵니다. 그 다음 x축상에서 화면 기울기를 취소해야합니다. 이것은 화면을 -tilt만큼 기울임으로써 해결할 수 있습니다. 이렇게 x와 y 회전을 취소시켜주면 별이 화면을 똑바로 바라볼 것 입니다.

We do this by cancelling any rotations that we've made, just before we draw the star. You cancel the rotations in reverse order. So above we tilted the screen, then we rotated to the stars current angle. In reverse order, we'd un-rotate (new word) the stars current angle. To do this we use the negative value of the angle, and rotate by that. So if we rotated the star by 10 degrees, rotating it back -10 degrees will make the star face the screen once again on that axis. So the first line below cancels the rotation on the y axis. Then we need to cancel the screen tilt on the x axis. To do that we just tilt the screen by -tilt. After we've cancelled the x and y rotations, the star will face the screen completely.     
        

        glRotatef(-star[loop].angle,0.0f,1.0f,0.0f);    // 별의 회전을 되돌린다.
        glRotatef(-tilt,1.0f,0.0f,0.0f);        // 화면 기울기를 취소한다.


        
만약 twinkle이 TRUE이면 회전하지 않는 별을 그립니다. 다른 색을 구하기 위해 별의 최대수(num)을 가져와 현재 별 색인(loop)을 뺀 뒤, 1을 뺍니다. 루프가 0부터 num - 1 까지 돌기 때문입니다. 만약 그 결과가 10이라면 10번째 별의 색을 가져와 사용합니다. 이런 방법을 사용하면 각 별의 색이 다르게 되겠죠. 썩 훌륭한 방법은 아닌데 효과적이죠. 마지막 값은 알파 값입니다. 이 값이 작을 수로 별이 어둡게 보입니다.

If twinkle is TRUE, we'll draw a non-spinning star on the screen. To get a different color, we take the maximum number of stars (num) and subtract the current stars number (loop), then subtract 1 because our loop only goes from 0 to num-1. If the result was 10 we'd use the color from star number 10. That way the color of the two stars is usually different. Not a good way to do it, but effective. The last value is the alpha value. The lower the value, the darker the star is.

twinkle이 참이라면 각 별을 두번씩 그립니다. 정말 오래된 컴퓨터에서는 이것 때문에 프로그램이 좀 느려질 것입니다. 만약 twinkle이 참이라면 두 별의 색을 혼합하여 정말 그럴듯한 색상을 만들 것입니다. 또한 이 별은 회전하지 않으므로 '반짝반짝 작은별 모드'(twinkle)모드가 켜있을 때 꼭 별이 움직이는(animated) 처럼 보일 겁니다. (무슨 말인지 모르시겠다면 직접 프로그램을 실행해보시면 아실 겁니다.)

If twinkle is enabled, each star will be drawn twice. This will slow down the program a little depending on what type of computer you have. If twinkle is enabled, the colors from the two stars will mix together creating some really nice colors. Also because this star does not spin, it will appear as if the stars are animated when twinkling is enabled. (look for yourself if you don't understand what I mean).

텍스처에 색을 더하는게 얼마나 쉬운지 한 번 보세요. 비록 텍스처가 흑백이지만 이걸 그리기 전에 선택하는 색상이 될 것입니다. 또한 부동수소점(float point) 대신 바이트를 색상값으로 사용하는 것도 눈여겨 봐주세요. 알파 값조차 바이트죠?

Notice how easy it is to add color to the texture. Even though the texture is black and white, it will become whatever color we select before we draw the texture. Also take note that we're using bytes for the color values rather than floating point numbers. Even the alpha value is a byte.     
        

        if (twinkle)                    // '반짝반짝 작은별 모드'가 켜있음
        {
            // 바이트로 색상을 지정해준다.
            glColor4ub(star[(num-loop)-1].r,star[(num-loop)-1].g,star[(num-loop)-1].b,255);
            glBegin(GL_QUADS);          // 텍스처를 입힌 쿼드를 그리기 시작
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
            glEnd();                    // 텍스처 쿼드 그리기를 마침
        }

이제 main 별을 그릴 차례입니다. 위의 코드와 다른 점이라면 이 별은 언제나 그려진다는 것과, z 축 주위를 회전한다는 것이지요.

Now we draw the main star. The only difference from the code above is that this star is always drawn, and this star spins on the z axis.     
        
        glRotatef(spin,0.0f,0.0f,1.0f);            // Z 축에서 별을 회전시킨다.
        // 바이트로 색상을 지정해 준다
        glColor4ub(star[loop].r,star[loop].g,star[loop].b,255);
        glBegin(GL_QUADS);                // 텍스처를 입힌 쿼드를 그리기 시작
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
        glEnd();                    // 텍스처 쿼드 그리기를 마침

        
이제 모든 이동을 처리하는 코드입니다. 일반적은 별들은 회전 값을 증가함으로써 회전시킵니다. 그 다음 각 별의 각도를 변경합니다. 각 별의 각도는 loop/num 만큼 증가시킵니다. 이러면 중앙에서 멀리 떨어져 있는 별들이 더 빠르게 회전을 하게 됩니다. 중앙에 가까이 있는 별들일수록 느리게 회전하구요. 마지막으로 각 별의 거리(화면 중앙으로부터의 거리)를 줄입니다. 이러면 별들이 화면의 중앙으로 빨려들어가는 것처럼 보이게 합니다.

Here's where we do all the movement. We spin the normal stars by increasing the value of spin. Then we change the angle of each star. The angle of each star is increased by loop/num. What this does is spins the stars that are farther from the center faster. The stars closer to the center spin slower. Finally we decrease the distance each star is from the center of the screen. This makes the stars look as if they are being sucked into the middle of the screen.     
        
        spin+=0.01f;                    // 별들을 회전시키는데 사용함
        star[loop].angle+=float(loop)/num;        // 별의 각도를 변경
        star[loop].dist-=0.01f;                // 별의 거리를 변경

        
아래의 코드는 별들이 화면의 중앙에 도착했는지를 확인합니다. 별들이 화면의 중앙에 도착하면 새로운 색삭을 입혀줍니다. 그리고 중앙으로부터 5 단위 떨어지게 이동시켜주어 새로운 별이 되어 다시 중앙으로 행진하는 별이 되게 해줍니다.

The lines below check to see if the stars have hit the center of the screen or not. When a star hits the center of the screen it's given a new color, and is moved 5 units from the center, so it can start it's journey back to the center as a new star.     
        

        if (star[loop].dist<0.0f)            // 별이 중앙에 도착했는가?
        {
            star[loop].dist+=5.0f;            // 중앙으로부터 5단위 떨어진 곳으로 이동시킨다.
            star[loop].r=rand()%256;        // 새로운 빨강 값을 줌
            star[loop].g=rand()%256;        // 새로운 녹색 값을 줌
            star[loop].b=rand()%256;        // 새로운 파랑 값을 줌
        }
    }
    return TRUE;                        // 모든 일이 제대로 진행되었음
}


이제 키보드가 눌리는 것을 검사하는 코드를 추가해봅시다. WinMain() 함수로 가보죠. SwapBuffers(hDC)란 코드를 찾으세요. 바로 이 아래에 키보드를 검사하는 코드를 추가합시다.
        
Now we're going to add code to check if any keys are being pressed. Go down to WinMain(). Look for the line SwapBuffers(hDC). We'll add our key checking code right under that line. lines of code.

아래의 코드는 T키가 처음 눌릴 떄를 검사합니다. 만약 twinkle이 FALSE라면 이것이 TRUE가 되고 TRUE라면 FALSE가 되지요. T가 처음 눌릴 때, tp가 TRUE가 되니 계속 키를 누르고 있는다고 해도 twinkle 값이 계속 바뀌지 않게 하는 효과가 있답니다.

The lines below check to see if the T key has been pressed. If it has been pressed and it's not being held down the following will happen. If twinkle is FALSE, it will become TRUE. If it was TRUE, it will become FALSE. Once T is pressed tp will become TRUE. This prevents the code from running over and over again if you hold down the T key.     
        

        SwapBuffers(hDC);                // 버퍼를 스왑한다 (이중 버퍼링)
        if (keys['T'] && !tp)                // T 가 눌리고 tp가 FALSE 이면
        {
            tp=TRUE;                // tp를 TRUE로 만들고
            twinkle=!twinkle;            // twinkle의 값을 반전한다.
        }


다음의 코드는 T키를 누르는 것을 멈출 떄를 검사합니다. 그런 경우 tp = FALSE로 만들어 주지요. tp가 FALSE가 되지 않는한 T 키를 눌러도 아무일도 일어나지 않으니 아래의 코드는 매우 중요합니다.
The code below checks to see if you've let go of the T key. If you have, it makes tp=FALSE. Pressing the T key will do nothing unless tp is FALSE, so this section of code is very important.     
        

        if (!keys['T'])                    // T 키가 눌리지 않으면
        {
            tp=FALSE;                // tp를 FALSE로 만든다.
        }


나머지 코드는 상하 화살표 키와 Page Up, Page Down 키가가 눌리는 것을 검사하는 코드입니다.

The rest of the code checks to see if the up arrow, down arrow, page up or page down keys are being pressed.     
        

        if (keys[VK_UP])                // 위쪽 화살표 키가 눌리면
        {
            tilt-=0.5f;                // 화면을 위로 기울인다
        }

        if (keys[VK_DOWN])                // 아래쪽 화살표 키가 눌리면
        {
            tilt+=0.5f;                // 화면을 아래쪽으로 기울인다
        }

        if (keys[VK_PRIOR])                // Page Up 키가 눌리면
        {
            zoom-=0.2f;                // 화면을 축소한다
        }

        if (keys[VK_NEXT])                // Page Down 키가 눌리면
        {
            zoom+=0.2f;                // 화면을 확대한다
        }


여지껏 살펴봤던 강좌에서 모두 그랬듯이 창의 젤 위에 있는 타이틀을 올바르게 고칩니다.

Like all the previous tutorials, make sure the title at the top of the window is correct.     
        

        if (keys[VK_F1])                // F1 키가 눌리면
        {
            keys[VK_F1]=FALSE;            // 이 키를 FALSE로 만들고
            KillGLWindow();                // GL 창을 닫은 뒤
            fullscreen=!fullscreen;            // 전체화면 / 창 모드를 토글한다
            // Recreate Our OpenGL Window
            if (!CreateGLWindow("NeHe's Textures, Lighting & Keyboard Tutorial",640,480,16,fullscreen))
            {
                return 0;            // 창을 만드는데 실패하면 프로그램을 종료한다.
            }
        }
    }
}


이 강좌에서는 그레이스케일 비트맵 이미지를 추가하는 방법, 이 이미지 주위에 있는 검정공간을 제거하는 방법(블렌딩을 이요하여), 이미지에 색상을 추가하는 방법, 3D 공간에서 이미지를 움직이는 방법 등을 자세히 설명 드렸습니다. 또한 비트맵을 두번 겹쳐 그려서 아름다운 색과 애니메이션을 추가하는 법도 보여드렸습니다. 여태까지 여러분께 가르쳐 드렸던 내용들을 잘 이해하신 독자분들은 스스로 3D 데모를 만드는 데도 아무 문제가 없으실 것입니다. 모든 기초내용들을 다 알려드렸거든요!

In this tutorial I have tried to explain in as much detail how to load in a gray scale bitmap image, remove the black space around the image (using blending), add color to the image, and move the image around the screen in 3D. I've also shown you how to create beautiful colors and animation by overlapping a second copy of the bitmap on top of the original bitmap. Once you have a good understanding of everything I've taught you up till now, you should have no problems making 3D demos ofyour own. All the basics have been covered!

소스코드 다운로드

이 강좌의 소스코드를 다운받으실 수 있습니다. 자신의 환경에 맞는 파일을 받아 사용하세요.


문서정보

원문

  • 저자: Jeff Molofee (NeHe)
  • 원문보기: Lesson 09

번역문

현재 상태

  • 초벌번역시작 (2006년 8월 7일)
  • 초벌번역종료 (2011년 1월 17일)

Comments