[펌] 자동으로 그림자를 만들어 보자

자동으로 그림자를 만들어 보자

오늘은 2D게임에서 캐릭터의 그림자를 자동으로 생성하는 기법을 이야기 해 보려고 한다.

이 기법은 1996년 12월에 한겨레정보통신(현 DDS)에서 처음으로 4명이 팀을 이루어 에스퍼라는
RPG를 제작하게 되면서 개발하게 된 것이다. 많지 않은 인원이 3개월만에 RPG를 만들어야
했었던 지라 모두가 고분분투하고 있었고 캐릭터작업은 한명이 담당하여 작업하고 있었다.
그러나 캐릭터의 양이 많아서 그림자에 대한 생각은 엄두도 내지 못하고 있었다.

여기서 잠깐 캐릭터의 그림자방식에 대해 이야기해 보죠. 일반적으로 캐릭터의 그림자방식에는
단순식과 실사식으로 나누어 이야기할수 있다. 단순식은 그저 캐릭터 밑에 검은 타원이 그림자처럼
존재하는 방식이고 실사식은 캐릭터의 모습을 비슷하게 닮은 검은 물체를 그림자로 사용하는 방식이죠.  

그래서 의논을 해 보았으나 그림자를 단순식으로 만들 수 밖에 없는 상황이었다. 또한 단순식도
그리 만만치 않아서 캐릭터디자이너가 해야 할 단순 작업이 너무 많았다. 캐릭터밑에 일일이
검은 타원을 붙여야 하기 때문이었죠.

그러나 이런 일들은 프로그래머가 조금만 시간을 내 캐릭터밑에 검은 타원을 만들어 주는 프로그램을
제작해주면 간단하게 해결되는 일이었다. 물론 내가 왜 캐릭터 그림자까지 신경써야 하지? 라는 생각을
가지는 프로그래머도 있을 수 있겠다. 만약 그러한 게임 프로그래머가 있다면 절대 게임을 제작하지
말고 다른 업종을 생각해 보기 바란다. 게임은 너가 할 일 따지고 내가 할 일 따지면 절대 좋은 작품이
나올 수 없기 때문이다.

필자는 여기서 좀더 진지하게 고민해 보았다. 물론 단순식으로 제작하는 것은 최후의 일이고
더 좋은 방법은 없을까?하고 생각한 것이다. 프로그램적으로 실사처럼 그림자를 만들어 준다면
이보다 좋은 방법은 없을 것이라 생각했다. 며칠 고민하니 좋은 생각이 떠올랐고 그 아이디어를
밑바탕으로 그림자 생성 프로그램을 만들어 봤더니 아주 괜찮았다. 그 기법을 이제 공개하겠다.
(그전부터 공개하려고 하였으나 계속 바뻐 글을 쓸 시간이 없었다. 핑계없는 무덤이 어디 없는가?)

서두에 말이 많았는데, 이 기법의 주된 바탕은 두 직선의 방정식을 토대로 하고 있다.

그림1

위의 그림처럼 캐릭터가 서있는 바닥이 있고 빛의 방향이 있다면 캐릭터의 한점 한점은 빛을 받아
바닥에 그려지게 된다. 이게 바로 그림자가 되는 것이다.

그림을 차근 차근 그려보면서 알아보자. 아래와 같은 그림에서 물건의 그림자를 그려보자.

 

물건의 상위의 첫 점인 빨간색은 빛의 방향에 따라 내려가다가 바닥의 선과 만나는 곳에 찍으면 된다.
그러면 다음과 같이 된다.

그 다음 점인 파란색도 그런식으로 하면 다음과 같이 되고

마찬가지로 이런 식으로 물건을 전부 그려 나가다 보면 다음과 같이 된다.

여기서 바닥과 만날 때 정해진 그림자 색깔로 그리면 바로 그림자가 되는 것이다.

 자. 어떠한가? 이론은 정말 간단하지 않은가? 

아래의 그림처럼 원래의 캐릭터그림을 회전시켜 만든 그림자하고는 차원이 다르다.

 

이 이론을 한번 공식으로 정립해 보자. 빛의 방향을 하나의 직선으로 생각하고 바닥또한
하나의 직선으로 생각해 보자. 원래의 점은 빛의 직선에 있고 그림자는 바닥의 직선에
생기므로 두 직선의 교차점을 찾으면 그것이 바로 그림자가 되겠다.

빛의 직선은  y1 = a * x1 + b 이라 하고

바닥의 직선을  y2 = c * x2 + d 라고 하자.

그러면 여기서 a는 빛의 각도를 알면 구할수 있게 된다. 빛의 각도를 LightAngle이라고 하면,
a는 직선의 기울기이므로  
a = tan(3.141592 * LightAngle / 180) 이 된다.

여기서 주의할 것은 PC스크린 상의 좌표는 Y가 밑으로 갈수로 +가 된다는 사항이다. 원래의
좌표계를 뒤집어 생각해야 한다.

그리고 c = -tan(3.141592 * ShadowAngle / 180) 이 된다. ShadowAngle은 바닥의 각도이고,

tan(360 - 각도) = -tan(각도) 이므로 - 값이 붙는다.

 

그럼 두 직선의 교점을 알아보자.

-)    y = c * x + d

- )   y = a * x + b

       x = (d - b ) / (a - c) 가 된다.   여기서 x 값을 구해 빛의 직선에 대입해 보면 y 값도 알게 되서

교점을 알수 있게 된다. 이 교점이 바로 한점의 그림자가 된다.

그럼 이제 실질적인 소스를 보면서 진행해 보자. 그러기 위해서 먼저 몇가지만 정의하고 가자.

  1. 데이타는 256 칼라이다. 
  2. 투과색 : TransColor, 그림자색은 : ShadowColor
  3. 3.141592 = PAIVALUE

BOOL BufMakeShadow(unsigned char*  buf, int width, int height) {
     int x, y;
     int address;
     int bottom;

     address = 0;
     bottom = 0; 
// 이 부분은 buf내에서 물체의 제일 밑부분을 알아내는 부분
     for(y = 0; y < height; y++) {
         for(x = 0; x < width; x++) {
             if(buf[address++] != TransColor) {
                 bottom = y;
             }
         }
     } 

     int data;
     double dA, dB, dC, dD, dX, dY;
     int mx, my;

// 빛의 기울기와 바닥의 기울기 계산
     dA = tan(PAIVALUE * LightAngle / 180);
     dC = -tan(PAIVALUE * ShadowAngle / 180);

     for(y = 0; y < bottom; y++) {
         for(x = 0; x < width; x++) {
             address = y * width + x;
             data = buf[address];        

             if(data != TransColor && data != ShadowColor) {
                 dB = y - dA * x;
                 dD = bottom - dC * j;

                 dX = (dD - dB) / (dA - dC);
                 dY = dA * dX + dB;

                           // 교점 계산
                 mx = (int)dX;     
                 my = (int)dY;         

                 if(mx < 0 || my < 0 || mx >= width || my >= bottom) {
                                 // 그림자가 버퍼내에 생성이 안된다면 빛의 각도나 바닥의 각도가 너무 크거나 작던지
                                 // 버퍼가 너무 작던지 하는 문제이다. 될수록 그림내에 캐릭터는 중앙에 위치하고 그림이
                                 // 캐릭터보다 2-3배정도는 커야 그림자를 그리기 수월하다
                     return FALSE;
                 }          

                 address = my * width + mx;
                 data = buf[address];
                 if(data == TransColor) {
                         /* 
                                           그림자를 격자로 만들려면...
                          if(y & 1) {
                         if(x & 1) buf[address] = ShadowColor;                  
                     } else {
                         if(!(x & 1)) buf[address] = ShadowColor;
                     }

                                  */
                     buf[address] = ShadowColor; 
                 }

             }
         }
     }
     return TRUE;
}

그리고 그림자를 생성할 때 다음과 같이 그림자를 격자형태로 만들 수도 있겠다.

이렇게 하면 그림자를 그릴 때 바닥이 보이는 면이 있어서 투과되어 보이는 것처럼
느낄수 있게된다. 물론 프로그램에서 그림자만 따로 저장하여 그림자를 그릴 때 바닥과
오버레이를 줄수도 있지만, 이렇게 하면 CPU가 바쁘게 되므로 잘 생각해서 선택하는 것이
중요하겠다.

마지막으로 해보면 알겠지만, 이 기법은 만능이 아님을 미리 밝힌다. 사람이 아닌 것(두발이 아닌
네발짐승이나 몬스터)이라든지, 특이한 동작을 하는 캐릭터는 그림자가 이상하게 생기며 샘플
프로그램에 사용된 소스에는 하나의 직선을 더 사용해서 보정의 기능이 들어가 있음을 밝힌다.

다만, 소스를 공개하는 만큼 사용자가 필요한 부분에서는 조금씩 변경하여 사용하면 될 것으로
사료된다.

샘플프로그램 다운로드

Creative Commons License
Creative Commons License
2004/07/18 15:42 2004/07/18 15:42
kallru
나만의 강의 2004/07/18 15:42

트랙백 주소 : 이 글에는 트랙백을 보낼 수 없습니다

댓글을 달아 주세요

Powerd by Textcube, designed by criuce
rss