본문 바로가기

그래픽스

[그래픽스] Depth Map

Depth Map은 우리가 보는 시점으로부터 물체로부터 거리를 가지는 Texture다.

안개나 그림자같은 효과를 표현하기 위해 Depth Map이 필요하다.

 

    // Depth 전용
    desc.Format = DXGI_FORMAT_R32_TYPELESS;
    desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    ThrowIfFailed(m_device->CreateTexture2D(&desc, NULL,
                                            m_depthOnlyBuffer.GetAddressOf()));


Depth Map은 다른 shader에 넣어 효과를 표현하기 위함이므로 shader resource로 만든다. 그런데 MSAA를 지원하는 texture2DMS는 shader resource를 지원하지 않으므로 MSAA를 지원하지 않는 (sampleDesc.Count = 1) 일반적인

Texture로 depthOnlyBuffer를 생성한다. 그리고 DepthOnly, Stencil이 필요 없으므로  포맷이 기존의 D24_UNORM_S8이 

아니라 R32_TYPELESS로 만든다. 

 

    D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
    ZeroMemory(&dsvDesc, sizeof(dsvDesc));
    dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
    dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    ThrowIfFailed(m_device->CreateDepthStencilView(
        m_depthOnlyBuffer.Get(), &dsvDesc, m_depthOnlyDSV.GetAddressOf()));

    D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
    ZeroMemory(&srvDesc, sizeof(srvDesc));
    srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
    srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MipLevels = 1;
    ThrowIfFailed(m_device->CreateShaderResourceView(
        m_depthOnlyBuffer.Get(), &srvDesc, m_depthOnlySRV.GetAddressOf()));

 

DSV와 SRV를 만들때에도 TEXTURE2D 형식으로 만든다. DSV포맷은 D32_FLOAT, RSV 포맷은 R32_FLOAT으로 한다.

 

 

    // Depth Only Pass
    m_context->OMSetRenderTargets(1, m_resolvedRTV.GetAddressOf(),
                                  m_depthOnlyDSV.Get());
    m_context->ClearDepthStencilView(m_depthOnlyDSV.Get(), D3D11_CLEAR_DEPTH,
                                     1.0f, 0);
    AppBase::SetPipelineState(Graphics::depthOnlyPSO);
    AppBase::SetGlobalConsts(m_globalConstsGPU);
    for (auto &i : m_basicList)
        i->Render(m_context);
    m_skybox->Render(m_context);
    m_mirror->Render(m_context);

 

Depth Map을 만드는 Pass다. OMSetRenderTargets 에서 RTV를 m_resolvedRTV, DSV를 depthOnlyDSV로 설정하는데

m_resolvedRTV는 임시적으로 넣은 건이고 주 목적은 depthOnlyDSV에 depth를 저장하기 위함이다. 그리고 depthOnlyPSO로 PSO를 설정해주고 GlobalConstants를 설정한 뒤 모든 물체를 렌더링한다.

 

 

    // PostEffects
    AppBase::SetPipelineState(Graphics::postEffectsPSO);
    vector<ID3D11ShaderResourceView *> postEffectsSRVs = {
        m_resolvedSRV.Get(), m_depthOnlySRV.Get()}; // 20번에 넣어줌
    m_context->PSSetShaderResources(20, UINT(postEffectsSRVs.size()),
                                    postEffectsSRVs.data());
    m_context->OMSetRenderTargets(1, m_postEffectsRTV.GetAddressOf(), NULL);
    m_context->PSSetConstantBuffers(3, 1,
                                    m_postEffectsConstsGPU.GetAddressOf());
    m_screenSquare->Render(m_context);

 

m_depthOnlySRV를 이용해서 Post Effects를 한다. 순서는 MSAA를 resolve 해서 NO MSAA(Texture2D)로 만들고 

그걸 가지고 Post Effects 추가한 것을 Post Processing으로 후처리 한 후 최종 백버퍼에 넣는다. 

 

 

그리고 지금 Depth Map을 만들기 위해 Depth Only Pass에서 렌더링을 따로 하고 그 뒤에 물체들을 렌더링을 한번 더 하고 있다. 쉐이더를 사용할 때는 MSAA를 사용하는 것이 더 품질이 좋은데 MSAA를 사용한 렌더링한 Depth 값을 Resolve 할 수가 없다. 그래서 MSAA를 사용하지 않는 Texture을 만들고 MSAA를 설정하지 않은 DSV에 Depth Map을 만들기 위한 

Depth Only Pass를 실행하는 것이다. 

 

 

// t20에서부터 시작
Texture2D renderTex : register(t20); // Rendering results
Texture2D depthOnlyTex : register(t21); // DepthOnly

 

PostEffectPS에서 아까 PSSetShaderResource로 20부터 받아주고 있었는데 20에 렌더링된 결과 resolvedSRV, 21에

depth 값 depthOnlySRV가 저장되었다. 이것을 바탕으로 PostEffectPS에서 Depth Map을 만들어보자.

 

 

View 좌표계에서 물체 각각의 Vertex들을 nearPlane으로 projection한다. view 좌표계의 정면은 z축 방향인데

nearPlane으로 부터 정점의 z축 좌표까지 거리를 z'로 정규화할 수 있다. 월드 좌표계가 아닌 near이 0이고

far이 1인 정규화된 좌표계에서 z'값이 저장되는 것이다. 거리는 nearPlane으로부터 정점까지 수직 거리이기 때문에

Depth Map을 렌더링한 결과를 보면 시점으로부터 수직한 직선으로 가까우면 어둡게 멀면 밝게 표현된다.

 

 

        float z = TexcoordToView(input.texcoord).z * depthScale;
        return float4(z, z, z, 1);

 

PS에서 DepthMap을 렌더링하는 코드다. 입력값으로 input.texcoord를 넣는데 nearPlane의 texcoord이다. 이것을 

invProj를 통해서 투영된 nearPlane의 texcoord를 역변환해서 물체의 좌표를 계산해준다.

 

 

float4 TexcoordToView(float2 texcoord)
{
    float4 posProj;

    // [0, 1]x[0, 1] -> [-1, 1]x[-1, 1]
    posProj.xy = texcoord * 2.0 - 1.0;
    posProj.y *= -1; // 주의: y 방향을 뒤집어줘야 합니다.
    posProj.z = depthOnlyTex.Sample(linearClampSampler, texcoord).r;
    posProj.w = 1.0;

    // ProjectSpace -> ViewSpace
    float4 posView = mul(posProj, invProj);
    posView.xyz /= posView.w;
    
    return posView;
}

 

TexcoordToView함수를 보면 [0,1] texture 좌표계에서 [-1,1] NDC 좌표계로 변환한다. 그리고 z값은 depthMap으로부터   가져온다. depthMap을 SRV로 만들때 포맷을 R32_FLOAT으로 했으므로 r값을 불러와서 저장한다. 그리고 NDC의

좌표를 invProj를 곱해 Projection에서 View로 좌표계를 변환한다. Projection을 할 때 w로 나눠 Homogenization을 했으므로 invProj를 곱한 후에 w로 나눠준다. 그러면 view좌표계에서 z값을 가지게 되고 그것을 (z,z,z,1) 색으로 렌더링한다.