[DirectX 12] #2-8. DepthStencilView
들어가며
이번 시간에는 3D 객체를 화면에 출력하기 위해 꼭 필요한 Projection과 Depth 개념을 알아보고, 픽셀의 깊이 값을 추적해 가장 가까운 객체의 픽셀만 출력되도록 처리하는 실습을 해본다.
DepthStencil
- Projection
- 3D 게임의 세상은 입체적이지만, 모니터에 출력되는 화면은 2D로 납작하다. 3D를 2D로 나타내기 위해 투영(Projection)이 필요하다.
💡 Projection이란?
카메라 좌표계를 투영 좌표계로 변환해 원근감을 적용한다.
- Depth
- 투영 과정에서 z값은 그리기 우선 순위를 판단하는 기준으로 사용된다.
- 겹치는 오브젝트가 있다면 더 가까운 오브젝트만 화면에 그리는 개념이다.
- Depth Buffer
- 역할
- 픽셀 단위로 Depth(깊이)값을 저장한다.
- z값을 비교해 가장 가까운 픽셀만 화면에 출력한다.
- 예시
- 두 사각형이 겹칠 경우, 카메라에 가까운 사각형만 보인다.
- 특징
- 깊이 값은 일반적으로 0.0 ~ 1.0이다.
- 1에 가까울 수록 멀리 있는 오브젝트이고, 1 이상은 아예 계산하지 않는다.
- 역할
- Stencil
- 실습에서 사용하지 않지만, 개념적으로 알아보자.
- 개념
- 미술에서 판에 구멍을 뚫고 잉크를 통과시켜 찍어내는 공판화 기법이다.
- 그래픽스에선 픽셀마다 스텐실 값을 기록하고, 특정 조건을 만족할 때만 해당 픽셀에 렌더링을 허용한다.
- 활용 예시
- 실루엣 처리, 미러 효과, 그림자 마스킹 등
DepthStencilBuffer 클래스
- 멤버 변수
- DepthStencilView 리소스와 뷰를 생성해 Depth 정보를 저장하는 버퍼를 추가한다.
ComPtr<ID3D12Resource> _dsvBuffer; ComPtr<ID3D12DescriptorHeap> _dsvHeap; D3D12_CPU_DESCRIPTOR_HANDLE _dsvHandle = {}; DXGI_FORMAT _dsvFormat = {};
- DepthStencilView 리소스와 뷰를 생성해 Depth 정보를 저장하는 버퍼를 추가한다.
DepthStencilView::Init()
추가- window를 매개변수로 갖는 이유는 화면 크기(픽셀 수)에 따라 버퍼를 만들어줘야 하기 때문이다.
- format
D3D12_HEAP_TYPE_DEFAULT
은 4byte를 모두 Depth 값으로 쓰겠다는 뜻이다.- 버퍼 크기 생성
- Heap 생성
- DepthStencilView 생성
void DepthStencilBuffer::Init(const WindowInfo& window, DXGI_FORMAT dsvFormat)
{
_dsvFormat = dsvFormat;
D3D12_HEAP_PROPERTIES heapProperty = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Tex2D(_dsvFormat, window.width, window.height);
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optimizedClearValue = CD3DX12_CLEAR_VALUE(_dsvFormat, 1.0f, 0);
DEVICE->CreateCommittedResource(
&heapProperty,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&optimizedClearValue,
IID_PPV_ARGS(&_dsvBuffer));
D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
heapDesc.NumDescriptors = 1;
heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
DEVICE->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&_dsvHeap));
_dsvHandle = _dsvHeap->GetCPUDescriptorHandleForHeapStart();
DEVICE->CreateDepthStencilView(_dsvBuffer.Get(), nullptr, _dsvHandle);
}
Engine 클래스
이제 Engine 클래스에 DSV를 연동해보자.
- 멤버 변수 및 함수 추가
shared_ptr<DepthStencilBuffer> _depthStencilBuffer; shared_ptr<DepthStencilBuffer> GetDepthStencilBuffer() { return _depthStencilBuffer; }
Engine::Init()
에 DepthStencilBuffer 생성_depthStencilBuffer = make_shared<DepthStencilBuffer>(); ResizeWindow(info.width, info.height);
Engine::ResizeWindow()
에 DepthStencilBuffer 초기화_depthStencilBuffer->Init()
Command Queue 클래스
CommandQueue::RenderBegin()
- DepthStencilView 설정 및 초기화
OMSetRenderTargets()
로 RenderTarget과 함께 설정ClearDepthStencilView()
로 깊이를 초기화D3D12_CPU_DESCRIPTOR_HANDLE depthStencilView = GEngine->GetDepthStencilBuffer()->GetDSVCpuHandle(); _cmdList->OMSetRenderTargets(1, &backBufferView, FALSE, &depthStencilView); _cmdList->ClearDepthStencilView(depthStencilView, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
- DepthStencilView 설정 및 초기화
Shader 클래스
default.hlsi
파일 수정VS_MAIN()
에서output.pos
에offset0
을 더해 z값 변화를 반영함으로써 깊이 차이를 나타낸다.VS_OUT VS_Main(VS_IN input) { VS_OUT output = (VS_OUT)0; output.pos = float4(input.pos, 1.f); output.pos += offset0; output.color = input.color; output.uv = input.uv; return output; }
Shader::Init()
- pipeline에 DSV를 추가한다.
DepthStencilState
기본값 사용DSVFormat
을 파이프라인에 등록_pipelineDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT); _pipelineDesc.DSVFormat = GEngine->GetDepthStencilBuffer()->GetDSVFormat();
- pipeline에 DSV를 추가한다.
Game 클래스
같은 위치의 두 Mesh를 z값만 다르게 설정하여 깊이 테스트를 해본다. z값이 더 작을 수록(앞에 있을수록) 화면에 그려진다.
- case1
- 오른쪽 Mesh의 z값에 0.2f 더한다.
- 왼쪽 Mesh의 z값에 0.0f 더한다.
- Depth 값이 더 작은(가까운) 왼쪽 Mesh가 앞에 그려진다.
- case2
- 오른쪽 Mesh의 z값에 0.2f 더한다.
- 왼쪽 Mesh의 z값에 0.3f 더한다.
- Depth 값이 더 작은(가까운) 오른쪽 Mesh가 앞에 그려진다.
마치며
- 이번 시간에 배운 것
- 3D 게임 세계를 2D 화면에 출력하기 위해 필요한 투영과 깊이에 대한 이해
- Depth 값을 이용해 화면에 가까운 오브젝트를 그리는 방법
게임 수학에서 배운 깊이 버퍼를 코드로 구현해보니 신기했다. 사각형 두개 그리는데 이렇게 많은 코드를 수정해야하는데, 게임에선 얼마나 복잡할까..?????
Leave a comment