키 입력 예제 : 세상을 둘러 보아요~ :)

키보드 입력을 다루는 재미있는 예제를 만들어봅시다. 이번 장에서는 눈사람이 서있는 작은 세상을 만드는 어플리케이션을 제작해보겠습니다. 그리고 방향키를 이용해서 그 세상속에서 카메라를 이리저리 움직여보는 것이죠! 왼쪽과 오른쪽키를 누르면 카메라가 Y 축을 중심으로 회전하게 만들고(XZ 평면상의 회전) 위쪽과 아래쪽키를 누르면 카메라가 현재 방향에서 앞뒤로 움직이게끔 만들어봅시다.

주석이 달린 코드가 아래에 쭈~욱 있습니다. 서둘지 말구요 우선 초기화부분부터 살펴보지요.

#include <math.h>
#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdlib.h>

static float angle=0.0,ratio;
static float x=0.0f,y=1.75f,z=5.0f;
static float lx=0.0f,ly=0.0f,lz=-1.0f;
static GLint snowman_display_list;

회전각을 계산할 때 수학관련 함수들이 필요하기 때문에 math.h 파일을 포함했습니다. 위에 선언된 변수를 보면 의미가 확실하지만 그래도 간단하게 설명해보겠습니다.:

  • angle: Y 축을 중심으로 회전할 때의 회전각입니다. 이 변수로 카메라를 회전할 수 있는거죠.

  • x,y,z: 카메라의 위치입니다.

  • lx,ly,lz: 시선의 방향을 나타내는 벡터입니다.

  • ratio: 창의 너비/높이의 비율입니다.

  • snowman_display_list: 한개의 눈사람을 그리는 디스플레이리스트의 인덱스입니다.

잠깐만! : 여러분들이 디스플레이리스트에 대해서 모른다면 무시하고 넘어가도 됩니다. 하지만 디스플레이리스트에 대해서 우선 알아야겠다 싶으면 다음 튜토리얼을 읽어보세요. :)

다음은 앞에서 살펴보았던 창의 크기변경을 다루는 함수입니다. 앞에서 만든 함수와 차이점이 있다면 gluLookAt 함수의 인자들이 상수값이 아니라 변수를 사용한다는 것입니다. 이 함수 때문에 앞장으로 가지 마세요. 간략하게 설명해드릴테니까요. gluLookAt 함수는 카메라 위치와 원점을 설정하는데 쉽고 직관적인 방법을 제공합니다. 이 함수는 세개의 그룹으로 나뉘는 인자들을 받습니다. 그리고 각 그룹의 인자들은 세개의 float 형 변수값으로 구성되어 있습니다. 처음 세개의 값들은 카메라의 위치를 나타내고 두번째의 값들은 우리가(카메라)가 보고있는 곳을 나타냅니다. 이 값은 우리의 시선 속 아무 위치나 될 수 있습니다. 마지막 값들은 카메라의 위쪽을 가리키는 벡터를 나타냅니다. 이 값을 (0.0, 1.0, 0.0) 으로 설정하면 카메라가 뒤집어지지 않은 것입니다. 카메라를 뒤집고 싶다면 이 값들을 조작하면 됩니다. 예를 들어서 세상을 뒤집어서 보고 싶다면 (0.0, -1.0, 0.0) 으로 이 값을 설정하면 됩니다.

앞에서도 말했듯이 x, y 그리고 z 는 카메라의 위치를 나타냅니다. 두번째 인자들의 값들은 시점을 나타내는데 시선을 나타내는 벡터에 카메라의 위치를 더해서 계산되는 값입니다.

  • 시점 = 시선벡터 + 카메라의 위치 ( Look At Point = Line Of Sight + Camera Position )

void changeSize(int w, int h)
{
    // 창이 너무 작아졌을때 0 으로 나뉘는 것을 방지합니다.
    if(h == 0)
        h = 1;

    ratio = 1.0f * w / h;
    // 좌표계를 수정하기 전에 초기화합니다.
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // 뷰포트를 창 전체크기로 설정합니다. 
    glViewport(0, 0, w, h);

    // 절단 공간을 설정합니다.
    gluPerspective(45,ratio,1,1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(x, y, z, 
            x + lx,y + ly,z + lz,
            0.0f,1.0f,0.0f);
}

다은은 디스플레이리스트를 정의하고 그림을 그리는 렌더링함수를 살펴봅시다. 이 부분은 건너뛰고 다음 부분을 읽어도 됩니다.

void drawSnowMan() 
{
    glColor3f(1.0f, 1.0f, 1.0f);

    // 몸을 그립니다.
    glTranslatef(0.0f ,0.75f, 0.0f);
    glutSolidSphere(0.75f,20,20);

    // 머리를 그립니다.
    glTranslatef(0.0f, 1.0f, 0.0f);
    glutSolidSphere(0.25f,20,20);

    // 두 눈을 그립니다.
    glPushMatrix();
        glColor3f(0.0f,0.0f,0.0f);
        glTranslatef(0.05f, 0.10f, 0.18f);
        glutSolidSphere(0.05f,10,10);
        glTranslatef(-0.1f, 0.0f, 0.0f);
        glutSolidSphere(0.05f,10,10);
    glPopMatrix();

    // 코를 그립니다.
    glColor3f(1.0f, 0.5f , 0.5f);
    glRotatef(0.0f,1.0f, 0.0f, 0.0f);
    glutSolidCone(0.08f,0.5f,10,2);
}

GLuint createDL()
{
    GLuint snowManDL;

    // 디스플레이리스트를 생성하고 id 를 반환합니다.
    snowManDL = glGenLists(1);

    // 디스플레이리스트를 시작합니다.
    glNewList(snowManDL,GL_COMPILE);

    // 렌더링을 수행하는 함수를 호출합니다.
   drawSnowMan();

    // 디스플레이리스트를 끝마칩니다.
    glEndList();

    return(snowManDL);
}

void initScene()
{
    glEnable(GL_DEPTH_TEST);
    snowman_display_list = createDL();
}

void renderScene(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 지형을 그립니다.
    glColor3f(0.9f, 0.9f, 0.9f);
    glBegin(GL_QUADS);
        glVertex3f(-100.0f, 0.0f, -100.0f);
        glVertex3f(-100.0f, 0.0f, 100.0f);
        glVertex3f( 100.0f, 0.0f, 100.0f);
        glVertex3f( 100.0f, 0.0f, -100.0f);
    glEnd();

    // 36개의 눈사람을 그립니다.
    for(int i = -3; i < 3; i++)
        for(int j=-3; j < 3; j++)
        {
            glPushMatrix();
                glTranslatef(i*10.0,0,j * 10.0);
                glCallList(snowman_display_list);;
            glPopMatrix();
        }
    glutSwapBuffers();
}

자 이제 특수키 이벤트를 다루는 함수를 만들어 봅시다. 왼쪽키와 오른쪽키는 카메라를 회전하는데 사용할 것입니다. 즉 시선을 정의하는 벡터를 변경하는 것이죠. 위쪽키와 아래쪽키는 현재 시선방향의 앞뒤로 움직이는데 사용할 것입니다.

void inputKey(int key, int x, int y)
{
    switch (key)
    {
    case GLUT_KEY_LEFT : 
        angle -= 0.01f;
        orientMe(angle);
        break;
    case GLUT_KEY_RIGHT : 
        angle +=0.01f;
        orientMe(angle);
        break;
    case GLUT_KEY_UP : 
        moveMeFlat(1);
        break;
    case GLUT_KEY_DOWN : 
        moveMeFlat(-1);
        break;
    }
}

왼쪽 또는 오른쪽 키를 누르면 angle 변수의 값이 알맞게 설정되고 orientMe 함수가 새로운 값과 함께 호출됩니다. 이 함수는 카메라를 알맞게 회전시킵니다. moveMeFlat 함수는 카메라를 XZ 평면상에서 시선의 방향으로 움직이게 합니다. 인자는 카메라가 움직일 방향을 나타냅니다.

다음에 나오는 함수는 카메라를 회전시킵니다. 이 함수는 각도를 받아서 시선 벡터의 새로운 x 와 z 값을 계산합니다. 이 예제에서는 카메라가 XZ 평면상에서만 움직이기 때문에 시선벡터의 y 좌표는 수정할 필요가 없습니다. 새로운 lx 와 lz 는 XZ 평면상의 단위원으로 사상됩니다. 그러므로 각 ang 에 대해서 lx 와 lz 값은 다음처럼 구합니다.:

  • lx = sin(ang)

  • lz = cos(ang)

orientMe 함수를 살펴보면, 극좌표(ang, 1)를 유클리드 좌표로 변환하고 나중에 카메라의 새로운 방향을 설정합니다. 투영 행렬을 알맞게 설정하기 위해서 우선 이전에 적용된 변환 효과를 없애야 하기 때문에 투영 행렬을 단위 행렬로 초기화합니다. gluPerspective 함수로 원근 투시값을 설정하고 gluLookAt 함수로는 카메라의 방향을 설정합니다. 카메라의 위치가 아직 같기 때문에 카메라는 움직이지 않습니다.

void orientMe(float ang)
{
    lx = sin(ang);
    lz = -cos(ang);
    glLoadIdentity();
    gluLookAt(x, y, z, 
            x + lx,y + ly,z + lz,
            0.0f,1.0f,0.0f);
}

다음 함수는 카메라를 움직이는데 사용합니다. 카메라를 시선방향으로 움직일 것이기 때문에 카메라는 항상 시선벡터상에 있습니다. 이렇게 하기 위해서 현재 위치에 시선 벡터를 더해줘야합니다. 다음과 같이 새로운 x 와 z 를 구합니다.:

  • x = x + direction(lx)*fraction

  • z = z + direction(lz)fraction

방향(direction)값이 -1 또는 1 이면 앞 뒤로 움직이는 것입니다. fraction 은 속도값으로 사용할 수 있습니다. lx 와 lz 가 단위 벡터(위에서 말했던 단위원 위의 한 점)이므로 fraction 이 상수이면 속도는 항상 같을 것입니다. fraction 을 증가시키면 카메라를 빠르게 움직일 수 있겠죠. 즉 각 프레임마다 더 멀리 움직이는 것입니다.

다음 단계는 orientMe 함수와 같습니다.

void moveMeFlat(int direction)
{
    x = x + direction*(lx)*0.1;
    z = z + direction*(lz)*0.1;
    glLoadIdentity();
    gluLookAt(x, y, z, 
            x + lx,y + ly,z + lz,
            0.0f,1.0f,0.0f);
}

다음은 GLUT 를 사용하는 main 함수의 코드입니다.

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(100,100);
    glutInitWindowSize(640,360);
    glutCreateWindow("SnowMen from 3D-Tech");

    initScene();

    glutSpecialFunc(inputKey);

    glutDisplayFunc(renderScene);
    glutIdleFunc(renderScene);

    glutReshapeFunc(changeSize);

    glutMainLoop();

    return(0);
}

VC 6.0 프로젝트 파일 을 받아서 카메라 설정 함수들을 가지고 놀아보세요 :)

Last updated