상세 컨텐츠

본문 제목

[포니 게임 개발] 32. 높이맵(Height Map) 타일맵(Tile Map)

프로그래밍/포니게임개발

by ∫2tdt=t²+c 2013. 8. 28. 14:56

본문






오랜만이네요. 원래는 PhysX를 적용해 보려고 물리 엔진을 뜯어 고쳤는데, 뭘 잘못했는지 자꾸 물리 시뮬레이션 이상하게 되어서 (캐릭터가 점점 세게 흔들리다가 어디론가 날아가버리면서 끝남...) 때려치고 다른 부분을 작업했습니다.


Opacity맵이 적용되도록 코드를 수정함 : 이제 투명한 부분이 제대로 투명하게 렌더링됩니다. (반투명은 구현 안 할거임. Deferred Lighting에서 반투명 물체 렌더링은 너무 난해한 과제라)

Height Tile맵 오브젝트를 작성함 : 이제 위의 사진에 나오는것 마냥 높이맵 기반 타일맵을 생성할 수 있습니다.


세세하게 설명하자면

Opacity맵이 적용되도록 코드를 수정함

처음에 Deferred Lightin 적용할 때는 투명한 부분 처리가 아예 불가능한 것인줄 알고 투명한 부분을 포함한 오브젝트는 아예 오브젝트를 분리해서 따로 렌더링하려고 했어요. 그렇게 생각한 이유는 0번 렌더타겟 서피스의 알파채널을 specular 세기를 저장하는데 사용했기 때문이죠. 그래서 알파채널을 투명도를 적용하는데 사용할 수 없었고, AlphaTest 도 할수가 없었습니다. 그래서 오브젝트를 통짜로 다 렌더링해야만 했었구요...

그런데! 픽셀셰이더에서 사용할수 있는 clip이라는 명령어가 있더라구요. (왜 이걸 이제야 알았지...) clip(x)에서 x에 음수값이 들어가면 픽셀셰이더는 현재 픽셀을 기각시켜버리고, 화면에 띄우지 않게 합니다. 그러니깐 픽셀 셰이더에서 Opacity맵을 참조해 그 값에 따라 clip을 호출해주면 투명한 부분은 굳이 그리지 않아도 되는거지요. 이렇게 해서 셰이더 코드를 보완했습니다.


Height Tile맵 오브젝트를 작성함 :

처음에는 굳이 지형부분을 담당할 Height Field Map이 필요없다고 생각했어요. 그냥 바닥에 적당한 오브젝트를 깔면 대체할수 있다고 생각했는데, 생각보다 맵 편집의 자유도가 많이 떨어지고, 편집이 너무 어려웠습니다. 괜히 Tile맵을 많이 쓰는게 아니더라구요. 높이값을 기준으로 맵 오브젝트를 생성하는건 어려운 일이 아니었습니다. 버텍스 노말을 빠르게 계산하는 방법은 이 페이지(http://www.gamedev.net/topic/163625-fast-way-to-calculate-heightmap-normals/)를 참조했어요.


문제는 여기에 타일을 입히는 거죠. 타일은 64개 정도를 사용할 수 있게하려는 계획이었는데, 타일별로 텍스쳐를 바꾸어가며 그리려면 DrawPrimitive 콜이랑 SetTexture 콜이 타일조각 숫자만큼 늘어가게됩니다. 이건 엄청난 손실이에요. 그래서 커다란 텍스쳐를 하나 만들고 가로 세로 8등분으로 타일 텍스쳐를 넣기로 했습니다. 그러면 타일 전체를 1번의 DP콜과 1번의 SetTexture콜로 그릴수 있죠.


두번째 문제는 여기서 타일조각마다 다른 UV값을 가지므로, 필요한 버텍스 갯수는 타일조각*4개가 됩니다. 64*64타일이라고 가정하면 총 64*64*4인 16384개의 버텍스가 사용되어야하죠. XYZ값, Normal값은 같고 UV값만 다른건데 이렇게 되면 억울할 수밖에 없어요. 만약 타일크기가 128*128을 넘기게 되면 버텍스 개수는 65536개를 넘어가게 됩니다... 그러면 결국 인덱스버퍼도 D3DFORMAT_INDEX16이 아니라 D3DPT_INDEX32를 써야하고... 이거 역시 낭비네요.


그래서 발상의 전환!을 했어요! 타일조각마다 다른 타일텍스쳐를 쓰는걸 아예 셰이더에게 맡겨버리기로 했습니다. 타일 버텍스의 UV는 자신의 XY좌표에 따라 차례로 (0, 0), (0, 1), (0, 2) ... (0, 64)에서 (1, 0), (1, 1) ... ... (64, 64)까지 배정해줬습니다. 픽셀셰이더에서는 이 UV값을 floor해줘요. 그러면 UV값의 정수부분만 추려지게 되고, 이 부분은 해당 픽셀이 속한 타일의 XY번호와 일치하게 됩니다. 그리고 이 값을 이용해서 픽셀셰이더는 타일조각의 정보가 담긴 D3DFORMAT_A8 포맷의 2차원 텍스쳐를 참조합니다. 이 텍스쳐에는 타일조각 별로 텍스쳐의 번호가 담겨있어요. 이 값을 이용해 UV값을 조작해주고, 이 UV로 타일 텍스쳐를 가져오는거죠.


여기서 실수하면 안 될부분은 타일조각의 정보가 담긴 텍스쳐는 절대로 보간해서는 안된다는 겁니다. 밉맵은 아예 생성도 하지 않았고, MINFILTER, MAGFILTER모두 POINT으로 해놓았습니다. 그러면 굳이 UV값에서 정수만 추릴필요도 없어요. POINT 필터링할때 알아서 floor해주기 때문이죠.


이렇게 하면 타일조각*4개의 버텍스가 아닌, (가로길이 1)*(세로길이 1) 개의 버텍스만 있으면 됩니다. 64*64 타일이라 가정하면 64*64*4 = 16384에서 65*65 = 4225개의 버텍스만 있으면 되는거죠. 이렇게하면 최대 255*255타일까지 D3DFORMAT_INDEX16 포맷의 인덱스버퍼를 사용해서 인덱싱할수 있습니다. (그 이상은 안됨ㅋ)


그런데! 이렇게 해보니 서로 다른 타일텍스쳐가 번져서 침범하는 현상이 발생했어요. Texture Atlas를 할때 필수적으로 텍스쳐들을 띄워서 배치하는 이유가 필터링 시에 서로 번져서 영향을 줄수 있기 때문이죠. 여기서도 그런현상이 발생한거에요. 이 문제는 MIPFILTER를 NONE으로 설정하면 해결되지만, 그렇게하면 전체적인 텍스쳐품질이 저하되므로 좋은 해결책이 아니더라구요. 그래서 타일 텍스쳐를 배치할때 2픽셀 정도씩 여유분을 두고 배치했지만, 그래도 텍스쳐가 번지는 현상을 막을수가 없었습니다. 고민하던 끝에 밉맵 레벨을 제한할수 있도록 코드를 수정했습니다. 전체 512*512 텍스쳐에 64*64크기의 타일 텍스쳐조각이 붙어있는 것인데, 7단계 밉맵까지가면 전체 텍스쳐 크기는 8*8이 되고, 한 타일은 1*1크기밖에 되지 않습니다. 여기서 필터링을 하게되면 필연적으로 텍스쳐번짐이 발생할 수 밖에 없는거죠. 64*64크기 타일에 상하좌우로 2픽셀씩 여유분을 두었으므로, 이 여유분이 제 역할을 발휘하려면 3단계 이상의 밉맵을 사용하면 안됩니다. 그래서 밉맵레벨은 3단계로 설정했는데, 텍스쳐 품질이 너무 저하되어서 밉맵레벨 4단계까지 사용하기로 타협봤습니다. 그랬더니 번짐현상도 적당히 줄어들고, 텍스쳐 품질도 적당히 보기 괜찮아서 만족스럽네요.




오늘작업내역은 여기까지구요, 앞으로는 타일맵 부분을 강화해서 LOD와 프러스텀 컬링이랑 오클루전 컬링이 적용되도록 하려구요. 

관련글 더보기

댓글 영역