꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.

Vertex Compression

프로그래밍 2011. 12. 12. 02:00
Posted by 비회원
예전에 개인 블로그에 적은 적이 있기는 한데, 다시 한번 정리해서 올려보겠습니다. 
이전에 Nebula3 라는 공개용 엔진을 참 많이 봤었는데, 그 엔진 기능 중에 Vertex Component Packing 이라는 제목의 관심 있는 포스팅이 하나 있었습니다. 말 그대로 버텍스의 사이즈를 줄여보자는 것입니다. 버텍스 사이즈를 줄임으로써 얻는 이득이 몇 가지가 있는데요.
- 첫째, 게임에서 mesh 리소스의 사이즈를 줄일 수 있습니다. 이게 별거 아닌 것 처럼 느껴지겠지만, 게임을 릴리즈 할 때 정도 되면, 엄청난 양의 리소스 데이터를 줄일 수 있다는 이야기가 됩니다.
- 두번째, 전체적이 로딩 시간을 줄일 수 있습니다.
- 세번재, 전체적인 버텍스 버퍼의 크기를 줄여주기 때문에, 그래픽 카드로 버텍스 데이터를 전송할 때, 버텍스 처리량(vertex though-put)이 좋아집니다.

물론, 추가적인 shader에서 연산이 들어갑니다. 하지만, 얻어지는 이득과는 Trade-Off 관계이기 때문에, 상황에 맞게 선택을 하면 되겠습니다.

Vertex 압축

패킹된 버텍스 구조의 최종 모습은 이렇습니다.

[Nebula3]

각 Comonent이 가지는 특성을 이용해서, 값의 범위에 따라 적절하게 값을 압축하는 형태입니다.


* Position
Position의 경우, 비교적 높은 정밀도와 표현 가능한 값의 범위가 굉장히 광범위 하게 때문에, 사실 압축을 하기란 쉽지가 않습니다. 따라서, 일반적으로 포지션 압축은 사용하지 않습니다.
 
*Texture Coord
텍스쳐 좌표의 경우, [0, 1] 사이의 범위를 가지고, 일반적인 텍스쳐의 경우에는 정밀도가 매우 중요한 편이 아닙니다. 따라서, Texture 좌표의 경우에는 압축을 시도해볼 만 합니다. float을 고정 소수점(Fixed Point)로 변환하여, float2 -> short2로 변환을 합니다. 이렇게 되면, 8Byte의 값을 4Byte로 변환을 할 수 있습니다. 4.12 Fixed Point 변환을 주로 이용하는데, 16비트 중 12비트를 왼쪽으로 이동시키는 것입니다. 혹은 4096으로 나누어 준다고 하면 빠르겠군요. 즉, c++에서 4096으로 나누어 주고, shader에서 4096을 곱해주어서 복원해주는 형태입니다.

- Normal/Tangent/Binormal :
노말은 [-1, 1]의 범위를 가지고 있고, 정밀도 또한 그렇게 중요한 편은 아닙니다. 이는 UBYTE4를 이용하면, 좋은데요. UBYTE4 계열의 특성에 대해서 먼저 알아보자면,

- UBYTE4 : 0 ~ 255 범위의 unsigned char형 4개 (BGRA)
- UBYTE4N : 0 ~ 1 범위의 unsigned char형 4개 (BGRA)
(geforce 7xxx 이상의 그래픽 카드에서부터 지원!!!) 
 

Normal의 경우에는 UBYTE4N을 사용합니다. 이 결과, 12Byte(float*3) -> 4Byte(ubyte4n)으로 줄일 수 있습니다.  http://www.gamedev.net/topic/491408-how-to-design-a-mesh-class-/ 를 참고하시면, 어떻게 압축을 하는지 코드를 보실 수 있습니다.

- Skin Weights
Skin Weight의 경우, [0~1] 사이 범위의 값을 가지기 때문에, 이는 UBYTE4N 형을 사용해서 압축을 하면, 그대로 이용이 가능한 범위입니다. 이를 통해서 float4(16byte)를 UBYTE4N(4byte)로 줄일 수 있군요. 하지만, 이 경우, 정밀도가 영향을 줄 수 있기 때문에, 셰이더에서 다시 한번 정규화를 해주어서, 전체적인 weight를 보정해줍니다.
기본적으로 normal과 동일하게 처리가 되지만, [0, 1]의 범위이기 때문에,  노말과 같이 [-1, 1] -> [0, 1]로 변환해주는 과정 (normal * 0.5f + 0.5f)을 할 필요가 없이 바로 UBYTE4N로 변환을 할 수 있습니다.

return (packedWeights / dot(packedWeights, float4(1, 1, 1, 1)));


- Skin Index
Skin 의 본 인덱스의 경우, [0, n]의 값 범위를 가지게 되는데, bone의 개수가 255를 넘지 않는다며, UBYTE4의 특성을 이용해서, 압축이 가능합니다. 이 결과로 float4(16byte)를 ubyte4(4byte)로 압축이 가능합니다.

이 결과 버텍스 하나가 position(float4), normal(float3), uv(float2), skinweights(float4), skinindices(float4)로 구성되어 있을 때, 압축을 하면, 64Byte -> 28Byte로 버텍스의 사이즈를 줄여줄 수 있습니다. 

결론
 최근 온라인 게임의 리소스 데이터의 용량이 1G가 넘어가는 경우는 이미 일반적입니다. 이러한 상황에서, 리소스 데이터를 다운로딩하는 시간을 줄이는 것 또한 많은 개발사에서 고민을 하고 있습니다. 특히, 해외 서비스를 하는 경우에는 더욱이 이러한 필요성을 더욱 많이 느끼고 있습니다.
이런 고민의 해결책 중 하나가 vertex 압축이 될 수도 있습니다. 약간의 vertex shader 추가적인 연산을 통해서, 데이터 사이즈를 1/3 정도로 줄일 수 있다면, 충분히 시도해볼만한 작업이라고 생각합니다.
 

참고자료
- http://cagetu.egloos.com/4941476
- http://zho.pe.kr/view.html?file_name=doc/vtxprg.txt
http://flohofwoe.blogspot.com/2008/03/vertex-component-packing.html
TAG

댓글을 달아 주세요