• 이전 글: ❎ Direct3D 11 Graphics Pipeline - 5. Pixel Shader 스테이지

  • Output-Merger(OM) 스테이지는 PS의 계산 결과를 Depth / Stencil 테스트와 Blending 규칙에 따라 최종 Render Target에 기록할지 결정하는 단계

    • 픽셀 정보를 계산하는 단계는 끝났고, 픽셀 정보를 쓴다 / 안 쓴다 / 이렇게 섞어 쓴다를 결정
  • OM 또한 Rasterizer같은 고정된 하드웨어(GPU) 기능으로, 프로그래머블하지 않고 State 객체를 통해 조정만 가능함

  • PS가 여러 Render Target에 동시 기록이 가능한 만큼, OM 스테이지도 여러 Render Target에 기록이 가능한 MRT를 지원함(고급 기능)

  • PS가 NULL인 경우 OM의 SV_Target 출력은 없지만, Depth / Stencil은 설정에 따라 여전히 기록 가능

1. 입력

  1. PS 출력: SV_Target0, SV_Target1, …같은 형태의 RGBA 색 값(+ 선택적으로 depth 값)

  2. Depth / Stencil Buffer: 기존 프레임에서 저장된 값으로, 깊이 테스트의 기준

  3. OM State 객체들: Depth-Stencil State, Blend State, Render Target View / Depth Stencil View 등

2. Depth Test

  • 특정 fragment가 이미 그려진 물체보다 뒤에 있으면 그릴 이유가 없기 때문에 이를 판단
  1. (Rasterizer에서 보간된 결과인) fragment의 depth 값을 가져와서,

  2. Depth Buffer의 기존 값과 비교

  3. 비교 함수에 따라 통과 / 실패 결정

  • 설정 예시

    D3D11_DEPTH_STENCIL_DESC desc = {};
    desc.DepthEnable = TRUE;
    desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
    desc.DepthFunc = D3D11_COMPARISON_LESS;
    

Early-Z

  • PS를 실행하기 전에 먼저 Depth Test를 수행하여 실패한 fragment는 PS가 실행되지 않도록 하는 방법으로 OM 전에 Depth Test를 수행하는 방법도 있음

  • Early-Z와 비교하여 OM 스테이지에서 수행하는 Depth Test를 Lazy-Z라고 부르기도 함

Stencil Test

  • 그렇게 자주 사용되는 것은 아님

  • 일종의 픽셀 단위 마스킹 / 조건부 렌더링 기능

    • UI 마스킹, 미니맵 클리핑 등에 활용

    • 색과 무관한 독립적 논리 마스크

  1. Stencil Buffer 값과 비교

  2. 통과 / 실패에 따라 fragment를 폐기하거나 stencil 값 갱신

Depth-Stencil 설정

  • Depth-Stencil에는 저장 공간(버퍼)와 동작 규칙(State), 두 개의 개념이 필요함

  • Depth-Stencil 버퍼: GPU 메모리에 있는 텍스처(대부분 Texture2D)로, 깊이 값과 선택적으로 Stencil 값(별도의 8bit)을 저장

  • Depth-Stencil State: Depth-Stencil 테스트를 어떻게 할지 규칙을 담은 객체

  1. Depth-Stencil 버퍼 만들기: Texture2D로 생성 후 DepthStencilView(DSV)로 이를 바라보도록(접근하도록) 하는 방식

    • DXGI_FORMAT_D24_UNORM_S8_UINT(depth 24bit + stencil 8bit), DXGI_FORMAT_D32_FLOAT(depth만 32bit) 포맷을 주로 사용

    • Texture2D 생성 시 bind flag에 D3D11_BIND_DEPTH_STENCIL 포함

    • Texture2D 크기는 보통 백 버퍼와 동일하게 창 크기 등으로 설정

  2. Depth-Stencil State 만들기

    • 핵심 필드

      • DepthEnable: Depth 테스트 수행 여부 결정

      • DepthWriteMask

        • ALL: Depth 버퍼에 깊이 값 기록

        • ZERO: Depth 연산만 하고 기록은 안함(반투명 오브젝트 렌더링에서 자주 사용)

      • DepthFunc: 깊이 비교 함수(LESS / LEQUAL / GREATER / ALWAYS 등)

      • StencilEnable: Stencil 테스트 수행 여부 결정

      • StencilReadMask / StencilWriteMask: Stencil 값을 읽고 쓸 때 어떤 비트를 사용할 지 마스킹

    • State 객체 설정

      ID3D11DepthStencilState* pDSState = nullptr;
      device->CreateDepthStencilState(&dsDesc, &pDSState);
      
  3. OM 스테이지에 바인딩

    • State 바인딩: context->OMSetDepthStencilState(pDSState, 1);

      • 두 번째 인자 1은 Stencil 레퍼런스 값(Stencil 비교의 기준값, 보통 0 또는 1)
    • DSV 바인딩: context->OMSetRenderTarget(1, &pRTV, pDSV)

      • 백 버퍼 RenderTargetView(RTV) 바인딩할 때 함께 바인딩

3. (Alpha) Blending

  • Depth Test 이후에 수행

  • 반투명한 오브젝트 등에서 새로 그릴 색과 이미 그려진 색을 어떻게 섞을 것인지 결정

  • 기본적인 Alpha Blending 공식 = 새로 그릴 색 * 새로 그릴 픽셀의 Alpha 값 + 원래 그려진 색 * (1 - 새로 그릴 픽셀의 Alpha 값)

  • Blend State를 통해 설정

    D3D11_BLEND_DESC desc = {};
    desc.RenderTarget[0].BlendEnable = TRUE;
    desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
    desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
    desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
    
  • Blending은 순서에 의존적이기 때문에 (반)투명 오브젝트는 depth 기준으로 정렬될 필요가 있음

Blend State 설정

  • 색 Blend 수식: OutRGB = Op(SrcRGB * SrcFactorRGB, DestRGB * DestFactorRGB)

  • Alpha Blend 수식: OutA = OpA(SrcA * SrcFactorA, DestA * DestFactorA)

  • 모든 입력 요소들과 BlendEnable(Blend 수행 여부), RenderTargetWriteMask(RGBA 중 어디 쓸지) 설정 가능

  • 반투명에 보편적으로 사용하는 설정

    D3D11_BLEND_DESC desc{};
    desc.AlphaToCoverageEnable = FALSE;
    desc.IndependentBlendEnable = FALSE;
    
    auto& rt0 = desc.RenderTarget[0];
    rt0.BlendEnable = TRUE;
    
    // 감산 블렌딩(섞을수록 어두워짐)
    rt0.SrcBlend = D3D11_BLEND_SRC_ALPHA;
    rt0.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
    rt0.BlendOp = D3D11_BLEND_OP_ADD;
    
    // 결과 알파 채널 계산
    rt0.SrcBlendAlpha = D3D11_BLEND_ONE;
    rt0.DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
    rt0.BlendOpAlpha = D3D11_BLEND_OP_ADD;
    
    // OM이 최종적으로 어떤 값들을 쓸지 결정
    // 만약 RGB만 유지하고 알파 채널은 이전대로 유지하고 싶으면 ALL 대신 RED, GREEN, BLUE를 |로 결합
    rt0.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
    
    ComPtr<ID3D11BlendState> alphaBlend;
    device->CreateBlendState(&desc, &alphaBlend);
    
    • 가산 블렌딩의 경우(빛 / 파티클)

      rt0.BlendEnable = TRUE;
      rt0.SrcBlend = D3D11_BLEND_ONE;
      rt0.DestBlend = D3D11_BLEND_ONE;
      rt0.BlendOp = D3D11_BLEND_OP_ADD;
      
  • 생성한 Blend State를 OM에 바인딩

    // D3D11_BLEND_BLEND_FACTOR 또는 D3D11_BLEND_INV_BLEND_FACTOR를 State에 사용했다면 아래의 blendFactor로 실제 값을 지정
    float blendFactor[4] = { 1, 1, 1, 1 };
    
    // MSAA 시 어떤 샘플을 업데이트할 지 제어하는 마스크
    UINT sampleMask = 0xffffffff;
    
    // D3D11_BLEND_BLEND_FACTOR나 MSAA를 사용하지 않더라도 반드시 형식상으로나마 더미값으로 넘겨주어야 함
    context->OMSetBlendState(alphaBlend.Get(), blendFactor, sampleMask);
    

Alpha-to-Coverage(A2C)?

  • MSAA 환경에서 PS 출력의 SV_Target0.a를 n-step 커버리지 마스크로 변환하여 투명 가장자리를 더 자연스럽게 보이게 하는 기법

  • 고급 기능이라 일단 지금은 이런게 있다 정도만…

PS 출력 2개를 블렌딩 입력으로 쓰기?

  • SRC1_COLOR, SRC1_ALPHA 같은 팩터를 써서 PS의 두 번째 출력을 블렌드 팩터로 활용하는 고급 기법도 있음

  • 마찬가지로 이런게 있다 정도만…

4. Z-Fighting과 Depth Bias

  • 우선 주의할 점! 이건 OM 기능이 아니라 Resterizer의 State로 설정하는 것!

    • OM의 Depth Test에 영향을 미치기 때문에 일단 여기서 정리함(MSDN 순서를 따라감)

Z-Fighting

  • 두 물체의 깊이 값이 거의 같은 경우(부동소수점 오차 등이 발생 시) GPU가 어떤 픽셀이 앞인지 뒤인지 프레임마다 다르게 판단하는 현상이 발생

  • 그 결과 표면이 깜빡거리거나 그림자에 잡음이 많아지게 되는데, 이를 Z-Fighting이라 함

  • 특히 Shadow Mapping에서 관련 현상이 더 심해지는데, 같은 Geometry를 광원 시점에서 한 번, 카메라 시점에서 한 번 렌더링 하다보니 깊이 값의 부동소수점 오차나 투영 / 보간 오차가 쌓이면서 자기 자신이 자기 그림자에 가려지는 등의 문제가 발생함(Shadow Acne)

Depth Bias

  • Z-Fighting 문제 완화를 위해 Resterizer 단계에서 생성되는 깊이 값에 의도적으로 작은 오프셋을 더해 깊이 비교 결과를 안정화시키는 것이 Depth Bias

    D3D11_RASTERIZER_DESC rsDesc{};
    
    // 깊이 값에 항상 일정한 오프셋을 더함(작은 양의 정수값 사용)
    rsDesc.DepthBias = 0;
    
    // 기울기 기반 바이어스로, 실제로는 제일 중요한 값
    // 표면이 광원에 대해 기울어질수록 깊이 오차는 더 커지기 때문에 그만큼 바이어스를 더 크게 적용할 필요가 있음
    rsDesc.SlopeScaledDepthBias = 0.0f;
    
    // Bias의 최대 한계로, Bias가 너무 커지는 것을 제한
    rsDesc.DepthBiasClamp = 0.0f;
    
  • 실제 Geometry 위치나 화면 상의 위치가 바뀌는 것은 아니며, Depth Test 시에 사용하는 값만 살짝 조정하는 것임

  • 다음 글: ❎ Direct3D 11 Graphics Pipeline - 7. 그래픽스 프로그램 구조와 주요 Direct3D 11 함수들