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


들어가며

요즘에는 스마트폰을 사용하지 않는 사람을 찾아보기가 힘들지요. 저 역시도 한시도 스마트폰을 품에서 떠나보내지 않고 생활하고 있습니다. 정말이지 스마트폰이 없던 시절에는 도대체 어떤 폰을 들고다녔나 싶습니다. 아마 다시 일반 피쳐폰 쓰라고 하면 돌아버릴거예요. WM6 폰을 쓰라고 해도 목매달아버릴지도 모르지요. 아마 다들 마찬가지이실거라 생각합니다. 그런데 지금으로부터 10년전인 2002년에는 어떤 핸드폰을 사용했는지 기억나시나요? 그 당시는 폴더형의 핸드폰이 주류를 이루고 있었고 LCD가  흑백에서 컬러로 바뀌는 과도기적인 시절이였습니다. 안테나도 당연히 존재하고 있었고 1년뒤인 2003년이 되서야 안테나가 없는 핸드폰이 나오기 시작하면서 '인테나'라는 용어가 생겨나기 시작했지요. MP3 재생같은 것도 상상조차 할 수 없었고 16poly가 지원되는 것도 놀라웠던 시절이지요. 참 그시절의 핸드폰은 어떻게 사용했나 싶습니다. 정말로 긴 세월이 흘렀네요.

이미지 출처 : http://tomorrowhistory.tistory.com/

하라는 게임 이야기는 안하고 갑자기 웬 뜬금없는 핸드폰 이야기냐구요? 실은 DirectX9에 대한 이야기를 하려고 합니다. 바로 이 놀라운 핸드폰들을 사용하던 2002년에 DirectX9가 처음 배포되었지요. 그러고서는 10년이 지난 지금까지도 계속 사용되고 있는 것입니다. 지속적인 업데이트가 되면서 마이너 버젼들이 바뀌긴 했지만 DirectX9의 목숨이 계속 유지되고 있는것입니다. 게다가 여전히 대부분의 게임들이 DirectX9으로 제작되고 있습니다. 핸드폰은 흑백에서 컬러로, 16화음에서 자연음으로, 폴더폰에서 스마트폰으로 바뀌는 긴 세월 동안 DirectX9이 사용되고있는 것입니다. DirectX10이 나오고 이제 11까지도 나왔지만 여전히 DirectX9을 버릴 수가 없습니다. 아직은 대부분의 가정에 있는 PC들이 여전히 windows XP를 이용고 있고 심지어 PC방조차 마찬가지입니다. 앞으로도 당분간은 DirectX10,11로는 개발할 일이 없어보입니다. 

하지만 10년 사이에 그래픽카드 H/W는 엄청난 속도로 발전을 해왔습니다. 2002년에 3dfx의 voodoo가 단종되고 그 10년뒤인 현재는 라데온 HD 7xxx, 지포스 GTX 6xx등이 나오고 있습니다. 메모리클럭은 166Mhz에서 6000hz로 30배 이상의 성능으로 발전했습니다. 

이 부두 카드에는 슬픈 전설이 있지. 하지만 난 전설따위 믿지 않아

이토록 긴 세월동안 동일한 DirectX9 사용해왔지만 GPU는 엄청난 변화를 겪어왔습니다. 따라서 DirectX9를 사용함에 있어서도 시간이 지나면서 그 효율도 변화하게 됩니다. 예전에는 당연시 되어 왔던 최적화 방법이 현대의 그래픽카드에서는 비효율적인 최적화 방법이 될 수도 있는것이지요. 이번 포스팅에서는 DirectX9에서  최적화 시, 예전과는 다른 현대의 GPU를 위한 주의점을 정리해보고자 합니다. 사실, 별건 없고 nVIDIA, ATi, Intel의 그래픽 프로그래밍 가이드 문서의 내용들 중에서 주관적인 견해로 정리한 것입니다. 제가 자의적으로 해석하고 받아들인 내용도 있으므로 시간 되신다면 원문을 모두 읽어보시길 권장합니다. 혹시라도 틀린 내용이 있다면 지적 부탁드립니다.

nVIDIA : http://developer.download.nvidia.com/GPU_Programming_Guide/GPU_Programming_Guide_G80.pdf

ATi : http://developer.amd.com/media/gpu_assets/ATI_Radeon_HD_2000_programming_guide.pdf

Intel : http://software.intel.com/file/34436


통합셰이더모델 (unified shader model)

여기서 말하는 "현대의 GPU"의 기준을 콕 찝어 이야기 하자면 DirectX 10 이상 지원 모델으로 삼으면 될 것 같습니다. nVIDIA Geforce 8xxx 시리즈 이상, ATi Radeon HD 2xxx 시리즈 이상, Intel GMA 3xxx 시리즈 이상부터 지원되는 것으로 알고있습니다. 시기적으로는 2008년 이후정도가 되지 않을까 싶습니다. 이 때부터 DirectX10 지원 카드가 본격적으로 출시되기 시작했지요. 

이 DirectX 10 지원 여부가 그래픽 카드 아키텍쳐의 큰 변화를 가져오게 되었습니다. 바로 통합 셰이더 모델( Unified Shader Model)이지요. 예전에는 버텍스 셰이더 (vertex shader) 처리 유닛과 픽셀 셰이더 (pixel shader) 처리 유닛이 물리적으로 따로 존재했었습니다. 하지만 DirectX 10부터는 아키텍쳐가 통합 셰이더 모델로 변경되었습니다. 버텍스든 픽셀이든 지오메트리든 같은 셰이더 처리 유닛을 사용하게 되는 것이지요.  

이미지 출처 : http://vlsi2.kaist.ac.kr/research/multimedia-processor/unified-shader

예전에는 버텍스들을 VS 전용 유닛에서 처리하고 그 결과물을 여차저차해서 PS 전용 유닛에서 처리하고 래스터라이징을 처리가 되었습니다만 통합 셰이더 아키텍쳐에서는 이러한 VS와 PS의 처리 유닛이 물리적으로 구분되어 있지 않습니다.

 아~ 그렇구나~ 근데 이게 뭐 어쨌다구? 아까부터 왜 쓸데없는 소리만 지껄임? 님 장난함?

이러한 통합 셰이더 모델은 DirectX 10, 11을 지원하기 위한 아키텍쳐지만 DirectX9에서도 물리적으로는 이 통합 셰이더 모델로 작동할 수 밖에 없습니다. 그렇게 되면서 엄청난 변화가 생기게 되었고 그 변화를 적극 활용하는 것이 현대 GPU를 위한 최적화의 중요한 포인트가 되는 것이지요.  


버텍스와 픽셀의 셰이더 로드 밸런싱

통합 셰이더 (Unified Shader) 이전의 기존 모델에서는 픽셀 세이더 처리가 간단하더라도 버텍스 셰이더 처리가 무거우면 버텍스 셰이더가 병목이되었고, 반대로 픽셀 셰이더 처리가 무거우면 버텍스 세이더가 간단하더라도 픽셀 세이더가 그 발목을 잡아 병목이 일어나는 시나리오가 발생을 합니다. 일반적으로 픽셀 셰이더 처리 능력이 버텍스 셰이더 처리 능력보다 허접했기 때문에 최대한 많은 연산을 버텍스 셰이더에서 처리하고 그 결과를 픽셀 셰이더에 념겨주도록 만드는 것이 미덕이였습니다. 

하지만, 통합 셰이더 모델에서는 이야기가 달라집니다. 버텍스 셰이더든 픽셀 셰이더든 동일한 처리 유닛이 사용 되기 때문에 누가 누구의 발목을 잡는 상황이 일어나는 걱정은 하지 않아도 됩니다. GPU가 적절히 셰이더 로드 밸런싱을 조절하게 됩니다. 오히려 버텍스 세이더에서 계산해서 픽셀 세이더로 넘겨주는 정보가 너무 많으면 오히려 캐시 퍼포먼스나 대역폭 병목 문제등이 발생 할 수 있으므로 확인해가면서 적절한 밸런스를 잘 판단하고 사용해야 합니다.

이미지 출처 : http://www.mips.com/Customer_newsletter_0908/partnerShowcase.htm


VTF (Vertex Texture Fetch)

버텍스 세이더에서 텍스쳐를 샘플링 할 수 있는 기능인 VTF는 계륵같은 존재였습니다. 막상 기능은 존재하지만 버텍스 세이더에서 텍스쳐 샘플링을 담당하는 유닛이 너무 느렸기 때문에 마음놓고 사용하기가 번거로운 기능이였지요. 하지만 통합 셰이더 모델에서는 텍스쳐 샘플링 유닛이 따로 구분되어 있지 않고 사용하기 때문에 버텍스 셰이더에서 텍스쳐를 샘플링 하는 것이 부담스럽지가 않습니다.

이미지 출처 : http://developer.nvidia.com/sites/default/files/akamai/tools/files/PerfHUD6-UserGuide.pdf

 GPU Gems 3권에서는 이를 이용하여 인스턴싱을 구현하는 방법이 소개되고 있고, 마비노기2에서는 이를 이용하여 스키닝을 처리하고 있는 등 통합 셰이더 아키텍쳐의 수혜를 적극적으로 받고있는 능력 중 하나가 되었습니다.

이미지 출처 : 마비노기2 랜더링 기술, 전형규


셰이더 모델 버젼

전통적으로는 가급적이면 셰이더 모델 버젼을 낮은 것을 사용하는 것이 권장되어 왔습니다. 비록 Shader Model 3.0이 지원되는 하드웨어일지라도 가급적이면 Shader Model 1.1을 사용하거나 여력이 되지 않는다면 Shader Model 2.0을 사용하는 것이 권장되어 왔습니다. 하지만 이제는 가급적이면 높은 버젼의 모델을 사용하는 것이 권장되고 있습니다. 통합 셰이더 아키텍쳐는 높은 버젼에 적합하게 만들어져 있는 것이지요. DirectX 9에서는 4.0 사용이 불가하니 3.0을 사용하는 것이 좋겠지요. 특히 SM 3.0에서는 VPOS같은 훌륭한(?) 시멘틱을 사용 할 수 있어 연산도 줄일 수 있지요. 또한 최신버젼으 셰이더 컴파일러가 최적화가 잘 되어 있으므로 fxc 역시 최신 버젼을 사용하는 것이 좋습니다.


동적 분기 ( Dynamic branch)

전통적으로는 if 분기문을 사용하는 것은 죄악으로 여겨져 왔습니다만 현대의 GPU에서는 다이나믹 브랜치의 퍼포먼스가 훌륭하기때문에 if 분기를 적절히 잘 사용하면 오히려 퍼포먼스 향상을 꾀할 수가 있습니다. 예를들면 디렉셔널 라이팅의 연산이 필요 없는 그림자 영역은 연산을 건너 뛴다거나, N dot L 연산이 0 이하면 스페큘라 연산을 건너 뛴다거나 하는 식으로 사용을 할 수도 있겠지요.  

KILLZONE2는 정적 그림자 영역은 라이팅 연산을 건너뜁니다. 

 다만 주의할 점은, 픽셀이면 8x8=64픽셀, 버텍스면 연속적으로 64개가 분기 조건이 같아야 한다는 점입니다. 그렇지 않을 경우 캐시 퍼포먼스가 떨어져서 오히려 성능 저하를 초래할 수 있습니다. 앞서 예시를 든 N dot L 결과로 스페큘라를 건너뛰는 경우는 고주파 노말맵으로만 이루어진 장면인 경우는 부적합한 상황이 될 수도 있겠지요. 

이러한 동적분기의 성능을 활용하여 우버 셰이더로 활용 할 수도 있습니다. 여러 매터리얼을 한 셰이더로 몰아넣어 셰이더 설정 비용을 줄이는 것이죠. 다만 셰이더가 너무 길어지면 셰이더 캐시 문제가 발생 할 수도 있습니다.


64비트 텍스쳐 

현대의 GPU는 64bit 텍스쳐도 한 싸이클에 처리합니다. 메모리 용량 문제만 제외한다면 64bit HDR 텍스쳐를 사용 시 성능 저하를 걱정할 필요는 없는 것이지요. 하지만 무조건 그런 것은 아니고 fixed point 64bit는 여전히 두 싸이클이 걸립니다. floating point 64bit 텍스쳐를 사용해야 한 싸이클 내 처리가 되는 것이지요. 또한 필터링 역시 Point와 Bilinear인 경우엔 한 싸이클이지만 Trilinear인 경우는 두 싸이클이 소모됩니다. 


깊이 테스트 (Z-test)

현대의 그래픽 카드는 깊이 테스트의 성능이 매우 탁월합니다. 드라이버 자체적으로 H/W Depth stencil을 축소한 버퍼를 따로 가지고 있어 이를 통해 사전 테스트하여 테스트 비용을 줄이는 방법을 사용하기 때문이지요. nVIDIA는 "Hyper Z"라는 이름이고 ATi는 "Fine-grained Z"라는 이름으로 사용하고 있던데 결국 같은 기법인데 이름만 다르게 사용하는 것으로 보입니다.

이미지 출처 : Z-Buffer Optimizations, Patrick Cozzi

Early-Z 패스를 추가하여 Z-test를 활용할 수도 있습니다. 랜더링 패스 이전에 한 단계 추가하여 컬러 출력 없이 깊이만을 그려서 Z버퍼를 채워서 비싼 은면의 연산을 초기에 차단하는 것이지요. 오브젝트를 깊이순으로 정렬하여 앞의 오브젝트를 먼저 그려 뒤에 가려지는 오브젝트의 오버드로우를 방지할 수도 있습니다. 

이와 비슷한 컨셉으로 스카이박스(Sky Box)를 맨 마지막에 그리는 것도 생각해볼만합니다. 전통적으로는 스카이 박스를 Z-test를 비활성화하여 맨 처음 랜더링하고 그 뒤에 터레인이나 오브젝트등을 랜더링해왔습니다. 하늘이야 항상 배경으로 그려지는 것이고 굳이 쓸데없이 Z-test 처리하며 그릴 필요가 없던 것이지요. 하지만 이제는 Z-test의 처리가 고속으로 이루어지므로 이에 대한 큰 부담이 없으며 오히려 화면에 보이지 않게 될 픽셀을 일찌감치 걸러내어 픽셀 연산 비용을 아끼기 위해 하늘을 맨 마지막에 그리는 것도 좋은 방법이 될 수 있습니다.

이러한 고속의 Z-test를 위해서는 clear()가 필수적으로 따라줘야 합니다. 예전에는 화면의 모든 픽셀이 갱신되게 될 것이므로 화면의 clear를 건너 뛰는 방법도 사용해왔습니다. 하지만 현대의 하드웨어에서는 clear시 고속 처리를 위한 정보들이 초기화 되므로 씬 랜더 시작 전에 반드시 항상 clear 수행되어야 합니다. 풀 스크린 쿼드를 그리는 경우에는 해당되지 않습니다.


마치며

기존의 하드웨어와 현대의 하드웨어의 차이만을 초점으로 맞춰서 말씀드렸습니다만 좀 더 큰 시각으로 최적화에 대한 내용을 알고 싶으시면 제 블로그의 글을 참고 바랍니다. (블로그 홍보 죄송합니다 꾸벅) 근데 사실 좀 더 많은 PC를 커버하기 위해서는 이대로 하는 것에는 좀 무리가 따릅니다. 국내야 이 글이 대상으로 삼는 하드웨어가 대부분인 것으로 판단해도 무리가 없겠지만 해외 특히 동남아의 경우는 더 낮은 사양도 많을 것으로 판단됩니다. 이래도 고민 저래도 고민이죠. 최적화는 하면 할수록 골치가 아픈 것 같습니다. 모두들 스트레스 받지 마시고 멘탈 보호해가면서 개발하세요. 꾸벅

제 블로그의 예전 자료 :  http://ozlael.egloos.com/3671648  


댓글을 달아 주세요

  1. Favicon of http://rhea.pe.kr/ Rhea君 2012.04.22 05:24  댓글주소  수정/삭제  댓글쓰기

    역시 렌더러는 훌륭한 분들께서...

  2. 66v 2012.04.22 13:48  댓글주소  수정/삭제  댓글쓰기

    좋은 글 잘 읽었습니다.

    읽던중 PS3(RSX-nVIDIA G70)는 동적분기가 없다는 얘기를 들은 기억이 나서 구글링을 하다보니, SM3.0의 사양과 그 세대의 GPU(특히 nVIDIA)는 동적분기 퍼포먼스 문제가 있어서, 성능상의 이득은 크게 바라기 힘들다는 정보가 많았습니다.

    http://game.watch.impress.co.jp/docs/20050313/far.htm
    '現行のSM3.0のピクセルシェーダ3.0における動的な条件分岐は非常にパフォーマンスインパクトが大きいのと、高度な構造プログラミングを実現するにはまだ制限が大きい(入出力レジスタに関連した制約がある)ため、その優位性を十分に発揮できないと判断。このことから、CRYTEKでは、この実装を断念した'
    파크라이의 경우엔 동적분기가 쓸만하지 못해서 SM3.0 지원을 단념했다는 설명도 나오고,

    http://forums.anandtech.com/archive/index.php/t-1844069.html
    'Also, it's obvious that dynamic branching is a major weakness of Nv cards, the g71 offers no improvement over the g70 in that area, except for higher clockspeeds.'
    nNIDIA의 고질적인 동적분기의 약점을 지적한 포럼의 유저 의견도 있었구요.

    http://game.watch.impress.co.jp/docs/series/3dcg/20090613_283112.html
    'PS3のRSXには動的分岐命令があり、これを活用することでバリエーション増加は抑えられるはずだが、実際に試してみたところ、この手法は遅くて使用に耐えなかったという。'
    캡콤에서 MT 프레임워크를 만들때에도 PS3의 동적분기를 실험해 봤는데 느려서 쓸만한게 못됐다는 내용도 있었습니다.

    DirectX 9.0 - SM3.0 세대를 지원하는 최적화를 한다면
    동적분기로 얻을수 있는 성능상의 이점은 그리 많지 않을수도 있다는것도
    유념해 두면 좋지 않을까 생각됩니다.

    • Favicon of https://gamedevforever.com ozlael 2012.04.22 23:28 신고  댓글주소  수정/삭제

      좋은 정보 감사합니다. 일어를 해독하시다니 능력자시네요. 부럽습니다 ㅠㅠ
      제가 정확히 속도를 재본것은 아니지만 SM3 초기의 하드웨어의 동적 분기 성능이 안좋지만 통합 아키텍쳐 이후에 좋아진것이 아닐까 추측합니다.
      또한 문서에서도 잘못써서 64개 연속적이지 않으면 역효과라는 점을 이야기 하고 있구요. 물론 분기 자체도 부하가 있으므로 가벼운 연산을 걸러내기 위해 분기를 쓰는 것은 어리석은 짓이구요.
      저의 경우에는 그림자 연산에 분기를 사용해서 이득을 보고 있습니다.
      분기가 좋다지만 말씀하신대로 뭐가 이득인지 항상 생각하고 써야지 남발하면 안될 것같아요 ^^

    • 66v 2012.04.23 02:32  댓글주소  수정/삭제

      그렇군요. 말씀해주신 부분을 예로 드신 킬존에 비추어 생각해보면 무조건 퍼포먼스가 떨어진다고 성급히 결론을 내려서는 안되겠네요 ^^;
      성능향상을 꾀할 수 있을만한 상황에 '적절하게' 사용하는것이 무엇보다도 중요하군요!

    • Favicon of https://gamedevforever.com 김포프 2012.04.24 09:27 신고  댓글주소  수정/삭제

      저희도 스페이스마린 만들떄 PS3 동적 분기 성능때문에 좀 곤욕을 겪었죠. 아예 동적분기가 없다고 보는게 낫습니다 -_-

    • cagetu79@gmail.com 2012.04.25 11:43  댓글주소  수정/삭제

      저는 VTF를 이용해서 스킨 계산을 할 때, Vertex Texture Fetch를 할 때, 4본 Blend가 아니면 Fetch를 하지 않도록 분기를 적용했는데, Fetch 같이 무거운 녀석을 동적 분기가 막아줄 것을 조금이나마 기대하고 있습니다. 물론, 모든 메쉬가 4-bone blend라면 뭐.. 의미 없겠죠.. ㅎㅎ

  3. Favicon of http://sumnomm.blog.me sumnomm 2012.04.24 01:15  댓글주소  수정/삭제  댓글쓰기

    예전에 SM2.0과 3.0기준으로 쉐이더를 작성할때 우버 쉐이더방식으로 작성하며 동적 분기를 쓰면서 속도가 느리다는 얘기는 들었는데 지포스 8xxx에서 테스트를 해봤을때 속도가 안떨이지길래 몰랐는데 통합 쉐이더모델에선 속도가 꽤나 받쳐주나 보네요.
    그나저나 SM4.0이상은 통합 쉐이더 모델이라는건 알았는데 SM4.0그래픽카드에서 SM3.0으로 컴파일해도 통합쉐이더 형식으로 되는지는 몰랐네요!!
    무조건 버텍스 쉐이더 픽셀쉐이더 따로 생각했었는데 이제 그러면 안되겠네요 ㅠㅠ

    많이 배워갑니다 ㅎㅎ

    • 66v 2012.04.24 02:07  댓글주소  수정/삭제

      통합 쉐이더 모델은 그래픽 카드의 구조가 어떻게 되어있느냐를 일컫는 것입니다. 그래서 같은 SM3.0 지원 카드 중에서도 개별 셰이더 유닛구조(PS3)인 경우가 있고 통합 셰이더 유닛구조(Xbox360)인 경우가 있습니다.

  4. Favicon of https://gamedevforever.com 김포프 2012.04.24 09:29 신고  댓글주소  수정/삭제  댓글쓰기

    FXC에서 더이상 ps_1_x 버전 컴파일을 지원하지도 않지요 이젠... ㅎㅎ