Programming/컴퓨터그래픽스 (DX 11)

[DirectX 11] 깊이 테스트(Depth Testing, Z-Test)와 DepthStencilView

양양줘 2025. 9. 23. 11:52

 

아래 정리할 내용은

DirectX 렌더파이프라인의 마지막 단계인

OutputMerger의 깊이 테스트에 대한 내용이다.

 

OM(OutputMerger, 출력병합기)는

픽셀 쉐이더가 출력한 색상(RGBA)과 깊이(Z)를 기준으로

최종적으로 화면에 출력할 픽셀을 결정하 Stage로

직접 프로그래밍인 불가능하지만,

스테이지의 상태를 변경하여 작동 방식을 변경시킬 수 있다.

 

크게 Depth, Stencil, Blend 3가지 처리를 담당하는데

3D 그래픽스에서 절대 빠져선 안될것이 바로

오늘 정리 주제인 Depth Test이다.

 

 

Depth Testing (깊이 테스트, Z-Test)


깊이 테스트(Depth Testing)는 화면상에서 여러 물체가 겹쳐 있을때 어느 픽셀이 앞에있고 뒤에 있는지를 판단하여 앞에 있는 픽셀을 기록하는 과정이다. Depth Buffer(Z-Buffer)라는 전용 메모리를 사용하여 매 프레임마다 버퍼를 초기화하고 그려질 픽셀들의 z를 기록된 값과 비교하여 가장 앞에 있는 픽셀을 최종적으로 기록하게 된다. 이 과정을 통해 뒤에 있는 픽셀들은 버려져서 불필요한 계산과 렌더링을 피할 수 있다.

카메라 시점
   |
   V
[화면 픽셀]
Depth Buffer 초기값: 1.0 (가장 먼 거리)

화면 상에 겹치는 물체:

       [A]  <- z = 0.3 (앞)
      [B]   <- z = 0.5 (뒤)
     [C]    <- z = 0.7 (더 뒤)

Depth Test 진행:
픽셀 A 그리기 -> DepthBuffer[px] = 0.3
픽셀 B 그리기 -> z=0.5 > DepthBuffer[px]=0.3 -> 버림
픽셀 C 그리기 -> z=0.7 > DepthBuffer[px]=0.3 -> 버림

최종 화면: A만 표시

 

 

Early-Z test (조기 깊이 테스트)

 

조기 깊이 테스트(Early-Z test)는 말 그래도 일찍이 깊이 테스트를 먼저 한번 실행해주는 것이다. 렌더 파이프라인 과정에서 깊이테스트는 최종 단계인 output merger에서 수행된다. 이때 내부적으로는 PS(pixel shader)단계 이전에 조기 깊이 테스트가 먼저 진행된다. 하드웨어 수준에서 자동으로 실행되는 조기 깊이 테스트로 먼저 뒤에 가려질 픽셀들을 걸러 무거운 PS를 건너 뛰 GPU 최적화가 이루어지는 것이다. 하지만 만약 픽셀쉐이더에서 깊이를 수정하는 코드가 있는 경우 조기 깊이 테스트는 수행되지 않는다.

  • Pixel Shader에서 SV_Depth 를 직접 수정하는 경우
  • Alpha Test(clip/discard) 를 하는 경우
  • 일부 블렌딩/멀티 샘플링 모드

 

DepthStencilView (DSV)

 

DirectX에서는 깊이 테스트를 수행하기 위해 DepthStencilView(DSV)가 필요하다. DSV는 Depth Buffer와 Stencil Buffer를 읽고 쓸 수 있도록 연결해주는 뷰(view)객체이다. 스텐실 테스트에 대해서는 다음에 정리할 예정이다.

 

3D공간에서 앞에 있냐, 뒤에있냐를 판단하는건 카메라와의 거리(깊이)이며, 깊이 테스트는 이미 projection이 끝난 뒤에 이루어지므로 0 ~ 1 (Near ~ Far)로 정규화 되어 있을 것이다. 때문에 매 프레임 렌더링 이전에 depthbuffer를 1(가장 먼 곳)으로 초기화해주고 초기화해준 DSV객체를 바인딩하여 각 draw과정에서 깊이 테스트를 수행할 수 있게 스테이지 설정을 해주면 된다.

// 1. DepthStencil Buffer 생성
D3D11_TEXTURE2D_DESC descDepth = {};
descDepth.Width = width;                     
descDepth.Height = height;                   
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; // 24bit Depth + 8bit Stencil
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;

ID3D11Texture2D* depthStencilBuffer = nullptr;
device->CreateTexture2D(&descDepth, nullptr, &depthStencilBuffer);

// 2. DepthStencilView (DSV) 생성
ID3D11DepthStencilView* depthStencilView = nullptr;
device->CreateDepthStencilView(depthStencilBuffer, nullptr, &depthStencilView);

// 3. 매 프레임 바인딩
void Render()
{
    // 렌더 타겟 셋팅
    context->OMSetRenderTargets(1, &renderTargetView, depthStencilView);

    // DepthBuffer 초기화
    float clearDepth = 1.0f;       // 가장 먼 깊이값
    context->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH, clearDepth, 0);
}