[OpenGL Tutorial] Sprite Processing by The Blending

이번장에서는 블랜딩의 또 다른 활용에 대해서 알아보겠는데 그 주제로 2차원 게임에서 많이 사용되는 스프라이트 처리 기법에 대한 예이다. 다음과 같은 그림이 준비되어있다. 첫째는 바탕화면 그림이고 둘째는 스프라이트가 될 이미지, 그리고 셋째는 스프라이트 이미지와 배경과의 조화를 위한 마스크 이미지이다.

사용자 삽입 이미지(배경 이미지)

사용자 삽입 이미지(스프라이트 이미지)

사용자 삽입 이미지(마스크 이미지)

위의 이미지가 섞여서 다음과 같은 스프라이트 효과를 얻는것이 이장의 목표이다.

사용자 삽입 이미지(최종 결과 화면)

실제로 우리는 OpenGL을 사용해서 위에서 주어진 스프라이트 이미지에서 위와 아래에 그려진 캐릭터의 두개의 동작을 주기적으로 반복해서 실제로 캐릭터가 움직이는 것 같은 모습으로 마무리를 짓겠다.


이장은 6장의 소스 코드에서 시작하도록 하겠다.


가장 먼저 해야할 것들은 배경 이미지와 스프라이트 이미지와 마스크 이미지를 읽어들어야 하는데 이것은 텍스쳐 맵핑 소스의 형태로 읽어들어야 한다. 먼어 이 세가지의 이미지를 읽어들이는 코드에 대해서 살펴보자. 이미지를 읽어들이는 코드는 InitGL 함수에서 해준다. InitGL 함수의 구현 부분을 완전이 새롭게 기술한다. 그 코드는 아래와 같다.

int InitGL(GLvoid)
{
    AUX_RGBImageRec *texRec[3];
    memset(texRec, 0, sizeof(AUX_RGBImageRec *));
    if((texRec[0]=LoadBMPFile("Image/back.bmp")) &&
       (texRec[1]=LoadBMPFile("Image/sprite.bmp")) &&
       (texRec[2]=LoadBMPFile("Image/mask.bmp"))) {
        glGenTextures(3, &texture[0]);
        for(int i=0; i<3; i++) {
          glBindTexture(GL_TEXTURE_2D, texture[i]);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//<*>
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//<*>
          gluBuild2DMipmaps(GL_TEXTURE_2D, 3, texRec[i]->sizeX, texRec[i]->sizeY,
                GL_RGB, GL_UNSIGNED_BYTE, texRec[i]->data);
        }
    } else return FALSE;
   
    for(int i=0; i<3; i++) {
        if(texRec[i]) {
            if(texRec[i]->data) free(texRec[i]->data);
            free(texRec[i]);
        } else return FALSE;
    }
   
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glShadeModel(GL_SMOOTH);
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
    glClearDepth(1.0f);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    return TRUE;
}

6장의 소스 코드에 크게 달라진 것은 없다. 바뀐 중요한 것은 맵핑 소스로 사용될 그림 파일이 3개로 늘어났다. 하지만 눈에 잘 보이지 않는 중요한 중요한 변화가 있는데 위 코드의 노란색 코드가 바로 그것이다. glTexParameteri의 세번째 인자가 GL_NEAREST로 변경되었다. GL_LINEAR로 사용할 경우 그림이 나쁘게 표현하면 누그러져(?)버리게 되는데 이점을 막기위함이다.

세개의 텍스쳐 맵핑 소스를 사용하는데 텍스쳐 맵핑 소스의 식별자로 사용되는 전역변수로 선언된 GLuint tex[3] 코드의 변수이름을 texture로 변경하기 바란다. 즉 다음과 같게 말이다.

GLuint texture[3];

자 이제 텍스쳐 맵핑 소스를 얻는 것은 이쯤에서 됬고 이제 실제로 스프라이트 효과를 나타내보자. 먼저 원리에 대해서 설명해 보겠다. 먼저 사각형의 폴리곤을 이용해서 그 사각형의 폴리곤에 배경 이미지로 텍스쳐 맵핑을 한다. 이렇게 하면 배경은 간단이 완성된다. 그리고 작은 사각형의 폴리곤을 이용해서 마스크 이미지로 텍스쳐 맵핑을 시키는데 이때 블랜딩 함수로 (GL_DST_COLOR, GL_ZERO)를 사용한다. 그리고 바로 다음에 또 다른 작은 사각형의 폴리곤을 이용해서 스프라이트 이미지로 텍스쳐 맵핑을 시키는데 이때의 블랜딩 함수로 (GL_ONE, GL_ONE)을 사용한다. 이때 두개의 작은 사각형의 폴리곤은 모두 똑 같은 크기이며 동일한 좌표에 위치해야만 한다. 이렇게 되면 스프라이트 효과를 간단이 얻을 수 있게 된다. 아래는 그 구현 코드이다.

int DrawGLScene(GLvoid)
{
    static GLuint frame = 0; // <1>
    static GLfloat x = -5.0f; // <2>
   
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
   
    glTranslatef(0.0f, 0.0f, -10.0f);
   
    glEnable(GL_DEPTH_TEST); // <3>
    glDisable(GL_BLEND); // <4>
    glBindTexture(GL_TEXTURE_2D, texture[0]); // <5>
    glBegin(GL_QUADS); // <6-1>
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.5f, -1.5f, 0.0f); // <6-2>
    glTexCoord2f(1.0f, 0.0f); glVertex3f(1.5f, -1.5f, 0.0f); // <6-3>
    glTexCoord2f(1.0f, 1.0f); glVertex3f(1.5f, 1.5f, 0.0f); // <6-4>
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.5f, 1.5f, 0.0f); // <6-5>
    glEnd();
   
    glDisable(GL_DEPTH_TEST); // <7>
    glEnable(GL_BLEND); // <8>
    glBlendFunc(GL_DST_COLOR, GL_ZERO); // <9>
    glBindTexture(GL_TEXTURE_2D, texture[2]); // <10>
    glBegin(GL_QUADS); // <11-1>
    glTexCoord2f(0.0f, frame*0.5f); glVertex3f(-.5f+x, -.5f, 0.0f); // <11-2>
    glTexCoord2f(1.0f, frame*0.5f); glVertex3f(.5f+x, -.5f, 0.0f); // <11-3>
    glTexCoord2f(1.0f, frame*0.5f+0.5f); glVertex3f(.5f+x, .5f, 0.0f); // <11-4>
    glTexCoord2f(0.0f, frame*0.5f+0.5f); glVertex3f(-.5f+x, .5f, 0.0f); // <11-5>
    glEnd();
   
    glBlendFunc(GL_ONE, GL_ONE); // <12>
    glBindTexture(GL_TEXTURE_2D, texture[1]); // <13>
    glBegin(GL_QUADS); // <14-1>
    glTexCoord2f(0.0f, frame*0.5f); glVertex3f(-.5f+x, -.5f, 0.0f); // <14-2>
    glTexCoord2f(1.0f, frame*0.5f); glVertex3f(.5f+x, -.5f, 0.0f); // <14-3>
    glTexCoord2f(1.0f, frame*0.5f+0.5f); glVertex3f(.5f+x, .5f, 0.0f); // <14-4>
    glTexCoord2f(0.0f, frame*0.5f+0.5f); glVertex3f(-.5f+x, .5f, 0.0f); // <14-5>
    glEnd();
   
    if(frame == 0) frame = 1; // <15-1>
    else frame = 0; // <15-2>
   
    Sleep(50); // <15-3>
   
    x += 0.025f; // <15-4>
    if(x>6.0f) x=-5.0f; // <15-5>
   
    return TRUE;
}

코드 하나하나 짚어 보며 살펴보자.

<1> 번 코드를 살펴보기에 앞서 스프라이트 이미지에 대해서 다시 보자. 이 이미지는 두개의 캐릭터 모습을 담고 있다. 즉 위쪽과 아래쪽에 각각 모습을 담고 있는데 바로 <1>번 코드의 frame이 위쪽과 아래쪽 모습중에 어떤 모습을 보여 줄것인지를 나타내게 된다. 즉 frame가 0이면 아랫쪽 모습을 1이면 위쪽 모습을 보여주게 된다.

<2> 번 코드는 캐릭터가 앞으로 움직이는데 그때 사용되는 좌표 변수이다.

<3>, <4> 번 코드는 배경 이미지를 그리기에 앞서 Depth Buffer를 사용하게 하고 블랜딩 기능을 사용하지 않도록 한다. 배경이미지를 그릴때는 블랜딩 기능을 사용해서는 않된다. 배경은 배경 자체로 그대로 그려져야 하기때문이다.

<5> 번 코드는 배경을 그리기 위해 배경 텍스쳐 맵핑 소스를 사용하도록 지시한다.

<6> 번 코드들은 실제로 사각형 폴리곤을 그리고 배경 텍스쳐 맵핑을 사용해서 배경 그림을 화면상에 그려준다.

<7> 번 코드 부터는 드디어 스프라이트를 그려주는 코드의 시작인데 먼저 Depth Buffer의 사용을 막는다. 이 프로그램에서는 굳이 필요치 않으나 일반적으로 블랜딩 기능을 사용할때는 블랜딩 함수에 적용에 방해를 받지 않도록 Depth Buffer를 사용하지 않는다.

<8> 번 코드는 블랜딩 기능을 활성 시킨다.

<9> 번 코드는 블랜딩 함수를 지정하게 된는데 마스크 이미지에 대한 블랜딩 처리에 대한 함수는 반드시 (GL_DST_COLOR, GL_ZERO)이여야만 한다.

<10> 번 코드는 마스크 이미지의 텍스쳐 맵핑 소스를 사용하도록 지시한다.

<11> 번 코드들은 작은 사각형에 마스크 이미지의 텍스쳐 맵핑을 해주는 코드이다. frame 변수와 x변수의 사용을 눈여겨 보기 바란다. frame 변수를 사용해서 텍스쳐 좌표의 각각 정확히 위, 아래의 반만을 취한다는 것을 알수있다. 여기까지 코드가 도달하면 아래와 같은 결과까지 얻게 된다.

사용자 삽입 이미지

이제 위의 그림 위에 스프라이트 이미지를 올려 놓기만 하면 되는데 <12> 번 코드 이후가 바로 그런 일을 하게 된다.

<12> 번 코드는 스프라이트 이미지를 위한 블랜딩 함수를 지정하는데 (GL_ONE, GL_ONE)여야만 한다.

<13> 번 코드는 스프라이트 이미지의 텍스쳐 맵핑 소스를 사용하도록 지시한다.

<14> 번 코드들은 역시 <11>번 코드들과 동일한 일을 한다.

<15-1>과 <15-2>코드는 스프라이트 이미지에 담긴 두개의 동작을 서로 반복해서 보여주기 위해서 frame 변수를 항상 0이나 1의 값을 반복적으로 갖도록 해주는 코드이다.

<15-3>은 화면의 갱신이 너무 빨라서 0.2초간 지연을 시켜주는 임시적으로 사용한 함수이다.

<15-4>와 <15-5>의 코드는 스프라이트를 앞으로 움직이게 해주는 x변수를 증가시키는 코드들이다.

이상으로 이렇게 하면 실제 스프라이트의 최종적인 결과를 얻을 수 있다. 간단하지 않은가?? 이제 OpenGL을 이용해서 2차원 게임도 만들수 있다는 느낌을 받을수도 있을 것이다. 하지만 OpenGL을 이용해서 스프라이트를 구현할 경우 많은 장점이 있는데 그것은 스프라이트를 원하는 크기로 쉽게 키우거나 줄일수있다는 것이고 또한 원하는 각도로 회전이 가능하다는 점이다. 게다가 OpenGL의 하드웨어 가속 기능을 지원받을 경우 상상을 초월할 정도의 속도를 얻을 수 있을지도 모르겠다.

“[OpenGL Tutorial] Sprite Processing by The Blending”에 대한 22개의 댓글

  1. Muhang님, 도움되셨다니 다행입니다~ 음.. openGL은 기본적인 내용을 통해 단계적으로 원하는 결과를 만들어 가는 면이 있는 것같습니다. 이를 통해 컴퓨터 그래픽을 더 잘 이해할 수 있게 하는 요소인것같구요~

  2. 아…….. 한달동안 삽질하던걸 여기서 찾네요 ㅡㅜ
    일단 댓글부터 달아봅니다 ㅡㅜ
    적용시키러 가볼게요..

  3. 와 진짜 이거 책에서 제대로 설명안해줘서 시부럴거리다가 인터넷찾아봤는데

    님덕분에 바로할수있겠네요 ㅠ

    특히 젤위에 댓글다신 kkabdol님께서 친절하게 여기 링크걸어주셨음

    와 다들너무감사합니다. ^^

    1. 요즘 이래 저래 일로 정신이 없어 댓글에 소홀했습니다. 댓글 감사드리구요. 보고 싶은 책 한권이 백배 만배 더 값진 스승일듯합니다.

  4. 이글을 보고 지금 했는데…ㅠ 저는 왜 이미지에 투명한 회색 부분이 생기면서…
    깔끔하지가 못할까요ㅠ.ㅠ

    1. 결과를 이미지로 보면 이해가 쉬울텐데… 그러지 못해 아쉽습니다만.. 먼저 짐작을 해보면.. 스프라이트 이미지와 배경 이미지의 색상이 각각 검정색, 그리고 하얀색인가요? 이 부분을 먼저 검사해보세요~

  5. 2d 애니메이션 말고 그냥 이미지 하나를 스프라이트 효과를 줄려면 RGBA로 해서 알파값은 안그리게 하는 방법이 있어서 해보고 있는데요.. 그림이 옛날 티비 지지직 거릴때 나오는 것처럼 bmp이미지가 깨지네요.. 어떡해 해야되는 거죠? 위처럼 해도 될것 같지만.. 그러면 코딩이 너무 길어질것 같아서요. 애니메이션도 아닌데 그럴 필요가 없어보이거든요.

    1. 이미지의 크기가 2의 자승인지.. 확인해 보시기 바랍니다. 2의 자승이 아닐경우 텍스쳐가 깨지는 현상이 있는 것으로 기억하거든요.

  6. 이미지 크기가 2의 자승 맞거든요…. 음… 2d 비트맵 이미지 스프라이트 효과가 안되네요… 잠시 미뤄뒀다가 다시 시작하니… 인제는 RGBA로 바꿔서 실행하면 바로 에러떠서 아예 실행 조차 안되고… 에휴… 어떡해 해야 될지도 모르겠고.. 그냥 코딩 길어지겠지만 이렇게 해봐야 겠네요.. 제 타입은 아니지만…

  7. 세상에나…. 찾아 헤메던 정보였습니다… 너무 오래지났긴했지만 드디어 문제를 알았네요 감사합니다 ㅠㅠㅠㅠㅠ

소재철에 답글 남기기 응답 취소

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다