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

안녕하세요. 2달 동안 놀아버린 랩하는좀비, 랩좀비군입니다. 오랜만이네요. 회사에서 막히는 일이 있어서 이것에 대해서 신경쓰고 있다보니, 글을 게시하는 것을 까맣게 잊고 있기는 개뿔, 그냥 안쓰고 있었습니다(...). 아직 문제는 풀리지 않았는데, 계속 생각하고 있어봤자 답은 나오지 않을 것 같아서 머리도 정리할 겸 이제 게시하네요. 실은 5월 17일에 게시 할려고 했는데, 블리자드의 은혜가 5월 15일이어서 잠시 잠적했습니다. 글은 썩어가고 있고, 작성자는 돌아오지 않고 있고, 리플은 달리고 있고, 가정이 무너지고, 사회가 무너지고, 악마 사냥꾼의 랩은 올라가고!!

이번에 구현해 볼 기능은 Screen Space Decal(이하 SSD) 기능입니다. Deferred Decal이라고도 하지요. 언차티드에서도 사용했고, 저번에 게임테크 2012에서 발표한 KUF에서도 잘 사용하고 있더군요. 그리고 우리의 꽃미남 천재 프로그래머이신 포프님이 참여하신 스페이스 마린에서도 잘 사용하고 있는 기능입니다. 스페이스마린의 SSD기능은 포프님이 KGC2012에 소스까지 공개하신다고 하니 기대를 해 보아요. 

그만큼 싸고(많이 싸지는 않은 것 같습니다. 아직 저희도 공격적으로 사용하고 있지 않아서 측정을 안해봤습니다.), 구현하기도 쉽고 해서 앞으로 이 기능이 세상을 널리 이롭게 하지 않을까 합니다. 이건 뭐 초딩도 20분이면 구현할 수도 있는 -하지만 전 하루 걸림 음하하- 기능이니 모두 구현해 보아요.

아무튼, 잡설은 이정도로 하고, 하나하나 썰을 풀어보도록 하겠습니다.

데칼에는 다음의 4가지 기능이 들어가 있어야 훌륭한 데칼 시스템이라 할 수 있습니다.

1. Scene의 Albedo뿐만 아니라, Normal, Specular Factor 등등 다른 surface들의 값들을 변경할 수 있어야 합니다.
2. Decal은 모든 동적인 것과 정적인 폴리곤의 표면에 잘 붙어야 합니다.
3. Decal은 붙는 표면에 따라서 잘라지거나 감싸거나 할 수 있어야 합니다.
4. Decal은 복잡하게 생긴 메쉬에도 잘 붙어야 합니다.

1번은 Deferred Rendering 시스템으로 렌더링 파이프라인이 구성되어 있다면 쉽게 적용할 수 있습니다.
문제는 2 ~4번까지인데, 기존의 데칼시스템으로는 조금 힘든 문제였습니다.

우리가 지금까지 구현했던 데칼 시스템은 - 이하 Forward Decal. 정확한 명칭은 저도 잘 모르겠습니다. 아시면 알려주세요.-

1. 데칼이 붙을 장소의 버텍스 정보를 알아온다.
2. 알아온 버텍스들을 잘 연결해서 이쁜 모양의 표면을 만들어 준다.
3. Z-Fighting이 발생하지 않도록 카메라와 거리를 이용해서 잘 조절하여 표면의 위쪽에 위치시켜 준다.

이런 방식으로 하고 있었지요. 위의 문제에 대해서,

2번의 동적인 메쉬일 경우 매 프레임 갱신되는 버텍스의 위치를 알고 있어야 했으며,
3번일 경우, 버텍스의 위치에 따라서 많은 계산을 통해서 잘 일그러지 않게 만들어야 했습니다.
4번은 그냥 계산하면 되지만 버텍스의 갯수가 많아질 수록 연산량이 많으 늘어나서 성능에 영향을 끼칩니다. 거기에다가 Scene의 LOD까지 가세해 버리면 이건 뭐 아오 빡쳐! LOD 하지마! 상태가 되었습니다.

하지만 SSD에서는 완벽하지는 않지만 기존의 구현과는 다르게 2, 3, 4번의 문제를 쉽게 해결 할 수 있는 방향이 있습니다. 여기서는 그 방법에 대해서 설명하진 않겠습니다. 저도 여기까지는 구현하지 않았거든요. 저는 직접 구현하지 않은 거 아니면 썰을 풀지 않습니다. 신용100%. 필요하면 그 때 구현하기로 하고, 역시나 올 말의 잘생기신 포프님의 발표를 기대해 보아요. 흐흐

SSD의 최고 장점은 말 그대로 화면 공간(Screen Space)에서 데칼을 그리는 것에 있습니다. FD에서는 버텍스의 위치값을 참조하면서 Z-Fighting이 발생하지 않도록 이것 저것 매직 밸류를 테스트하면서 많은 시간을 소모했던 것에 반해서 SSD는 그런 문제가 전혀 없습니다. 말 그대로 이미 렌더링된 2D화면에 덧칠하는 방법이거든요. 그래서 FD와는 다르게 카메라를 눕혀서 본다고 해도 데칼이 붕~ 뜨거나 하진 않습니다. 게다가 버텍스 정보를 가지고 있을 필요도 없구요. Depth Texture를 알고 있어야 한다는 제약이 있지만, 요즘에는 기본적으로 Depth를 먼저 렌더링하지 않을까 하는 생각이 듭니다. PostEffect 만들 때 참
조하면 좋기도 하구요.

그럼 어떻게 구현해야 하는지 하나 하나 스텝으로 알아보도록 하지요.

1. Depth를 화면에 렌더링합니다.
Depth를 렌더링하는 이슈는 여러 가지가 있습니다. w로 나눌 것인지, far plane 상수를 두고 나눌 것인지 등등, 이것들은 상관 없고 나중에 Depth값을 이용해서 Decal의 Local공간으로 변환을 할 수 있는 방법만 알고 계시면 됩니다. 전 far plane의 상수값을 두어서 실행했습니다.

2. 데칼을 렌더링하기 위한 기하구조를 만듭니다.
모양은 Box나 Sphere가 주로 사용되는 것 같습니다. 여기서는 그냥 Box를 사용하도록 하지요.

3. 이제 이놈을 화면에 렌더링하면서 Local위치를 가지고 UV를 계산해 냅니다.
위치는 표면에 위치하도록 하면 되겠지요? UV까지 잘 계산하였다면 다음과 같이 화면에 데칼용 Box가 렌더링됩니다.

  

간단한 Shader코드는 다음과 같습니다.

float pixelDepth = tex2D(depthtex, vPos);     //Depth값을 알아옵니다.
float3 worldPos = ComputeWorldPos(uv, pixelDepth); //Depth값을 이용해서 WorldPos를 계산합니다.
float3 decalLocalpos = worldPos * invWorldTM; //WorldPos를 Decal의 Local공간으로 이동시킵니다.
float2 decalUV = decalLocalpos.xy + 0.5f; // 0.5를 더해서 0.0과 1.0 사이로 맵핑시킵니다.

4번째 줄에서 0.5를 더한 이유는 Decal의 Local Vertex의 위치가 -0.5 ~ 0.5로 맵핑되어 있기 때문입니다.
즉, 2번에서 Box형 기하구조를 생성할 때, Vertex의 위치값을 -0.5와 0.5를 이용해서 만드시면 됩니다.
조금만 생각해 보시면 쉽게 알 수 있으니 그냥 스무스하게 넘어가겠습니다.

5. 이제 렌더링되는 부분이 아닌 다른 모든 부분을 AlphaTest나 AlphaBlending으로 날려버립니다. 
어느 것으로 할지는 렌더링 되는 데칼에 따라 다릅니다. 뭐 쉬운 부분이니까 잘 하시면 됩니다. 간단한 셰이더 코드는 다음과 같습니다.

float dist = abs(decalLocalPos.z);        //Decal의 Local깊이를 구합니다.
float scaleDistance = max(dist * 2.0f, 1.0f); //Local깊이를 0.0과 1.0으로 맵핑시킵니다.
float fadeOut = 1.0f - scaleDistance;     //이제 FadeOut값을 구해서..
float4 Color = decalColor;                //최종 칼라를 구해주고
Color.a *= fadeOut;                       // FadeOut값을 곱해주면 표면의 부분 이외는 사라지게 됩니다.

   

...어라 되었네요? 이건 뭐 초딩도 30분이면 할 수 있는..
3D들도 새로 나온 기술들을 보면 어려워 보이지만 실제로 구현해보면 '와 쉽네잉' 이라는 생각이 드는 게 대부분인 것 같습니다.
좀 더 많은 정보를 원하시는 분은 게임 엔진 젬스 1권에 내용이 나와 있으니 참고하시길 바랍니다.

자 그럼 랩좀비군은 이만 총총총. 

댓글을 달아 주세요

  1. Favicon of https://gamedevforever.com 김포프 2012.05.11 16:05 신고  댓글주소  수정/삭제  댓글쓰기

    어라... 제가 KGC 2011에서 잠시 맛뵈기로 발표했고... 올해 시그래프(2012)에서도 발표했던 주제인데.... 물론 KGC 2012에서는 더 자세히 해서 스페이스마린에 들어갔던 소스코드까지 공개할 계획입니다.

  2. Favicon of https://gamedevforever.com 끼로 2012.05.11 20:15 신고  댓글주소  수정/삭제  댓글쓰기

    이제 본문의 뙇! 은 지울 수 없을겁니다

  3. Favicon of https://gamedevforever.com 랩.좀비 2012.05.17 08:48 신고  댓글주소  수정/삭제  댓글쓰기

    아옥. 디아블로에서 벗어날 수 없당!

  4. Favicon of https://gamedevforever.com 랩.좀비 2012.06.01 15:23 신고  댓글주소  수정/삭제  댓글쓰기

    마지막 이미지 보이시나요? 왜 메인컴에서는 보이고 노트북에서는 안보이지...

  5. kps 2013.01.25 14:56  댓글주소  수정/삭제  댓글쓰기

    이제 막 게임 개발 회사에서 많은것을 배우고 있는 사회 초년생입니다.
    SSD기법에 대해서 이해가 잘 안되는 부분이 많습니다. 우선 위의 코드를 참조해서
    픽셀의 깊이를 읽어와서 깊이와 픽셀의 스크린 좌표로 월드 좌표를 찾을 수가 있는데
    거기에 월드 역행렬을 곱하면 그냥 로컬 좌표가 아니라 왜 데칼의 로컬영역으로 보낸다는건지
    이해가 잘 안됩니다. 혹시 글 읽어보신다면 답변좀 해주시면 감사하겠습니다ㅠㅠ

    • 행인A 2013.02.03 00:50  댓글주소  수정/삭제

      로컬 좌표 = 데칼의 로컬 영역입니다.

      주로 박스 형태의 메쉬를 통해서 데칼을 렌더링하는데, 월드 행렬을 곱하지 않은 박스 메쉬의 크기(로컬 좌표의 크기)가 1 이기 때문에 로컬 좌표에서의 위치를 텍스쳐의 UV 로 변환해서 사용하는 거죠.

  6. 유리멘탈 2013.09.05 17:05  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 현재 ssd 구현 하면서 생긴 문제가 있어서 여쭤 보려고 합니다.
    혹시 데칼이 카메라 위치나 시야에 따라서 제대로 나오다가 카메라를 돌리거나 하면 uv가 일그러지거나 하는 증상이 있는데... 혹시 이런 증상 있으신 분 계신가요?
    텍스쳐는 지형에 잘 발리기는 하는데.. 흑

    해결 했습니다. 오브젝트 포지션의 y값이 문제가 있었고, 오브젝트의 xy가 아닌 xz로 하니 해결이 됩니다. 감사합니다. ^^