개발하는 리프터 꽃게맨입니다.

[DX11 물방울책 요약 정리] 챕터4: Direct3D 초기화 본문

컴퓨터 그래픽스/DirectX 11

[DX11 물방울책 요약 정리] 챕터4: Direct3D 초기화

파워꽃게맨 2024. 9. 8. 20:27

파트2: Direct3D 살펴보기

이 부분에서는 이 책의 나머지 부분에서 사용되는 Direct3D의 기본 개념과 기법들을 학습한다.

이 기초들을 숙달하면 더 흥미로운 응용 프로그램을 작성할 수 있다.

이 부분에 포함된 각 장의 간단한 설명은 다음과 같다.

 

챕터 4: Direct3D 초기화

이 장에서는 Direct3D가 무엇인지, 그리고 3D 그리기를 준비하기 위해 어떻게 초기화하는 지 배운다.

serface, pixel format, page fliping, depth buffer, multi sampling 과 같은 기본 Direct3D 주제들도 소개된다. 또한 성능 카운터를 사용해 초당 렌더링된 프레임 수를 계산하는 방법도 배우며, Driect3D 애플리케이션을 디버깅하는 몇 가지 팁도 제공한다. 우리는 SDK의 프레임워크가 아닌, 자체 애플리케이션 프레임워크를 개발하고 사용한다.

 

챕터 5: 렌더링 파이프라인

이 장에서는 렌더링 파이프라인에 대해 철처히 소개한다. 렌더링 파이프라인은 가상 카메라가 보는 세상을 기반으로 2D 이미지를 생성하는 데 필요한 일련의 단계를 말한다. 우리는 3D 세계를 정의하고, 가상 카메라를 제어하여, 3D 기하학을 2D 이미지 평면에 투영하는 방법을 배운다.

 

챕터 6: Driect3D에서 그리기

이 장은 렌더링 파이프라인을 구성하고, 정점 및 픽셀 셰이더를 정의하여, 기하학을 렌더링 파이프라인에 제출하여 그리기 위한 Direect3D API 인터페이스 및 메서드에 중점을 둔다.

이펙트 프레임워크도 소개 된다. 이 장을 마치면, 그리드, 상자, 구, 원기둥을 그릴 수 있게 된다.

 

챕터  7, 조명

이 장에서는 광원을 만들고, 빛과 표면 간의 상호작용을 재료를 통해 정의하는 방법을 보여준다. 특히, 정점 및 픽셀 셰이더로 방향성 조명, 점 조명, 스포트라이트를 구현하는 방법을 배운다.

 

챕터  8, 텍스처링

이 장에서는 텍스처 매핑에 대해 설명한다. 텍스처 매핑은 2D 이미지 데이터를 3D 프리미티브(원시 도형)에 매핑하여 장면의 사실성을 높이는 기법이다. 예를 들어, 텍스처 매핑을 사용하여 3D 직사각형에 2D 벽돌 벽 이미지를 적용하여 벽돌 벽을 모델링할 수 있다. 텍스처 타일링과 애니메이션 텍스처 변환과 같은 중요한 텍스처링 주제들도 다룬다.

 

챕터  9, 블렌딩

블렌딩을 사용하면 투명성과 같은 여러 특수 효과를 구현할 수 있다. 또한, 이미지의 특정 부분을 표시되지 않도록 마스크 처리하는 본질적인 클립 함수에 대해서도 설명한다. 이를 통해 울타리나 문과 같은 것을 구현할 수 있다. 우리는 또한 안개 효과를 구현하는 방법을 보여준다.

 

챕터  10, 스텐실링

이 장에서는 스텐실 버퍼에 대해 설명한다. 스텐실 버퍼는 스텐실처럼 픽셀이 그려지는 것을 막을 수 있다. 픽셀을 마스킹하는 것은 다양한 상황에서 유용한 도구이다. 이 장의 아이디어를 설명하기 위해 스텐실 버퍼를 사용하여 평면 반사와 평면 그림자를 구현하는 방법을 자세히 설명한다.

 

챕터  11, 지오메트리 셰이더

이 장에서는 지오메트리 셰이더를 프로그래밍하는 방법을 보여준다. 지오메트리 셰이더는 특별한데, 그 이유는 전체 기하학적 프리미티브를 생성하거나 제거할 수 있기 때문이다. 응용 프로그램으로는 빌보드, 털 렌더링, 세부 분할, 입자 시스템 등이 있다. 또한, 프리미티브 ID와 텍스처 배열에 대해서도 설명한다.

 

챕터  12, 컴퓨트 셰이더

컴퓨트 셰이더는 Direct3D가 노출하는 프로그래머블 셰이더로, 렌더링 파이프라인의 직접적인 일부가 아니다. 이것은 애플리케이션이 그래픽 처리 장치(GPU)를 사용하여 범용 계산을 수행할 수 있게 해준다. 예를 들어, 이미지 처리 애플리케이션은 컴퓨트 셰이더를 사용하여 이미지 처리 알고리즘을 GPU에서 구현하여 처리 속도를 높일 수 있다. 컴퓨트 셰이더는 Direct3D의 일부이므로 Direct3D 리소스를 읽고 쓸 수 있으며, 이를 통해 결과를 렌더링 파이프라인에 직접 통합할 수 있다. 따라서 범용 계산 외에도 컴퓨트 셰이더는 여전히 3D 렌더링에 적용될 수 있다.

 

챕터  13, 테셀레이션 단계

이 장에서는 렌더링 파이프라인의 테셀레이션 단계를 탐구한다. 테셀레이션은 기하학을 더 작은 삼각형으로 세분한 다음, 새로 생성된 정점을 특정 방식으로 오프셋하는 과정을 의미한다. 삼각형 수를 늘리는 이유는 메시의 세부 사항을 추가하기 위함이다. 이 장의 아이디어를 설명하기 위해 우리는 거리 기반으로 쿼드 패치를 테셀레이션하는 방법을 보여주며, 큐빅 베지에 쿼드 패치 표면을 렌더링하는 방법을 보여준다.

 

챕터 4: Direct3D 초기화

Direct3D의 초기화 과정은 몇 가지 기본적인 Direct3D 타입과 그래픽스 개념에 대한 이해가 필요하다.

그 다음으로 Direct3D를 초기화하는 데 필요한 단계들을 자세히 설명한다.

 

그 후, 실시간 그래픽스 응용 프로그램에 필요한 정확한 타이밍과 시간 측정을 소개하는 장이 이어진다.

마지막으로, 이 책의 모든 데모 응용 프로그램이 따르는 일관된 인터페이스를 제공하기 위해 샘플 코드를 살펴본다.

 

학습 목표

1) Direct3D가 3D 하드웨어 프로그래밍에서 수행하는 역할에 대한 기본적인 이해를 얻는다.

2) COMJ이 Direct3D에서 수행하는 역할을 이해한다.

3) 2D 이미지가 어떻게 저장되는지, 페이지 플리핑, 깊이 버퍼링, 멀티샘플링 같은 기본적인 그래픽스 개념을 배운다.

4) 고해상도 타이머 읽기를 얻기 위해 성능 카운터 함수를 사용하는 방법을 배운다.

5) Direct3D를 초기화하는 방법을 알아보낟.

6) 이 책의 모든 데모가 사용하는 애플리케이션 프레임워크의 일반적인 구조에 익숙해진다.

 

기본사항: Driect3D 개요

Direct3D의 초기화 과정은 몇 가지 기본적인 Direct3D 타입과 그래픽스 개념에 대한 이해가 필요하다.

 

Direct3D는 3D 하드웨어 가속을 사용하여 3D 세게를 렌더링할 수 있게 하는 저수준 그래픽스 API다.

본질적으로 Direct3D는 그래픽스 하드웨어를 제어하는 소프트웨어 인터페이스를 제공한다.

 

예를 들어, 화면(= 렌더타겟)을 지우기 위해서는 Direct3D 메소드인 ID3D11DeviceContext::ClearRenderTargetView를 호출해야 한다.

 

애플리케이션과 그래픽스 하드웨어 사이에 Direct3D 라는 API 계층이 존재한다.

그렇기 때문에 그래픽스 하드웨어가 Direct3D 11을 지원만 한다면 3D 하드웨어의 세부 사항에 대해 걱정할 필요없이 응용 프로그램을 만들 수 있다. (역으로 말하면, 그래픽 카드가 Direct3D 11을 지원하지 않는다면 Direct3D11 기반 프로그램은 동작하지 않는다.)

 

Driect3D 11을 지원하는 그래픽스 장치는 Direct3D 11  명령어 집합 전체를 지원해야만 프로그래밍을 수행할 수 있다.

(멀티샘플링 수와 같은 일부 사항은 Direct3D 11 하드웨어마다 다를 수 있으므로 조회가 필요하다.)

 

이는 Direct3D 9 과는 대조적이다. Direct3D 9에서는 장치가 Direct3D 9 기능 중 일부만 지원하더라도 프로그래밍이 가능하였다. 대신.. 하드웨어가 특정 기능을 지원하지 않으면 Direct3D 9 함수 호출이 실패로 이어졌다.

 

Direct3D 11에서는 장치의 스펙을 검사할 필요가 없어진 대신, 해당 장치가 Direct3D 11 명령어 집합 전체를 구현해야 한다는 엄격한 요구 사항이 적용된다.

 

COM

Component Object Model (COM) 은 DirectX 가 프로그래밍 언어에 종속되지 않고 하위 호환성을 유지할 수 있게 하는 기술이다. COM 덕분에 DirectX는 다양한 언어에서 동일한 구조로 사용될 수 있고, 이전 버전으로 개발된 프로그램이 여전히 최소 DirectX 버전에서 동작한다.

 

일반적으로 우리는 COM 객체를 인터페이스라고 부르며, 이는 C++ 클래스처럼 생각하고 사용할 수 있다. DirectX를 C++ 로 프로그래밍할 때 대부분의 COM 세부 사항은 숨겨져 있다.

 

우리가 알아야 할 유일할 것은 COM 인터페이스를 특수 함수나 다른 COM 인터페이스의 메소드를 통해 얻는다는 것이며, C++ 의 new 키워드로 COM 인터페이스를 생성하지 않는다는 것이다.

또한, 인터페이스 사용이 끝나면 deleta 대신 Release 메소드를 호출해야 한다.

모든 COM 인터페이스는 IUnknown COM 인터페이스에서 기능을 상속받아 Release 메소드를 제공한다.

COM 객체는 자체적으로 메모리를 관리한다.

 

물론 COM에는 더 많은 내용이 있지만, DirectX를 효과적으로 사용하기 위해서는 더 이상의 세부 사항이 필요하지 않다.

 

COM 인터페이스는 대문자 I로 시작한다.

예를 들어, 2D 텍스처를 나타내는 COM 인터페이스는 ID3D11Texture2D 라고 불린다.

 

텍스처와 데이터 리소스 형식

2D 텍스처는 데이터 요소의 행렬이다.

2D 텍스처의 한 가지 용도는 2D 이미지 데이터를 저장하는 것으로, 텍스처의 각 요소는 픽셀의 색상을 저장한다.

 

그러나 이것이 텍스처의 유일한 용도는 아니다. Normal mapping이라는 고급 기법에서는 텍스처의 각 버퍼에 3D 벡터를 저장한다.

 

따라서 텍스처를 이미지 데이터를 저장하는 것으로 생각하는 것이 일반적으로, 일반적으로 그렇게 사용되지만 사실 그보다 더 범용적이다.

 

1D 텍스처는 데이터 요소의 1D 배열과 같고, 3D 텍스처는 데이터 요소의 3D 배열과 같다.

 

후속 장에서 논의되겠지만, 텍스처는 단순한 데이터 배열 이상의 의미를 가지며, miamap 레벨 (해상도 관련 개념)을 가질 수 이쏙, GPU는 필터링과 멀티샘플링과 같은 특수 작업을 수행할 수 있다.

 

또한, 텍스처는 임의의 데이터를 저장할 수 없으며, DXGI_FORMAT 열거형 타입으로 설명된 특정 데이터 형식만 저장할 수 있다.

(여기서 DXGI는 Direct X Graphics Infrastructure 의 준말로 그래픽 리소스 관리를 위해 사용되는 인터페이스 집합니다.)

 

R G B A 문자는 각각 빨강, 초록, 파랑, 알파를 나타낸다.

색상은 기본적으로 R, G, B 의 조합으로 만들어진다.

 

A는 투명도를 제어하는 데 사용된다.

 

앞서 말했듯 텍스처가 반드시 색상 정보를 저장할 필요는 없다

R32G32B32_FLOAT 같은 경우 3D 벡터를 저장할 수  있다.

 

또한, 타입없는 TYPELESS 형식도 있는데, 이는 메모리만 예약하고 나중에 텍스처가 파이프라인에 바인딩될 때 데이터를 어떻게 재해석할지 지정하는 방식이다.

 

어떤 DXGI를 선택하느냐에 따라서, 텍스처의 한 버퍼가 얼만큼의 용량을 차지하는지 달라질 수 있다.

 

스왑 체인과 페이지 플리핑

애니메이션에서 깜빡임을 방지하기 위해 전체 애니메이션 프레임을 백 버퍼라는 스크린에서 보이지않는 텍스처에 그리는 것이 가장 좋다.

 

애니메이션 프레임의 전체 장면이 백 버퍼에 그려지면, 화면에 전체 프레임으로 표시된다. 이렇게 하면 시청자는 프레임이 그려지는 과정을 보지 않고 완성된 프레임만 보게 된다.

 

이 작업을 구현하기 위해 하드웨어는 두 개의 텍스처 버퍼를 유지한다. 하나는 프론트 버퍼, 다른 하나는 백 버퍼라고 한다.

 

프론트 버퍼는 현재 모니터에 표시되고 있는 이미지 데이터를 저장하고, 다음 애니메이션 프레임은 백 버퍼에 그려진다.

프레임이 백 버퍼에 다 그려지면, 백 버퍼와 프론트 버퍼의 역할이 바뀐다. 백 버퍼가 프론트 버퍼가 되고, 프론트 버퍼는 다음 애니메이션 프레임을 위한 백 버퍼가 된다.

 

백 버퍼와 프론트 버퍼의 역할을 바꾸는 것을 프레젠팅이라고 한다.

프레젠팅은 프론트 버퍼와 백 버퍼의 포인터만 교체하면 되기에 매우 효율적인 작업이다.

 

 

프론트 버퍼와 백 버퍼는 스왑 체인을 구성한다. Direct3D에서 스왑 체인은 IDXGISwapChain 인터페이스로 표현된다.

 

이 인터페이스는 프론트 버퍼와 백 버터 텍스처를 저장하고, 버퍼 크기 조정, 프레젠팅을 위한 메소드를 제공한다.

 

스왑체인을 위해 두 개의 버퍼를 사용하는 것을 더블 버퍼링이라고 한다.

두 개 이상의 버퍼를 사용하여, 세 개의 버퍼를 유지하는 경우도 있다. 이를 트리플 버퍼링이라고 한다.

 

그러나 일반적으로 2개의 버퍼면 충분하다.

 

백 버퍼는 텍스처이기 때문에 그 단위를 텍셀이라고 불러야 하지만, 백 버퍼는 색상 정보를 저장하므로 우리는 종종 그 요소를 픽셀이라고 부른다. 

때로는 텍스처가 색상 정보를 저장하지 않더라도 텍스처의 요소를 픽셀이라고 부르기도 한다. (굳어진 표현인듯)

 

픽셀은 단순하게 색상 값만을 저장하는 버퍼를 의미하고

텍셀은 텍스처를 이루는 버퍼이며, 색상 값뿐만 아니라 벡터, 깊이 정보 등도 저장할 수 있다.

이 둘을 잘 구분하자.

 

스왑 체인 방식으로 텍스처 버퍼들 끼리 프레임 전환이 이루어지며, 더블 버퍼링을 통해 깜빡임 없는 애니메이션을 완성할 수 있다.

 

(용어정리)

스왑체인: 프레임이 깜빡거리는 현상을 방지하기 위해 프론트 버퍼와 백 버퍼를 교체하여 화면에 부드럽게 프레임을 표시하는 기법. 버퍼들의 전환을 관리하는 시스템.

더블 버퍼링, 트리플 버퍼링: 스왑 체인에서 사용되는 버퍼들의 수

프레젠팅: 백 버퍼에 그려진 프레임을 화면에 표시하는 과정. 복사, 포인터 교체 등이 있음

페이지 플리핑: 프레젠팅 중 포인터 교체를 사용하는 기법을 뜻함

 

깊이 버퍼

깊이 버퍼는 깊이 정보를 저장하는 텍스처이다.

(텍스처지만 이미지 정보는 포함하지 않는다!)

 

가능한 깊이 값의 범위는 [0, 1] 이며, 모니터 안쪽으로 들어갈수록 깊이 값은 커진다.

즉, 0이 관찰자에게 가장 가까운 위치, 1은 가장 먼 위치이다.

 

깊이 버퍼의 각 요소는 백 버퍼의 각 픽셀과 일대일로 대응된다.

따라서 백 버퍼가 1280 x 1024 해상도를 가지고 있다면, 깊이 항목도 1280 x 1024 개 존재한다.

 

 

Direct3D는 깊이 버퍼를 참고하여 깊이감을 표현한다.

만약 해당 픽셀의 깊이 값이 찍고자 하는 픽셀보다 0에 가까울 경우 그리기를 패스한다.

 

그러므로 깊이 버퍼를 사용할 경우 객체를 그리는 순서가 중요하지 않게 된다.

 

이런 방식으로 깊이를 처리하는 방식을 z-buffering, 깊이 버퍼.. 등으로 부른다.

 

깊이 문제를 처리하기 위해 가장 먼저할 수 있는 생각은 가장 먼 객체의 순서로 객체를 정렬하는 방법도 있다.

그러면 시점에서 가장 먼 객체가 먼저 그려지기 때문에, 가까운 객체가 먼 객체를 덮어버리게 될 것이다.

 

그러나 이 방법에는 문제점이 있는데, 정렬에 드는 오버헤드와 교차하는 기하구조를 처리하는 문제 등이 있다.

 

게다가 그래픽 하드웨어는 기본적으로 깊이 버퍼링을 제공하기 때문에.. 해당 방법은 사용할 필요가 없긴하다. 

 

관찰자가 보는 3D 장면과

바로 옆에서 바라도는 측면 뷰가 있다.

 

그림에서 우리는 3개의 서로 다른 픽셀이 윈도우에 그려지기 위해 경쟁하고 있음을 알 수 있다.

 

물로노 우리는 가장 가까운 픽셀이 화면에 렌더링되어야 한다는 것을 알고 있지만.. 따로 처리해주지 않는 이상 먼저 그려지지 않을 것이다.

 

렌더링이 시작하기 전, 백 버퍼는 기본 색상으로 지워지고, 깊이 값은 모두 1 (가장 깊은 값)으로 초기화된다.

 

이제 원기둥, 구, 원뿔 순서로 객체가 렌더링된다고 가정하자.

 

표가 나와서 당황했다면 걱정말라! 글쓴이가 쉽게 설명할 수 있다!

 

먼저, 원기둥이 먼저 픽셀 P를 검사한다.

그다음 픽셀 P의 깊이 값을 확인한다.

초기 깊이는 1이고, 현재 원기둥의 깊이갚은 아무래도 1이하이기 때문에 해당 픽셀에 점을 찍게 된다.

그 다음, P의 깊이 값을 자신의 깊이값으로 갱신한다.

 

그 다음, 구 역시 픽셀 P의 깊이 값을 확인한다.

저장되어있는 깊이 값을 확인한다.

원기둥의 깊이 값은 자신의 깊이값보다 클 것이다. 그러므로 해당 픽셀에 구는 점을 찍을 수 있다.

해당 픽셀에 점을 찍은 다음 P의 깊이 값을 자신의 깊이 값으로 갱신한다.

 

마지막으로 원뿔이 픽셀 P를 확인한다.

픽셀 P에 저장된 깊이 값은 자신의 깊이 값보다 작을 것이다.

그러므로 자신을 그리지않고 넘어간다.

 

이런 로직은 결국 깊이가 가장 얕은 픽셀이 렌더링되도록 한다.

 

정리하면, 깊이 버퍼링은 각 픽셀에 대해 깊이 값을 계산하고 깊이 테스트를 수행하는 방식으로 작동한다. 

 

깊이 테스트란 백 버퍼의 특정 픽셀 위치에 저장된 깊이 값을 자신의 깊이값과 비교하는 것이다.

깊이가 0이 가까운 픽셀이 이기고, 해당 픽셀이 백 버퍼에 렌더링되고 깊이 값을 갱신한다.

 

깊이 버퍼는 텍스처이므로 특정 데이터 형식으로 생성되어야 한다.

 

깊이 버퍼를 담당하는 COM 객체이다.

DepthStencilView 라는 이름으로 되어있는 이유는 스텐실 버퍼와 깊이 버퍼는 같이 다뤄져야 하기 때문이다.

 

 

이런 식으로 초기화할 수 있는데 포맷부분만 살펴보자.

 

스텐실 버퍼는 더 고급 주제이며, 나중에 다루도록 한다.

 

텍스처 리소스 뷰

텍스처는 렌더링 파이프라인의 다양한 단계에 바인딩될 수 있다.

일반적으로는 텍스처를 렌더 타겟과 셰이더 리소스로 사용한다.

 

텍스처를 렌더 타겟로 사용하다는 의미는, Direct3D가 텍스처에 직접 그림을 그린다는 의미이다. 렌더 타겟이란 윈도우 GDI로 치면, HDC에 연결된 HBITMAP과 동일한 역할을 한다. 즉, 이미지나 데이터를 저정하는 일종의 캔버스 역할을 수행한다. 그렇다면 렌더 타겟으로 텍스처를 사용한다는 의미는, 텍스처에 그림을 그린다는 것으로 이해하면 된다.

 

텍스처를 셰이더 리소스로 사용한다는 것은 셰이터에서 텍스처를 읽어와 샘플링할 수 있다는 의미이다. 셰이더는 GPU에서 실행되는 작은 프로그램으로, 셰이더는 텍스처를 이용해 화면에 그림을 그릴 수 있다.

 

이 두 가지 목적으로 생성된 텍스처 리소스에는 위와 같은 바인드 플래그가 지정된다.

D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE

 

이는 텍스처가 바인딜될 두 파이프라인 단계를 나타낸다.

리소스가 직접 파이프라인 단계에서 바인딩되는 것은 아니고, 관련된 리소스 뷰라는 개체가 다양한 파이프라인 단계에 리

소스들을 바인딩한다.

 

그러므로, 텍스처를 특정 목적으로 사용하기 위해서는 관련된 리소스 뷰를 Direct3D 초기화시 생성해야 한다.

 

이는 대부분 효율성을 위한 것이며, SDK 문서에서는 다음과 같이 설명하고 있다.

"이것은 런타임 및 드라이버에서 뷰 생성 시 유효성 검사와 매핑이 발생하도록 하여, 바인딩 시점의 타입 검사를 최소화한다."

뷰 생성 시 유효성 검사를 미리 수행하고, 해당 텍스처가 어떻게 사용될지를 미리 매핑해 둠으로써, 이후 텍스처를 사용할 때 추가적인 확인 과정을 생략할 수 있다. 이러한 최적화를 통해 성능을 향상시킬 수 있으며, 바로 이 때문에 리소스 뷰가 필요한 것이다

 

따라서 텍스처를 렌더 타겟과 셰이더 리소스로 사용하는 경우, 우리는 렌더 타겟뷰 (D3D11RenderTargetView)와 셰이더 리소스 뷰 (ID3D11ShaderResourceView)를 생성해야한다.

 

리소스 뷰는 본질적으로 두 가지 역할을 한다.

1) Direct3D에 리소스가 파이프라인의 어떤 단계에서 바인딜될지 알려준다.

2) typeless 포맷의 리소스가 어떤 단계에서 바인딩될지 알려준다,

 

typeless 라는 포맷은 특별한 리소스 포맷인데, 해당 형식은 R8G8B8A8_UNORM 처럼 데이터 비트가 어떤 형식으로 저장되고 해석될지 지정하지 않는다. 대신 바인딩플래그에 따라서 정수 형식으로 해석될 수도 있고, 다른 단계에서는 부동소수점 형식으로 해석될 수도 있다.

 

SDK 문서에서는 "완전한 타입의 리소스를 생성하면 해당 리소스는 해당 형식에 제한된다. 이는 런타임이 접근을 최적화할 수 있게 한다. 라고 적혀있다.

따라서, 여러 뷰로 데이터를 다양한 방식으로 재해석할 수 있는 Typeless 형식의 유연성은 성능 저하를 일으킬 여지가 있다는 의미이다. 즉, 꼭 필요한 경우가 아니라면 완전한 타입의 리소스를 생성하는 것이 좋다.

 

특정 리소스에 대한 뷰를 생성하려면 해당 리소스가 그에 맞는 바인드 플래그로 생성되어야 한다.

예를 들어, 어떤 텍스처가 D3D11_BIND_DEPTH_STENCIL 바인드 플래그가 설정되지 않았다면, 해당 텍스처로는 ID3D11DepthStencilView를 생성할 수 없다.

 

멀티 샘플링 이론

모니터의 픽셀은 무한히 작지 않기 때문에 임의의 선은 컴퓨터 모니터에 완벽하게 표현될 수 없다.

 

 

그러므로, 위 그림처럼 픽셀을 선으로 근사할 때는 계단같은 선이 생기는.. 에일리아싱 현상이 발생한다.

한국어로는 계단현상이라고 한다.

 

모니터의 해상도를 높어 픽셀의 크기를 더 줄일 수 있다면 에일리아싱 효과를 감소시킬 수 있다.

그러나 모니터의 해상도를 무한히 높일 수는 없으므로 에일리어싱 현상을 없애기 위한 보정 기술을 안티에일리어싱이라고 한다.

 

슈퍼 샘플링이라고 불리는 기술은 백 버퍼와 깊이 버퍼를 화면 해상도보다 4배 크게 만들어, 더 큰 렌더 타겟에 그림이 그려지게 한 다음, 스왑 체인을 수행하면서 백 버퍼의 4개의 픽셀 블록을 평균화하여 하나의 픽셀을 얻어낸다.

이러면 해상도가 낮아지는 다운 샘플링 현상이 발생하긴하지만, 에일리어싱 현상을 크게 줄일 수 있다.

 

 

슈퍼샘플링의 원리를 보여주는 예시

4개의 서브픽셀을 평균화하여 다각형의 가장자리에 연한 색을 얻는 모습

이로 인해 다각형의 가장자리를 따라 계단형 효과가 완화되어 이미지가 더 부드러워진다..

 

Supersampling 은 픽셀 처리량과 메모리 사용량을 4배로 증가시키기 때문에 비용이 많이 든다.

 

Driect3D는 멀티샘플링이라는 타협적인 안티앨리어싱 기술을 지원하는데, 이는 서브픽셀 간에 일부 계산 정보를 공유하여 슈퍼샘플링보다 비용이 덜 든다.

 

멀티 샘플링 역기 화면 해상도보다 4배 큰 백 버퍼와 깊이 버퍼를 사용한다.

그러나 각 서브픽셀에 대해 이미지 색상을 계산하는 대신, 픽셀 

 

(나중에 보충, 원리는 다른 개념을 더 접목해야 할듯)

 

어쨌든 멀티샘플링이 슈퍼샘플링보다 정확성은 떨어지지만 더 나은 성능을 보임

이미지 색상 계산은 그래픽스 파이프라인에서 가장 비용이 많이 드는 단계 중 하나이다.

 

Direct3D에서의 멀티샘플링

 

DXGI_SAMPLE_DESC 구조체를 작성하여 멀티샘플링을 구현할 수 있다.

해당 구조체의 맴버는 Count, Quality

 

Count는 픽셀당 사용할 샘플 수를 지정하고,

Quality는 원하는 품질 수준을 지정하는 데 사용된다.

(품질 수준이 의미하는 바는 하드웨어 마다 다를 수 있다,)

 

샘플 개수가 많거나 품질이 높을수록 렌더링 비용이 많이 드는데

성능과 속도 간의 절충이 필요하다.

 

품질 수준은 텍스처 형식, 픽셀당 가져올 샘플 수에 따라 달라진다.

 

그러므로 사용할 수 있는 품질 수준을 직접 얻어와야한다.

 

해당 함수를 통해 Format 형식과 사용할 샘플 수를 매개변수로 입력하면

pNumQualityLevels에 주어진 조합에 대한 품질 수준의 개수가 저장되어 매개변수를 통해 반화나된다.

 

pNumQualityLevels 에 저장된 값을 i라고 하면

0 ~ i-1 값에 해당하는 품질을 설정할 수 있다.

 

 

픽셀당 가져올 수 있는 최대 샘플 수는 D3D11 기준 32개이다.

그러나 성능과 메모리 비용을 적절하게 유지하기 위해.. 보통은 4 또는 8의 샘플 수가 사용된다.

 

멀티샘플링을 사용하지 않으려면 샘플 수를 1로 설정하고 품질 수준을 0으로 설정하면 된다.

 

모든 Driect3D 11 지원 장치는 모든 렌더 타겟 형식에 대해 4x 멀티샘플링을 지원한다.

DXGI_SAMPLE_DESC 구조체는 스왑 체인 버퍼(즉, 백버퍼)와 깊이 버퍼 모두에 대해 작성되어야 한다.

 

더하여, 백 버퍼와 깊이 버퍼는 동일한 멀티샘플링 설정으로 생성되어야 한다.

 

피처 레벨

Direct3D 11은 피처 레벨이라는 개념이 존재하는데, 이는 D3D_FeATURE_LEVEL 열거형 타입으로 코드에서 표현된다.

 

피처 레벨은 엄격한 기능 집합을 정의하며, 각 피처 레벨이 지원하는 구체적인 기능은 SDK 문서를 참조하면 된다.

이 개념은 사용자의 하드웨어가 특정 피처 레벨을 지원하지 않을 경우, 애플리케이션이 이전 피처 레벨로 대체될 수 있다는 것이다.

 

즉, 더 넓은 사용자층을 지원하기 위해.. 애플리케이션은 DirectX11 로 개발되었다 하더라도

10.1, 10, 9.3 등.. 하위 레벨의 하드웨어를 지원할 수 있다.

 

애플리케이션은 최신부터, 오래된 순서로 피처 레벨 지원을 확인한다.

예를 들어 Direct3D 11이 지원되는지 확인하고, 그다음 10.1, 10, 9.3, ... 순서로 확인한다.

 

(이 부분도 추후에 코드보고 업데이트 해야할 듯)

 

 

Direct3D 초기화

초기화는 다음과 같은 단계로 수행한다.

 

1.  D3D11CreateDevice 함수를 이용해서

ID3D11Device

ID3D11DeviceContext

인터페이스를 생성한다.

2. CheckMultisampleQualityLevels 메서드를 사용하여 4xMSAA 품질 수준을 확인한다.

3. DXGI_SWAP_CHAIN_DESC 구조체의 인스턴스를 생성하여 생성할 스왑 체인의 특성을 기술한다.

4. IDXGISwapChain 인스턴스를 생성한다.

5. 스왑 체인의 백 버퍼에 대한 렌더 타켓 뷰(프론트 버퍼)를 생성한다.

6. 깊이/스텐실 버퍼와 그에 연관된 깊이/스텐실 뷰를 생성한다.

7. 렌더 타겟 뷰와 깊이/스텐실 뷰를 렌더링 파이프라인 출력 병합 단계에 바인딩하여 Direct3D에서 사용할 수 있도록 한다.

8. 뷰포트를 설정한다.

 

1. Device와 Device Context 생성

Direct3D 초기화는 ID3D11Device와 ID3D11DeviceContext를 생성하는 것부터 시작한다.

 

이 두 인터페이스가 Direct3D의 주요 인터페이스로, 실제 그래픽 장치 하드웨어의 소프트웨어 컨트롤러다.

이 인터페이스를 통해 하드웨어와 상호작용하고 GPU 메모리에 리소스를 할당하거나, 백 버퍼를 지우거나, 다양한 파이프라인 단계에 리소스를 바인딩하거나, 기하학적 구조를 그리도록 지시할 수 있다.

 

즉, Direct3D 의 핵심이라는 것

 

ID3D11Device 인터페이스는 기능 지원을 확인하고 리소스를 할당하는 데 사용된다.

ID3D11DeviceContext 인터페이스는 렌더 상태를 설정하고, 리소스를 그래픽 파이프라인에 바인딩하여, 렌더링 명령을 발행하는 데 사용된다.

 

 

관련 함수는 자세하게 적어놨으니 확인하라.

CreateDevice를 통해서 device랑 divicecontext를 동시에 초기화한다.

 

 

이런 방식으로 현재 컴퓨터가 D3D11 피처 레벨을 지원하는지도 확인할 수 있다.

 

우리는 devicecontext를 흔히 즉시 컨텍스트라고 부른다.

D3D11 에는 멀티스레딩을 지원하는 지연 컨텍스트도 존재한다.

이때 사용하는 것은 CreateDeferredContext라는 것이다.

 

멀티스레딩은 이 책에서 다루지 않지만, 기본적으로 다음과 같이 동작한다.

1. 주 렌더링 스레드에서는 즉시 컨텍스트(일반 device context)

2. 지연 스레드는 워커 스레드에서 사용

 

워커 스레드는 커맨드 리스트에 그래픽 명령을 기록하는 등.. 여러 사항들이 있지만 더 이상 자세히 다루지는 않는다.

 

2. 4X MSAA 품질 지원 확인

이제 장치를 생성했으니 4X MSAA 품질 수준 지원을 확인해보자. 

 

모든 Direct3D 11 지원 장치는 모든 렌더 타겟 형식에서 4X MSAA를 지원한다는 것을 기억하라.

그러나 그래픽카드에 따라 지원되는 품질 수준은 다를 수 있다.

 

 

3. 스왑 체인

초기화 과정의 다음 단계는 스왑 체인을 생성하는 것이다.

이는 우리가 생성할 스왑 체인의 특성을 설명하는 DXGI_SWAP_CHAIN_DESC 구조체의 인스턴스를 먼저 작성함으로써 이루어진다.

 

typedef struct DXGI_SWAP_CHAIN_DESC {
  DXGI_MODE_DESC   BufferDesc;
  DXGI_SAMPLE_DESC SampleDesc;
  DXGI_USAGE       BufferUsage;
  UINT             BufferCount;
  HWND             OutputWindow;
  BOOL             Windowed;
  DXGI_SWAP_EFFECT SwapEffect;
  UINT             Flags;
} DXGI_SWAP_CHAIN_DESC;

기본 적으로 는 이러하고

 

내부에 구조체가 더 있다...

필요한거만 알아보도록 하자.

 

 

이 구조체는 우리가 생성할 백 버퍼의 속성을 설명한다. 주로 신경 써야 할 속성은 너비, 높이, 그리고 픽셀 포맷이다.

 

차례대로

2. 멀티 샘플 수와 품질 수준

3. 백 버퍼 사용 목적 DXGI_USAGE_RENDER_TARGET_OUTPUT 설정하여 백 버퍼에 그릴 것을 지정한다.

4. BufferCount 스왑 체인에서 사용할 백 버퍼의 수, 더블 버퍼링 기준 1

5. 렌더링할 창의 Hwnd

6. 창 모드로 실행시 true, 전체 모드일 시 false

7. 디스플레이 드라이버가 가장 효율적인 표시 방법을 선택하도록 DXGI_SWAP_EFFECT_DISCARD

8. 선택적 플래그다. DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH 플래그를 지정하면 애플리케이션이 전체 화면 모드로 전환할 때 백 버퍼 설정에 맞는 디스플레이 모드로 전환할 수 있다.

 

 

설정

 

여기서 멀티 샘플링에 관한 데이터가 들어가는데..

멀티 샘플링 옵션을 변경하려면 스왑 체인을 파괴하고 다시 생성해야 한다.

 

대부분의 모니터는 24비트 컬러 이상을 지원하지 않기 때문에 DXGI_FORMAT_R8G8B8A8_UNORM 을 텍스처 포맷으로 많이 사용한다.

 

나머지 8비트는 알파 채널로 사용할 수 있다.

 

이제 스왑 체인을 생성해보자.

 

해당 함수를 이용하면 스왑체인을 만들 수 있다.

 

첫 번째는 디바이스 포인터를 주면 된다.

두 번째는 스왑체인 desc 포인터를 주면 된다. 

ppSwapChain은 반환값이다.

 

해당 CreateSwapChain 함수는 IDXGIFactory의 멤버함수이기 때문에

IDXGIFactory 변수를 얻어내야 하는데, 생각보다 그 과정이 까다롭다.

코드 개괄을 보면 다음과 같다.

 

 

ms에서는 Direct3D 11.1 부터 해당 함수를 사용하여 스왑 체인을 만드는 것을 지양하라고 한다.

대신 CreateSwawpChainForHwnd 사용을 권장하며

윈도우 스토어 앱에서 사용하는 응용의 경우 CreateSwapChainForCoreWindow 를 사용하는 것을 권장한다.

 

 

 

이런 식으로 만들어낸다.

느낌은 비슷한듯..

 

자세한 것은 msdn 문제를 꾸준히 보면서 익히도록 하자.

 

렌더 타겟 뷰 생성

앞서 언급했듯이, 우리는 리소스를 파이프라인 단계에 직접 바인딩하지 않고, 리소스에 대한 뷰를 생성한 후 해당 뷰를 파이프라인 단계에 바인딩해야 한다.

 

특히, 백 버퍼를 파이프라인의 output merger 단계에 바인딩하기 위해서는 백 버퍼에 대한 렌더 타겟 뷰를 생성해야 한다.

 

코드는 다음과 같다.

 

1. GetBuffer를 이용하여 스왑 체인의 백 버퍼에 대한 포인터를 얻는다.

첫 번째 매개변수는 여러 개의 백 버퍼가 있는 경우, 얻고자하는 특정 백 버퍼를 식별하는 인덱스인데, 우리는 하나의 백 버퍼만 사용하므로 그 인덱스는 0이다.

 

두 번째 매개변수는 버퍼의 인터페이스 타입이며, 보통 2D 텍스처로 지정된다.

 

세 번째 매개변수는 백 버퍼에 대한 포인터를 반환한다.

 

2. 렌더타겟부를 생성하기 위해서는 device->CreateRenderTargetView() 메서드를 사용한다.

첫 번째 매개변수는 렌더 타겟으로 사용할 리소스를 지정하는데, 이는 백 버퍼에 해당된다.

 

두 번째 매개변수 D3D11_RENDER_TARGET_VIEW_DESC 구조체에 대한 포인터다.

리소스 타입이 typeless가 아니라면 null로 지정할 수 있따.

이는 해당 리소스의 첫 번째 밉맵 레벨에 대한 뷰를 생성하고, 리소스가 생성된 형식을 그대로 사용한다는 의미다.

 

세번째 매개변수는 반환값이다.

 

3. 초기화 과정에서 리소스를 해제해주는 과정이 있어야하나.. 스마트 포인터를 사용하고 있으므로 딱히 신경쓰지 않아도 된다.

 

깊이 스텐실 버퍼 및 뷰 생성

깊이 버퍼는 깊이 정보를 저장하는 2D 텍스처이고, 스텐실과 깊이는 묶여서 관리되기 때문에 깊이-스탠실 버퍼에 스텐실 정보도 같이 저장된다.

 

먼저 텍스처를 생성해야 하기에, CreateTexture2D 함수를 호출해야 한다.

 

 

매개변수 설정 과정인데 신경쓸건 딱히 없다.

SampleDesc가 렌더 타겟 설정과 같아야 하는 것 정도는 중요할 것 같다.

 

깊이/스텐실 버퍼를 사용하기 전에, 파이프라인에 바인딩할 깊이/스텐실 뷰를 생성해야 한다.

다음 예제는 깊이/스텐실 텍스처에 상응하는 깊이/스텐실 뷰를 생성하는 방법을 보여준다.

 

 

중간 중간 옵션들은 문서를 찾아보도록 하자.

 

뷰를 출력 병합 단계에 바인딩하기

백 버퍼와 깊이 버퍼에 대한 뷰를 생성했으므로, 이제 이 뷰들을 파이프라인의 OM 단계에 바인딩하여 리소스를 렌더 타겟 및 깊이/스텐실 버퍼로 사용할 수 있다.

 

첫 번째 매개변수는 바인딩할 렌더 타겟의 수를 지정한다. 여기서는 하나만 바인딩하지만, 여러 개를 동시에 바인딩하여 여러 렌더 타겟에 동시에 렌더링할 수 있다.

 

두 번 째 매개변수는 렌더 타겟 뷰 포인터 배열의 첫번째 요소에 대한 포인터다.

 

세 번째 매개변수는 파이프라인에 바인딩할 깊이/스텐실 뷰에 대한 포인터다.

 

렌더 타겟 뷰는 배열로 여러개 설정할 수 있지만,

깊이/스탠실 뷰는 하나만 설정할 수 있다.

 

뷰포트 설정하기

일반적으로 3D 장면을 백 버퍼 전체에 그리지만, 때로는 백 버퍼의 하위 사각형 영역에만 그릴 때가 있다.

 

그리는 백 버퍼의 하위 사각형 영역을 뷰포트라고 하며, 이는 다음과 같은 구조체로 설명된다.

 

처음 4개의 멤버는 각각

뷰포트 왼쪽 X위치, 뷰포트 위쪽 Y위치, 뷰포트의 너비, 뷰포트의 높이를 의미한다.

 

MinDepth, MaxDepth는 각각 깊이 버퍼의 최소 최댓값을 의미하는데, 특별한 이유가 있지 않다면 0, 1로 설정한다.

 

RSSetViewports 메소드를 사용하여 Direct3D 에서 뷰포트를 설정한다.

 

뷰포트를 사용하여 2인용 게임 모드를 위한 분할 화면을 구현할 수 있다.

또는, 뷰포트를 사용하여 화면의 하위 사각형 영역에만 렌더링하고, 나머지 영역에는 버튼, 슬라이더, 리스트 상자와 같은 UI 컨트롤을 채울 수 있다.

 

 

DirectX 11 초기화 코드

https://github.com/powercrabman/MyDX3DFramework/tree/DirectX11_Initalize

 

GitHub - powercrabman/MyDX3DFramework

Contribute to powercrabman/MyDX3DFramework development by creating an account on GitHub.

github.com

 

이 뒤 내용은 소프트웨어 설계 부분이라.. 넘어갑니다.

자세한 코드는 위 깃허브 확인해주세요.

 

책은 워낙 오래된 코드라.. 동작이 잘 안되기에

제가 처음부터 그냥 다 다시 썼습니다.

 

책 완독까지 계속 이어나갈테니 지켜봐주세용..