12 분 소요

들어가기 앞서

내용 자체는 그렇게 어렵지 않았으나
코드 + 설명을 하다보니 포스팅 크기가 너무 길어지는 듯 하여
분할하여 올릴 예정이다

AppBase::InitDirect3D()

Direct3D와 관련된 요소들을 생성한다

각 요소들에 대한 간략한 개념(구조체/클래스)

Device (ID3D11Device)

  • GPU 리소스(버퍼, 텍스쳐, 쉐이더 등)을 생성하는 ‘팩토리’ 역할
  • GPU와 직접 연결된 논리적인 장치(디바이스)
  • 리소스를 만드는 위주의 역할 (draw 등은 context에서)
    (CreateBuffer, CreateTexture2D 등의 리소스 생성 함수)

Context (ID3D11DeviceContext)

  • 파이프라인 상태를 설정하며, 실제로 draw/ dispatch 명령을 내린다
    (IASetVertexBuffers,VSSetShader,RSSetState,DrawIndexed 등)
  • Device가 만든 리소스를 파이프라인에 바인딩하여 GPU에서 실행시킴
  • 일반적으로는 Immediate Context(실시간 실행)을 사용
    멀티 스레드 환경에선 Deferred Context(명령 리스트에 기록 후 실행 타이밍에 실행)도 고려 가능

SwapChain (IDXGISwapChain)

  • 프레임 버퍼(Back Buffer) 와 화면 출력(Window Surface) 사이를 연결하는 인터페이스
    (버퍼 관리 + 화면 출력 + Window 와의 호환)
  • 더블 / 트리플 버퍼링 지원
  • 렌더링 완료된 이미지를 Present() 호출로 실제 윈도우에 출력

    • OM 단계 완료까지가 GPU 파이프 라인의 마무리이며
      이것이 RenderTargetView에 저장 (Back Buffer)
    • 그러나 화면에는 아직 보이지 않는다
      (Back Buffer 데이터는 GPU에 존재)
    • SwapChain->Present() 호출을 통하여 OS/드라이버가
      BackBuffer와 FrontBuffer를 ‘교체’한 후 출력

RenderTargetView(ID3D11RenderTargetView)

  • GPU가 렌더링 결과를 기록할 수 있도록 만드는 ‘뷰’(View)
  • SwapChain의 BackBuffer나 Texture를 렌더링 대상으로 사용할 때 필요
  • RTV를 통해 OMSetRenderTargets로 출력 타겟 지정
    (BackBuffer -> RTV -> OMSetRenderTargets()로
    파이프라인의 Output Merge 단계에 설정)

ViewPort (D3D11_VIEWPORT)

  • 렌더링 결과를 어느 화면 영역(ViewPort)에 그릴지 결정
  • NDC(-1 ~ 1 좌표)를 실제 픽셀 좌표로 변환
  • 윈도우 크기와 동일하게 설정하는 편이지만
    일부만 사용도 가능(Split-screen 등)

RasterizerState (ID3D11RasterizerState)

  • RS(래스터화) 단계의 동작 정의
  • 삼각형이 픽셀로 변환되는 과정에서 어떻게 처리할지 결정
  • Culling Mode(앞/뒷 면 제거), Fill Mode, 깊이 관련 등
  • Device -> CreateRasterizerState() , Context -> RSSetState()를
    통하여 생성 및 설정
    (세부 설정은 D3D11_RASTERIZER_DESC 를 통해 설정한다)

요소들의 간략한 관계도

(ID3D11Device) ← 리소스 생성
        │
        ▼
(ID3D11DeviceContext) ← 리소스 바인딩 + Draw 호출
        │
        ├─ (ID3D11RasterizerState, Viewport) 등 파이프라인 상태 설정
        ├─ (ID3D11RenderTargetView) 에 렌더링 출력
        ▼
(IDXGISwapChain) ← BackBuffer 관리 → Present() → 화면 출력

AppBase::InitDirect3D() 1. Device, Context 생성 부분

bool AppBase::InitDirect3D() {
    // 이 예제는 Intel 내장 그래픽스 칩으로 실행을 확인하였습니다.
    // (LG 그램, 17Z90n, Intel Iris Plus Graphics)
    // 만약 그래픽스 카드 호환성 문제로 D3D11CreateDevice()가 실패하는 경우에는
    // D3D_DRIVER_TYPE_HARDWARE 대신 D3D_DRIVER_TYPE_WARP 사용해보세요
    // const D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_WARP;
    const D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_HARDWARE;
    
    // 여기서 생성하는 것들
    // m_device, - 장치
    // m_context, - 문맥
    // m_swapChain, - 스왑체인
    // m_renderTargetView, - 렌더 대상
    // m_screenViewport, - 스크린의 어떤 위치에 그릴 지
    // m_rasterizerSate - 래스터화 관련 옵션? 상태?

    // m_device, m_context 를 먼저 생성

    UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    // 지역변수로 먼저 생성 후, 문제 없으면 멤버 변수로
    ComPtr<ID3D11Device> device;
    ComPtr<ID3D11DeviceContext> context;

    const D3D_FEATURE_LEVEL featureLevels[2] = {
        D3D_FEATURE_LEVEL_11_0, // 더 높은 버전이 먼저 오도록 설정
        D3D_FEATURE_LEVEL_9_3};
    D3D_FEATURE_LEVEL featureLevel;

    if (FAILED(D3D11CreateDevice(
            nullptr,                  // Specify nullptr to use the default adapter. (DXGI : 여러 버전의 D3D가 공통적으로 사용할 수 있게 디스플레이에 대한 저수준 제어를 묶어 놓은 것 + (D2D + D3D)에서도 사용)
            driverType,               // Create a device using the hardware graphics driver. (드라이버 타입 - 하드웨어)
            0,                        // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE. (소프트라면 이쪽에서 NUll이 아닌 값을 지정)
            createDeviceFlags,        // Set debug and Direct2D compatibility flags. (옵션 지정 - 디버깅 등)
            featureLevels,            // List of feature levels this app can support. (버전)
            ARRAYSIZE(featureLevels), // Size of the list above. (배열 사이즈)
            D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Microsoft Store apps.
            &device,           // Returns the Direct3D device created. (결과물 : Device를 리턴)
            &featureLevel,     // Returns feature level of device created. 
            &context           // Returns the device immediate context. (결과물 : Context 리턴)
            ))) {
        cout << "D3D11CreateDevice() failed." << endl;
        return false;
    }

    /* 참고: 오류가 있을 경우 예외 발생 방법

    // MS 예제
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            // Set a breakpoint on this line to catch Win32 API errors.
            throw Platform::Exception::CreateException(hr);
        }
    }

    // Luna DX12 교재
    #ifndef ThrowIfFailed
    #define ThrowIfFailed(x)                                              \
    {                                                                     \
        HRESULT hr__ = (x);                                               \
        std::wstring wfn = AnsiToWString(__FILE__);                       \
        if(FAILED(hr__)) { throw DxException(hr__, L#x, wfn, __LINE__); } \
    }
    #endif
    */

    if (featureLevel != D3D_FEATURE_LEVEL_11_0) {
        cout << "D3D Feature Level 11 unsupported." << endl;
        return false;
    }

  ...

}

먼저 Device를 생성하는 부분을 보자

  • ComPtr 를 통해 생성하여
    실패하는 경우를 감안하여 바로 변수에 생성하지 않고
    안전한 스마트 포인터를 통해 설정

  • 사용자의 DX 버전을 고려한 featureLevels
    실패하는 경우, 더 낮은 버전으로 시도하는 처리를
    할 수 있음
    (테스트용이기에 별도의 실패처리는 하지 않음)

  • D3D11CreateDevice
    HResult를 반환하기에 FAILED 매크로를 통해 성공여부 검사하고
    설정된 결과값의 device,featureLevel,context를 반환(Out)
    (이런식
    공식 문서가 존재하며 파라미터 등을 설명해주는 페이지가 존재한다)
  • DXGI
    여러 버전의 D3D가 공통적으로 사용할 수 있게
    디스플레이에 대한 저수준 제어를 묶어 놓은 것
    • (D2D + D3D)에서도 사용

AppBase::InitDirect3D() 2. SwapChain 생성 부분

bool AppBase::InitDirect3D() {
    ... Device, Context 생성 부분

    // 4X MSAA 지원하는지 확인
    UINT numQualityLevels;
    device->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &numQualityLevels);
    if (numQualityLevels <= 0) {
        cout << "MSAA not supported." << endl;
    }

    // numQualityLevels = 0; // MSAA를 강제로 끄기

    // 옵션 서술용 구조체 (_DESC) - 이걸로 CREATE 해줌
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd)); // 옵션이 많아서 0으로 초기화
    sd.BufferDesc.Width = m_screenWidth;               // set the back buffer width
    sd.BufferDesc.Height = m_screenHeight;             // set the back buffer height
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // use 32-bit color (색 표현 포맷 32비트(RGBA 각 8비트 사용) - UNORM : Unsigned Normalized Integer 0~1 로 만들어준다(255로 나눔))
    sd.BufferCount = 2;                                // Double-buffering
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1; // 위와 합쳐서 초당 몇프레임 결정
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;  // how swap chain is to be used
    sd.OutputWindow = m_mainWindow;                    // the window to be used (출력할 윈도우)
    sd.Windowed = TRUE;                                // windowed/full-screen mode (true : 전체화면 아님)
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // allow full-screen switching (전체화면과 왔다갔다 허용)
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    if (numQualityLevels > 0) {
        sd.SampleDesc.Count = 4; // how many multisamples
        sd.SampleDesc.Quality = numQualityLevels - 1;
    } else {
        sd.SampleDesc.Count = 1; // how many multisamples
        sd.SampleDesc.Quality = 0;
    }

    if (FAILED(device.As(&m_device))) {
        cout << "device.AS() failed." << endl;
        return false;
    }

    if (FAILED(context.As(&m_context))) {
        cout << "context.As() failed." << endl;
        return false;
    }

    // 참고: IDXGIFactory를 이용한 CreateSwapChain()
    /*
    ComPtr<IDXGIDevice3> dxgiDevice;
    m_device.As(&dxgiDevice);

    ComPtr<IDXGIAdapter> dxgiAdapter;
    dxgiDevice->GetAdapter(&dxgiAdapter);

    ComPtr<IDXGIFactory> dxgiFactory;
    dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));

    ComPtr<IDXGISwapChain> swapChain;
    dxgiFactory->CreateSwapChain(m_device.Get(), &sd, &swapChain);

    swapChain.As(&m_swapChain);
    */

    // 참고: IDXGIFactory4를 이용한 CreateSwapChainForHwnd()
    /*
    ComPtr<IDXGIFactory4> dxgiFactory;
    dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));

    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
    swapChainDesc.Width = lround(m_screenWidth); // Match the size of the window.
    swapChainDesc.Height = lround(m_screenHeight);
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
    swapChainDesc.Stereo = false;
    swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
    swapChainDesc.SwapEffect =
        DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Microsoft Store apps must use this SwapEffect.
    swapChainDesc.Flags = 0;
    swapChainDesc.Scaling = DXGI_SCALING_NONE;
    swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

    ComPtr<IDXGISwapChain1> swapChain;
    dxgiFactory->CreateSwapChainForHwnd(m_device.Get(), m_mainWindow, &swapChainDesc, nullptr,
    nullptr, swapChain.GetAddressOf());
    */

    if (FAILED(D3D11CreateDeviceAndSwapChain(0, // Default adapter
                                            driverType,
                                            0, // No software device
                                            createDeviceFlags, featureLevels, 1, D3D11_SDK_VERSION,
                                            &sd, &m_swapChain, &m_device, &featureLevel,
                                            &m_context))) {
        cout << "D3D11CreateDeviceAndSwapChain() failed." << endl;
        return false;
    }
}

MSAA 지원 여부의 확인, 이후 SwapChain 생성 부분

  • Anti-Aliasing
    도형을 그릴때 나타나는 ‘계단 현상’을
    제거하기 위한 기법
    Blur를 이용하여 구현하는 방식이나 (FXAA,SMAA,TAA 등이 존재)
    Sampling 을 이용하여 구현 (SSAA, MSAA 등)
    • SSAA(Super-Sample Anti-Aliasing)?
      더 큰 해상도로 렌더링을 한 후
      다시 축소하는 방식
      (고품질이지만 성능 비쌈)
  • MSAA(MultiSample Anti-Aliasing)
    안티 앨리어싱 기법 중 하나로서
    픽셀 내부를 ‘여러 샘플 지점’으로 나눠서 검사
    각각의 샘플마다 피사체의 ‘내부/외부’를 검사하고
    그 결과를 평균 내서 픽셀 색상 결정
    (경계를 부드럽게 하나, 내부의 ‘계단현상’은 못잡는 경우 존재)
  • _DESC 구조체
    ‘옵션 서술용 구조체’이며
    Direct X에서 무언가를 생성할 때
    데이터나 옵션을 세팅할 때 사용하는 일종의 규약같은 구조체
    (이 규약에 맞게 만들어 주세요 라는 의미의 구조체다)
  • ZeroMemory : memcpy()를 이용한 0으로 싹미는 매크로
    (보통 _DESC는 옵션이 매우 많기에 제로 메모리로 싹 밀고 시작한다)

  • DXGI_SWAP_CHAIN_DESC
    사용할 버퍼의 해상도 지정,
    색 표현에 따른 포맷팅,
    버퍼 갯수와 초당 프레임 갯수,
    버퍼 사용처(DXGI의 플래그의 일종, 상황에 맞게 설정하자),
    출력할 윈도우(이전에 CreateWindow()로 m_mainWindow의 핸들을 받아놓았음),
    전체화면 / 창 모드 설정 등이 존재
    (더 자세한 내용은 공식문서 등을 찾아보자)
  • 포맷팅 방식(DXGI_FORMAT_R8G8B8A8_UNORM)
    사용하는 4Byte를 ‘어떻게 나누어 사용할지’를 미리 알린다
    RGBA를 각각 1Byte로 사용(_R8G8B8A8)
    또한 그 값들을 0.0 ~ 1.0 부동소수 값으로 표현 (UNORM - Unsigned Normalized)
    (필요에 따라 더 큰 Byte 사용 가능)
  • D3D11CreateDeviceAndSwapChain 을 통해 SwapChain 생성
    참고로 이 함수는 Device와 Context도 다시 만듦
    (CreateSwapChain()등 같이 다시 안 만드는 함수도 존재한다)
    (두 주석들 참고)
    (-> 초기화 부분이라서 딜레이를 어느정도는 용납하는 듯 보인다)

SwapChain 추가 내용

DXGI에 구현되어 있음

  • Present() 호출 시의 예시

Image

Front Buffer와 BackBuffer를 교체(Swap)하는 지시를 내린다

  • Tripple Buffer 사용 시의 Swap Chain
    Image

이러한 SwapChain을 사용함으로서
버퍼를 1개 사용할 때 나타나는
‘깜빡임’ 현상을 없앨 수 있음
(부드럽게 화면이 이어지도록)

ScreenBuffer(FrontBuffer)가 출력되는 동안
그래픽스 API가 BackBuffer에 그림을 그린다

ScreenBuffer가 Output(실제 모니터)에 데이터를 전송하는 동안
(+ 모니터가 화면에 출력하는 시간)

미리 BackBuffers에 그림을 그려놓고
순식간에 Screen Buffer와 ‘바꿔치기’ 함으로서
‘그려지는 빈 순간’을 없애기 위함


  • 버퍼를 2개 사용하는 경우도 비슷하다
    Image

Back Buffer와 Front Buffer를 서로 바꿔주며 반복
(Page Flipping 이라는 용어로도 사용됨)

  • 메모리를 복사하는게 아니라
    버퍼를 가리키는 ‘포인터’를 교체함으로서
    빠른 swap이 가능하다

AppBase::InitDirect3D() 3. RenderTargetView 생성 부분

bool AppBase::InitDirect3D() {
  ... SwapChain 생성 부분

  // CreateRenderTarget
  ID3D11Texture2D *pBackBuffer; // 컬러값을 2차원 배열로 저장된 메모리 공간 (이미지와 버퍼 등 다양한 곳에서 사용)
  m_swapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
  if (pBackBuffer) {
      m_device->CreateRenderTargetView(pBackBuffer, NULL, &m_renderTargetView);
      pBackBuffer->Release();
  } else {
      cout << "CreateRenderTargetView() failed." << endl;
      return false;
  }

}

위에서 만든 SwapChain의 버퍼를 가져다
RenderTargetView를 만듦

  • RTV는 버퍼의 메모리를 가져다 쓰는 방식은 아님
    메모리의 주체는 Buffer 이며
    RTV는 버퍼의 메모리를 소유하지 않고,
    그 버퍼를 ‘렌더링용 타겟’으로 해석하는 View 객체이다
    (포인터용 객체라 생각해도 무방하다)
    (아니면 이 버퍼를 ‘RTV’로 보겠다는 캐스팅 방식이나)

  • RTV 사실상 화살표 처럼 ‘어느 버퍼’에 그릴지를 가리킨다
    그렇기에 OM 스테이지 이후
    RTV를 바인딩하고, 그 출력이 RTV가 가리킨
    버퍼에 기록된다
    (이후 present 호출 시, 이 버퍼와 front buffer로
    교체 -> 버퍼 포인터 교환(flip))

  • 백버퍼가 아니여도 RTV로 지정한 곳(ID3D11Texture2D 등)에도 지정할 수 있음
    (포스트 프로세싱, 그림자 등 다양한 곳에 응용 가능)

  • 요점은 ‘OM 출력(그래픽 파이프라인 결과)를 기록할 수 있는 창구’라 인식하자

  • ID3D11Texture2D
    Color 값 등이 2차원 배열로 저장된 텍스쳐 처럼 보이나
    실제로는 ‘2차원 배열 형태의
    GPU 메모리 리소스를 추상화한 범용 컨테이너’
    라는 인식으로 사용된다
    (Color, Depth, Stencil 버퍼 뿐 아니라
    렌더 타겟, 쉐이더의 결과 등 매우 다양한 곳에 사용)

AppBase::InitDirect3D() 4. Viewport 설정 부분

bool AppBase::InitDirect3D() {
  ... RenderTargetView 생성 부분

  // Set the viewport
  ZeroMemory(&m_screenViewport, sizeof(D3D11_VIEWPORT));
  m_screenViewport.TopLeftX = 0;
  m_screenViewport.TopLeftY = 0;
  m_screenViewport.Width = float(m_screenWidth); // /2 등을 통해 왼쪽에서만 렌더링도 가능
  m_screenViewport.Height = float(m_screenHeight);
  // m_screenViewport.Width = static_cast<float>(m_screenHeight);
  m_screenViewport.MinDepth = 0.0f;
  m_screenViewport.MaxDepth = 1.0f; // Note: important for depth buffering
  
  // 뷰포트 적용
  m_context->RSSetViewports(1, &m_screenViewport);

}

뷰포트 설정 초기화

  • viewport?
    래스터라이저(RS) 단계에서 쓰이는
    ‘출력되는 좌표계’를 ‘어떻게 자를지/변환할지’가 표시되는 영역
    (D3D11_VIEWPORT)
    • 직접적인 영역이 아닌, RS 단계에서
      최종적으로 ‘픽셀 위치’를 구하기 위한 일종의 규칙

    • 화면이라는 ‘도화지’에서
      ‘이 안’에서만 쓰세요
      같은 느낌이라 생각해보자
      (그렇기에 Width나 Height를 절반으로 줄이면
      그에 맞게 ‘절반’에만 출력하게 됨)

  • Depth Buffer를 사용하기 위해서는 Depth 설정에 유의할 것

  • Context의 RSSetViewports 를 통해
    Viewport를 설정한다
    (RS 단계(NDC -> 화면 좌표계)에서 뷰포트의
    범위를 이용하기에 출력이 바뀌게 된다)

AppBase::InitDirect3D() 5. RasterizerState 생성 부분

bool AppBase::InitDirect3D() {
  ... Viewport 설정 부분

  // 설정을 위해 _DESC 구조체
  // Create a rasterizer state
  D3D11_RASTERIZER_DESC rastDesc;
  ZeroMemory(&rastDesc, sizeof(D3D11_RASTERIZER_DESC)); // Need this
  rastDesc.FillMode = D3D11_FILL_MODE::D3D11_FILL_SOLID;
  // rastDesc.FillMode = D3D11_FILL_MODE::D3D11_FILL_WIREFRAME;
  rastDesc.CullMode = D3D11_CULL_MODE::D3D11_CULL_NONE;
  rastDesc.FrontCounterClockwise = false;

  m_device->CreateRasterizerState(&rastDesc, &m_rasterizerSate);

}

관련 _Desc 구조체를 생성하고
그 내용을 세팅한 후
device에 ‘생성’을 요구한다
(리소스 생성이므로 Device에 요구)

  • D3D11_FILL_WIREFRAME
    와이어 프레임만 그리는 경우
    (디버깅, 정점 확인 등에 사용)
    (언리얼에서 와이어프레임 표시하는 것처럼 나온다)
  • CullMode
    None이면 모든 삼각형을 그림
    (front / back은 아래 옵션과 같이 이용)
    front : 정면 x
    back : 후면 x
  • FrontCounterClockwise
    정면이 시계/반시계인지 세팅하는 옵션
    (false 가 ‘시계’)
    (렌더링하는 정점의 방향을 결정한다)

이렇게 만든 RasterizerState 는 렌더링에서 사용한다

AppBase::InitDirect3D() 6. Depth Buffer 생성 부분

bool AppBase::InitDirect3D() {
  ... RasterizerState 생성 부분

  // Create depth buffer
  // Depth 저장을 위해 Texture 2D 사용
  D3D11_TEXTURE2D_DESC depthStencilBufferDesc;
  depthStencilBufferDesc.Width = m_screenWidth; // 해상도 맞춰준다
  depthStencilBufferDesc.Height = m_screenHeight;
  depthStencilBufferDesc.MipLevels = 1;
  depthStencilBufferDesc.ArraySize = 1;
  depthStencilBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; // 24비트 사용, UNORM + Stencil 8비트 에 UINT로

  // MSAA의 경우 옵션을 맞춰준다
  if (numQualityLevels > 0) {
      depthStencilBufferDesc.SampleDesc.Count = 4; // how many multisamples
      depthStencilBufferDesc.SampleDesc.Quality = numQualityLevels - 1;
  } else {
      depthStencilBufferDesc.SampleDesc.Count = 1; // how many multisamples
      depthStencilBufferDesc.SampleDesc.Quality = 0;
  }
  // 메모리 사용처 - CPU,GPU가 접근이 가능한지를 설정
  // Default 면 GPU가 읽고 쓸 수 있음
  // IMMUTABLE 이면 GPU가 읽기만 (CPU는 못읽음)
  // DYNAMIC 이면 GPU는 읽기만 CPU는 쓸수만 있음 - 물리 엔진을 CPU에서 계산 등
  // STAGING 이면 GPU -> CPU 일때 사용 (Compute 쉐이딩 등에서 사용)
  depthStencilBufferDesc.Usage = D3D11_USAGE_DEFAULT; 
  depthStencilBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; // Depth + Stencil 버퍼로 사용
  depthStencilBufferDesc.CPUAccessFlags = 0;
  depthStencilBufferDesc.MiscFlags = 0;

  if (FAILED(m_device->CreateTexture2D(&depthStencilBufferDesc, 0,
                                      m_depthStencilBuffer.GetAddressOf()))) {
      cout << "CreateTexture2D() failed." << endl;
  }

  if (FAILED(
          m_device->CreateDepthStencilView(m_depthStencilBuffer.Get(), 0, &m_depthStencilView))) {
      cout << "CreateDepthStencilView() failed." << endl;
  }

}

DepthBuffer : Depth 값을 ‘저장’하는 버퍼
그렇기에 Texture2D를 사용하여 구현
(RS 진행 후, Z 값이 ‘평면’에서 같아지기에
깊이 값을 따로 저장하여 나중에 그릴 순서를 잡아준다)
(그렇기에 z 값 검사할때 보통 투명 객체는 별도처리 하는 편)
(뒤에서 앞으로 그린다던가)

  • DXGI_FORMAT_D24_UNORM_S8_UINT
    텍스쳐 포맷으로 24비트 사용
    형식은 UNORM
    Stencil 에 8비트, 그 형식을 UINT로
    (32비트를 결국 다 사용)
  • Usage
    텍스쳐 2D의 사용처
    (사실상 2차원 메모리 공간을 ‘어떻게 사용할지’에 대한 설정)
    • D3D11_USAGE_DEFAULT : GPU가 읽고 씀
    • D3D11_USAGE_IMMUTABLE : GPU가 읽기만 (CPU는 못읽음) -> 만들 때 초기화할 것
    • D3D11_USAGE_DYNAMIC : GPU는 읽기만 CPU는 쓸수만(물리 엔진 CPU 계산 등)
    • D3D11_USAGE_STAGING : GPU -> CPU 복사에 사용(Compute Shading 등)
  • BindFlag : D3D11_BIND_DEPTH_STENCIL
    (Depth + Stencil 버퍼로도 사용)

  • Device를 통해 GPU에 메모리를 잡아달라고 한다
    (Texture2D는 GPU의 메모리 뭉치다)

  • Depth Buffer는 Texture2D로 선언
    RTV처럼 그냥 Texture2D로 공간 잡아놓고
    다르게 사용하는 것
    (결국 얘도 버퍼하나 잡아놓고 나중에 그 안에 있는걸
    Depth 값으로 인식해서 쓴단 말)

  • CreateDepthStencilView(m_depthStencilBuffer.Get(), 0, &m_depthStencilView)
    m_depthStencilBuffer를
    ‘깊이/스텐실 버퍼’로 해석해서 사용한다고 Device에 알려주는 것
    • Depth Stencil View?
      OM 스테이지에 바인딩할 수 있는 핸들
      (그냥 OM에서 깊이값 사용할때, 제공하기 위한 view이다)
  • Stencil??
    일종의 ‘마스크’용 버퍼
    GPU 파이프라인에서 ‘그릴 픽셀’과 ‘필요 없는 픽셀’을 구분하기 위한
    커스텀 규칙을 제공하는 버퍼
    (보통 StencilFunc 옵션을 통해 비교하며
    이를 사용자가 비교 함수를 통해 설정한다)
    • 원하는 영역을 렌더링하면서 ‘색’은 안남기고 Stencil 버퍼 값만 세팅(=1)
    • 조건 부 렌더링(Stencil Test 이용)을 통하여 Stencil == 1 인 녀석들만 렌더링 한다
    • 이후, OM 단계에서 통합할땐, Stencil 값을 세팅한 녀석들만 그려짐
    • 거울, 미니맵, UI 마스크, 아웃라인 등 다양한 연출에 사용 가능

AppBase::InitDirect3D() 7. Depth Stencil state 생성 부분

bool AppBase::InitDirect3D() {
  ... Depth Buffer 생성 부분

  // Depth Stencil View에 대한 상태 설정 (_DESC)
  // Create depth stencil state

  D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
  ZeroMemory(&depthStencilDesc, sizeof(D3D11_DEPTH_STENCIL_DESC));
  depthStencilDesc.DepthEnable = true; // false
  depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK::D3D11_DEPTH_WRITE_MASK_ALL; // 껏다 키거나 할때 고려
  depthStencilDesc.DepthFunc = D3D11_COMPARISON_FUNC::D3D11_COMPARISON_LESS_EQUAL; // Depth 비교 방식 (지금은 작거나 같을때 그려주기 - 가까운걸 그려주는 옵션이다)
  if (FAILED(m_device->CreateDepthStencilState(&depthStencilDesc,
                                              m_depthStencilState.GetAddressOf()))) {
      cout << "CreateDepthStencilState() failed." << endl;
  }

  return true;
}

위의 DSV(Depth Stencil View)를 어떻게 사용할지
_DESC 구조체와 함께 설정

  • DepthEnable : 깊이값 사용 여부

  • DepthWriteMask : Depth 사용여부를 껐다 킨다던가 할때 고려하는 옵션

  • DepthFunc : Depth 비교 방식

  • Depth Stencil state?
    깊이 / 스텐실을 어떻게 사용할지에 대한 설정 상태
    Device->CreateDepthStencilState을 통해 생성하고
    나중에 OM 스테이지에서 해당 설정을 사용

댓글남기기