꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by 알 수 없는 사용자
안녕하세요. 매월 12, 27일에 글을 올리는 cagetu입니다.
늦었지면, 새해 복 많이 받으시고, 다들 대박 나는 한 해가 되시길 기도할게요. ^^
(저도 기도 좀... ㅎㅎ)

오늘은 Vertex Texture Fetch(
VTF)에 대해서 이야기 해볼까 합니다.
이름은 많이들 들어보셨겠죠?! 기술 자체가 새로운 것은 아닙니다. 하지만, 구현을 한 사례가 많이 알려지지도 않았고, 의외로 자료를 찾아보면, 많이 없습니다. 헐~
그래서, 제가 해보면서 수집한 자료와 다른 것들을 보면서 사용되는 부분들에 대해서 눈여겨 봤던 부분들을 묶어서, 그냥 정리나 해볼까 합니다.


먼저 Texture Fetch라는 용어를 먼저 정의하는 것이 좋을 듯 합니다. 흔히 우리가 텍스쳐를 읽어온다라는 표현의 정식 명칭(?)입니다. 즉, tex2D(sampler, uv);를 Texture Fetch라고 말합니다.

그러니, Vertex Texture Fetch라는 것은 말그대로 Vertex 에서 텍스쳐를 읽어온다는 의미로 받아들이시면 됩니다. 더 직관적으로 말하면, "Vertex Shader에서 텍스처를 읽어오는 기능"입니다. 
 
(셰이더를 기준으로 이야기 하도록 하겠습니다. 고정파이프라인에서의 설정은 참고자료의 링크를 참고하세요~

[출처 : R2VB 발표자료(amd)]

버텍스 연산에서 어떻게 보면 예외적(?)으로 사용하는 기능입니다. 따라서, 규제가 좀 심한 편입니다. 조건은 다음과 같습니다. 

- nvidia기준 6 시리즈 이상만 사용 가능
- 텍스쳐 포멧은 A32R32G32B32F 혹은 R32G32F만 사용가능. (최근 다른 포멧도 지원한다고는 하기는 합니다.)
- Bilinear 혹은 Trilinear Filtering을 지원하지 않음. (필요하면 직접 구현해야 합니다)
 
- 밉맵은 단계를 자동으로 계산할 수 없음. 밉맵 단계를 명시해주어야 함. (tex2Dlod 만 사용가능)

VTF를 사용하는 목적은 버텍스 셰이더에서 텍스쳐의 정보를 이용하여, 버텍스의 정보를 조작할 수 있기 때문입니다. 


가장 일반적으로는 
스킨드 인스턴싱(Skinned Instancing)이나, 오브젝트 모션블러(Object Motion Blur) 처리 등에 사용됩니다.
셰이더의 변수 레지스터의 개수 제한 때문에, 장면의 스키닝 되는 오브젝트에 대해서, 모든 뼈대의 정보를 셰이더로 전달할 수 없을 때, 텍스쳐에 뼈대의 변환 정보를 기록해 두었다가, 셰이더에서 처리를 하는 방식으로 보통 많이 사용됩니다. 
 

[Lost Planet 모션블러 예]

[Skinned Instancing]

사실 VTF를 이용하는 구현 방법은 크게 어렵지는 않습니다. 간단하게 설명하면, 텍스쳐에 원하는 정보를 기록하고, 셰이더에서 텍스쳐에서 정보를 읽어오기만 하면 됩니다. (단, tex2Dlod 명령어를 이용합니다.) 

NVIDIA의 Skinned Instancing Sampling Code에서 구현된 VTF에 뼈대의 변환 정보를 저장해 두었다가, 읽어오는 방법을 살펴보도록 하지요.
(
http://developer.download.nvidia.com/SDK/10.5/direct3d/samples.html)

텍스쳐에 저장하기 

먼저, 텍스쳐에 뼈대 정보를 기록합니다. 1개의 텍셀이 32-bit float 4개를 기록할 수 있으므로, matrix 1개를 기록하기 위해서는 4개의 
텍셀 을 이용할 수 있습니다.
즉, A32R32G32B32F 포멧으로 텍스쳐를 만들어서, 4개의 텍셀에 1개의 매트릭스를 저장한다는 의미입니다.
물론, 4x4 matrix라고 하더라도, 4x3개의 matrix만을 이용해서, 매트릭스를 처리할 수 있기 때문에 3개의 텍셀을 이용해서 처리를 할 수 있지만, 2D 텍스쳐의 경우, 2의 배수로 만들어지기 때문에, 4개의 
텍셀 단위로 매트릭스를 얻어오기 편하도록 편의상 4x4 matrix를 저장합니다. (마지막은 padding). 이렇게 되면, 4의 배수로 인덱스를 얻어 올 수 있기 때문에, 작업에 편리함이 있습니다. 

하지만, 1D 텍스쳐를 사용한다면, 언리얼의 모션블러에서 VTF를 사용하는 사례에서 처럼, 4096 크기의 1D 텍스쳐에 하나의 본을 3개의 텍셀을 저장하여 사용할 수 있습니다. (http://udn.epicgames.com/Three/MotionBlurSkinningKR.html)

텍스쳐에서 읽어오기 
Vertex Shader 에서는 다음과 같이, Vertex 정보가 기록된 텍스쳐에서 Vertex 정보를 읽어옵니다. 아래의 샘플 코드는 DX10의 예입니다만, DX9의 경우에는 tex2Dlod(uv, 0, mipmaplevel) 명령어를 사용하여 텍스쳐 정보를 읽어옵니다. 
// Read a matrix(3 texture reads) from a texture containing animation data
float4x4 loadBoneMatrix(uint3 animationData,float bone)
{
   float4x4 rval = g_Identity;
   // if this texture were 1D, what would be the offset?
   uint baseIndex = animationData.x + animationData.y;
   // 4*bone is since each bone is 4 texels to form a float4x4
   baseIndex += (4 * bone);
   // Now turn that into 2D coords
   uint baseU = baseIndex % g_InstanceMatricesWidth;
   uint baseV = baseIndex / g_InstanceMatricesWidth;
   // Note that we assume the width of the texture
   // is an even multiple of the # of texels per bone,
   // otherwise we'd have to recalculate the V component per lookup.
   float4 mat1 = g_txAnimations.Load( uint3(baseU,baseV,0));
   float4 mat2 = g_txAnimations.Load( uint3(baseU+1,baseV,0));
   float4 mat3 = g_txAnimations.Load( uint3(baseU+2,baseV,0));
   // only load 3 of the 4 values, and deocde the matrix from them.
   rval = decodeMatrix(float3x4(mat1,mat2,mat3));
   return rval;
}
bone의 인덱스를 가지고, Vertex Texture 내에서의 저장된 텍셀 좌표를 구하여, 4개의 텍셀을 읽어와 1개의 매트릭스로 만들어서 버텍스 셰이더에서 스키닝 계산에 사용하면 됩니다.

위와 같은 처리를 좀 더 극대화 시켜서 처리시킨 NDC10에서 소개된 "마비노기2 캐릭터 렌더링 기술"(
http://www.slideshare.net/henjeon/ndc2010-2)입니다. VTF를 이용하여 스키닝을 한다고 하더라도, 여러 패스를 렌더링(스키닝, 모션블러 등)에서 동일한 처리를 해주어야 합니다. (물론, MRT를 사용하면 한번에 할 수 있습니다.) 따라서, 기본 아이디어는 완전 스키닝까지 완료한 버텍스 정보를 Vertex Texture에 기록해놓고, 그냥 정점 정보 자체를 뽑아서 사용한다는 것입니다. 굉장히 재밌는 아이디어죠?! 마치 그냥 Cache 메모리 처럼 텍스쳐를 사용하는데, 멋집니다. ㅎㅎ. 
 

Terrain의 경우, 점점 더 넓은 처리가 요구가 되고, 절차적 지형에 대한 관심이 많아지고 있기 때문에, 최근 BattleField, Unreal등의 엔진과 같이 대규모의 지형처리를 하는 경우에는 버텍스 셰이더에서 절차적 높이맵 정보를 읽어와서 버텍스를 처리합니다. 
UDK - Landscape
Terrain Rendering in Frostbite using Procedural Shader
 
- GPU Gems2 : Terrain Rendering Using GPU-Based Geometry Clipmaps

이 처럼, VTF를 이용하면, 셰이더에서 다양한 처리를 할 수 있도록 도와주기 때문에, 효율적인 렌더링 처리를 할 수 있도록 많은 가능성을 열어줍니다.
 
하지만, 불행하게도 VTF를 사용하기에는 커다란 단점이 있습니다. 바로 
속도 입니다. Vertex Texture Fetch의 속도는 굉장히 느립니다. 따라서, VTF를 사용할 때에는 최대한 텍스쳐를 읽어오는 부분을 줄이는 방향을 고민해야 합니다.
(언리얼 모션블러의 경우, texture fetch를 버텍스당 12번씩 하는 필요로 하기 때문에, VTF를 사용한 오브젝트 모션블러의 경우, 사용하지 않은 카메라(rigid) 모션블러와 비교하여, 60%의 성능 차이가 있을 수 있다고 하는군요. 모든 오브젝트가 움직여서, 렌더링을 생략할 수 없을 경우에...)

알려진 바로는 DirectX10이상에서는 VTF의 속도가 안심하고 쓸 수 있을 정도라고 하고, DirectX11을 많이 사용하면서는 조금 더 다양하게 사용하는 것 같다는 느낌도 있기는 있습니다. 
저는 DirectX9 기반으로만 사용을 해봐서, 그 차이가 어느 정도 인지는 모르겠구요. 혹시 테스트 데이터가 있으신 분들은 공유해주시면 좋겠네요. ^^

실제로 구현해보면, 텍스쳐에 기록하고 셰이더에서 읽어오는 부분 이외의 최적화 부분이 가장 중요한데요. 이 부분은 다른 게임이나 엔진에서 처리된 내용을 토대로 지속적으로 연구를 해서, 적절한 범위로 사용을 할 필요가 있을 듯 합니다.

Vertex Texture Fetch라는 기술에 대해서 간략하게 정리를 해보았습니다. 이 정리가 조금이나마 VTF에 대한 이해와 VTF를 이용하여 처리되는 작업들에 대한 이해를 하는데 도움이 되었으면 좋겠네요. ㅎㅎㅎ. 

참고자료 
http://duckii.egloos.com/692042
http://developer.download.nvidia.com/SDK/10/direct3d/Source/SkinnedInstancing/doc/SkinnedInstancingWhitePaper.pdf
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter18.html 
http://developer.amd.com/media/gpu_assets/R2VB_programming.pdf  
-
http://www.scribd.com/doc/16937461/54/Vertex-Texture-Fetch
반응형
,