Search

'DirectX11'에 해당되는 글 36건

  1. 2011.12.21 [미리보는 C++ AMP-3] array와 array_view
  2. 2011.12.01 [미리보는 C++ AMP-2] C++ AMP 맛 보기
  3. 2011.11.22 [미리보는 C++ AMP-1] C++ AMP를 구상해 보다! (4)
  4. 2011.11.14 [KGC 2011] 발표 자료 (4)
  5. 2011.09.28 [StartD2D-8] 투명 이미지 다루기 (2)
  6. 2011.09.19 [짤막소식] Direct3D 11.1 릴리즈 소식과 Visual Studio 11 소식 (4)
  7. 2011.07.04 [StartD2D-4] WIC 를 이용한 이미지 작업하기 (3)
  8. 2011.05.09 [JumpToDX11-22] DirectX11의 테셀레이션 ( 간단한 테셀레이션 샘플편 ) (2)
  9. 2011.04.11 [JumpToDX11-21] DirectX11의 테셀레이션 ( Domain Shader 의 역할편 )
  10. 2011.03.08 [JumpToDX11-20] DirectX11의 테셀레이션 ( 테셀레이터의 역할편 ) (6)
  11. 2011.01.25 [JumpToDX11-19] DirectX11의 테셀레이션 ( Hull Shader 역할편 )
  12. 2011.01.18 [알콜코더의 미리 배워보는 DX11-입문편] 1.튜터리얼01:백버퍼의 설정 #2 (5)
  13. 2011.01.14 [알콜코더의 미리 배워보는 DX11-입문편] 1.튜터리얼01:백버퍼의 설정 #1 (3)
  14. 2010.12.29 [알콜코더의 미리배워보는 DX11-입문편] 1. 튜터리얼01 : 디바이스와 스왑체인의 생성 (3)
  15. 2010.12.28 [알콜코더의 미리 배워보는DX11 입문편] DirectX 11의 특징들 (3)
  16. 2010.11.24 [JumpToDX11-18] DirectX11의 테셀레이션 ( 테셀레이션을 위한 하드웨어의 등장편 ) (2)
  17. 2010.11.13 [발표자료] 예제로 느껴보는 다이렉트X 11의 매력 (3)
  18. 2010.10.11 [JumpToDX11-17] DirectX9 세대의 테셀레이션( ATI 라이브러리편 ) (2)
  19. 2010.10.07 [JumpToDX11-16] DirectX9 세대의 테셀레이션( D3DXTessellateNPatches편 )
  20. 2010.09.08 [알콜코더의 미리 배워보는 DirectX11-입문편] 1.튜터리얼 01 : 다이렉트 3D 기초 #1 (10)

[미리보는 C++ AMP-3] array와 array_view

DirectX 11 2011.12.21 08:00 Posted by 조진현

들어가기 앞서 지금까지 AMP가 GPU를 활용하는 프로그래밍 기법이라고,
제가 지속적으로 언급해 왔었습니다.
사실 이 말은 적절하지 않는 표현이였습니다.

얼마 전까지만 해도, 개발자에게 주어지는 프로세싱 유닛은 CPU와 GPU 뿐이였습니다.
CPU는 개발자의 활용 영역에 있었지만, GPU는 제한적으로 사용할 수 있었습니다.
왜냐하면 GPU를 사용하기 위해서는 DirectX API 사용이 필수였기 때문입니다.
그 DirectX 의 영역을 일반적인 개발자 영역으로 확장하는 것이 C++ AMP 입니다.
그런데 최근에 CPU와 GPU를 통합한 APU 라는 것이 등장했습니다.
앞으로 또 다른 프로세싱 유닛이 등장할지도 모르는 일입니다.
그래서 이런 프로세싱 유닛들을 통합한 용어가 필요하게 되었고,
C++ AMP에서는 이를 accelerator 라고 합니다.
즉, CPU와 GPU 그리고 APU 가 이 accelerator 에 속한다고 할 수 있습니다.
accelerator 는 C++ AMP 코드가 실행될 수 있는 이런 타겟을 표현합니다.
그래서 C++ AMP는 이 accelerator를 활용하는 프로그래밍 기법이라고
해석하는 것이
더 적절한 표현입니다.
앞으로 이 accelerator 라는 표현을 많이 사용할 것이니 확실히 알아두시기 바랍니다.


앞서 간단하게 작성했던 샘플을 다시 한번 보겠습니다.
 

void AddArrays(int n, int * pA, int * pB, int * pC)

{

    array_view<int,1> a(n, pA);

    array_view<int,1> b(n, pB);

    array_view<int,1> sum(n, pC);

 

    parallel_for_each(

        sum.grid,

        [=](index<1> i) restrict(direct3d)

        {

            sum[i] = a[i] + b[i];

        }

     );

}



array_view 라는 것이 먼저 눈에 보입니다.
C++ AMP 에서는 대규모 메모리를 의미하는 클래스로
array 와 array_view 라는 것이 있습니다.
기본적으로 이 두 클래스의 목적은
accelerator 상으로 데이터를 옮기기 위함 입니다.


array 의 경우에는 실제 데이터 배열입니다.
STL 의 컨테이너와 유사합니다.
반면 array_view 는 데이터 배열의 일종의 래퍼( wrapper ) 입니다.
그래서 array_view 는 STL의 이터레이터( iterator ) 와 유사한 동작을 합니다.
array_view는 한 번에 여러 데이터의 동시에 접근할 수 있으며,
랜덤 액세스( random-access ) 가 가능합니다.

array 에 의해서 정의되는 배열 데이터는 accelerator 상에 메모리를 가지게 됩니다.
이것은 개발자가 직접 정의해서 할당할 수도 있고,
런타임( runtime ) 에 의해서 자동적으로 생성될 수도 있습니다.
그렇기 때문에 실제 데이터가 생성되어질 때 깊은 복사( deep-copy )를 하게 됩니다.
우리가 일반적으로 오브젝트를 메모리에 생성했을 때와 같다고 생각하시면 됩니다.
array 는 다음과 같이 사용할 수 있습니다.( 샘플은 msdn 에서 가져왔습니다 )

vector<int> data(5);
for (int count = 0; count < 5; count++)
{
    data[count] = count;
}

array<int, 1> a(5, data);

parallel_for_each(
    a.grid,
    [=, &a](index<1> idx) restrict(direct3d)
    {
        a[idx] = a[idx] * 10;
    }
);

data = a;
for (int i = 0; i < 5; i++)
{
    cout << data[i] << "\n";
}



반면에 array_view는 이름에서 유추할 수 있듯이,
실제 데이터들은 다른 accelerator 상에 있고,
이를 연산을 위해서 복사를 하는 개념
입니다.

즉, 커널 함수가 실행될 때, 데이터가 복사됩니다.
( 커널 함수는 AMP 내의 람다 함수 부분을 의미합니다. )

이 array_view 개념은 DirectX11 에서 보셨던 분들은 쉽게 이해할 수 있는 개념입니다.
바로 ComputeShader 를 위해서 데이터들을 연결하는 바로 그 개념이기 때문입니다.
아래의 그림은 ComputeShader 의 동작 방식을 보여주는데,
SRV( shader resource view )와 UAV( unordered access view ) 라는 것이
결국 view 의 역할을 하는 것입니다.




DirectX11 과 연계해서 생각한다면,
array 라는 메모리 배열도 결국 텍스쳐 메모리라는 것을
눈치챌 수 있을 것입니다.
DirectX10 부터 텍스쳐 인터페이스는 꼭 이미지 데이터를 의미하지 않습니다.
대용량의 메모리 블럭의 의미에 더 가깝다는 것을 알아두시기 바랍니다.
텍스쳐의 개념을 사용하기 때문에 동시에 여러 데이터에 접근이 가능하고,
랜덤 액세스도 가능한 것입니다.^^

신고

[미리보는 C++ AMP-2] C++ AMP 맛 보기

DirectX 11 2011.12.01 08:00 Posted by 조진현


백문이 불여일견이라고들 하죠?
글로써 언급하는 것보다,
프로그래머들은 코드로 볼 때 더 직관적인 이해를 할 수 있는 경우가 많습니다.

간단하게 두 배열의 합을 구하는 코드를 통해서,
이를 AMP 적으로 어떻게 작성하는지를 보겠습니다.

아래는 우리가 일반적으로 생각할 수 있는 CPU를 활용해서
합을 구하는 코드입니다.

void AddArrays(int n, int * pA, int * pB, int * pC)

{

   for (int i=0; i<n; i++)

   {

      pC[i] = pA[i] + pB[i];

   }

}


자세한 설명은 생략해도 될 것이라 생각합니다.^^
아래는 C++ AMP로 작성된 합을 구하는 코드입니다.

#include <amp.h>

using namespace concurrency;

void AddArrays(int n, int * pA, int * pB, int * pC)

{

   array_view<int,1> a(n, pA);

   array_view<int,1> b(n, pB);

   array_view<int,1> sum(n, pC);

   parallel_for_each( sum.grid,

                                          [=](index<1> i) restrict(direct3d)

   {

      sum[i] = a[i] + b[i];

   }   );

}


 

위의 AMP 구현 부분에서 색상이 들어간 부분이 CPU를 활용한 부분과 다른 부분입니다.
코드량이 증가해버린 단순한 사실을 우리는 확인할 수 있습니다.
코드가 증가한 가장 기본적인 이유는 메모리 문제입니다.
우리가 지금까지 C++ 에서 사용하는 메모리는 CPU 가 접근할 수 있는 시스템 메모리입니다.
이 메모리를 GPU 로 처리하기 위해서는 GPU가 직접적으로 접근 가능해야 합니다.
그런데 C++ 에서 할당한 메모리는 GPU가 접근할 수가 없습니다.
그래서 비디오-메모리에 시스템-메모리의 데이터를 복사하는 과정이 필요합니다.
그 과정이 바로 코드의 증가를 불러오는 것입니다.
( 복사라고 보기는 조금 모호합니다만, 지금은 그냥 넘어가겠습니다. )

이 증가한 코드들에 대해서 지금부터 살펴보겠습니다.


#include <
amp.h>

using namespace concurrency;


AMP를 사용하기 위한 헤더의 선언입니다.
기본적으로 AMP를 사용하기 위해서는 람다식과 concurrency  에 대한 이해가 있어야 합니다.


array_view
<int,1> a(n, pA);

array_view<int,1> b(n, pB);

array_view<int,1> sum(n, pC);

이 부분은 앞서 언급했던 GPU가 접근할 수 있는 메모리 영역으로
데이터를 만드는 부분입니다.
이 데이터를 만들 수 있는 메모리 영역이
array 와 array_view 라는 것으로
구분됩니다.
이 둘의 차이는 이후에 다루어 드릴테니,
지금은 GPU가 접근할 수 있는 메모리 영역으로 생각해 주셨으면 합니다.^^


parallel_for_each(
 ... ) restrict( direct3d )

c++ 에 main(...) 이 있다면, AMP 에는 parallel_for_each( ... ) restrict( direct3d ) 가 있습니다.
이 부분은 GPU가 연산을 시작하는 진입점( EntryPoint ) 입니다.

parallel_for_each를 잘 모르시는 분들은 아래의 링크를 참고하시 바랍니다.
http://vsts2010.net/123
더 자세한 사항은 이 블로그의 VC++ 10 Concurrency Runtime 카테고리를 참고하시기 바랍니다.

 

제가 단순하게 정리해 드리면,
기존에 VC++ 10 에서 사용되는 parallel_for_each 는 CPU를 활용해서 병렬적으로 처리하는 것이지만,
뒤에 restrict( direct3d )를 명시함으로써 이를 GPU에서 병렬적으로 처리
하도록 합니다.
 

이 진입 함수는 parallel_for_each(  람다식 ) 형태를 가지게 됩니다.
이는 GPU의 많은 스레드들에게 '이 람다식을 각각 실행해 주세요' 라고 명령을 내리는 것입니다.
역시 람다( Lambda ) 에 대해서 잘 모르시는 분은 옆의 카테고리에서
c++0x 를 보시기 바랍니다.
람다의 첫번째 설명 링크는 아래와 같습니다.
http://vsts2010.net/73

 

그러면 얼마나 많은 스레드들이 람다식을 실행해야 하는지에 대한 명시가 있어야 합니다.
그것이 바로 paralle_for_each( ... ) 의 첫번째 인자인 sum.grid 입니다.

grid 에 대한 설명은 뒷부분에서 자세히 다루겠으니,
지금은 스레드 갯수에 대한 정의로 보시면 충분합니다.

람다식의 인자로 index<1> idx 가 보이실 것입니다.
이 인자는 람다식에 전달되는 스레드들의 ID들입니다.
이 ID들을 통해서 스레들을 식별할 수 있습니다.
스레드들의 ID를 통해서 배열 형태의 데이터를 캡쳐해서 값을 저장하는 것입니다.

간단한 프로그램이지만, 사실 이런 형태가 C++ AMP의 전부입니다.^^

물론 이렇게 간단히 끝나면 무척 행복하겠지만,
난이도는 역시 알면 알수록 높아집니다.^^


본 글에서 사용된 예제들은 MS에서 사용된 예제들입니다.
제가 구현한 것들이 아님을 알려드립니다.^^

신고

GPU를 활용하는 일은 모든 개발자에게 열려있는 길이여야 합니다.
하지만 DirectX를 직접적으로 활용해야만 하는
MS의 GPGPU 플랫폼인 DirectCompute는 그렇지가 않습니다.

그래픽카드라는게 원래 특수한 목적성을 가지고 등장한 장치이기 때문에,
이를 활용하는 사람들 또한 특정 영역에 국한되어 있는게 현실입니다.
'이제부터 GPGPU 를 적극 활용합시다!' 라고 생각을 하더라도, 
실제로 그것을 활용하기 위한 진입 장벽은 굉장히 높을 수 밖에 없습니다.

그러면 어떻게 해야만 이 장벽을 조금이라도 낮출 수 있을까요?
엔비디아의 CUDA 를 보면, 힌트가 있습니다.
하지만 몰라도 상관없습니다.^^
C++ 파일 내에서 컴파일러에 의해서 자동적으로 처리가 될 수 있으면 가장 좋지 않을까요?
순수 C++ 의 기능만 사용해서 컴파일러가 자동적으로 처리해 준다면,
개발자는 DirectX와 ComputeShader 에서 해방될 수 있을 것입니다.
그것이 바로 C++ AMP 가 등장하는 배경
입니다.
C++ AMP는 다음 버전의 VisualStudio 에 탑재 되어져서 등장할 예정이라고 합니다.


어떤 함수가 아래와 같이 있습니다.
void Func( ... )
{
    코드
}

위의 함수는 결국 컴파일러에 의해서 CPU 와 관련한 명령어를 생성하게 됩니다.
이를 AMP 적으로 확장하면 정확히 아래와 같이 구성됩니다.
void Func( ... ) restrict( cpu )
{
   코드
}

restrict 이라는 키워드를 함수에 적용함으로써 간단히 이를 구현합니다.
눈치가 좀 빠르신 분들이라면
'저 cpu를 gpu 로만 변경하면, gpu 로 컴파일 되어지는 것인가?' 라고 생각이 드실 겁니다.
네. 맞습니다.
그것이 바로 C++ AMP 가 DirectCompute 를 구현하는 방법입니다.
정확히는 아래와 같습니다.
void Func( ... ) restrict( direct3d )
{
   코드
}
'direct3d' 가 바로 'gpu' 를 의미합니다.
현재 이 옵션용 예약어는 확정적인 것은 아닙니다.
'direct3d' 가 확정될 수도 있고, 그렇지 않을 수도 있습니다.
아직 C++ AMP가 출시되지 않아서 유동적인 부분이 있습니다.
그 점 주의해서 읽어주시기 바랍니다.^^

다음 버전의 Visual C++ 부터는 
함수마다 저렇게 restrict 한정자에 컴파일 옵션을 지정해주어야 합니다.

물론 지정을 하지 않았을 때는, 디폴트로 restrict( cpu ) 로 자동 처리할 것입니다.

그러면 한 함수 내에서 CPU와 GPU를 활용해야 하는 경우는 어떻게 해야할까요?
void Func( ... ) restrict( direct3d, cpu )
{
   GPU를 사용하는 코드
   CPU를 사용하는 코드
}

위와 같이 혼합해서 사용하는 것도 가능합니다.
또한 오버로드와 관련한 이슈도 문제 없이 처리될 것입니다.
void Func( ... );
void Func( ... ) restrict( direct3d );

간단히 위와 같이 restrict 만으로 GPU를 사용하는 것이 완전히 된다면 얼마나 좋겠습니까만,
restrict( direct3d ) 로 정의되어지는 함수들은 그에 상응하는 규칙으로 코딩 작업을
해야만 합니다.
이것이 사실 그렇게 쉬운 개념만으로 이해할 수 있는 것은 아닙니다.
하지만 DirectCompute를 직접 제어하는 것보다는 쉽습니다.

다음 시간부터 C++ AMP 로 프로그래밍 하는 개념에 대해서 살펴보겠습니다.^^
신고

[KGC 2011] 발표 자료

DirectX 11 2011.11.14 08:00 Posted by 조진현


안녕하세요~ 조진현입니다.
얼마 전 대구에서 KGC 2011 행사가 있었습니다.
저는 그 곳에서 DirectX11 과 관련한 발표를 진행하고 왔습니다.
그래서 발표 슬라이드를 공개해 드립니다.

특히나 이번 발표 때, C++ AMP 에 대한 언급이 있었습니다.
AMP는 아래 링크에 흥배님이 자세히 설명해 주셨습니다.
http://vsts2010.net/591

빠른 시간 내에 발표 때 언급했던 C++ AMP 와 관련한 내용을
팀 블로그에 게재하도록 하겠습니다..^^

[조진현] [Kgc2011]direct x11 이야기
View more presentations from 진현 조.
신고

[StartD2D-8] 투명 이미지 다루기

DirectX 11 2011.09.28 08:00 Posted by 조진현


이번 시간에 다룰 것은 투명 이미지 입니다.
얼마 전 댓글로 문의하신 내용인데 답변을 달기가 조금 부족한 듯 싶어서,
이렇게 별도로 아티클(?)로 남깁니다.

예전에 Win32 API 로 알파가 있는 이미지를 표현하는 작업은 무척 번거로운 작업이였습니다.
이미지 색상에 알파가 고려되어지면, 각 색상 성분마다 알파 연산을 해주어야 합니다.
또한 RGB 각 성분이 8비트씩 사용하는데 A성분이 추가되어지면서,
다시 8비트의 추가 데이터들이 각 색상값들에 필요하게 됩니다.
용량이 커지면 성능에 문제가 생기게 되는 것은 당연한 일입니다.

그래서 이를 흉내내기 위한 대안으로 마련된 것이 ColorKey 라고 불리는 기법입니다.
이 기법은 이미지 내의 특정 색상을 표현하지 않음으로써 구현됩니다.
아래의 그림을 예로 들어보겠습니다.



이미지 내에서 배경이 모두 붉은 색으로 되어있습니다.
이런 경우에 붉은 색을 ColorKey로 지정해서 데이터를 읽지 않는 것입니다.
그러면, 캐릭터 관련 색상만 메모리에 기록되게 됩니다.
이 방법을 사용하면 24비트 비트맵만으로 캐릭터를 표현할 수 있습니다.
주의해야 할 점은,
ColorKey 에 해당하는 색상 값을 아티스트들에게 사용하지 말 것에 대한
사전 협의가 있어야 겠지요.

아쉽게도(?) Direct2D에서 이 ColorKey 사용에 대한 API를 찾지 못했습니다.
( Direct3D 에는 있습니다.^^ )

사실 ColorKey 방식이 널리 이용되긴 하지만,
근본적으로 알파 처리를 이용하며 관련 효과를 모두 구현할 수 있습니다.
그렇기 때문에, 굳이 ColorKey 를 염두할 필요성은 없습니다.

이번에 샘플에 사용한 이미지는 알파가 있습니다.

 


우측의 흑백 이미지가 알파 성분만 표현한 이미지 입니다.
당연히 검은 부분은 알파 성분이 0 이기 때문에
해당 영역은 화면에 표현되지 않을 것입니다.

이번에 살펴볼 샘플은 아래와 같습니다.



똑같은 이미지 파일에서 데이터를 읽었지만,
위의 그림은 알파 처리가 되어서 동물 부분만 출력이 되었습니다.
하지만 아래 부분은 알파처리가 이루어지지 않아서 이미지 영역이 모두 출력되었습니다.

이번 결과의 차이는 WIC를 이용한 것입니다.
우리가 지금껏 무심코(?) 지나쳤던 WIC의 컨버터를 기억하십니까?


제가 이번 샘플을 위해서 약간 개량을 했습니다.
두 사용법에 별 차이는 없지만, GUID_WICPixelFormat... 부분이 보일 것입니다.
이 인자가 바로 두 이미지의 차이를 만들어낸 부분입니다.
즉, 컨버터에서 데이터를 어떤 포맷으로 읽어들일지를 설정하는 부분입니다.

첫번째는 알파처리를 수행하는 방법으로 데이터를 읽어들입니다.
GUID_WICPixelFormat32bppPBGRA는
4개의 색상 채널을 가지고 채널당 8개의 비트를 가지고 있으며,
픽셀 당 32비트를 표현하며 UINT로 각 색상이 저장되어 있는 포맷을 의미합니다.

두번째 알파처리를 하지 않는 GUID_WICPixelFormat32bppBGR은
3개의 색상 채널과 8비트로 각 채널을 표현하는 32비트 픽셀 포맷을 의미합니다.
이 포맷도 UINT로 각 색상 성분을 표현합니다.
이 경우에는 알파 채널이 존재하지만, 실제로 읽어들이지 않습니다.
즉, 알파 채널을 무시하는 것입니다.

관련 포맷이 매우 방대하기 때문에, 여기서 더 이상 자세히 다루지 않습니다.
중요한 것은 이 컨버터 덕분에 별다른 수고 없이 알파 처리를 쉽게 수행할 수 있습니다.

Direct2D 가 사실 많은 부분을 우리가 모르게 자동적으로 처리하는 부분이 많이 있습니다.
Direct2D에서는 프로퍼티를 생성하는 부분들이 많이 있습니다.
예를 들면 다음과 같은 것들입니다.



사실 이 프로퍼티 정보들을 별도로 설정하지 않으면,
자동적으로 Direct2D에서 처리를 해버립니다.
혹은 디폴트 생성자들이 모두 들어있습니다.
자동적으로 처리되는 부분들 이외의 기능이 필요하다면,
이들을 잘 제어해야만 하겠지요..^^

부족하지만 샘플을 참조해 드립니다..^^

신고


안녕하세요..^^

잠잠하던 DirectX 게시판에 이번 달에 작은 소식이 몇 가지 발표되었습니다.
먼저 Direct3D 11.1 이 릴리즈가 되었습니다.
아래 링크를 보시면 특징들을 확인하실 수 있습니다.
큰 기능의 추가는 다행이(?) 없으니 안심하시기 바랍니다.^^

http://msdn.microsoft.com/en-us/library/hh404562%28v=VS.85%29.aspx


다른 소식은 Visual Studio 11 소식입니다.
이번 Visual Studio 11 에서는 게임 개발 관련한 편의를 위해서
몇 가지 비쥬얼 적인 에디팅을 지원하는 듯 합니다.

아래는 관련 링크입니다.
http://blogs.msdn.com/b/jasonz/archive/2011/09/14/announcing-visual-studio-11-developer-preview.aspx


특히나 이번에 주목한 부분은 쉐이더 에디터가 Visual Studio 11로 통합되어진 것입니다.
( 설마 나중에 빠지는 것은 아니겠지요? ^^ )
앞으로 더 정보를 지켜봐야 하겠지만, 상당히 기대되는 부분인 것은 분명한 것 같습니다.^^





신고

 

  

이번 시간에는 직접 이미지를 화면에 표현하는 방법에 대해서 언급합니다.

Win32 API를 이용할 때, 우리는 '비트맵(Bitmap)' 이라는

그래픽 데이터 포맷을 읽어서 화면에 그려주었습니다.

사람마다 차이는 있겠지만, 일반적으로 다음과 같은 순서를 따라서 구성했을 것입니다.

 

  1. 비트맵을 읽기 위해서 파일을 오픈한다.

  2. 파일에서 헤더를 읽어 들인다.


  3. 비트맵 헤더의 정보를 통해서 관련 메모리를 생성하고,

    파일에서 색상 데이터에 대한 정보를 읽는다.


  4. DIBSection 을 생성하고, 실제 데이터를 읽는다.
  5. 그리고 마무리 한다.

 

이 순서는 수 많은 방법 중에 하나일 뿐이지만,

기본적으로 파일을 열어서 헤더를 먼저 읽고, 관련 메모리를 생성하고,

이후에 실제 데이터를 채우게 되는 순서는 공통된 작업입니다.

Direct2D 에서도 이와 같이 작업을 해도 되지만,

이미 편의를 위해 만들어진 라이브러리를 사용해서 조금 더 확장성 있는 작업을 할 필요가 있습니다.

지금부터는 WIC를 이용한 간단한 이미지 뷰어 작업을 해보겠습니다.

  

WIC( Windows Imaging Component )

 

DirectX 가 윈도우 운영체제 전반으로 광범위하게 활용되면서,

이들과 관련한 내용들을 분리할 필요가 있었습니다.

과거까지는 DirectX 는 게임 개발자들의 전유물에 가까웠기 때문에,

다른 개발자들도 손쉬운 개념으로 접근할 수 있는 그런 분류가 필요했습니다.


결과적으로 아래와 같이 분류가 되었습니다.  

 

WIC는 모든 이미지를 쉽게 처리할 수 있도록 만들어낸 COM 기반의 프레임워크입니다.

그림에서 보듯이 WIC도 하나의 큰 영역으로서 자리 잡고 있습니다.

( 참고로 DXVA는 영상 처리를 위한 프레임워크입니다. )

 

WIC를 이용한 이미지 처리는 앞서 GDI 기반에서 작성했던 것과는 완전히 다릅니다.

WIC는 PNG, JPG, GIF 등과 같은 거의 모든 주요한 이미지 형식을 포함하고,

기본 코덱들을 지원하고 있습니다.

 

말이 참 어렵죠?

쉽게 말해서, Direct2D 기반에서 이미지 처리를 하려면 WIC를 사용하면 쉽게 할 수 있다는 것입니다.

우리는 이것을 사용하는 순서와 방법에 대해서 배우기 위해서,

윈도우 화면에 이미지를 그려주는 간단한 애플리케이션을 만들어 볼 것입니다.

 

 

기본 WIC 프로그래밍

 

애플리케이션 마법사로 새로운 프로젝트를 만들고, stdax.h 에 다음을 추가를 합니다.

 

'WindowsCodecs.lib'와 'wincodec.h' 가 바로 WIC를 사용하기 위해 추가시킨 것입니다.

눈치 빠른 분들이라면, 이름에서 약간 앞으로의 작업 방향을 예측할 수 있을 것입니다.

 

이번 프로젝트에서 사용할 전역 변수들은 아래와 같습니다.

 

 

익숙한 개념이 눈에 보이지 않으십니까?

바로 IWICImagingFactory 입니다. 네 그렇습니다~

WIC 도 바로 팩토리 형태로 생성이 됩니다.

 

추가된 변수들은 아래와 같은 절차에 의해 값이 채워집니다.

즉, 아래는 WIC의 처리 과정입니다.

 

  1. WIC 팩토리를 만든다.

  2. 파일 경로를 기반으로 해서 디코더를 만든다.

  3. 디코딩된 프레임을 가져온다.

  4. 변환기에 넣어서 Direct2D 형식으로 변환한다.

  5. Direct2D 비트맵을 생성하고, 이를 렌더링한다.

 

이제 위의 절차를 실제로 어떻게 처리하는지를 차근차근 살펴보겠습니다.

 

가장 먼저하는 초기화 작업입니다.

앞서 언급했듯이, WIC 도 팩토리 개념으로 생성됩니다.

COM 기반이기 때문에 API 인자들이 굉장히 어려워 보일 수도 있지만, 관심을 둘 부분은 아닙니다.

위와 같은 방법으로만 하면, WIC가 생성 되어진다는 개념으로만 인식하고 다음 단계로 넘어갑니다.

 

 

 

다음 단계는 디코더를 만들고, 이를 기반으로 해서 Direct2D 형식으로 데이터를 변환하는 것입니다.

이를 위해서 가장 먼저 해야 하는 일은

이미지를 읽어들이기 위한 디코더( Decoder )를 만드는 일입니다.


갑자기 등장한 생소한 용어에 조금 혼란스러울 것 같습니다.

우리가 사용하는 모든 멀티미디어 파일( 이미지, 영상, 사운드 등 )들은

굉장히 어려운 방법으로 압축이 되어있습니다.


이들에 대한 원리나 형식을 이해하는 것도 중요한 일일 수도 있지만,

이는 간단하게 본 페이지에서 설명할 수 있는 내용이 아닙니다.

물론 저도 이와 관련한 전문가는 더더욱 아닙니다.

우리는 단지 API만으로 이들에 대한 고민을 해결할 수 있습니다.

바로 그것이 WIC의 존재 이유 중 하나 일 것입니다.^^

 

즉, 우리는 이미 만들어진 API를 이용해서 손쉽게 이미지 파일을 읽어올 수 있습니다.

그런 역할을 하는 것이 바로 디코더입니다.

아래는 디코더를 가지고 실제 작업을 하는 부분입니다.

 

 

디코더는 WIC 팩토리 멤버함수로써 생성이 되어집니다.

우리가 사용했던 이 API의 원형은 다음과 같습니다.

<코드>

HRESULT CreateDecoderFromFilename(

[in] LPCWSTR wzFilename,

[in] const GUID *pguidVendor,

[in] DWORD dwDesiredAccess,

[in] WICDecodeOptions metadataOptions,

[out, retval] IWICBitmapDecoder **ppIDecoder

);

</코드>

 

이 API는 주어진 이미지 파일을 기반으로 해서 디코더를 생성해 줍니다.

첫 번째 인자로 파일명이 들어갑니다.

이 파일을 기반으로 해서 적합한 디코더를 생성해 주게 되는 것입니다.

예를 들어, PNG 파일이면 PNG에 대한 디코더가 필요하다고 인식하고,

그에 맞는 디코더를 자동적으로 생성해 주는 것입니다.

 

두 번째 인자는 선호하는 디코더 벤더(vendor)의 GUID를 입력해야 하는데, 지금은 NULL을 사용합니다.

 

세 번째 인자로는 디코더에 대한 접근 방법을 명시합니다.

읽기(read), 쓰기(write), 혹은 둘 다 가능한지를 넣어주면,

가장 최적화된 방법과 메모리 위치를 가지는 디코드를 생성해 줍니다.

위의 예제에서는 읽기용으로만 디코더를 만들었습니다.

 

네 번째 인자는 디코더의 캐시 관련 옵션입니다.

우리가 인자로 넘긴 WICDecodeMetadataCacheOnDemand는

필요한 이미지 정보만 캐시 하도록 옵션을 준 것입니다.

다음 번에 언급할 지도 모르지만, 하나의 이미지 파일에는 여러 이미지들을 포함하고 있을 수 있습니다.

예를 들면 GIF 애니메이션 이미지 같은 것들이다.

이런 경우에 유용하게 캐시하려면, 다른 옵션을 주어야 할 것입니다.

 

마지막 인자는 생성된 디코더를 저장할 디코더의 포인터입니다.

여기까지 작업하면, 우린 이제 파일을 읽은 디코더를 소유하게 되는 것입니다.

뭔가 절차 상으로 굉장히 복잡한 것처럼 느껴지죠?

 

 

다음으로 할 작업은 프레임(frame) 작업입니다.

프레임이라는 것은 실제 픽셀 데이터를 가지고 있는 비트맵입니다.

앞서 잠깐 언급했듯이, 하나의 이미지 파일은 여러 장의 이미지가 존재할 수 있습니다.

그런 경우를 대비해서 체크를 해야겠지만,

우린 여기서 단 하나의 프레임만이 존재한다고 가정할 것입니다.

디코더의 멤버 함수인 GetFrame()를 통해서 우린 가장 첫 번째 프레임을 얻을 수 있습니다.

이 프레임을 얻는다는 것은 우리가 화면에 표현할 수 있는 이미지를 얻었다는 것입니다.

 

이제 우리는 디코더를 통해서 이미지를 Direct2D에서 표현할 수 있도록

적절하게 변환을 해주어야
합니다.

CreateFormatConverter() API는 이를 위해서 컨버터를 만들어줍니다.

그리고 이 컨버터를 우리가 원하는 형태로 초기화를 시켜 줍니다.

컨버터의 멤버함수 Initialize() 는 이미지를 컨버팅 하면서

픽셀 정보를 보정해 줄 수 있는 많은 옵션을 가지고 있습니다.

이들 옵션에 대한 세부 설정을 하지 않았습니다.

그래서 위에 인자들 형태로 주면, 별다른 이미지의 수정 없이 32비트 포맷으로 남게 됩니다.

 

이제 마지막으로 실제 렌더링 가능한 형태의 메모리를 생성해야 합니다.

렌더타겟의 멤버함수인 CreateBitmapFromWicBitmap() API를 통해서 이 작업을 하게 됩니다.

여기까지 하면, 이미지를 렌더링 하기 위한 준비작업이 모두 끝난 것입니다.

 

저는 여기에 모든 옵션들을 나열하지 않습니다.

( 기본 목적인 이미지를 띄우는데 충실하고자 합니다.^^ )

 

 

생소한 API들이 눈에 많이 띄지만, 이들은 일련의 절차에 지나지 않습니다.

중요한 개념은 이미지를 읽어 들일 디코더를 만들고,

이 이미지 데이터를 Direct2D가 표현할 수 있는 픽셀 데이터로 변환하는 것입니다.

그리고 이 데이터를 렌더타겟에서 표현할 수 있는 비트맵으로 만들어서

렌더링 가능한 상태로 만듭니다.

위의 코드는 바로 이 개념들을 표현하고 있는 것입니다.

 

그러면 실제 WM_PAINT 메시지를 통해서 이들이 어떻게 화면에 그려야 하는지 살펴보겠습니다.

 

WM_PAINT 메시지에서는 렌더타겟이 존재하지 않는 경우, 렌더타겟을 생성합니다.

렌더 타겟이 존재한다면, 비트맵을 그리고 있습니다.

렌더타겟의 렌더링 작업도 BeginDraw() / EndDraw() 의 매커니즘 내부에서

특정 상태를 기반으로 작업을 수행하게 됩니다.

우리는 Clear() 라는 API를 통해서 렌더타겟의 메모리를 흰색으로 채우고 있습니다.

그리고 현재 우리가 이미지를 (0,0) 위치에 (300,300) 크기로 렌더링 합니다.

 

마법의 함수 DrawBitmap()

 

앞선 작업을 통해서 우린 Direct2D를 이용해서 이미지를 화면에 그릴 수 있었습니다.

만약 우리가 읽어 들인 이미지의 일부분만을 화면에 그리고 싶다면 어떻게 해야 할까요?

혹은 흐릿한 효과를 주고 싶다면 어떻게 해야 할까요?

굉장히 어려운 일들 같지만, 이들 기능은 DrawBitmap() 에 모두 옵션 인자로서 존재하고 있습니다.

( 무척 고마운 일이지요..^^ )

그렇기 때문에, 우리는 이 함수를 잘 사용할 수 있어야 합니다.

API의 원형은 다음과 같습니다.

 

<코드>

virtual void DrawBitmap(

[in] ID2D1Bitmap *bitmap,

[in, optional] const D2D1_RECT_F *destinationRectangle = NULL,

         FLOAT opacity = 1.0f,

D2D1_BITMAP_INTERPOLATION_MODE interpolationMode =

D2D1_BITMAP_INTERPOLATION_MODE_LINEAR

,

[in, optional] const D2D1_RECT_F *sourceRectangle = NULL

) = 0;

</코드>

 

첫 번째 인자는 우리가 렌더링 작업을 수행할 이미지입니다.

 

두 번째 인자부터는 옵션적으로 설정할 수 있다.

두 번째 인자는 렌더링 작업을 수행할 화면의 영역을 설정합니다.

NULL 로 설정한다면, 렌더타겟의 원점에 그리게 됩니다.

만약 이미지 크기보다 크게 설정된다면, 자동적으로 이미지를 확대해서 보여주게 됩니다.

 

세 번째 인자는 투명도를 설정합니다.

범위는 0.0~1.0 사이의 값으로 0.0은 투명한 상태를 나타내고 1.0은 불투명한 상태를 나타냅니다.

 

네 번째 인자는 우리가 렌더링하는 이미지가 회전을 하거나 크기가 조정되었을 때,

어떻게 부드럽게 보일 것인가에 대한 옵션을 설정하는 부분입니다.

즉, 보간( interpolation ) 옵션입니다.

 

마지막 인자는 원본 이미지에서 일정 영역을 보여주고 싶을 때 영역을 입력하는 옵션입니다.

이 때 단위는 해당 이미지 파일의 사이즈를 기준으로 영역을 설정해 주면 됩니다.

 

그러면, 간단하게 실제로 이미지의 일부 영역을 약간 투명하게 보여지는 것을 프로그램으로 구현해자면,

앞서 작성했던, 이미지 뷰어의 기능에서 DrawBitmap()만 변경해주면 됩니다.

 

<코드>

HRESULT hr = E_FAIL;

::g_ipRT->BeginDraw();

::g_ipRT->SetTransform( ::D2D1::Matrix3x2F::Identity() );

::g_ipRT->Clear( ::D2D1::ColorF( ::D2D1::ColorF::White ) );

                            

if( ::g_ipD2DBitmap != nullptr )

{

    ::D2D1_RECT_F dxArea = ::D2D1::RectF( 0.0f, 0.0f, 500.0f, 500.0f );

    ::D2D1_RECT_F dxSrc = D2D1::RectF( 0.0f, 0.0f, 250.0f, 250.0f );

    ::g_ipRT->DrawBitmap( ::g_ipD2DBitmap, dxArea, 0.3f,

D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &dxSrc );

                

}

hr = ::g_ipRT->EndDraw();                

</코드>

 

우리는 간단하게 DrawBitmap() 의 인자들만 변경해주는 것만으로 이미지의 일부 영역만을 보여주고,

투명도를 조절할 수 있음을 확인해 보았습니다.

각각의 값을 변경시키면서, 여러가지 아이디어를 구상해 보기 바랍니다. ^^


아래 소스코드를 첨부합니다..


신고

이번 시간에는 지난 시간들까지 언급한 내용을 기반으로 해서,
간단한 테셀레이션 작업을 구현해 보려 합니다.

당연한 얘기이겠지만,
하드웨어 기반의 테셀레이션은 하드웨어의 지원이 없으면 매우 느립니다.
즉 DirectX11 이상을 지원하는 그래픽 카드가 아니면,
효과를 눈으로 확인하는 것조차 무척 고통스럽습니다.

그래서 이번 시간에 만들 테셀레이션은 간단히 삼각형 하나를 이용합니다.
우리는 이 삼각형 하나를 가지고 테셀레이션 작업을 수행할 것이며,
DirectX11 을 지원하지 않는 그래픽카드라면
강제적으로 REF 모드로 테셀레이션 작업을 수행하도록 합니다.

먼저 결과 샘플을 보면 아래와 같습니다.



이제 우리가 만들려는 그림이 그려졌으니, 직접 코딩 작업을 시작하겠습니다.
이 글에서는 DirectX11 의 기본 셋팅과 관련한 사항은 생략합니다..^^
자세한 API 적인 설명은 생략을 하니 DirectX 2010 6월 버전의 SDK 의 튜토리얼을 참고하시거나,
'알코코더의 DirectX11' 을 참고하시기 바랍니다.^^

우리가 이번 샘플에서 사용할 버텍스 데이터의 형식은 위치 정보만 있으면 됩니다.
이번 샘플에서는 최대한 간단하게 작성하는 것을 목적으로 했기 때문에,
많은 정보를 필요로 하지는 않습니다..^^
그래서 아래와 같이 간단한 버텍스 형식을 정의했습니다..^^



생소한 데이터 타입이 보입니다. 바로 XMFLOAT3 입니다.
DirectX11 부터는 D3DX 계열의 수학 데이터 타입들은 더 이상 업데이트 되지 않습니다.
지금부터는 XNA Math 라는 수학 라이브러리를 사용합니다.
그렇다고 더 이상 D3DX 계열의 수학 데이터 타입들을 사용할 수 없는 것은 아니니, 안심하시기 바랍니다.
이들에 대해서는 향후 언급할 기회가 있으니,
지금은 D3DX 계열의 수학 클래스 대신에 XNA Math 라는
새로운 수학 클래스를 사용한다는 정도로만 인식하고 넘어가겠습니다.^^


아래는 우리가 애플리케이션 전역으로 사용할 변수들의 선언입니다.



그 동안의 DirectX11을 언급하면서 꾸준히 언급되던 내용이기에 자세한 설명은 생략하겠습니다.

특이할 만한 것이라면, 래스터라이져 스테이트 오브젝트를 2개 만드는 것입니다.
이는 우리의 샘플이 솔리드( Solid ) 한 렌더링과 와이어프레임( Wire-Frame ) 기반의 렌더링으로
전환이 가능하기 때문입니다.

다음은 상수버퍼( ConstantBuffer ) 에 관한 전역 선언들 입니다.



우리는 월드 좌표계의 정점을 버퍼에 입력할 것입니다.
그래서 View-Projection 행렬만 변환을 위해서 필요합니다.
그리고 얼마나 테셀레이션 작업을 세밀하게 할지를 결정하는 상수를 하나 추가합니다.



쉐이더를 컴파일 해주는 보조 함수를 다음과 같이 하나 만듭니다.


이제 본격적으로 시작을 합니다.
InitD3D() 에 각종 초기화 작업을 수행합니다.
앞서 잠깐 언급드렸듯이,
DirectX11을 지원하는 하드웨어가 아니면, 강제로 REF 모드로 동작하도록 합니다.
또한 이 함수에서는 각 쉐이더 스테이지에 대응되는 HLSL 코드를 컴파일 해줍니다.
그리고 이들에 대한 각 오브젝트를 만듭니다.
초기화 작업은 주로 반복적인 작업이 많기 때문에, 설명은 생략합니다.

InitD3D() 에 버텍스버퍼의 데이터를 설정해 줘야 합니다.
이번 샘플에서는 월드 좌표로 정의된 삼각형을 사용할 것입니다.
또한 카메라 공간에 대한 설정도 같이 해 줍니다.
이들에 대한 코드는 아래와 같습니다.


이 정도로 초기화와 관련된 작업을 마무리 합니다.
이제는 프레임 관련한 처리를 작성합니다.( Render() )

이 Render() 부분에서는 상수버퍼에 설정할 데이터들을 다음과 같이 업데이트 합니다.

 


우리는 와이어프레임 모드와 솔리드 모드의 렌더링 방식 둘 다를 표현할 것이기에,
이들에 대한 설정도 아래와 같이 고려해 주어야 합니다.



그리고 마지막으로 입력되는 버텍스 형식을 알려주고 버텍스 버퍼를 연결한 후에,
그리기 작업을 수행합니다.^^



이제 키보드 이벤트에 따라 약간의 변화를 주는 작업을 합니다.
현재는 'w' 키로 렌더링 모드를 Wire 와 Solid 간의 토글이 되도록 설정합니다.
그리고 위/아래 방향키로 테셀레이션의 분할 정도를 증감합니다.

이번 작업은 여기까지 입니다.
지금까지 DX11을 살펴보면서, 언급된 내용들이 대부분이라 전체적으로 설명드리지는 않습니다.
( HLSL 코드도 최대한 간결하게 작성했습니다..^^ )
샘플을 같이 첨부드리니, 직접 작성하시면서 익혀보시기 바랍니다.^^

신고


이제 테셀레이션 작업의 마지막 단계입니다.
테셀레이터를 통한 결과와 Hull Shader 단계에서의 결과인 패치 데이터가
Domain Shader 의 입력으로 전달
이 되게 됩니다.

Domain Shader 에서는
우리가 DX9 세대에서 주로 수행했던 Vertex 변환 작업을 수행하게 됩니다.
즉, 실제적으로 Projection 변환까지 Domain Shader 단계에서 이루어지게 됩니다.


테셀레이션 작업만으로는 폴리곤의 퀄리티를 향상시키는데에 효과적이지 못합니다.
그래서 실제적으로 높이 정보를 포함하고 있는 텍스쳐를 사용하게 되는데,
이 텍스쳐를 사용하는 것을 'Displacement mapping' 이라고 합니다.

아래의 그림을 살펴보도록 하겠습니다.




첫번째 모델은 로우 폴리곤으로 제작된 것입니다.
두번째가 바로 테셀레이션 작업이 종료된 폴리곤 모델입니다.
( 아주 미끄러운 캐러멜 같은 느낌이지요? ^^ )

세번째는 테셀레이션 작업을 마치고, Displacement mapping 까지 마친 모델입니다.
Displacement mapping 의 특징은 실제 Vertex 데이터를 조작하는 것에 있습니다.
위의 작업을 단순하게 표현해 보면 아래의 그림과 같습니다.



수년 전부터 가장 많이 쓰이고 있는 Bump mapping 기법은 우리의 눈을 속이기 위한 방법이라면,
Displacement mapping 은 눈속임이 아니라, 실제로 데이터를 조작하는 텍스쳐 기법입니다.
아래의 그림은 이들에 대한 차이점을 질감적으로 잘 표현해 주고 있습니다. ^^
Bump mapping의 경우에는 실제로 평면이지만,
텍스쳐링 효과를 이용해서 우리에게 질감의 느낌을 전달해 주고 있습니다.
반면에 Displacement mapping은 실제 Vertex 데이터를 조작해서 질감의 느낌을 전달합니다.



Displacement mapping 의 설명에 제가 열을 올리는 이유는
바로 이 작업 Domain Shader 단계에서 계산되어서 적용되기 때문입니다.
앞서 설명했듯이, Displacement mapping 은 실제 Vertex 를 조작하는 기법이기 때문에
최종 Vertex 를 생성하는 Domain Shader 에서 적용되는 것이 당연한 일입니다.^^
이 작업 자체가 어려운 일은 당연히 아닙니다.
하지만, Displacement mapping을 고려하게 됨으로써,
Vertex 변환 단계가 조금 복잡해 진 것은 사실입니다.
텍스쳐링의 단계를 수행하고, 그 텍셀의 수치만큼 실제 Vetex를 조정해 주어야 하기 때문입니다.
거기다, 쉐이더 단계에서 LOD 레벨을 고려해야하기 때문에
쉐이더의 Texture 멤버함수로 SampleLevel() 를 이용하는 상황이 생기기도 할 것입니다.
또 하나의 더 큰 효과를 위한 하나의 방법이 늘었다고 생각하시면 더 좋을 것 같습니다.^^

신고


< Tessellator >

테셀레이터는 Hull Shader 의 결과를 입력으로 받아서 작업을 합니다.
이 스테이지는 프로그래머가 제어할 수 없는 영역입니다.( 정말 다행이죠? ^^ )
앞선 Hull Shader 스테이지에서 정의된 폴리곤 분할 방법과 분할 수치에 따라서
실제로 Vertex 데이터들을 생성할 수 있는 정보를 주게 됩니다. 
즉, 우리는 큰 덩어리 형태의 Vertex 데이터만 HullShader 를 통해서 전달할 뿐입니다.
테셀레이터의 정해진 연산에 의해서,
도메인 쉐이더( DomainShader )에 무게 중심 좌표( BarycentricCoordinates )들을 전달
하게 됩니다.



< 무게 중심 좌표( BarycentricCoordinates ) >

무게 중심 좌표를 언급하기 전에, 벡터의 외적의 성질에 대해서 언급할 사항이 있습니다.
우리가 이미 알고 있듯이, 두 벡터의 외적 연산으로 두 벡터에 수직인 벡터를 구할 수 있습니다.
지금부터 여기에 주목할 것은 이렇게 외적 연산을 통해서 얻어진 벡터의 길이입니다.
이렇게 구해진 벡터의 길이는 기하학적으로 두 벡터를 평행사변형을 만들었을 때, 넓이를 의미합니다.
아래의 그림이 이해에 도움이 되었으면 좋겠습니다.^^
꽤 재미있는 성질이지 않습니까? ^^
( 이미 다들 알고 계셨을 것이라 생각하지만, 처음 접했을때, 저는 꽤 재미있는 성질이라고 생각했습니다..^^ )



두 벡터의 외적으로 나온 결과 벡터의 길이가 평행사변형의 넓이라는 사실을 인지한다면,
우리는 이제 무게 중심 좌표에 대해서 얘기할 수 있습니다.
힌트를 드리면, 무게 중심 좌표는 다른 말로 면적 좌표로도 불리기도 합니다.


삼각형 내부의 임의의 점 P는 점 A,B,C를 구성하는 삼각형들의 비율로 표현할 수 있습니다.
위의 그림에서 나오는 공식과 그림은 바로 이를 보여드리고 있습니다.
w들은 가중치 상수를 의미합니다.
각 가중치들의 합은 반드시 1.0 이여야 합니다.
만약 C의 가중치인 w3 의 경우에는 삼각형 APB 의 넓이 / 삼각형 ABC의 넓이 가 되는 것입니다.
이런 식으로 서로 대응되는 각 가중치들을 삼각형을 구성하는 각각의  정점 위치에 대응시키면,
우리가 원하는 P의 위치를 구할 수 있습니다.


벡터 외적의 기하학적 특징을 이용해서 가중치를 구하는 코드는 아래와 같습니다.
이 코드에서는 삼각형 넓이를 구할 때 수행하는 2를 나누는 작업이 생략되어 있습니다.
이유는 어차피 이 코드의 결과는 비율에 대한 가중치이기 때문에, 2를 나누는 작업은 의미가 없기 때문입니다.


이처럼 무게중심좌표를 구하는 일이 DirectX11의 테셀레이터의 임무 중 하나입니다.
삼각형을 구성하는 세 정점이 주어졌을 때 세 정점의 가중치를 구할 수 있다면,
임의의 점 P를 구할 수 있습니다.
바로 이 역활을 수행하는 것이 테셀레이터의 기능 중 하나입니다.
앞선 언급했듯이 테셀레이터의 기능은 우리가 조작 할 수 있지 않습니다.
즉, 고정 기능입니다.

우리는 Hull Shader를 통해서 Patch를 정의하고,
이렇게 정의된 패치 데이터는 이후에 가공되지 않고, 바로 Domain Shader 에서도 사용됩니다.
( 테셀레이터에서도 이 데이터를 사용해서 연산을 합니다. )
테셀레이터 단계에서는 이 패치 데이터에 대응되는 가중치들을 구성해서,
바로 다음 단계인 Domain Shader 로 전달
하게 되는 것입니다.
물론 내부적으로는 더욱 복잡한 과정을 거치겠지만,
우리가 코딩관점에서 관심을 가질 수 있는 변수 정보는 이들 뿐입니다.

Domain Shader의 기본적인 형태는 다음과 같습니다.

[domain("tri")]
DS_OUTPUT DS( HS_CONSTANT_DATA_OUTPUT input,
                    float3 UVW : SV_DomainLocation,
                    const OutputPatch<HS_OUTPUT, 3> patches
{
   DS_OUTPUT Output;
    ...
    return Output;   
}

Domain Shader의 입력으로 들어오는 인자들을 유심히 보시기 바랍니다.
( 패치 정보와 UVW 에 바로 가중치 정보가 입력됩니다. )
이들에 대해서는 다음 시간에 살펴보도록 하겠습니다.

신고



DirectX11의 파이프라인은 앞선 시간에서 우리는 꾸준히 보았습니다.
복습의 의미에서 이번에는 http://www.realtimerendering.com 에 정의된 Direct3D 의 파이프라인입니다.

Direct3D 10 Pipeline
< Direct3D 10 pipline >

Direct3D 11 Pipeline
< Direct3D 11 pipline >

우리가 그래픽스에서 사용하는 폴리곤은 굉장히 복잡한 방식으로 처리가 됩니다.
많은 스테이지를 통해서 결국 우리는 화면 픽셀로 변환된 최종 결과를 확인하게 되는 것입니다.
그 과정 속에서 Direct3D 9에서는 Vertex와 Pixel 을 조작할 수 있도록 변화되어 왔습니다.
Direct3D 10 은 여기에 Geometry 까지 조작할 수 있도록 프로그래머들에게 개방되었습니다.
Direct3D 11 은 무려 3개의 스테이지가 추가되었습니다.
Hull Shader, Tessellator, Domain Shader 가 바로 그것들입니다.

이 중에 프로그래머가 제어하는 부분은 Hull / Domain Shader 이며,
Tessellator 의 경우에는 하드웨어가 직접 처리하게 됩니다.

테셀레이션을 언급하면서 가장 많이 나오는 주제는 현재 LOD( Level of Detail ) 처리 이지만,
정확하게 테셀레이션이 필요한 이유는 글인 http://vsts2010.net/331 을 통해서 확인할 수 있습니다.

현재 그래픽 파이프라인에서 테셀레이션 작업은 현재 옵션으로 설정되어 있습니다.
여러분이 이 기능을 사용하기 원하지 않는다면, 이들을 활성화 시키지 않으시면 됩니다.
그렇게 된다면, 기존의 파이프라인과 동일한 방식으로 Vertex 데이터를 처리하게 됩니다.


< Hull Shader >

Hull Shader 는 테셀레이션 작업의 시작입니다.
하지만, 실제로 프로그래머의 시작은 Vertex Shader 입니다.
DirectX9에서 VertexShader 는 World-View-Projection 변환을 수행하는 것이 가장 큰 목적이였습니다.
DirectX11에서 VertexShader 의 목적은 Hull Shader 로의 데이터를 전달하는 것입니다.
즉, 테셀레이션이 목적인 경우에는
DirectX11에서 VertexShader 스테이지에서 World-View-Projection 을 수행해서는 안됩니다.
테셀레이션 작업시 VertexShader 에서 처리되는 Vertex는 실제 우리가 사용하는 데이터가 아닙니다.
우리는 VertexShader 의 입력으로 들어오는 데이터를 모아서,
많은 수의 Vertex를 새롭게 생성시켜야 합니다.
그래서 테셀레이션 작업시 VertexShader 스테이지에서는 Vertex를 월드 변환까지만 수행합니다.

Hull Shader 에서는 '폴리곤을 어떻게 분할할 것인가?' 와 '폴리곤을 얼마나 분할할 것인가?' 를 결정합니다.
가장 단순한 형태로 이 Hul Shader의 기능을 표현하면 다음과 같습니다.

Diagram of the hull-shader stage

위의 그림은 MSDN 의 그림입니다.

Hull Shader 는 두 가지의 작업을 동시에 수행합니다.
그것은 제어점( Control Point ) 를 생성하는 작업과 Patch Constant Data 를 계산하는 작업입니다.
이들 작업은 병렬적으로 수행되게 됩니다.
HLSL 코드는 실제로 드라이버 수준의 하드웨어 명령어를 생성하게 되는데,
이 때, 병렬처리가 가능한 형태로 변환되게 됩니다.
이는 Hull Shader 가 빠르게 동작할 수 있는 중요한 이유이기도 합니다. 
 
Hull Shader 의 입력으로 들어오는 제어점( Control Point )들은
낮은 차수의 면을 표현하는 정점들입니다.
이를 높은 차수의 면을 표현하는 제어점들로 만들어 내게 됩니다.
이 때 생성된 제어점들은 Tessellator 스테이지에서 사용되는 것이 아니라,
그 다음 스테이지인 Domain Shader 에서 사용됩니다.



위의 그림은 베지어(Bezier) 제어점들을 이용해서 베지어 곡면을 표현한 것입니다.

근본적으로 테셀레이션은 평면을 곡면으로 생성시키는 개념과 매우 비슷합니다.
( 굳이 평면을 많은 갯수의 폴리곤으로 표현할 필요는 없기 때문이겠죠. )
그렇기 때문에, 분할 방법으로 사용되는 알고리즘들은 베지어처럼 게임 프로그래머들에게 친숙한
개념들이 사용됩니다.

Hull Shader 의 또 하나의 중요한 역활은 불필요한 연산을 줄이기 위해
테셀레이션 단계를 스킵할지를 결정할 수 있다는 것입니다.
즉, Hull Shader 에서 Tessellation Factor 가 0 이하인 경우에
이 패치는 컬링
되어 버린 것으로 간주됩니다.
( Tessellation Factor 는 얼마나 분할할지를 나타내는 수치적 비율입니다. )
이로인해 더 이상 파이프라인 처리가 이루어지지 않음으로써,
성능 향상을 도모할 수 있습니다.
( 폴리곤을 처리하지 않는 것이 가장 큰 성능의 이득이겠죠..^^ )


그러면 과연 Hull Shader 에서의 '폴리곤을 어떻게 분할할 것인가?' 와 '폴리곤을 얼마나 분할할 것인가?'
프로그램 코드에서는 어떻게 표현해야 할까요?

현재 MSDN 에 나와있는 Hull Shader 의 가장 단순한 형태는 다음과 같습니다.
( 물론 실제로 구현되고 동작되는 내용들의 예들은 DirectX11 샘플에 있습니다. )


[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(16)]
[patchconstantfunc("SubDToBezierConstantsHS")]
BEZIER_CONTROL_POINT MainHS( InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, 
                                                    uint i : SV_OutputControlPointID,  uint PatchID : SV_PrimitiveID )
{
    VS_CONTROL_POINT_OUTPUT Output;

    // Insert code to compute Output here.    
    return Output;
}

위의 Hull Shader 는 동작 방식을 설정합니다.
몇몇 정의된 값들을 셋팅해 주면, 이는 테셀레이션 작업을 하는 동안에 사용되게 됩니다.
즉, 위의 셋팅들은 '폴리곤을 어떻게 분할할것인가?' 에 준하는 프로그램 코드라 할 수 있습니다.

이제 남은 것은 '폴리곤을 얼마나 분할할 것인가?' 입니다.
이는 PatchConstantFunc 을 통해서 병렬적으로 처리된다고 앞서 설명을 했습니다.
이곳에서는 Tessellation Factor 를 계산하게 되는데, 그 결과에 따라서 컬링 작업이 실행됩니다.
( 이 값이 0 이하의 경우에는 더 이상 처리가 필요하지 않습니다. )
이 작업을 하는 함수를 우리는 직접 작성해서,
 위의 [patchconstantfunc("SubDToBezierConstantsHS")] 처럼 설정해 주면 자동적으로 동작합니다.
MSDN 에 나와있는 PatchConstantFunc의 기본적인 형태는 다음과 같습니다.

#define MAX_POINTS 32

// Patch Constant Function
HS_CONSTANT_DATA_OUTPUT
SubDToBezierConstantsHS( InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip,
                                         uint PatchID : SV_PrimitiveID )

    HS_CONSTANT_DATA_OUTPUT Output;

    // Insert code to compute Output here    
    return Output;
}

이 PatchConstantFunc 의 결과에 바로 '폴리곤을 얼마나 세밀하게 분할할 것인가?' 에 대한 정보들이 있습니다.

// Output patch constant data.
struct HS_CONSTANT_DATA_OUTPUT
{
    float Edges[4]        : SV_TessFactor;
    float Inside[2]       : SV_InsideTessFactor;
    ...
};

위의 경우의 결과 구조체는 사각형을 분할한 경우이며,
우리가 주로 사용하는 삼각형 분할의 경우에는 다음과 같을 것입니다.

// Output patch constant data.
struct HS_CONSTANT_DATA_OUTPUT
{
    float Edges[3]        : SV_TessFactor;
    float Inside       : SV_InsideTessFactor;
    ...
};

지금까지 Hull Shader의 기본적인 개념과 역활에 대해서 언급해 드렸습니다.
이렇게 얻어진 결과는 테셀레이터로 전달되게 됩니다.
세부적인 Hull Shader 의 작성은 이후의 시간들을 통해서 살펴볼 예정입니다.
( 현재 본 글들은, 개념 위주의 설명에 포커스를 두고 있습니다. ^^ )

신고


참고 소스 : DirectX SDK – DirectX 11 Tutorial 02

위 소스를 기반으로 연재를 진행합니다.

연재외에 자세한 내용은 소스와 DX SDK 문서를 참조하시면 도움이 됩니다.

 

 


렌더타겟에 렌더링을 하기위해서는 뷰포트의 설정도 필요합니다. 뷰포트라는 것은 렌더타겟의 렌더링 영역에 관한 설정입니다. 렌더타겟에 넓이와 높이, 그리고 깊이값으로 렌더링 영역을 설정할 수 있습니다. 그렇기 때문에 뷰포트는 각 렌더타겟별로 설정합니다. 즉 8개의 렌더타겟이 있으면, 뷰포트 설정 역시 8개를 설정해야 합니다. 뷰포트는 특별한 인터페이스가 있는 것이 아니라, 값을 설정하는 것이기 때문에 뷰포트 구조체에 값을 채우고, 렌더타겟에 세팅해주기만 하면 됩니다.

 

Diagram of the viewport rectangle

 

 

뷰포트는 'ID3D11DeviceContext::RSSetViewports 메소드'로 설정합니다. RS는 래스터라이저(RS) 스테이지를 의미합니다. 즉 파이프라인에서 레스터라이저 스테이지의 설정입니다. 뷰포트의 설정에는 'D3D11_VIEWPORT 구조체'를 사용합니다. 이전의 DX10과는 달리 값들을 실수값으로 지정합니다.

 

'지오메트리 셰이더(또는 버텍스 셰이더)'에서 출력되는 정점의 위치좌표(3D)는 주사변환이 완료된 주사변환 좌표계의 값입니다. (-1<=x<=1, -1<=y<=1, 0<=z<=1) 래스터라이저는 뷰포트 변환을 통해서, 이 3D 좌표 위치로부터 2D 스크린 좌표(0<=x<=1, 0<=y<=1)을 계산합니다.

스크린좌표는 왼쪽위가 (0,0)이고, Y축이 아래쪽 방향인 좌표계입니다. 쉽게 이야기해서 우리가 일반적으로 모니터 화면에서 보는 좌표와 방향이 같습니다. 다만 해상도로 계산하는 것이 아니라 0~1사이의 값으로 높이와 넓이를 계산하는 것입니다. 그래서 백버퍼의 왼쪽위가 (0,0)이 되고, 오른쪽 하단이 (1,1)이 되는 좌표계가 됩니다.

 

뷰포트 변환의 공식은 아래와 같습니다.

X = (x+1) * Viewport.Width * 0.5 + Viewport.TopLeftX

Y = (1-y) * Viewport.Height * 0.5 + Viewport.TopLeftY

Z = Viewport.MinDepth + z * (Viewport.MaxDepth – Viewport.MinDepth)

 

레스터라이저는 렌더타겟의 뷰포트의 내부에 있는 프리미티브(3D모델)로부터 픽셀을 생성합니다. 깊이버퍼의 값은 뷰포트의 깊이값의 범위로 스케일링됩니다.

 

예를 들어, 처음에 깊이값의 범위를 0.0~0.5로 렌더링하고, 다음에 0.5~1.0로 렌더링하게되면, 처음에 렌더링했던 내용이 뒤에 렌더링했던 내용보다도 화면 앞에 렌더링 됩니다. (깊이 버퍼가 유효할 때)

뷰포트의 깊이값은 일반적으로 보통 0.0~1.0사이로 설정합니다.

 

파이프라인에 렌더타겟이 1개가 설정되어 있을 때, 뷰포트를 렌더타겟의 (0,0) ~ (640,480)의 영역으로 설정하는 코드는 아래처럼 됩니다.

 

 

void RSSetViewports(

[in]  UINT NumViewports,

[in]  const D3D11_VIEWPORT *pViewports

);

NumViewports

설정할 뷰포트의 개수

pViewports

뷰포틀 설정할 뷰포트 구조체의 배열

 

D3D11_VIEWPORT 구조체

typedef struct D3D11_VIEWPORT {

FLOAT TopLeftX;

FLOAT TopLeftY;

FLOAT Width;

FLOAT Height;

FLOAT MinDepth;

FLOAT MaxDepth;

D3D11_VIEWPORT;

TopLeftX

뷰포트의 좌상단 X좌표.

범위는 D3D11_VIEWPORT_BOUNDS_MIN ~ D3D11_VIEWPORT_BOUNDS_MAX

TopLeftY

뷰포트의 좌상단 Y좌표.

범위는 D3D11_VIEWPORT_BOUNDS_MIN ~ D3D11_VIEWPORT_BOUNDS_MAX

Width

뷰포트의 넓이. 값은 0이상

Height

뷰포트의 높이. 값은 0이상

MinDepth

뷰포트 깊이값의 최소값. 0.0~1.0 사이값

MaxDepth

뷰포트 깊이값의 최대값. 0.0~1.0 사이값

 

이상으로 백버퍼/렌더타겟뷰/뷰포트를 설정하는 방법을 설명하였습니다. ^^

다음 시간에는 깊이/스텐실 버퍼의 설정에 대해서 이야기하겠습니다.

 

 


알콜코더 민군

언젠가 우즈벡에 미소녀 게임 회사를 차리고 싶은 개발자

3D게임 클라이언트 프로그래머, 살짝 오타쿠…

드래곤볼 온라인 개발, 현재는 네오위즈 서식중

'신입게임 개발자의 서울상경기'와 '초중급 게임개발자 스터디' 운영중

 

신고
TAG DirectX11


참고 소스 : DirectX SDK – DirectX 11 Tutorial 02

위 소스를 기반으로 연재를 진행합니다.

연재외에 자세한 내용은 소스와 DX SDK 문서를 참조하시면 도움이 됩니다.

 

안녕하세요. 알콜코더 민군입니다. ^^

지난 연재에서는 DirectX11의 3D 그래픽 디바이스를 생성하는 방법을 배웠습니다. 하지만 3D 디바이스는 어디까지나 그래픽 카드 드라이버의 디바이스의 인터페이스일뿐, 실제로 게임 화면 내용을 렌더링하는 인터페이스는 아닙니다. 실제 게임 화면은 디바이스에 설정된 렌더타겟에 렌더링 됩니다. 저번 연재 내용에서 디바이스와 함께 스왑체인을 생성했기 때문에, 스왑체인의 백버퍼도 당연히 같이 생성이 되었지만, Direct3D의 렌더타겟으로는 아직 설정되지는 않았습니다. 그래서 생성된 스왑체인으로부터 백버퍼를 얻어와서 디바이스의 렌더타겟으로 설정해야만 합니다. 그리고 깊이/스텐실 버퍼를 사용하는 경우에는 렌더타겟의 설정과 동시에 깊이/스텐실 버퍼도 설정합니다.

 

이번 연재에서는 이렇게 실제로 렌더링을 하기위한 렌더타겟으로 사용하기 위해서 백버퍼를 설정 하는 방법에 대해서 이야기 하겠습니다.

 

 


 

 

 

버퍼를 렌더타겟으로 설정하기 위해서는, 우선 버퍼의 '렌더타겟 뷰(RenderTarget View)'를 작성하여, 그 뷰를 디바이스의 렌더타겟으로 설정해야 합니다. 렌더타겟 뷰에는 'ID3D11RenderTargetView 인터페이스'를 사용합니다.


 


우선 스왑체인으로부터 백버퍼의 포인터를 받아와야 합니다. 스왑체인으로부터 백버퍼를 가져오기 위해서는'IDXGISwapChain::GetBuffer()' 메소드를 사용합니다.

다음의 코드에서는 2D텍스쳐(ID3D11Texture2D 인터페이스)로서 백버퍼를 가져옵니다. 백버퍼는 기본적으로2D텍스쳐로 만들어져 있기 때문입니다.

 


 

HRESULT GetBuffer(

[in]       UINT Buffer,

[in]       REFIID riid,

[in, out]  void **ppSurface

);

Buffer

접근할 버퍼의 번호(인덱스)

Riid

백버퍼를 받을 인터페이스의 타입

ppSurface

반환된 인터페이스를 받을 변수의 포인터

 


텍스쳐는 파이프라인으로부터 '뷰(View)'를 통해서 접근이 가능합니다. 렌더타겟도 텍스쳐의 일종이기 때문에 역시 뷰를 이용하여 접근해야 합니다. 렌더타겟에 접근할 때는 '렌더타겟 뷰'를 사용하여 접근할 수 있습니다.

렌더타겟 뷰는 'ID3D11Device::CreateRenderTargetView 메소드'를 이용해서 생성할 수 있으며, 얻어온 렌더타겟 뷰는 'ID3D11RenderTargetView 인터페이스'로서 사용됩니다.

첫번째 인자는 뷰에서 액세스하는 리소스, 두번째 인자는 렌더타겟 뷰의 정의, 3번째 인자는 반환된 렌더타겟뷰의 포인터를 받을 변수의 포인터를 넘겨줍니다.

 

렌더타겟 뷰의 정의는 'D3D11_RENDER_TARGET_VIEW_DESC 구조체'로 정의됩니다. 하지만 일반적인 경우에는 디폴트값으로 NULL을 넘겨주면 됩니다.

 

렌더타겟 뷰가 생성된 이후에는 직접 백버퍼의 포인터에 접근하지 않고, 이 렌더타겟 뷰를 사용합니다. 그렇기 때문에 렌더타겟 뷰의 인터페이스를 얻고 난 이후에는 백버퍼의 포인터를 해제합니다.


 

HRESULT CreateRenderTargetView(

[in]   ID3D11Resource *pResource,

[in]   const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,

[out]  ID3D11RenderTargetView **ppRTView

);

pResource

렌더타겟 뷰에서 엑세스하는 리소스

pDesc

렌더타겟 뷰를 정의하는 'D3D11_RENDER_TARGET_VIEW_DESC'구조체의 포인터. NULL을 넘겨주면, 리소스가 만들어졌을때의 포맷을 그대로 사용하며, 모든 리소스의 밉맵 레벨 0에 액세스하는 뷰를 생성한다.

ppRTView

ID3D11RenderTargetView 인터페이스를 받는 변수의 포인터. NULL을 넘겨주게 되면, 다른 인자들의 유효성을 체크할 수 있다.

 

typedef struct D3D11_RENDER_TARGET_VIEW_DESC {

DXGI_FORMAT         Format;

D3D11_RTV_DIMENSION ViewDimension;

union {

D3D11_BUFFER_RTV        Buffer;

D3D11_TEX1D_RTV         Texture1D;

D3D11_TEX1D_ARRAY_RTV   Texture1DArray;

D3D11_TEX2D_RTV         Texture2D;

D3D11_TEX2D_ARRAY_RTV   Texture2DArray;

D3D11_TEX2DMS_RTV       Texture2DMS;

D3D11_TEX2DMS_ARRAY_RTV Texture2DMSArray;

D3D11_TEX3D_RTV         Texture3D;

} ;

D3D11_RENDER_TARGET_VIEW_DESC;

Format

리소스내의 데이터를 해석하는 포맷. 'DXGI_FORMAT_UNKNOWN'을 넘겨주면, 리소스에 설정되어 있는 포맷이 사용된다.

ViewDimension

리소스가 액세스 되는 방법. 이 값에 의해서 아래의 union의 멤버 가운데, 사용할 변수가 결정된다.

Buffer

버퍼 리소스로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_BUFFER)

Texture1D

1D텍스쳐로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_TEXTURE1D)

Texture1DArray

1D텍스쳐의 배열로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_TEXTURE1DARRAY)

Texture2D

2D텍스쳐로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_TEXTURE2D)

Texture2DArray

2D텍스쳐의 배열로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY)

Texture2DMS

멀티샘플링된 2D텍스쳐로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_TEXTURE2DMS)

다만, 멀티샘플링된 2D텍스쳐는 보통 1개의 서브 리소스를 가지고 있기 때문에, 설정은 필요없다.

Texture2DMSArray

멀티샘플링된 2D텍스쳐의 배열로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY)

Texture3D

3D텍스쳐로 접근하는 경우의 정의

(ViewDemension = D3D11_RTV_DIMENSION_TEXTURE2D)

각 구조체의 내용에 대해서는 MSDN을 참조하시면 됩니다. 하지만 지금은 딱히 별로 건드릴만한 내용은 없습니다. ^^

 


렌더타겟은 'Ouput-Merger(OM)' 스테이지에 'ID3D11DeviceContext::OMSetRenderTargets 메소드'로 설정합니다.

그리고 렌더타겟은 DX11에서 동시에 최대 8개까지 설정이 가능합니다. 그러나 그럴 경우에도 설정할 수 있는 깊이/스텐실 버퍼는 하나뿐입니다.

 

'OMSetRenderTargets 메소드'에서는 렌더타겟 뷰와 함께 깊이/스텐실 버퍼도 동시에 같이 설정합니다. 이 예제에서는 깊이/스텐실 버퍼를 사용하지 않기 떄문에 NULL을 설정합니다. 깊이/스텐실 버퍼에 관해서는 다음 연재에서 다루겠습니다. ^^;

 

이 메소드는 넘겨진 인터페이스의 참조를 유지합니다. 이것은 'Direct3D 10'과 달라진 부분입니다.

 

 

void OMSetRenderTargets(

[in]  UINT NumViews,

[in]  ID3D11RenderTargetView *const **ppRenderTargetViews,

[in]  ID3D11DepthStencilView *pDepthStencilView

);

NumViews

설정하는 렌더타겟의 개수. 최대 8개까지 설정가능하다.

ppRenderTargetViews

렌더링 파이프라인에 설정하는 렌더타겟 뷰의 배열.

NULL을 넘기면, 렌더타겟이 설정되지 않게 된다.

pDepthStencilView

렌더링 파이프라인에 넘겨주는 깊이/스텐실 뷰의 포인터.

NULL을 넘기면, 깊이/스텐실 버퍼가 설정되지 않게 된다.




알콜코더 민군

언젠가 우즈벡에 미소녀 게임 회사를 차리고 싶은 개발자

3D게임 클라이언트 프로그래머, 살짝 오타쿠…

드래곤볼 온라인 개발, 현재는 네오위즈 서식중

'신입게임 개발자의 서울상경기'와 '초중급 게임개발자 스터디' 운영중

 

신고
TAG DirectX11

 

 

 

 

 

참고 소스 : DirectX SDK – DirectX 11 Tutorial 01

위 소스를 기반으로 연재를 진행합니다.

연재외에 자세한 내용은 소스와 DX SDK 문서를 참조하시면 도움이 됩니다.

 

안녕하세요. 알콜코더 민군입니다. ^^

그동안 연재를 본의 아니게 많이 쉬었습니다. 거의 두달만에 다시 연재를 재개하는 것 같네요..OTL

회사 프로젝트가 바쁜데다가 여러 가지 발표와 행사가 겹쳐서 너무 오래 연재를 쉰 것 같네요. 그럴리는 없겠지만… 혹시나 이런 허접한 연재를 기다리신 분들에게는 죄송합니다. T^T 앞으로는 더욱 열심히 자주 업데이트 하도록 하겠습니다. ^^;;

이번 연재에서는 저번 연재 마지막에 언급했던 DX11 디바이스와 스왑 체인을 생성하는 방법에 대해서, 좀더 자세히 알아볼까 합니다. DX를 이용한 모든 프로그램에서는 DX 디바이스를 생성하는 것이 가장 기본이 됩니다. 그전까지의 과정들은 전부 디바이스를 생성하기 위한 준비단계에 해당합니다. 그리고 DX10 버전부터는 DXGI 기반 구조로 변경되면서 스왑체인(SwapChain)과 디바이스 컨텍스트를 생성해야 합니다. 스왑체인에 대한 설명은 이전 연재를 참조하시면 됩니다.

 

 

디바이스와 스왑체인의 생성

 

디바이스와 스왑체인은 'D3D11CreateDeviceAndSwapChain' 함수를 이용해서 동시에 생성합니다.

이때 실제 렌더링에 사용하는 백버퍼인 Device Context 역시 함께 생성됩니다.


 

함수는 위와 같은 형태로 사용됩니다. 들어가는 인자들이 꽤 많죠. ^^

DX11의 가장 기본이자 핵심인 함수이기 때문에 세팅을 해야하는 내용들이 꽤 많습니다.

이제부터 하나하나 들어갈 인자들을 설명해 드리겠습니다.

여기에 들어가는 인자들을 만드는 방법은 이전 연재를 참고하시면 됩니다.

 

함수 원형 및 인자들에 대한 설명은 아래와 같습니다.


HRESULT D3D11CreateDeviceAndSwapChain(

__in   IDXGIAdapter               *pAdapter,

__in   D3D_DRIVER_TYPE      DriverType,

__in   HMODULE                     Software,

__in   UINT                                Flags,

__in   const D3D_FEATURE_LEVEL *pFeatureLevels,

__in   UINT                               FeatureLevels,

__in   UINT                               SDKVersion,

__in   const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,

__out  IDXGISwapChain        **ppSwapChain,

__out  ID3D11Device             **ppDevice,

__out  D3D_FEATURE_LEVEL *pFeatureLevel,

__out  ID3D11DeviceContext **ppImmediateContext

);

pAdapter 

표시할 '디스플레이 디바이스(비디오 카드)'의 'IDXGIAdapter 인터페이스'를 설정합니다. 이것은 DXGI의 인터페이스입니다. NULL을 지정하면 최초에 발견한 디바이스를 사용합니다. 비디오카드를 지정할 필요가 있을때를 제외하고는 기본적으로 NULL로 설정하면 됩니다

DriverType 

생성할 DX11 디바이스의 종류를 지정합니다. 보통은 하드웨어 가속을 사용하기 위해서 'D3D_DRIVER_TYPE_HARDWARE'를 지정합니다. pAdapter에 NULL 이외의 값을 지정한 경우에는'D3D_DRIVER_TYPE_UNKNOWN'을 지정합니다.

Software 

소프트웨어 래스터라이저가 구현되어 있는 DLL이 핸들을 지정합니다.보통은 NULL을 설정하면 됩니다.

Flags 

사용할 DX11의 API 레이어를 D3D_CREATE_DEVICE_FLAG 값들을 조합하여 설정합니다.

pFeatureLevels

피처레벨(지원 기능 레벨) 배열을 설정합니다. 우선 순위가 높은 순서대로 배열을 채웁니다. 만약 NULL로 설정하면 DX11->DX10->DX9 순으로 모든 레벨을 설정합니다.

FeatureLevels 

위에서 설정한 피처레벨 배열의 개수를 입력합니다

SDKVersion

사용하고 있는 DX SDK의 버전을 넘겨줍니다.

pSwapChainDesc 

스왑체인의 설정값들을 저장한 구조체의 포인터를 넘겨줍니다.

ppSwapChain 

생성된 스왑체인 인터페이스의 포인터를 담을 변수를 설정합니다.

ppDevice 

생성된 디바이스 인터페이스의 포인터를 담을 변수를 설정합니다.

pFeatureLevel 

생성에 성공한 경우에는 pFeatureLevels에서 지정했던 배열의 처음값을 돌려줍니다. 실패한 경우에는 0이 반환됩니다.

ppImmediateContext 

생성된 디바이스 컨텍스트 인터페이스의 포인터를 담을 변수를 설정합니다.

 

D3D_DRIVER_TYPE

D3D_DRIVER_TYPE_HARDWARE

하드웨어 드라이버 (HAL 드라이버)

D3D_DRIVER_TYPE_REFERENCE 

레퍼런스 레스터라이저 (REF 드라이버)

D3D_DRIVER_TYPE_NULL 

NULL 디바이스, 렌더링이 불가능한 레퍼런스 드라이버

D3D_DRIVER_TYPE_SOFTWARE 

예약만 되어있음. 사용하지 않습니다.

D3D_DRIVER_TYPE_WARP 

고속 퍼포먼스의 소프트웨어 레스터라이저(WARP). 피처레벨 9_1 ~ 10_1을 지원합니다.

D3D_DRIVER_TYPE_UNKNOWN 

종류 불명

 

D3D11_CREATE_DEVICE_FLAG

D3D11_CREATE_DEVICE_SINGLETHREADED

싱글 스레드 지원의 디바이스를 지정. 억지로 스레드 세이프 레이어를 무효로 하고 싶은 경우에 지정합니다.

D3D11_CREATE_DEVICE_DEBUG

디버그 레이어를 사용합니다.

D3D11_CREATE_DEVICE_SWITCH_TO_REF

레퍼런스 타입으로 교체할 수 있는 레이어를 사용합니다.

D3D11_CREATE_DEVICE_PREVENT_

INTERNAL_THREADING_OPTIMIZATIONS

복수의 스레드가 만들어지지 않도록 한다. 일반적으로는 사용되지 않습니다.

D3D11_CREATE_DEVICE_BGRA_SUPPORT

Direct2D 와 Direct3D 리소스의 상호운영을 가능하게 합니다.

 

 

디버그용 디바이스

 

'D3D11CreateDeviceAndSwapChain' 함수의 3번째 인자에는 D3D_CREATE_DEVICE_FLAG 의 조합이 들어가게 됩니다. 이 조합된 플래그값으로 게임에서 사용할 Direct3D11의 API 레이어를 지정할 수 있습니다. 여기서 디버그용 디바이스 플래그도 설정이 가능합니다. 아래와 같이 디버그용 버전에서는 디버그 레이어를 켜두면 여러가지 디버그 정보들을 확인할 수 있습니다.


위와 같이 설정하고 D3D11CreateDeviceAndSwapChain 함수의 3번째 인자에 위 플래그를 설정해주면, 디버그 버전으로 컴파일 되는 경우에만 디버그 레이어를 사용할 수 있습니다. 디버그 레이어를 사용 가능하게 해두면 프로그램의 성능이 떨어지기 때문에, 릴리즈에서는 사용하면 안됩니다.

 

 

WARP 디바이스의 생성

 

DX11에서는 하드웨어 드라이버가 사용할 수 없는 환경에서도, 높은 퍼포먼스의 소프트웨어 레스터라이저인 WARP(D3D_DRIVER_TYPE_WARP) 디바이스를 사용할 수 있습니다. 이전 DX 버전에서는 하드웨어가 지원되지 않는 경우 100% 소프트웨어 레스터라이저인 레퍼런스 디바이스를 사용해야 했는데, DX11과 윈도우 비스타 이상에서는 윈도우의 기본적인 아키텍쳐가 하드웨어 가속이 지원 되기 때문에 이 WARP 디바이스를 사용하면 훨씬 더 고속의 렌더링을 할 수 있습니다. 즉, 프로그램이 실행되는 환경의 그래픽 카드가 피처레벨이 지원되지 않는 그래픽카드라고 할지라도, DX11 SDK로 WARP 디바이스를 생성하면 DX11 프로그램을 동작시킬 수 있습니다. 하지만 WARP 디바이스가 지원하는 피처레벨은 'D3D_FEATURE_LEVEL_9_1' ~ 'D3D_FEATURE_LEVEL_10_1' 까지입니다.

WARP 디바이스는 윈도우 비스타이상에서만 사용 가능합니다.


 

 

레퍼런스 디바이스의 생성

 

앞서 이야기한 WARP는 고속이긴 하지만, DX10 버전의 기능들까지만 지원하기 때문에 DX11의 기능들은 사용할 수가 없습니다. 하지만 프로그램을 개발할 때 DX11 지원 그래픽카드가 없는 환경에서, DX11의 모든 기능을 테스트 해보기 위해서는 DX11의 모든 기능들이 구현되어 있는 레퍼런스 디바이스를 사용해야 합니다. 주의할 점은 레퍼런스 드라이버는 'DirectX SDK'가 인스톨되어있는 환경에서만 사용 가능합니다.

튜터리얼의 소스 코드에서는 DX11을 지원하지 않는 그래픽카드를 위해서 '하드웨어 디바이스 -> WARP 디바이스 -> 레퍼런스 디바이스' 순으로 생성을 해보는 코드가 포함되어 있습니다. 그래서 만약 성공했다면 그 디바이스를 사용하고, 만약 앞의 단계에서 생성에 실패했다면 다음 단계의 디바이스를 생성해 보게 됩니다. 그래서 DX11을 지원하지 않는 그래픽 카드를 가지고 있어도 샘플 코드의 실행이 가능합니다. 만약 윈도우 비스타 이하 버전에 DX11을 지원하지 않는 그래픽카드를 사용하고 있다면, WARP를 사용할 수 없기 때문에 레퍼런스 디바이스가 생성됩니다. (물론 DX11 지원 그래픽 카드라면 당근 하드웨어 디바이스가 생성되겠죠)


 

 

다음 시간에는…

 

다음 연재에서는 위와 같이 방법으로 생성한 스왑체인으로 백버퍼를 가져와서, 렌더 타겟 뷰를 생성하고, 생성된 렌더타겟뷰를 디바이스 컨텍스트에 설정하는 방법을 다루겠습니다. 즉, 실제로 게임 화면을 렌더링하는 백버퍼를 생성하는 단계에 대해서 이야기 해보겠습니다. ^^

 

Ps. 조금이라도 도움이 되셨다면, 응원의 리플을~~!!! 굽신~굽신~~

 

알콜코더 민군

언젠가 우즈벡에 미소녀 게임 회사를 차리고 싶은 개발자

3D게임 클라이언트 프로그래머, 살짝 오타쿠…

드래곤볼 온라인 개발, 현재는 네오위즈 서식중

'신입게임 개발자의 서울상경기' '초중급 게임개발자 스터디' 운영중

 

신고


안녕하세요. 알콜코더 민군입니다. ^^

그동안 연재를 한동안 못하고 있었습니다. 에궁.

회사의 게임 개발일이 바쁘다보니 그동안 시간을 제대로 못냈네요.

죄송합니다.. 앞으로 열심히 하겠습니다. T^T

 

그동안 연재가 많이 끊겨서, 그전의 내용의 복습겸해서…

이번 연재에서는 DX 11의 특징에 대해서 간략하게 정리하였습니다.

그동안 잊고 있엇던(?) 내용들을 다시 한번 새롭게 정리하는 기회가 되시길 바랍니다. ^^

 

DirectX 11의 특징    

Direct3D 11은 윈도우 비스타, 윈도우 7에서 지원되는 Direct 3D의 최신 버전입니다. 기능의 확장, 퍼포먼스의 개선, GPU 메모리의 완전한 추상화, GPGPU 지원 기능, 등 보다 유연하고 뛰어난 기능들이 심플한 API셋트로 제공됩니다.

Direct3D 11은 Direct3D 9까지의 Direct3D와는 하드웨어 레벨부터 소프트웨어 레벨까지 전부 근본적으로 변경되었습니다. Direct3D 10을 확장한 구조로 되어 있어서, Direct3D 10에서의 개발 이행이 용이합니다.

 

  • 컴퓨트 셰이더 (Compute Shader)

    OS나 일반 어플리케이션 프로그램 등을 실행하는 범용 프로세서인 CPU에 대해서, 그래픽스 처리에 특화된 전용 프로세서를 일반적으로 GPU라고 말합니다만, Direct3D 11에서 지원되는 [컴퓨트 셰이더](Compute Shader)을 사용하면, 그래픽스 처리 이외에도, 데이터를 병렬 처리하는 범용적인 프로세서로 GPU를 활용하는 것이 가능합니다.

    컴퓨트 셰이더는 Direct3D 디바이스를 통해서 그래픽스계열 셰이더와 리소스를 공유 가능합니다만, 다른 셰이더와는 직접 연결이 불가능합니다.

     

    테셀레이션(Tessellation)

    테셀레이션은 디테일이 낮은 모델을 분할하여, 보다 세밀한 프리미티브를 생성하여 출력하는 기능입니다.

    Direct3D 11의 그래픽스 파이프라인에는 테셀레이션을 수행하는 새로운 하나의 스테이지와 2개의 셰이더가 추가 되어서, 리얼타임에서 테셀레이션의 실행이 가능합니다.

     

    멀티스레드 렌더링

    Direct3D 11에서는 멀티스레드의 대응이 강화되어, Direct3D 10의 [디바이스](ID3D10Device 인터페이스)의 기능은 [디바이스](ID3D11Device 인터페이스)와 [디바이스 컨텍스트](ID3D11DeviceContext 인터페이스)로 분할 되었습니다.

    [디바이스 컨텍스트]에서는 [이미디어트 컨텍스트](Immediate Context)와 [디퍼트 컨텍스트](Deferred Context)가 있습니다.

    이미디어트 컨텍스트는 디바이스에 직접 렌더링하는 디바이스 컨텍스트이며, 디바이스에 하나가 존재합니다. 싱글 스레드의 어플리케이션은 이미디어트 컨텍스트만을 사용합니다.

    디퍼드 컨텍스트는 메인 렌더링 스레드 이외의 워커 스레드로 사용되는 디바이스 컨텍스트입니다.

    또한 [디바이스](ID3D11Device 인터페이스)의 메소드는 멀티 스레드에 안전하게 되어있지만, [디바이스 컨텍스트](ID3D11DeviceContext 인터페이스)의 메소드는 프리 스레드화 되어 있지 않습니다.

     

    동적 셰이더 링크

    일반적인 렌더링에서는 각종 마테리얼을 여러가지로 조합하면 렌더링을 하게 됩니다. 그 때문에 각각의 렌더링마다 서로 다른 셰이더가 필요하게 됩니다.

    이러한 셰이더를 작성하는 방법으로는 "(1)모든 기능을 포함하고 있는 셰이더를 만드는 방법" 과 "(2) 필요한 조합합의 셰이더만을 만든느 방법"이 있습니다만, (1)의 방법에서는 셰이더의 퍼포먼스가 희생이 되고, (2)의 방법에서는 셰이더의 조합이 조금씩 늘어나는 것만으로 상당히 많은 양의 세이더를 처리할 필요가 생깁니다.

    Direct3D 11에 도입된 '동적 셰이더 링크'를 사용하면, 셰이더를 파이프라인에 분할 할당하여, 셰이더 코드를 드라이버에서 최적화가 가능하게 되었습니다.

     

    WARP(Windows Advanced Rasterizer Platform)

    WARP는 실제 어플리케이션에서 이용 가능한, 고속의 '멀티 코어 스케일링 래스터라이저'입니다. Direct3D 10.1 레벨의 기능을 지원하는 어플리케이션에서, 지원 가능한 하드웨어를 찾지 못하는 경우에 WARP 디바이스를 선택합니다.

    비슷한 기능으로, Direct3D의 모든 기능을 소프트웨에서 구현했던 '레퍼런스 레스터라이저'가 있습니다만, 이 것은 실행 속도가 상당히 느리고, 개발용으로 밖에 사용할 수 밖에 없다는 한계가 있습니다.

     

    Direct3D 9/10/10.1 레벨의 하드웨어를 지원

    Direct3D 10을 사용한 프로그램을 실행하기 위해서는 Direct3D 10을 지원하는 그래픽스 하드웨어가 필요했었습니다. 그러나 Direct3D 11에서는 6단계의 '기능 레벨'(Feature Level)을 정의하여서, Direct3D 9/10/10.1의 디바이스에서도 대응하는 '기능 레벨'에 맞게 DirectX 11의 기능을 사용한 프로그램이 실행 가능합니다.

     

    세이더 모델 5.0 (Shader Model 5.0)

    Direct3D 11에서는 Direct3D 10과 동일하게 '통합형 셰이더(Unified Shader) 아키텍쳐'가 채용되었기 대문에, '컴퓨트 셰이더'를 포함한 모든 셰이더를 하나의 HLSL(High Level Language : 고수준 셰이딩 언어)에 작성 가능합니다.

    Direct3D 11의 셰이더 모델은 '셰이더 모델 5.0(SM5.0)'입니다. 또한 셰이더 모델 4.0의 기능도 확장되었습니다.

     

    리소스

    Direct3D 11에서는 '읽고/쓰기 버퍼 (텍스쳐)', '구조화 버퍼', '바이트 어드레스 버퍼', 'Unodered Access Buffer(텍스쳐)'등의 새로운 리소스 타입이 정의되었습니다.

    또한 4GB보다 큰 리소스를 지원하게 되었습니다. (다만, 리소스 인덱스는 32bit 입니다)

     

    Direct3D 10의 특징을 승계

    Direct3D 11은 Direct3D 10을 확장한 설계 형태로 되어 있습니다. 그래서 Direct3D 10의 '디바이스의 소멸 처리가 필요없음', 'CAPS 비트의 폐지', '고정 기능의 폐지', '지오메트리 셰이더'등의 특징을 물려받았습니다.

     

     

    Ps. 다음 시간부터는 그동안 중지되었던 DX11 튜터리얼 연재와 함께 DX11의 기본 개념들에 대한 연재도 같이 진행하겠습니다.

    Ps2. 그동안 연재가 너무 많이 쉬었습니다. 흑.. OTL 다음 연재부터는 빨리 빨리 올리도록 하겠습니다. ^^



                                                                                                                                                             

    언젠가 우즈벡에 게임 회사를 차리고 싶은 오타쿠 개발자

    3D게임 클라이언트 프로그래머

    드래곤볼 온라인 개발, 현재 네오위즈 서식중

    '신입게임 개발자의 서울상경기'와 '초중급 게임개발자 스터디' 운영중


신고



앞선 시간들을 통해서, 우리는 테셀레이션에 대해서 꾸준히 살펴보았습니다.
DirectX9 세대에서부터 테셀레이션을 사용했었으며,
ATI 의 일부 그래픽 카드들은 하드웨어 기반의 테셀레이터를 지원했었습니다.

DirectX11의 테셀레이션 작업은 하드웨어 기반으로 처리됩니다.
즉, 이는 무수히 많은 연산을 처리해서 많은 폴리곤을 화면에 보여주겠다는 하나의 의지입니다.
이들이 강력한 이유는 이전 글인 다음을 참고해 주시기 바랍니다.
http://vsts2010.net/331

요약해 보자면,
이제는 텍스쳐링보다는 폴리곤 갯수를 증가시켜서 퀄리티의 향상을 도모하겠다는 것입니다.
사실 이것에 관해서는 많은 우려와 논란이 많았던 것이 사실입니다.
하지만, 현재의 하드웨어 상황은 이들 테셀레이션 기능을 중심으로 변화하고 있는 것이 사실입니다.
아래는 초창기 ATI의 DirectX11 기반의 하드웨어 구조입니다.



ATI의 경우에는 렌더링 목적에 집중하기 위해서 하나의 테셀레이터와 두개의 래스터라이저로 처리를 했었습니다.
물론 이것은 초기 DirectX11을 지원하는 하드웨어의 경우입니다.

ATI가 이런 테셀레이션 기반의 하드웨어를 출시하자,
상대적으로 후발주자였던 NVIDIA의 경우에는 이 테셀레이터를 더 많이 사용한
DirectX11 기반의 하드웨어를 출시하게 됩니다.




위의 빨간 동그라미 영역에 4개씩 보이는 노란 박스가 모두 테셀레이터입니다.
즉 위의 경우에는 16개의 테셀레이터가 존재합니다.( 4개의 래스터라이져 )
NVIDIA의 이런 과감한(?) 테셀레이터의 지원은 ATI의 향후 대응을 기대하게 만들기도 했습니다.

이런 하드웨어적인 논란은 본 글의 취지와는 맞지 않습니다.
이 글은 이런 현재의 상황에 어떻게 대응하는 API 단계의 개발자들을 주요 대상으로 하기 때문입니다.
즉, 어느 것이 더 효과적인지는 분별하기 어렵습니다.
다만 현재까지 나온 의견을 종합해 본다면,
ATI의 경우에는 테셀레이션과 래스터라이져의 본질적인 기능을 중심으로 설계가 되었고,
NVDIA의 경우에는 테셀레이션과 래스터라이져 외에도 GPGPU 환경의 기능을 더 고려해서 설계가 되었다고 합니다.
( NVIDIA의 경우에는 CUDA라는 GPGPU 플랫폼을 XP세대에서부터 강력히 지원했었습니다.^^ )

테셀레이션은 현재까지도 많은 논란이 있습니다.
그 논란의 중심에는 '빠를까?' 라는 의구심과 '엄청난 양의 연산' 에 대한 우려가 있습니다.
또한 하드웨어 기반의 테셀레이션으로의 패러다임 전환이 쉽지 않은 것도 사실입니다.

실제로 테셀레이션 관련 샘플을 실행시켜보면, GPU의 성능에 굉장히 의존적입니다.( 당연한 얘기겠지만요...^^ )
테셀레이션 샘플들은 DirectX11 기반의 하드웨어에서 그렇게 빠르지도, 또한 느리지도 않습니다.
오히려 일반적인 상황에서는 약간의 성능 저하가 일어날 수도 있으며,
최적화를 잘한 경우에는 테셀레이션 처리가 더 느릴 수도 있습니다.
하지만, 이제 하드웨어는 테셀레이터라는 기능을 장착을 했으며,
앞으로는 테셀레이터 기반으로 최적화하는 것이 더 개발 패러다임에 적합할 것입니다.

당분간 개발 패러다임이 과도기적인 상태를 보이겠지만,
이미 그래픽카드의 발전 방향이 테셀레이터 기반으로 변경되고 있다는 것에 우리는 주목해야 합니다.
신고

안녕하세요. 알콜코더 민군입니다. 


지난 11월 9일날 양재동에서 있었던 

'C++&게임 개발자를 위한 개발 생산성 및 퍼포먼스 향상 세미나' 에서 제가 발표했던 발표 자료입니다. 


부족한 발표였는데도 참석해 주신분들에게 다시 한번 감사드립니다. 꾸벅. (^^)(__)



신고

오늘은 DX9 세대의 테셀레이션 마지막입니다.
ATI 는 DirectX9를 지원하는 일부 그래픽카드들은 하드웨어 기반의 테셀레이션 작업을 지원합니다.
( HD 2000 시리즈 이후 지원되었다고 합니다. )
이 방법은 왜 DirectX11의 테셀레이션 작업이 강력한지를 이해하는 좋은 출발점이 될 수 있습니다.

이 경우에는그래픽 파이프라인 구조가 다음과 같습니다.




이는 현재 X-BOX 360 에도 동일하게 적용되는 그래픽 파이프라인 구조입니다.
주목할 만한 것은 Tessellator의 위치입니다.
즉, 버텍스 쉐이더( VertexShader ) 스테이지의 앞단계에 위치하고 있습니다.
이 위치는 DX11 세대에서는 버텍스 쉐이더 다음 단계로 변경됩니다.


아래의 그림은 ATI 카드에서 지원되는 DX9 기반의 테셀레이션 작업을 보여줍니다.



DirectX9의 테셀레이션을 위해서 총 3번의 패스를 통과해야 합니다.
즉, 3번의 렌더링 작업이 필요합니다.
이렇게 많은 패스가 필요한 이유는 테셀레이션을 위해서 인접한 정점의 정보가 필요하기 때문입니다.
DX9 의 시대에서는 VertexShader 단계에서 인접한 정점의 정보를 쉽게 확인할 수 있는 방법이 없습니다.
그래서 인접한 정보를 구성하는 단계가 첫번째 패스입니다.

첫번째 패스의 렌더타겟은 백버퍼가 아니라, 텍스쳐입니다.
이 텍스쳐에 정점 정보와 정점의 인덱스를 기록하게 됩니다.
즉, rgb 에는 위치 정보가 기록되며 a 에는 정점의 인덱스 정보가 기록됩니다.

이 때, 주의할 것은 메시가 인덱스 버퍼 기반으로 렌더링 되어지는 경우입니다.
이 경우에는 인덱스 버퍼를 모두 풀어서 새로운 버텍스 버퍼를 만들어야 합니다.
우리가 필요한 것은 폴리곤을 렌더링하는 작업이 아닙니다.
인접정보를 구성하는 일임을 잊지 말아야 합니다.
첫번째 패스에서의 렌더링은 TRIANGLELIST가 아니라, POINTLIST 로 수행하게 됩니다.

또 하나 주의할 것이 있습니다.
POINTLIST 로 렌더링을 수행할 때는 WVP( World-View-Projection ) 변환이 아니라,
World-View까지만 변환
을 해야 합니다.
이유는 간단합니다.
테셀레이션은 주로 시점에 근거해서  얼마나 많은 폴리곤을 생성할지를 판단해야 합니다.
이를 앞 시간들을 통해서 Adaptive 한 방식이라고 언급을 했었습니다.
이후의 패스에서는 이들 정점에 근거해서 LOD를 판정해서 Tessellation Factor 를 연산하게 되니다.
그래서 View 좌표계까지만 변환을 합니다.
첫번째 패스에서는 이렇게 View 공간으로 POINTLIST들을 텍스쳐에 렌더링 합니다.

이렇게 생성된 텍스쳐를 기반으로 해서 두번째 패스를 진행할 수 있습니다.
DX9 를 지원하는 모든 그래픽카드가 VertexShader 단계에서 텍스쳐 데이터를 읽어올 수 있는 것은 아닙니다.
이런 제약 사항들은 이제 큰 의미가 있는 것이 아니기 때문에,
개념적인 것에 포커스를 두시기 바랍니다.^^

두번째 패스의 목적은 Tessellation Factor를 구하는 것입니다.
즉, 얼마나 폴리곤을 세분화 할지를 결정합니다.
두번째 패스도 역시 POINTLIST 로 렌더링을 합니다.
그리고 첫번째 패스에서 생성해둔 인접 정점 정보를 가진 텍스쳐를 바인딩 합니다.
인접 정보가 있기 때문에 현재 정점을 기준으로 Tessellation Factor 를 계산할 수 있습니다.
두번째 패스에서 주의할 것은 이들 Tessellation Factor 를 저장하기 위해
R2VB 라는 일종의 버퍼에 렌더링
을 한다는 것입니다.
이는 ATI 테셀레이션 라이브러리에만 존재하는 개념입니다.

세번째 패스는 실제로 지오메트리(Geometry)를 렌더링 하는 단계입니다.
실제 렌더링 작업은 TRIANGLELIST 로 렌더링 합니다.
인덱스 기반의 렌더링이 아니라,
우리가 인덱스를 풀어서 생성한 버텍스버퍼로 렌더링 하는 것에 주의해야 합니다.
이때 스트림(Stream) 을 하나 더 연결하는데,
이것은 앞서 우리가 렌더링 했던 R2VB 라는 버퍼
입니다.

결과적으로 VertexShader 에는 Barycentric coordiate 기반의 가중치 값이 전달됩니다.
즉, 무게 중심 좌표입니다.

float3 vPosTessOS = i.vPositionVert0.xyz * i.vBarycentric.x +
                              i.vPositionVert1.xyz * i.vBarycentric.y + 
                              i.vPositionVert2.xyz * i.vBarycentric.z;


정점을 구성하는 방법은 위처럼 해야 합니다.

이상으로 DX9 세대의 테셀레이션 작업들에 대해서 아주 간단히 살펴보았습니다.
메인으로 다룰 내용이 아니라서, 쉽게 넘어간 부분이 많습니다.
아무래도 거의 사용하지 않기 때문에, 깊이있게 다루는 것은 의미가 없다고 생각합니다.

하지만, DX9 세대의 테셀레이션 작업은 이렇게 복잡한 방법과 절차를 통과해야 합니다.
DX11 의 테셀레이션 작업은 상대적으로 빠른 성능으로 구현이 됩니다.
왜냐하면 1 Pass 이기 때문입니다.


ATI 는 DX9 세대의 테셀레이션 작업을 위해서, 라이브러리를 제공하고 있습니다.
더 필요한 정보가 있으시면, 아래의 링크를 참고하시기 바랍니다.

http://developer.amd.com/gpu/radeon/Tessellation/Pages/default.aspx#d3d9
신고


DirectX SDK February 2010  버전까지는 'EnhancedMesh' 라는 샘플이 있었습니다.
아쉽게도 2010 June 버전에서 이 샘플은 사라졌습니다.
메시의 퀄리티를 향상시키는 샘플인데, 실제로는 폴리곤 갯수를 증가시키고 있습니다.
굳이 실행을 실켜보실 이유는 없습니다. ^^

ID3DXMesh 인터페이스에는 멤버함수로 CloneMeshFVF() 를 가지고 있습니다.
이 멤버함수의 옵션으로 D3DXMESH_NPATCHES 을 사용하게 되면,
하드웨어 가속을 받아서 폴리곤을 증가시킬 수 있습니다.
물론 내부적으로는 많은 연산을 수행할 것입니다.



만약 테셀레이션 작업이 그래픽카드에서 지원을 해주지 않는다면,
이는 CPU 기반으로 작업을 수행해야 합니다.
바로 이를 도와주는 API 가 D3DXTessellateNPatches() 입니다.



이렇듯 DirectX9 세대에도 테셀레이션을 위해서 API들을 지원해 주고 있었습니다.
물론 정식으로 그래픽카드에서 지원을 하지 않았기 때문에,
성능에 많은 문제점을 가지고 있었습니다.
테셀레이션 자체가 근본적으로 많은 연산을 수반하기 때문입니다.

다음 시간에는, 마지막으로 ATI의 DirectX9 기반의 테셀레이션 작업에 대해서 살펴보도록 하겠습니다.^^

신고

 

 

 

 


안녕하세요. 알콜코더 민군입니다. ^^

이제부터 본격적으로 연재를 시작하게 되었습니다. 많은 응원 부탁드립니다.

 

전에 예고했던 DX 11 튜터리얼을 하나씩 예제로 공부하면서 기초 단계부터 하나하나씩 밟고 올라가겠습니다. 이미 튜터리얼쯤이야 건너뛰신 분에게는 별로 도움이 안될지도…(쿨럭;;)

 

자 그럼 대망의 첫번째 튜터리얼을 공부해 보죠

 

 

 

이번 예제의 목표

 

l  DirectX 11을 이용하여 간단한 기본 어플리케이션을 만들어 본다

l  DirectX 11을 사용하기 위한 기본 디바이스 및 오브젝트들을 셋업한다

 

위가 이번 튜터리얼 01의 목표입니다. 우선은 3D 렌더링 화면을 가진 윈도우를 뛰우는것부터 시작해보죠. 3D 화면에 맵을 뛰우고 캐릭터가 뛰어다니는 게임을 만들어 보고 싶으시겠지만천리길도 한걸음 부터입니다. 우선 차근 차근 하나씩 시작해 보죠. ^^

 

 

참고 소스

 

l  DirectX 11 Sample – Tutorial 01 : Direct3D Basics

 

Direct X SDK에 포함되어 있는 샘플입니다. 이 샘플을 기반으로 설명하겠습니다.

직접 SDK에서 설치해서 공부하셔도 되고, 제가 첨부한 파일을 받아서 보셔도 됩니다.

 

 

사전에 준비해야 할 것

 

우선 기본적으로 튜터리얼 역시 윈도우 프로그래밍이기 때문에, 기본적인 윈도우는 뛰우고, DirectX를 설정해야 겠죠? ^^

윈도우를 뛰우는건 기본적인 내용이고 이 연재의 진행을 위해서도 과감하게 생략하겠습니다. (__)

하지만 공부하는 데 도움이 되시라고, 간단히 기본 윈도우를 뛰우는 프로그램을 첨부파일로 첨부합니다. (DX10의 튜터리얼 0번 샘플)

 

직접 코딩을 하면서 공부를 해보기 위해서는 샘플 파일을 받아서 기본적인 윈도우 프로그래밍 기반을 세팅한 후에 아래 내용을 따라서 DX11을 직접 작성해 보면 큰 도움이 될 겁니다.

 

첨부파일 – DirectX 10 Tutorial 00 : Win32 Basics

DirectX 를 아직 세팅하지 않고, 빈윈도우만 뛰우는 기본적인 프로그램입니다.

 

 

Direct3D 디바이스 오브젝트를 셋업하자

 

3D 디바이스 셋업은 3D 프로그래밍의 시작이자, 가장 필수적인 과정입니다.

3D 디바이스, 즉 그래픽카드를 셋업해야 그래픽카드를 통해서 3D 맵을 그리던가, 캐릭터를 렌더링하거나 할 수 있죠. 이 부분이 여타 일반 프로그래밍과 3D 프로그래밍이 시작부터 다른 점입니다. 일반 윈도우 프로그래밍은 그래픽 카드에 접근할 일이 없기 때문에 이런 과정이 당연히 없습니다. ^^

(나름 3D 프로그래머만의 특권일지아니면 지옥의 시작일지…)

 

DX11에서는 우선 아래 세가지 오브젝트를 만들어야 합니다

 

l  Device

l  Immediate Context

l  Swap Chain

 

Device

3D 그래픽 카드와 연결되는 기본 디바이스 객체

 

DX9의 디바이스와 같은 계열이지만, DX 11에서부터는 멀티스레드를 최적화하기 위하여 직접 디바이스를 사용하지 않고, Context를 이용하여 디바이스에 명령을 내립니다. 따라서 별로 사용할 일이 많지는 않을겁니다.

Immediate Context

DX11에 새로 추가된 오브젝트

 

DX11에서는 디바이스 객체에 직접 접근하지 않고, 이 객체를 이용해서 디바이스에 명령을 내린다고 생각하시면 됩니다.

 

DX11에서는 멀티코어/멀티스레드를 최적화 하기 위해서 Device의 하위에 Context를 분리하여 사용합니다. 현재는 이 Context로 디바이스에 명령을 내린다고만 알고 넘어가면 됩니다.

Swap Chain

화면에 렌더링할 백버퍼를 관리하고, 실제로 화면에 렌더링 하는 역할을 담당하는 오브젝트


이 오브젝트를 이용하여 백버퍼의 렌더 타겟을 얻어내 3D  오브젝트를 렌더 타겟에 렌더링하고, 프론트 버퍼와 백버퍼를 스왑(플립(Flip)이라고도 합니다)하여 실제 모니터에 게임 화면을 렌더링하는 역할을 합니다.

 

 

 

기본적인 셋업 및 오브젝트 생성 순서

 

기본적인 DX11의 셋업 순서는 아래와 같습니다.

 

1.   ID3D11Device SwapChain을 생성한다

2.   SwapChain에서 백버퍼를 가져온다(2D Texture)

3.   백버페 실제 렌더링할 렌더 타겟 뷰를 생성하여 바인딩 시킨다

4.   뷰포트를 생성한다

5.   매 프레임마다 위에서 생성한 렌더 타겟 뷰에 게임 화면을 렌더링한다.

6.   SwapChain을 이용하여 디바이스에 화면을 그린다

 

위 순서에서 가장 큰 목적은 바로 렌더 타겟 뷰(Render Target View)’를 생성하는 것입니다. 바로 이 렌더 타겟 뷰가 실제로 우리가 3D 게임 화면을 렌더링할 타겟이 됩니다. (그러니까 렌더타겟이라고 부르는 거겠죠.. ==)

 

이 랜더타겟뷰는 리소스뷰의 일종으로 디바이스의 백버퍼에 바운딩되면서 생성됩니다. 그래서 렌더타겟뷰에 게임 화면을 렌더링한다는 것은 백버퍼에 렌더링하는것과 같습니다. 그리고 백버퍼에 모든 화면을 렌더링하고 난 다음에는 SwapChain을 이용해서 백버퍼를 화면에 그리면 되는겁니다. 정확히는 백퍼버를 프론트버퍼와 바꾸는 스와핑 (또는 플립(Flip))이 일어나는 것이죠.

 

간단히 이야기면, SwapChain을 생성하고, SwapChain안에서 백버퍼를 꺼내오고, 꺼내온 백버퍼를 가지고 렌더타겟뷰라는 오브젝트에 바운딩합니다.

 

그리고 실제 내부에서는 SwapChain이 백버퍼라는 리소스를 가지고 있고, 개발자는 그 백버퍼를 렌더 타겟 뷰에 바인딩하여 사용하는 것입니다.

 

 

 

 

다음 내용으로 이어집니다. ^^;; 

 

 

언젠가 우즈벡에 미소녀 게임 회사를 차리고 싶은 개발자

3D게임 클라이언트 프로그래머, 살짝 오타쿠

드래곤볼 온라인 개발, 현재는 네오위즈 서식중

신입게임 개발자의 서울상경기’초중급 게임개발자 스터디’ 운영중

 

 

신고