• 북마크
  • 추가메뉴
어디로 앱에서 쉽고 간편하게!
애플 중고 거래 전문 플랫폼
오늘 하루 보지 않기
KMUG 케이머그

소프트웨어

[App 개발] NeHe Lesson 7

본문

원문이 윈도우 소스를 기준으로 서술되어 있어서 많은 부분이 바뀌어 있습니다. 특히 키보드 입력 부분은 제가 따로 첨부하는 소스를 참조하시는 것이 더 나을 것입니다. (사실 소스를 잘 만들진 못했습니다. 대충 돌아가게만... ^^;;;) 그래도 없는 것보다는 낫겠죠.

************************

이번에는 세 가지의 다른 텍스쳐 필터를 사용하는 방법, 키보드를 이용해서 물체를 움직이는 방법, 그리고 OpenGL 씬에 간단한 광원을 추가하는 방법을 설명하겠습니다. 이번 강의에서는 다양한 내용을 다루게 될 것이기 때문에 지금까지의 강의를 따라오는 데 어려움이 있으신 분들은 다시 한 번 살펴보십시오. 다음 과정으로 나아가기 전에 먼저 기초를 잘 다지는 것이 중요합니다.

언제나 그랬듯이 첫 번째 강의에서 만들었던 코드를 다시 사용할 것입니다. 코드에 변경이 많을 때에는 변경된 부분의 전체 함수를 출력하겠습니다. 먼저 새로운 변수를 추가하는 것부터 시작하죠.


#include <stdio.h>    // 기본 입출력 라이브러리


bool keys[256];    // 키보드 루틴에 사용할 배열
bool active=TRUE;  // 윈도우 활성화 플래그
bool fullscreen=TRUE;    // 전체화면 플래그


다음의 코드는 새로 추가된 것입니다. 세 가지의 논리 변수를 추가하겠습니다. BOOL이라는 것은 TRUE와 FALSE만을 가질 수 있는 변수를 뜻합니다. 먼저 light라는 변수를 만들어서 광원이 켜졌는지 꺼졌는지를 기록하는 변수로 사용할 것입니다. lp와 fp는 키보드'L' 과 'F'가 눌렸는지를 기록하는 변수입니다. 이런 변수들이 왜 필요한지는 이후에 설명하겠습니다. 일단 지금은 아주 중요하다는 것만 기억하시면 됩니다.


BOOL light;    // 광원 ON / OFF
BOOL lp;    // L 키보드
BOOL fp;    // F 키보드


이제 선언하는 다섯 개의 변수는 먼저 x축 회전 각도(xrot), y축 회전 각도(yrot), 나무 상자의 x축 회전 속도(xspeed), 상자의 y축 회전 속도 (yspeed), 그리고 나무 상자가 화면 속으로(z축 방향으로) 얼마나 깊이 들어가 있는지를 가리키는 z라는 변수입니다.


GLfloat xrot;    // X축 회전
GLfloat yrot;    // Y축 회전
GLfloat xspeed;    // X축 회전속도
GLfloat yspeed;    // Y축 회전속도
GLfloat z=-5.0f;    // 화면 속으로의 깊이


다음의 배열은 빛을 만드는 데 사용할 것입니다. 두 가지 종류의 빛을 만들 것인데, 첫 번째는 환경광(ambient light)입니다. 이 빛은 모든 방향에서 오는 것입니다. 여러분이 만드는 씬의 물체들은 환경광으로 비춰집니다. 두 번째 광원은 발산광(diffuse light)입니다. 이것은 광원으로부터 만들어져서 씬의 물체에 반사되어 보이는 빛입니다. 이 빛을 직접 받는 물체의 표면은 아주 밝은 빛을 가지게 되고 빛을 적게 받는 부분은 어둡게 보입니다. 이것은 우리가 만드는 상자의 옆면에 아주 멋있는 그림자 현상을 만들어냅니다.

빛은 색깔을 만드는 것과 동일한 방법으로 만듭니다. 첫 번재 인수가 1.0f이고 나머지 둘이 0.0f라면 밝은 붉은 색 빛이 만들어집니다. 만약 세 번째 인수가 1.0f이고 처음 둘이 0.0f라면 밝은 푸른 색이 됩니다. 맨 마지막 인수는 알파값입니다. 일단은 1.0f으로 놓아두겠습니다.

중간 밝기(0.5f)를 가지는 백색광을 만드는 인수를 저장해 두겠습니다. 모든 인수가 0.5f이기 때문에 빛은 완전히 어두운 색(검은색)과 완전히 밝은 색(흰색)의 중간값을 가집니다. 빨간색, 녹색, 파란색이 모두 같은 값을 가지므로 색상은 검은색과 흰 색 사이의 그림자 색상을 만듭니다. 환경광이 없으면 발산광을 받지 못한 부분은 매우 어둡게 표현될 것입니다.


GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };    // 환경광 인수값


이번에는 엄청 밝은 최고 밝기의 발산광 인수를 저장하는 배열입니다. 모든 값이 1.0f이기 때문에 낼 수 있는 최대 밝기의 빛이 됩니다. 발산광은 우리가 만드는 상자의 앞부분을 멋있게 비추어줄 것입니다.


GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };    // 발산광 인수값


마지막으로 빛의 위치를 저장해 둡니다. 처음의 세 값은 glTranslate()함수의 인수와 같은 값입니다. 첫 번째 숫자가 x축으로 오른쪽이나 왼쪽으로 움직이는 값, 두 번째 숫자가 y축으로 위나 아래로 움직이는 값, 세 번째 숫자가 z축으로 화면 안쪽이나 바깥쪽으로 움직이는 값입니다. 우리는 상자 바라 앞쪽에서 빛을 비출 것이기 때문에 왼쪽이나 오른쪽으로는 움직이지 않으므로 첫 번째 값은 0.0f(x축으로 움직임 없음)이 되며, 위아래로도 움직이지 않기 때문에 두 번째 값도 0.0f입니다. 빛은 항상 상자의 앞쪽에서 비추이도록 만들 것이기 때문에 광원은 화면 안쪽의 보는 사람 위치에 놓이게 됩니다. 모니터의 유리를 z축의 0.0f로 놓아 봅시다. 그러면 광원은 z축으로 2.0f위치에 놓이게 됩니다. 만약 빛을 진짜로 볼 수 있다면 그 빛은 모니터의 유리 앞쪽에 둥둥 떠 있는 모양이 될 것입니다. 이렇게 되면 빛이 상자 뒷면에 있게 하는 방법은 상자 역시 모니터의 유리 앞쪽에 놓이게 하는 방법밖에 없습니다. 물론 상자가 모니터 유리 뒤에 놓이지 않는다면 우리는 더 이상 상자를 볼 수 없게 되고, 그 때에는 빛이 어디 있건 아무 쓸모가 없습니다. 이해가 되십니까?  

세 번째 인수를 설명하는 것은 참 어렵습니다. 알아두어야 할 것은 -2.0f이 -5.0f보다 가깝다는 점, 그리고 -100.0f는 모니터 안쪽 깊숙히 위치한다는 점입니다. 이것이 0.0f로 오게 되면 이미지는 엄청나게 커져서 전체 모니터를 꽉 채울 것입니다.  이 값이 양수가 되면 화면에 더 이상 표시되지 않습니다 왜냐하면 "보이는 화면보다 먼저 지나가 버렸기 때문"입니다. 이것이 제가 화면 밖으로 나갔다고 하는 표현의 의미입니다. 물체는 그 곳에 있지만 더 이상 볼 수 없는 상태입니다.

마지막 숫자를 1.0f로 놓습니다. 이 값은 OpenGL이 광원을 만드는 위치를 지정하는 값입니다. 자세한 것은 나중에 설명하겠습니다.


GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };    // 광원의 위치


filter라는 변수는 어떤 텍스쳐를 현재 쓰고 있는지를 가리키는 변수입니다. 첫 번째 텍스쳐(0번)는 gl_nearest 로 만들어졌습니다. 두 번째 텍스쳐(1번)는 gl_linear 로 만들어 smoothing이 되어있는 상태입니다. 세 번째 텍스쳐(2번)은 mipmapped 텍스쳐입니다. filter변수의 값은 현재 사용하는 텍스쳐에 따라서 0, 1, 2의 값을 갖게 됩니다.

texture[3]변수는 텍스쳐 세 개의 값을 각각 texture[0], texture[1], texture[2]로 저장하는 변수입니다.


GLuint filter;    // 어떤 텍스쳐를 쓰고 있나
GLuint texture[3];    // 텍스쳐 저장소


이제 각각의 비트맵 파일을 읽어서 텍스쳐를 만들겠습니다. 예제에서는 glaux라이브러리를 써서 비트맵을 읽기 때문에 컴파일하기 전에 반드시 glaux라이브러리가 첨부되었는지 확인해야 합니다. 델파이와 비쥬얼 C++은 모두 glaux라이브러리가 있습니다만 다른 언어에서 지원하는지 확실하지 않습니다. 이제 새로 첨가되는 코드에 대해서 설명할 것입니다. 만약에 주석이 달리지 않은 코드가 어떤 일을 하는지 잘 모르시겠으면, tutorial 6을 보시면 비트맵을 읽어서 텍스쳐를 만드는 방법을 자세하게 소개하고 있습니다.

위의 코드와 ResizeGLScene()함수 사이에 다음의 코드를 첨가하겠습니다. tutorial 6에서 쓰던 비트맵 읽는 함수와 동일한 코드입니다. 이 코드가 어떻게 움직이는지 잘 모르시겠으면 tutorial 6으로 가서 보시면 자세한 설명이 있습니다.


AUX_RGBImageRec *LoadBMP(char *Filename)                                        {
    FILE *File=NULL;

    if (!Filename)                                                                
    {
        return NULL;
    }

    File=fopen(Filename,"r");

    if (File)                                                                      
    {
        fclose(File);
        return auxDIBImageLoad(Filename);
    }
        
    return NULL;
}


이번에는 위의 함수를 불러서 세 개의 비트맵 파일을 읽어서 텍스쳐로 만드는 부분입니다. Status라는 변수는 텍스쳐가 만들어졌는지 아닌지를 기록하는 변수입니다.


int LoadGLTextures()    // 비트맵을 읽어서 텍스쳐를 만든다
{
    int Status=FALSE;    // 상태 변수

    AUX_RGBImageRec *TextureImage[1];    // 텍스쳐 저장 변수

    memset(TextureImage,0,sizeof(void *)*1);    // 메모리의 초기화


이제 비트맵을 읽어서 텍스쳐를 만듭니다. TextureImage[0]=LoadBMP("Data/Crate.bmp")를 실행하면 LoadBMP()함수를 호출하여 Data디렉토리에 있는 Crate.bmp를 읽습니다. 모든 것이 순조롭게 되면 이미지 데이터는 TextureImage[0]에 저장되고 상태 변수를 TRUE로 바꾸어준 후 텍스쳐를 만들기 시작합니다.


    // 비트맵을 읽어서 오류가 있으면 끝
    if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
    {
        Status=TRUE;    // 오류가 없으므로 상태 변수는 TRUE



지금까지 이미지를 TextureImage[0]에 읽어들였고, 이제 이 데이터로 3개의 텍스쳐를 만들겠습니다. 다음의 코드는 OpenGL에게 세 개의 텍스쳐를 만들어서 texture[0], texture[1], texture[2]에 저장하도록 합니다.


glGenTextures(3, &texture[0]);    // 텍스쳐 세 개를 만든다


tutorial 6에서 사용한 선형 필터링 텍스쳐 맵은 많은 계산을 필요로 하지만 눈에 보기에는 아주 좋았습니다. 이번 강의에서 만드는 첫 번째 텍스쳐는 GL_NEAREST를 사용하는데, 이것은 기본적으로 전혀 필터링을 하지 않는 방법이라서 계산이 적게 드는 장점이 있지만 보기에는 별로 좋지 않습니다. 혹시 여러분이 해 보신 게임중에 텍스쳐가 울퉁불퉁해 보이는 것이 있었다면 아마 이 방법을 썼을 것입니다. 이 필터링의 유일한 장점은 느린 컴퓨터에서 빠른 속도로 텍스쳐를 사용할 수 있다는 점입니다.

MIN 과 MAG, 두 가지 모두 GL_NEAREST를 사용합니다. GL_NEAREST와 GL_LINEAR를 섞어서 써도 되고 보기에도좀 더 나아질 것입니다만, 일단 우리는 속도를 확인하는 목적에서 두 가지 다 이 방법을 쓰겠습니다. MIN_FILTER는 텍스쳐보다 출력 화면 크기가 작을 때에 쓰여질 필터를 지정하는 것이고, MAG_FILTER는 텍스쳐보다 출력 화면 크기가 클 때에 쓰여질 필터를 지정하는 것입니다.


glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);


다음에 만드는 텍스쳐는 tutorial 6에서 만든 것과 동일한 방식입니다. 한 가지 바뀐 것은 이번에는 texture[0] 대신 texture[1]에 저장한다는 점이죠. 왜냐면 이것이 우리가 쓸 두 번째 텍스쳐이기 때문입니다. 만약 texture[0]에다 저장하면 방금 위에서 만든 GL_NEAREST 텍스쳐를 덮어쓰게 됩니다.


        glBindTexture(GL_TEXTURE_2D, texture[1]);
        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);


텍스쳐를 만드는 새로운 방법, mipmapping입니다. 이미지를 화면에 아주 작게 출력하면 이미지의 세밀한 부분들이 사라져 버리고, 멋있어 보이던 패턴들이 헝클어지기 시작할 것입니다. OpenGL에게 텍스쳐를 mipmapped 방식으로 만들도록 하면 OpenGL은 다른 크기의 고품질 텍스쳐들을 만들어 놓고, 텍스쳐를 화면에 출력할 때 원래 이미지의 크기를 조절하는 대신 가장 세밀한 부분이 잘 드러나 있는 텍스쳐를 하나 골라서 사용하게 되어 세밀한 부분이 사라져 버리는 일을 방지하게 됩니다.

Tutorial 6을 공부할 때 텍스쳐의 넓이와 높이를 64, 128, 256 등의 2의 급수로 만들어야 한다고 말씀드렸습니다. 그러나 mipmapped 텍스쳐를 만들 때에는 어떤 크기의 이미지를 사용하셔도 상관없습니다. OpenGL이 자동으로 적당한 넓이와 높이로 조정할 것이기 때문입니다.

이것이 우리가 만드는 세 번째 텍스쳐이고, texture[2]에 저장합니다. 이제 우리는 필터링을 하지 않는 texture[0], 선형 필터링을 사용하는 texture[1], 그리고 mipmapped 텍스쳐 texture[2]를 이번 강의의 예제에서 사용할 것입니다.


glBindTexture(GL_TEXTURE_2D, texture[2]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);


다음 코드에서 mipmapped 텍스쳐를 만듭니다. 우리는 3원색 (빨간색, 녹색, 파란색)을 이용해서 2D 텍스쳐를 만들고 있습니다. TextureImage[0]->sizeX는 비트맵의 넓이, TextureImage[0]->sizeY는 비트맵의 높이, GL_RGB는 빨간색, 녹색, 파란색 순서로 비트맵 데이터가 구성되어있다는 뜻입니다. GL_UNSIGNED_BYTE는 텍스쳐 이미지 데이터 형식이 부호 없는 바이트 형식이라는 뜻이고, TextureImage[0]->data는 텍스쳐를 만들게 될 비트맵 이미지의 데이터 포인터입니다.


gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
        
}


이제 비트맵 데이터를 만들 때 사용한 메모리를 풀어줍니다. 비트맵 데이터가 TextureImage[0]에 저장되어 있는지를 확인하고, 데이터 포인터가 할당되어 있는지를 확인해서 지워줍니다. 그리고 이미지 구조체를 지워서 사용한 메모리가 모두 풀어졌는지 확인합니다.


if (TextureImage[0])    // 텍스쳐가 있으면
        {
                if (TextureImage[0]->data)    // 텍스쳐 이미지가 있으면
                {
free(TextureImage[0]->data);    // 지워준다
                }

                free(TextureImage[0]);    // 이미지 구조체를 지운다
        }


마지막으로 상태 변수를 리턴합니다. 모든것이 순조로웠다면 Status 변수의 값은 TRUE가 될 것이고, 문제가 있었다면 FALSE가 되었을 것입니다.

        return Status;    // 상태 변수 리턴
}


이제 텍스쳐를 읽고 OpenGL의 설정을 초기화합니다. InitGL()함수의 첫 번째 줄에서 위의 함수를 호출하여 텍스쳐를 읽습니다. 텍스쳐가 만들어진 다음 glEnable(GL_TEXTURE_2D) 함수를 호출하여 텍스쳐 매핑 기능을 켭니다. 음영 방식은 smooth shading으로 설정하고 배경색은 검정색, depth testing을 활성화하고 원근 계산을 최상으로 설정하겠습니다.


int InitGL(GLvoid)    // OpenGL 셋업
{
      if (!LoadGLTextures())    // 텍스쳐를 읽는다
        {
                return FALSE;    // 실패하면 대략 낭패
        }

        glEnable(GL_TEXTURE_2D);    // 텍스쳐 매핑을 켜고
        glShadeModel(GL_SMOOTH);    // Smooth Shading을 선택하고
        glClearColor(0.0f, 0.0f, 0.0f, 0.5f);    // 배경은 검정색
        glClearDepth(1.0f);    // Depth Buffer 초기화
        glEnable(GL_DEPTH_TEST);        
      glDepthFunc(GL_LEQUAL);    
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // 원근 계산은 최상으로


이제 광원을 설치할 차례군요. 다음 코드에서 light1이 만들어낼 환경광의 크기를 정합니다. 이번 강의 처음 부분에서 LightAmbient라는 이름으로 저장해 둔 1/2 밝기의 환경광 배열을 사용합니다.


glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);    // 환경광 설정


다음은 light1 이 비추는 발산광의 크기를 설정할 차례입니다. LightDiffuse라는 이름으로 저장해 두었던 최대 밝기의 발산광 배열을 여기서 사용합니다.


glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);    // 발산광 설정


이제 광원의 위치를 지정합니다. LightPosition이라는 이름으로 저장된 광원 위치 배열을 사용합니다. 광원의 위치는 바로 정면, x축으로 0, y축으로 0, 그리고 보는 사람 방향으로 z축으로 2유닛(화면으로 튀어나와서)에 위치하고 있습니다.


glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);    // 광원의 위치


마지막으로 light1을 켭니다. 아직 GL_LIGHTING을 활성화하지 않았기 때문에 아직은 빛을 볼 수 없습니다. 광원이 설정되고 위치도 결정되고 활성화도 되었지만, GL_LIGHTING이 활성화되지 않았기 때문에 아직 빛은 비추이지 않습니다.


glEnable(GL_LIGHT1);    // Light1 활성화
        return TRUE;    // 초기화 끝
}


이제 텍스쳐 매핑된 육면체를 그립니다. 새로 첨가된 코드에만 주석을 달아 놓았기 때문에 혹시 주석이 되지 않은 코드에서 이해가 안 되는 부분이 있으시면 tutorial 6 의 내용을 참조하십시오.


int DrawGLScene(GLvoid)                                                        
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                
        glLoadIdentity();


다음은 텍스쳐 매핑된 육면체를 이동하고 회전시키는 루틴입니다. glTranslatef(0.0f,0.0f,z)함수는 육면체를 z평면에서 (보는 사람 방향으로 안쪽이나 바깥쪽으로) 이동시킵니다. glRotatef(xrot,1.0f,0.0f,0.0f)함수는 x축을 중심으로 xrot변수의 크기만큼 회전시킵니다. glRotatef(yrot,0.0f,1.0f,0.0f)함수는 육면체를 y축을 기준으로 yrot변수의 크기만큼 회전시킵니다.


        glTranslatef(0.0f,0.0f,z);    // z방향으로 육면체 이동
        glRotatef(xrot,1.0f,0.0f,0.0f);    // x축에서 회전
        glRotatef(yrot,0.0f,1.0f,0.0f);         // y축에서 회전


다음 코드는 tutorial 6에서 쓴 것과 비슷합니다만 texture[0] 대신에 texture[filter]를 연결합니다. 사용자가 키보드 ‘F’를 누르게 되면 filter변수의 값이 증가하게 됩니다. 만약 이 변수의 값이 2보다 커지면 값을 다시 0으로 되돌려 놓습니다. 프로그램이 시작할 때 filter변수의 초기값은 0이고, 결국 glBindTexture(GL_TEXTURE_2D, texture[0])을 실행하는 것과 같습니다. 키보드 ‘F’를 한 번 누르면 filter 변수는 1이 되고, glBindTexture(GL_TEXTURE_2D, texture[1])이 실행됩니다. 이런 방식으로 미리 만들어 둔 세 개의 텍스쳐 중 하나를 골라서 사용합니다.


glBindTexture(GL_TEXTURE_2D, texture[filter]);    // filter 변수에 따라서 텍스쳐 선택

        glBegin(GL_QUADS);    // 사각형을 그리기 시작


glNormal3f()는 처음 등장하는 함수입니다. Normal(법선)이라는 것은 다각형의 중앙에서 90도 각도로 직선으로 뻗어 나오는 선분을 의미합니다. 빛을 쓰기 위해서는 normal을 정의해서 OpenGL에게 다각형이 어디를 향하고 있는지를 알려줘야 합니다. Normal을 정의하지 않으면 빛이 비추지 않아야 될 다각형이 빛난다든지, 다각형의 반대 면이 빛난다든지 하는 여러 가지 예측 불가능한 일들이 벌어지게 됩니다. Normal은 다각형에서 바깥을 향하도록 정의해야 합니다.

육면체의 앞면을 정의하는 부분을 보시면 normal의 z축 값이 양수입니다. 이것은 normal이 보는 사람을 향하고 있다는 뜻입니다. 바로 정면이 향해야 하는 방향이죠. 반대로 뒷면의 normal은 보는 사람 반대편을 향하여 화면 안쪽으로 정의되어 있습니다. 역시 이것도 바로 뒷면이 향해야 하는 방향입니다. 만약에 육면체가 x축이나 y축을 중심으로 180도 회전한다면 앞면은 뒤를 향하게 되고 뒷면은 앞을 향하게 됩니다. 어떤 면이 보는 사람을 향하고 있다 하더라도 앞에 있는 면의 normal값은 항상 보는 사람을 향하게 됩니다. 광원이 관찰자와 더 가까이 있기 때문에 관찰자를 향하는 normal벡터는 항상 광원을 향하게 되고, 앞면은 항상 밝게 빛나게 됩니다. 만약 관찰자가 정 가운데, 상자 안으로 들어가게 되면 온통 어두운 화면이 됩니다. 왜냐하면 육면체 모든 면의 normal은 바깥을 향하고 있기 때문에 안쪽으로는 빛이 들어오지 않게 됩니다. 역시 이것도 우리가 원하는 바입니다.


// 정면
                glNormal3f( 0.0f, 0.0f, 1.0f);    // normal값은 관찰자를 향한다
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
                // 뒷면
                glNormal3f( 0.0f, 0.0f,-1.0f);    // 관찰자 반대 방향으로
                glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
                glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
                // 윗면
                glNormal3f( 0.0f, 1.0f, 0.0f);    // normal은 위를 향한다
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
                // 밑면
glNormal3f( 0.0f,-1.0f, 0.0f);    // normal은 밑을 향하여
                glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
                glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
                // 오른쪽면
                glNormal3f( 1.0f, 0.0f, 0.0f);    // normal은 오른쪽으로
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
                glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
                // 왼쪽면
                glNormal3f(-1.0f, 0.0f, 0.0f);    // normal은 왼쪽으로
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
        glEnd();    // 사각형 그리기 끝


다음 두 줄은 xrot와 yrot값을 xspeed, yspeed값만큼 변경합니다. xspeed와 yspeed값이 크면 xrot와 yrot값이 빨리 증가하게 되어 육면체가 축을 중심으로 더 빨리 회전하게 됩니다.


        xrot+=xspeed;
        yrot+=yspeed;
        return TRUE;
}


이제 WinMain()으로 가셔서 빛을 켜고 끄는 루틴, 상자를 회전하는 루틴, 필터를 바꾸는 루틴, 상자를 화면 안팎으로 움직이는 루틴을 추가하겠습니다. WinMain()함수의 맨 밑을 보시면 SwapBuffers(hdc)함수 호출 부분이 있는데 바로 그 밑에 다음의 코드를 추가합니다.

이 코드는 키보드에서 'L'이 눌렸는지를 검사합니다. 만약 'L'이 눌렸고 lp 변수의 값이 FALSE가 아니라면 이미 눌려져 있는 키스트로크라는 뜻이므로 아무 일도 하지 않고 그냥 넘어가게 됩니다.


    SwapBuffers(hDC);    // 더블 버퍼 바꿔치기
    if (keys['L'] && !lp)    // 키보드 L 이 새로 눌렸는가
    {


만약 lp가 FALSE라면 'L'키를 누른 적이 없었거나 지난번에 눌렀다 떼었다는 뜻이 됩니다. 이제 이 코드로 다시 돌아오기 전에 lp를 TRUE로 바꾸어서 프로그램이 이것을 지나치게 합니다. 키가 계속 눌려있다는 것을 검사하지 않으면 프로그램은 이 부분에서 사용자가 키를 누르고 있다고 생각하게 되고 따라서 키보드가 눌려있는 동안 빛이 계속 깜빡거리게 될 것입니다.

한 번 lp가 TRUE가 되면 컴퓨터는 'L'키가 눌려있다는 것을 알게 되고 빛을 한 번 켜거나 끄게 됩니다. light라는 변수는 TRUE아니면 FALSE값만을 갖게 됩니다. 즉, light=!light라는 표현은 light변수는 NOT light이 됩니다. 인간 언어로 번역하면 만약 light이 TRUE라면 light을 NOT TRUE(FALSE)로 만들고, 만약 light이 FALSE라면 light을 NOT FALSE(TRUE)로 만듭니다. 따라서 light이 참이었다면 이제 거짓이 되고, 거짓이었다면 참이 됩니다. (어렵다... ㅡㅡ;)


        lp=TRUE;    // lp 는 TRUE 가 된다
        light=!light;    // TRUE/FALSE 값을 바꾼다


이제 light변수의 상태를 점검합니다. 아래 코드의 첫 번째 줄을 인간 말로 하면 "만약 light이 FALSE라면"이 됩니다. 괄호 안의 내용을 다 해서 해석하면 이런 뜻입니다. 만약 light이 FALSE라면 빛을 비활성화 시킨다. 따라서 모든 빛은 꺼진다. 'else' 부분은 이렇게 됩니다. 만약 light 이 FALSE가 아니라면 당근 TRUE일 것이고, 따라서 빛을 켠다.


        if (!light)    // Not Light
        {
            glDisable(GL_LIGHTING);    // 빛을 비활성화
        }
        else    // 아니라면
        {
            glEnable(GL_LIGHTING);     // 빛을 활성화
        }
    }


다음은 'L'키가 그만 눌렸는지를 검사합니다. 만약 키가 떨어져 있다면 lp값을 FALSE로 바꾸어 L키가 눌리지 않았음을 표시합니다. 이렇게 해 주지 않게 되면 우리는 빛을 한 번 켤 수는 있지만 컴퓨터에서는 계속해서 L키가 눌려 있다고 생각하기 때문에 다시는 빛을 끌 수 없게 될 것입니다.


    if (!keys['L'])    // L 키가 떨어졌나
    {
        lp=FALSE;    // lp 는 FALSE 가 되고 키가 떨어져 있는 상태를 표시한다
    }


비슷한 방법으로 F키를 처리합니다. 키가 눌렸는데 계속 누르고 있던 상태가 아니었다면 fp변수를 TRUE로 만들어 이제 키가 눌렸다는 것을 표시합니다. 그리고 filter변수의 값을 증가시킵니다. 만약 filter변수의 값이 2보다 커지면 (큰 값이 되면 texture[3]을 가리키게 되는데 이것은 우리 프로그램에 없는 값이죠) 변수를 초기화하여 다시 0으로 만듭니다.


    if (keys['F'] && !fp)    // F 키가 눌렸나
    {
        fp=TRUE;    // fp 를 TRUE 로 바꾸어 키가 눌렸음을 표시
        filter+=1;    // 변수 값을 하나 증가
        if (filter>2)    // 변수값이 2보다 커졌나
        {
            filter=0;    // 그렇다면 0으로 초기화
        }
    }
    if (!keys['F'])    // F 키가 떨어졌나
    {
        fp=FALSE;    // 그렇다면 키가 떨여졌음을 표시
    }


다음의 네 줄은 'Page Up'키가 눌렸는지를 확인해서 그렇다면 z값을 감소시킵니다. z값이 감소되면 DrawGLScene()함수에서 호출하는 glTranslatef(0.0f,0.0f,z) 함수의 영향으로 육면체는 점점 멀어져 보이게 됩니다.


    if (keys[VK_PRIOR])    // Page Up 이 눌렸나
    {
        z-=0.02f;    // 그렇다면 화면 안쪽으로 전진
    }


다음 네 줄은 'Page Down'키가 눌렸는지를 검사해서 z값을 증가키며, DrawGLScene()함수의 glTranslatef(0.0f,0.0f,z)함수의 영향으로 육면체는 보는 사람 쪽으로 다가오게 됩니다.


    if (keys[VK_NEXT])    // Page Down 이 눌렸나
    {
        z+=0.02f;    // 그렇다면 관찰자 쪽으로 전진
    }


이제 화살표키를 검사하는 부분입니다. 왼쪽이나 오른쪽 화살표키가 눌리면 xspeed값이 증가 혹은 감소됩니다. 화살표키의 위 아래 방향을 누르면 yspeed값이 증가 혹은 감소됩니다. 앞에서 말씀드렸듯이 xspeed나 yspeed값이 점점 커지면 육면체는 점점 더 빠르게 회전합니다. 화살표키를 계속 누르고 있으면 육면체는 그 방향으로 더 빠르게 회전하게 됩니다.


    if (keys[VK_UP])    // 화살표키 위쪽이 눌렸으면
    {
        xspeed-=0.01f;    // xspeed 값을 감소
    }
    if (keys[VK_DOWN])    // 아래쪽이 눌렸으면
    {
        xspeed+=0.01f;    // xspeed 값을 증가
    }
    if (keys[VK_RIGHT])    // 오른쪽 키 눌렸으면
    {
        yspeed+=0.01f;    // yspeed 값을 증가
    }
    if (keys[VK_LEFT])    // 왼쪽 키 눌렸으면
    {
        yspeed-=0.01f;    // yspeed 값을 감소
    }
0 0
로그인 후 추천 또는 비추천하실 수 있습니다.
포인트 228,692
가입일 :
2003-02-18 14:12:30
서명 :
미입력
자기소개 :
미입력

최신글이 없습니다.

최신글이 없습니다.

댓글목록 0

등록된 댓글이 없습니다.
전체 121 건 - 3 페이지
2004.01
25

[App 개발] CoreGraphics의 풀스크린 화면 만들기

박진철님께서 올리셨던 질문 중에 코코아에서 퀵드로우의 CopyBits대신 사용할 수 있는 방법으로서 아시는 분들은 다 아실만한 CGDirectDisplay 를 이용한 방법을 한 번 시도해 보았습니다. 프로그램은 심히 완성도가 떨어지는 관계로 감히…

2004.01
23

[App 개발] Cocoa Design Patterns (5)

Commands GOF의 패턴에 익숙하신 분들은 아마 타겟/액션이 마치 커맨드 패턴의 구현이라고 생각될 것입니다. 그러나 커맨드 패턴같은 것이 타겟/액션과 같은 구현이 이용된다 하더라도 Objective-C에서는 그것이 필요가 없습니다. 커맨드 패턴…

2004.01
22

[App 개발] Cocoa Design Patterns (4)

Enumeration 코코아의 모든 콜랙션 클래스는 계수기를 제공합니다. 이 패턴은 GOF의 반복기 패턴과 비슷합니다. 계수기는 객체의 집합을 훑어내고 각 객체에 어떤 일을 수행하는 방법을 제공합니다. 특정한 집합에 대하여 특수한 루프를 짜 주는 대…

2004.01
22

[App 개발] NeHe Lesson 16

이번 강의는 Chris Aliotta가 제작하였습니다. 당신의 OpenGL 프로그램에 안개를 넣고 싶으십니까 이번 강의에서 바로 그 방법을 보여드리려고 합니다. 저는 강의를 쓰는 것은 처음이고, OpenGL/C++ 프로그래밍은 근래에 시작한 것이…

2004.01
18

[App 개발] Cocoa Design Patterns (3)

평일은 바쁘니까 아무래도 주말에 열심히 진도를 나가는 것이 좋겠지요 번역이 많이 서툴러서 읽으시기에 불편하실지 모르겠습니다. 죄송합니다. ***** Class Clusters 클래스 클러스터는 복잡한 상속 구조를 숨기는 방법입니다. 기본적…

2004.01
18

[App 개발] NeHe Lesson 13

바로 뒤에 이어지는 두 개의 폰트 강의는 그냥 건너뛰려고 합니다. 윈도우 전용 코드 설명에 많은 부분이 할애되는 것도 그렇고, agl 함수 래퍼런스도 저에게 부족해서 (그리고 제 실력도 부족해서... 하하...) 일단은 이정도 선에서 폰트는 접도록 하…

2004.01
17

[App 개발] Cocoa Design Patterns (2)

Model-View-Controller 모델-뷰-컨트롤러, 줄여서 MVC 패턴은 패턴 이상의 구조물이라고 간주됩니다. 왜냐하면 이것은 응용 프로그램을 정리하는 기본적인 방법이며 모든 패턴들을 아우르는 상위의 구조적인 요소이기 때문입니다. 왜냐하면…

2004.01
17

[App 개발] Cocoa Design Patterns (1)

코코아를 배울 때, 코코아가 사용하는 용어 때문에 어려움을 겪으시는 분이 많으실 것입니다. 그 중에서도 코코아 설계에 응용된 디자인 패턴의 개념이 익숙하지 않아서 더 힘들게 느껴지시는 분들이 계실 줄 압니다. 제가 갖고 있는 책 Cocoa Progra…

2004.01
15

[App 개발] Nehe Lesson 12

이번 강의에서는 출력 리스트를 이용하는 법을 배우겠습니다. 단순히 리스트를 만들어 빠르게 하는 것 뿐만 아니라 간단한 GL 씬을 만들어야 할 때 몇 줄로 나누어 따로 사용할 수 있습니다. 예를 들어서 각 판을 두 개의 운석으로부터 시작하는 ast…

2004.01
13

[App 개발] NeHe Lesson 11

대충 소스를 돌아가게끔만 만들어서 캡춰해서 게재를 하다보니 혹시라도 소스코드상에 오류나 옛날 루틴의 찌꺼기등이 많이 끼어있을지도 모릅니다. ^^; 너그럽게 봐 주시고, 어쨌든 빨랑빨랑 진도를 나가는 방향으로 해 보겠습니다. 목표는 3D 게임 엔진 …

2004.01
10

[App 개발] NeHe Lesson 10

이 예제는 Lionel Brits가 만들었습니다. 여기서는 코드의 어떤 부분이 추가되었는지만 설명하고 있기 때문에 이 글에서 소개하는 코드만 가지고는 프로그램이 동작하지 않을 것입니다. 만약에 어떤 부분에 어떻게 코드가 첨가되었는지 알고 싶으시면 소스…

2004.01
09

[App 개발] NeHe Lesson 9

9번 강좌까지 오신 여러분 수고가 많습니다. 본 코스는… 쿨럭~ ㅡㅡ; 지금까지의 공부를 통해서 OpenGL윈도우를 여는 것부터 시작해서 광원과 투명처리를 한 텍스쳐 물체를 회전시키는 것까지, OpenGL에 대하여 많은 이해가 있으셨을 것입니다. 이번…

2004.01
08

[App 개발] NeHe Lesson 8

투명 OpenGL의 많은 특수 효과들은 블렌딩 기능을 이용합니다. 블렌딩이란 기존에 그려져 있는 픽셀과 새로 그리는 픽셀의 색상을 섞는 일입니다. 어떻게 색상을 섞는가는 색상의 alpha값과 블렌딩 함수에 따라 달라집니다. alpha값이란 색상을…

2004.01
07

열람중 [App 개발] NeHe Lesson 7

원문이 윈도우 소스를 기준으로 서술되어 있어서 많은 부분이 바뀌어 있습니다. 특히 키보드 입력 부분은 제가 따로 첨부하는 소스를 참조하시는 것이 더 나을 것입니다. (사실 소스를 잘 만들진 못했습니다. 대충 돌아가게만... ^^;;;) 그래도 없는 것…

2004.01
06

[App 개발] Carbon과 Cocoa중에서 어떤 것을 선택할까?

저는 C/C++ 프로그래머입니다. 옛날 터보씨 시절부터 C를 썼기 때문에 사실 이것을 고치기가 쉽지 않습니다. 자바 프로그래밍은 밥먹고 사는 문제 때문에 프로젝트를 하면서 배우게 되었습니다. 처음에는 C++의 개념을 갖고 접근하는 바람에 많이 헤매었는…

2004.01
05

[App 개발] NeHe Lesson 6

텍스쳐 매핑을 배우면 여러가지 잇점이 있습니다. 만약 화면을 가로질러 날아가는 미사일을 표현한다고 합시다. 지금까지 배운 것을 토대로 하자면 미사일 전체를 형형색색의 다각형을 모아서 만들어야만 합니다. 텍스쳐 매핑을 사용한다면 진짜 미사일 사진 한 장…

2004.01
04

[App 개발] NeHe Lesson 5

오늘은 지난 시간의 프로그램을 확장하여 3차원 공간에 2차원 물체 대신 3차원 물체를 만들어 보도록 하겠습니다. 삼각형 의 왼쪽, 오른쪽, 뒤쪽 면을 덧붙이고, 사각형에는 왼쪽, 오른쪽, 뒷면, 밑면을 덧붙일 것입니다. 그리고 나면 삼각형은 피라밋 모…

2003.12
30

[App 개발] NeHe Lesson 4

지난시간에는 삼각형과 사각형에 색상을 입히는 것을 배웠습니다. 오늘은 이 도형들을 축을 기준으로 회전하도록 만들겠습니다. 지난 시간에 썼던 코드에 몇 가지만 추가하면 됩니다. 밑에는 전체 코드를 기록할 것이기 때문에 어떤 부분이 추가되었고 어떤 부분…

2003.12
28

[App 개발] NeHe Lesson 3

지난 시간에는 삼각형과 사각형을 화면에 출력하는 것을 배웠습니다. 오늘은 도형에 색상을 입하는 두 가지 방법에 대해서 배울 것입니다. Flat Coloring으로 사각형을 단색으로 칠할 것이고, Smooth Coloring으로 삼각형의 꼭지점에 각각 …

2003.12
27

[App 개발] NeHe Lesson 2

원래는 Lesson 1부터 해야 하는데, 이미 살펴보신 분은 아시겠습니다만, 윈도우 여는 방법 설명이 반 이상입니다. MUG 게시판에 디바이스 컨텍스트가 어쩌고 저쩌고 설명하고 있으면 짜증나시겠죠 ^^; 빠진 부분은 나중에 다루어질 것으로 믿고, Le…

2003.12
26

[App 개발] NeHe Tutorial 따라가기 (0)

OpenGL 사이트 중에서 꽤 명성이 있는 NeHe 프로젝트의 튜토리얼을 따라가 보려고 합니다. 저는 지금까지 Win32와 DirectX 프로그래밍을 주로 해 왔고, 따라서 매킨토시의 Xcode도 처음이고, OpenGL도 처음입니다. 저 혼자 공부하는…