들어가기 앞서 지금까지 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 에서 가져왔습니다 )
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 부터 텍스쳐 인터페이스는 꼭 이미지 데이터를 의미하지 않습니다. 대용량의 메모리 블럭의 의미에 더 가깝다는 것을 알아두시기 바랍니다. 텍스쳐의 개념을 사용하기 때문에 동시에 여러 데이터에 접근이 가능하고, 랜덤 액세스도 가능한 것입니다.^^
백문이 불여일견이라고들 하죠? 글로써 언급하는 것보다, 프로그래머들은 코드로 볼 때 더 직관적인 이해를 할 수 있는 경우가 많습니다.
간단하게 두 배열의 합을 구하는 코드를 통해서, 이를 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를 직접 제어하는 것보다는 쉽습니다.
이번 시간에 다룰 것은 투명 이미지 입니다. 얼마 전 댓글로 문의하신 내용인데 답변을 달기가 조금 부족한 듯 싶어서, 이렇게 별도로 아티클(?)로 남깁니다.
예전에 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에서 처리를 해버립니다. 혹은 디폴트 생성자들이 모두 들어있습니다. 자동적으로 처리되는 부분들 이외의 기능이 필요하다면, 이들을 잘 제어해야만 하겠지요..^^
지금 D2D1을 공부중입니다.
혹시나 해서 질문을 올려보는데요.
RanderTarge->DrawBitmap();//해주고
RanderTarge->SetTransform();//을 해주면 비트맵이 변화가 되서 나옵니다. 그런데 이때 궁금 한점이 여러장의 비트맵을 생성했을 경우 DrawBitmap에는 각각의 비트맵을 선택해서 생성을 할수가 있는데 SetTransform은 일괄적으로 바껴버리네요 이걸 어떻게 해야하는건가요???
너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
이번 시간에는 지난 시간들까지 언급한 내용을 기반으로 해서,
간단한 테셀레이션 작업을 구현해 보려 합니다.
당연한 얘기이겠지만,
하드웨어 기반의 테셀레이션은 하드웨어의 지원이 없으면 매우 느립니다.
즉 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() 를 이용하는 상황이 생기기도 할 것입니다.
또 하나의 더 큰 효과를 위한 하나의 방법이 늘었다고 생각하시면 더 좋을 것 같습니다.^^
너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
테셀레이터는 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 로 전달하게 되는 것입니다.
물론 내부적으로는 더욱 복잡한 과정을 거치겠지만,
우리가 코딩관점에서 관심을 가질 수 있는 변수 정보는 이들 뿐입니다.
질문 있습니다.
vecResult.z를 구하는 코드를 보면 ::D3DXVec3Cross( &vecCross, &a2b, &a2c); 이렇게 되어 있는데 ::D3DXVec3Cross( &vecCross, &a2p, &a2c); 이게 맞지 않나요?
a2b 와 a2c 외적을 구해서 길이를 구하게되면 rTotalArea 과 vecResult.z 값이 서로 같아지는것 같은데요.
너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
우리가 그래픽스에서 사용하는 폴리곤은 굉장히 복잡한 방식으로 처리가 됩니다.
많은 스테이지를 통해서 결국 우리는 화면 픽셀로 변환된 최종 결과를 확인하게 되는 것입니다.
그 과정 속에서 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의 기능을 표현하면 다음과 같습니다.
위의 그림은 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 샘플에 있습니다. )
// Insert code to compute Output here.
return Output;
}
위의 Hull Shader 는 동작 방식을 설정합니다.
몇몇 정의된 값들을 셋팅해 주면, 이는 테셀레이션 작업을 하는 동안에 사용되게 됩니다.
즉, 위의 셋팅들은 '폴리곤을 어떻게 분할할것인가?' 에 준하는 프로그램 코드라 할 수 있습니다.
이제 남은 것은 '폴리곤을 얼마나 분할할 것인가?' 입니다.
이는 PatchConstantFunc 을 통해서 병렬적으로 처리된다고 앞서 설명을 했습니다.
이곳에서는 Tessellation Factor 를 계산하게 되는데, 그 결과에 따라서 컬링 작업이 실행됩니다.
( 이 값이 0 이하의 경우에는 더 이상 처리가 필요하지 않습니다. )
이 작업을 하는 함수를 우리는 직접 작성해서,
위의 [patchconstantfunc("SubDToBezierConstantsHS")] 처럼 설정해 주면 자동적으로 동작합니다.
MSDN 에 나와있는 PatchConstantFunc의 기본적인 형태는 다음과 같습니다.
지금까지 Hull Shader의 기본적인 개념과 역활에 대해서 언급해 드렸습니다.
이렇게 얻어진 결과는 테셀레이터로 전달되게 됩니다.
세부적인 Hull Shader 의 작성은 이후의 시간들을 통해서 살펴볼 예정입니다.
( 현재 본 글들은, 개념 위주의 설명에 포커스를 두고 있습니다. ^^ )
렌더타겟에 렌더링을 하기위해서는 뷰포트의 설정도 필요합니다. 뷰포트라는 것은 렌더타겟의 렌더링 영역에 관한 설정입니다. 렌더타겟에 넓이와 높이, 그리고 깊이값으로 렌더링 영역을 설정할 수 있습니다. 그렇기 때문에 뷰포트는 각 렌더타겟별로 설정합니다. 즉 8개의 렌더타겟이 있으면, 뷰포트 설정 역시 8개를 설정해야 합니다. 뷰포트는 특별한 인터페이스가 있는 것이 아니라, 값을 설정하는 것이기 때문에 뷰포트 구조체에 값을 채우고, 렌더타겟에 세팅해주기만 하면 됩니다.
Diagram of the viewport rectangle
뷰포트는 'ID3D11DeviceContext::RSSetViewports 메소드'로 설정합니다. RS는 래스터라이저(RS) 스테이지를 의미합니다. 즉 파이프라인에서 레스터라이저 스테이지의 설정입니다. 뷰포트의 설정에는 'D3D11_VIEWPORT 구조체'를 사용합니다. 이전의 DX10과는 달리 값들을 실수값으로 지정합니다.
스크린좌표는 왼쪽위가 (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
지난 연재에서는 DirectX11의 3D 그래픽 디바이스를 생성하는 방법을 배웠습니다. 하지만 3D 디바이스는 어디까지나 그래픽 카드 드라이버의 디바이스의 인터페이스일뿐, 실제로 게임 화면 내용을 렌더링하는 인터페이스는 아닙니다. 실제 게임 화면은 디바이스에 설정된 렌더타겟에 렌더링 됩니다. 저번 연재 내용에서 디바이스와 함께 스왑체인을 생성했기 때문에, 스왑체인의 백버퍼도 당연히 같이 생성이 되었지만, Direct3D의 렌더타겟으로는 아직 설정되지는 않았습니다. 그래서 생성된 스왑체인으로부터 백버퍼를 얻어와서 디바이스의 렌더타겟으로 설정해야만 합니다. 그리고 깊이/스텐실 버퍼를 사용하는 경우에는 렌더타겟의 설정과 동시에 깊이/스텐실 버퍼도 설정합니다.
이번 연재에서는 이렇게 실제로 렌더링을 하기위한 렌더타겟으로 사용하기 위해서 백버퍼를 설정 하는 방법에 대해서 이야기 하겠습니다.
너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
그동안 연재를 본의 아니게 많이 쉬었습니다. 거의 두달만에 다시 연재를 재개하는 것 같네요..OTL
회사 프로젝트가 바쁜데다가 여러 가지 발표와 행사가 겹쳐서 너무 오래 연재를 쉰 것 같네요. 그럴리는 없겠지만… 혹시나 이런 허접한 연재를 기다리신 분들에게는 죄송합니다. T^T 앞으로는 더욱 열심히 자주 업데이트 하도록 하겠습니다. ^^;;
이번 연재에서는 저번 연재 마지막에 언급했던 DX11 디바이스와 스왑 체인을 생성하는 방법에 대해서, 좀더 자세히 알아볼까 합니다. DX를 이용한 모든 프로그램에서는 DX 디바이스를 생성하는 것이 가장 기본이 됩니다. 그전까지의 과정들은 전부 디바이스를 생성하기 위한 준비단계에 해당합니다. 그리고 DX10 버전부터는 DXGI 기반 구조로 변경되면서 스왑체인(SwapChain)과 디바이스 컨텍스트를 생성해야 합니다. 스왑체인에 대한 설명은 이전 연재를 참조하시면 됩니다.
디바이스와 스왑체인의 생성
디바이스와 스왑체인은 'D3D11CreateDeviceAndSwapChain' 함수를 이용해서 동시에 생성합니다.
표시할 '디스플레이 디바이스(비디오 카드)'의 '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 지원 그래픽 카드라면 당근 하드웨어 디바이스가 생성되겠죠)
다음 시간에는…
다음 연재에서는 위와 같이 방법으로 생성한 스왑체인으로 백버퍼를 가져와서, 렌더 타겟 뷰를 생성하고, 생성된 렌더타겟뷰를 디바이스 컨텍스트에 설정하는 방법을 다루겠습니다. 즉, 실제로 게임 화면을 렌더링하는 백버퍼를 생성하는 단계에 대해서 이야기 해보겠습니다. ^^
그동안 잊고 있엇던(?) 내용들을 다시 한번 새롭게 정리하는 기회가 되시길 바랍니다. ^^
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의 기능도 확장되었습니다.
앞선 시간들을 통해서, 우리는 테셀레이션에 대해서 꾸준히 살펴보았습니다.
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 기반의 하드웨어에서 그렇게 빠르지도, 또한 느리지도 않습니다.
오히려 일반적인 상황에서는 약간의 성능 저하가 일어날 수도 있으며,
최적화를 잘한 경우에는 테셀레이션 처리가 더 느릴 수도 있습니다.
하지만, 이제 하드웨어는 테셀레이터라는 기능을 장착을 했으며,
앞으로는 테셀레이터 기반으로 최적화하는 것이 더 개발 패러다임에 적합할 것입니다.
당분간 개발 패러다임이 과도기적인 상태를 보이겠지만, 이미 그래픽카드의 발전 방향이 테셀레이터 기반으로 변경되고 있다는 것에 우리는 주목해야 합니다.
오늘은 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 기반의 가중치 값이 전달됩니다.
즉, 무게 중심 좌표입니다.
DirectX SDK February 2010 버전까지는 'EnhancedMesh' 라는 샘플이 있었습니다.
아쉽게도 2010 June 버전에서 이 샘플은 사라졌습니다.
메시의 퀄리티를 향상시키는 샘플인데, 실제로는 폴리곤 갯수를 증가시키고 있습니다.
굳이 실행을 실켜보실 이유는 없습니다. ^^
ID3DXMesh 인터페이스에는 멤버함수로 CloneMeshFVF() 를 가지고 있습니다.
이 멤버함수의 옵션으로 D3DXMESH_NPATCHES 을 사용하게 되면,
하드웨어 가속을 받아서 폴리곤을 증가시킬 수 있습니다.
물론 내부적으로는 많은 연산을 수행할 것입니다.
만약 테셀레이션 작업이 그래픽카드에서 지원을 해주지 않는다면,
이는 CPU 기반으로 작업을 수행해야 합니다.
바로 이를 도와주는 API 가 D3DXTessellateNPatches() 입니다.
이렇듯 DirectX9 세대에도 테셀레이션을 위해서 API들을 지원해 주고 있었습니다.
물론 정식으로 그래픽카드에서 지원을 하지 않았기 때문에,
성능에 많은 문제점을 가지고 있었습니다.
테셀레이션 자체가 근본적으로 많은 연산을 수반하기 때문입니다.
다음 시간에는, 마지막으로 ATI의 DirectX9 기반의 테셀레이션 작업에 대해서 살펴보도록 하겠습니다.^^
우선 기본적으로 튜터리얼 역시 윈도우 프로그래밍이기 때문에, 기본적인
윈도우는 뛰우고, DirectX를 설정해야 겠죠? ^^
윈도우를 뛰우는건 기본적인 내용이고 이 연재의 진행을 위해서도 과감하게 생략하겠습니다. (__)
하지만 공부하는 데 도움이 되시라고, 간단히 기본 윈도우를
뛰우는 프로그램을 첨부파일로 첨부합니다. (DX10의 튜터리얼 0번
샘플)
직접 코딩을 하면서 공부를 해보기 위해서는 샘플 파일을 받아서 기본적인 윈도우 프로그래밍 기반을 세팅한
후에 아래 내용을 따라서 DX11을 직접 작성해 보면 큰 도움이 될 겁니다.
첨부파일 – DirectX 10 Tutorial 00 : Win32 Basics
DirectX
를 아직 세팅하지 않고, 빈윈도우만
뛰우는 기본적인 프로그램입니다.
Direct3D 디바이스 오브젝트를 셋업하자
3D 디바이스 셋업은 3D 프로그래밍의 시작이자, 가장 필수적인 과정입니다.
3D 디바이스, 즉 그래픽카드를 셋업해야 그래픽카드를
통해서 3D 맵을 그리던가, 캐릭터를 렌더링하거나 할 수
있죠. 이 부분이 여타 일반 프로그래밍과 3D 프로그래밍이
시작부터 다른 점입니다. 일반 윈도우 프로그래밍은 그래픽 카드에 접근할 일이 없기 때문에 이런 과정이
당연히 없습니다. ^^
(나름 3D 프로그래머만의 특권일지… 아니면 지옥의 시작일지…)
DX11에서는 우선 아래 세가지 오브젝트를 만들어야 합니다
lDevice
lImmediate Context
lSwap 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이 백버퍼라는 리소스를
가지고 있고, 개발자는 그 백버퍼를 렌더 타겟 뷰에 바인딩하여 사용하는 것입니다.
댓글을 달아 주세요
용량의 메모리 블럭의 의미에 더 가깝다는 것을 알