본문 바로가기

그래픽스

[그래픽스] Picking (렌더타겟 2개 이용, GPU->CPU로 데이터 복사)

       m_context->ResolveSubresource(m_tempTexture.Get(), 0,
                                     backBuffer.Get(), 0,
                                     DXGI_FORMAT_R8G8B8A8_UNORM);
                                     
       D3D11Utils::WriteToFile(m_device, m_context, m_tempTexture,
                                        "captured.png");

 

context->ResolveSubResource

GPU내부에서 텍스쳐를 복사하는 함수이다. 그리고 captured.png 파일을 만들기 위해 복사한 m_tempTexture을 

이용해서 WriteToFile함수를 실행한다. 백버퍼를 바로 넣는 게 아니라 ResolveSubResource로 복사하는 이유는

첫번째로 멀티 샘플링 요소가 포함되어 있기 때문에 Texture2D형식으로 바꾸기 위함이고,

두번째로 GPU의 최적화 때문에 텍스쳐의 용도를 명확히 하기 위함이다. 

 

 

  D3D11_TEXTURE2D_DESC desc;
    textureToWrite->GetDesc(&desc);
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.BindFlags = 0;
    desc.MiscFlags = 0;
    desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; // CPU에서 읽기 가능
    desc.Usage = D3D11_USAGE_STAGING; // GPU에서 CPU로 보낼 데이터를 임시 보관

    ComPtr<ID3D11Texture2D> stagingTexture;
    if (FAILED(device->CreateTexture2D(&desc, nullptr,
                                       stagingTexture.GetAddressOf()))) {
        cout << "Failed()" << endl;
    }

    // 참고: 전체 복사할 때
    // context->CopyResource(stagingTexture.Get(), pTemp.Get());

    // 일부만 복사할 때 사용
    D3D11_BOX box;
    box.left = 0;
    box.right = desc.Width;
    box.top = 0;
    box.bottom = desc.Height;
    box.front = 0;
    box.back = 1;
    context->CopySubresourceRegion(stagingTexture.Get(), 0, 0, 0, 0,
                                   textureToWrite.Get(), 0, &box);

 

WriteToFile은 맨 밑줄의 textureToWrite를 매개변수로 받고 있는데 여기 복사한 m_tempTexture을 넣는다.

함수의 처음에 StagingTexture을 만들고, 이 텍스쳐는 CPU에서 읽기 가능하고 GPU에서 CPU로 보낼 데이터를 보관하는 용이라고 Desc에서 설정한다. 그리고 m_tempTexture의 일부를 CopySubResourceRegion으로 복사한다.

이렇게 하면 텍스쳐가 GPU에서 CPU로 보낼 데이터를 보관하는 용도라고 명확히 할 수 있다.

 

 // R8G8B8A8 이라고 가정
    std::vector<uint8_t> pixels(desc.Width * desc.Height * 4);

    D3D11_MAPPED_SUBRESOURCE ms;
    context->Map(stagingTexture.Get(), NULL, D3D11_MAP_READ, NULL,
                 &ms); // D3D11_MAP_READ 주의

    // 텍스춰가 작을 경우에는
    // ms.RowPitch가 width * sizeof(uint8_t) * 4보다 클 수도 있어서
    // for문으로 가로줄 하나씩 복사
    uint8_t *pData = (uint8_t *)ms.pData;
    for (unsigned int h = 0; h < desc.Height; h++) {
        memcpy(&pixels[h * desc.Width * 4], &pData[h * ms.RowPitch],
               desc.Width * sizeof(uint8_t) * 4);
    }

    context->Unmap(stagingTexture.Get(), NULL);

 

그리고 GPU(pData)에서 CPU(pixels)로 복사를 한다. ms.RowPitch는 한 행당 너비(바이트)이고

for문으로 루프를 돌며 가로줄 하나씩 데이터를 복사하고 있다.

 

 

------- 지금까지 GPU에서 CPU로 데이터를 복사하는 것에 대해 알아봤는데 이것을 활용해 Picking을 해보자.

 

 

    ID3D11RenderTargetView *targets[] = {m_renderTargetView.Get(),
                                         m_indexRenderTargetView.Get()};
    m_context->OMSetRenderTargets(2, targets, m_depthStencilView.Get());

 

이번에는 렌더타겟을 두개 이용해서 Picking을 할 것이다. 첫번째 렌더타겟은 기본적으로 쉐이딩 되어 있는 것이고,

두번째 렌더타겟은 구는 빨간색, 큐브는 초록색으로 되어있는 렌더타겟이다.

 

struct PixelShaderOutput
{
    float4 pixelColor : SV_Target0;
    float4 indexColor : SV_Target1;
};

 

Pixel Shader에서 Output 구조체를 이렇게 정하면 첫번째 렌더타겟의 Output을 pixelColor에, 두번째 렌더타겟의 

Output을 indexColor에 넣어주면 된다.

 

 

   	desc.BindFlags = 0;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
        desc.Usage = D3D11_USAGE_STAGING;
        desc.Width = 1;
        desc.Height = 1;

        if (FAILED(m_device->CreateTexture2D(
                &desc, nullptr, m_indexStagingTexture.GetAddressOf()))) {
            cout << "Failed()" << endl;
        }

 

Picking할 픽셀을 1x1 텍스쳐로 만든다. GPU에서 CPU로 보내는 Staging 형식이다.

그리고 Picking에 사용할 indexColor을 PS로 보내는 BasicPixelConstantData에 추가한다.

 

 

  m_context->ResolveSubresource(m_indexTempTexture.Get(), 0,
                                  m_indexTexture.Get(), 0,
                                  DXGI_FORMAT_R8G8B8A8_UNORM);

    // 일부만 복사할 때 사용
    D3D11_BOX box;
    box.left = m_cursorX;
    box.right = m_cursorX + 1;
    box.top = m_cursorY;
    box.bottom = m_cursorY + 1;
    box.front = 0;
    box.back = 1;
    m_context->CopySubresourceRegion(m_indexStagingTexture.Get(), 0, 0, 0, 0,
                                     m_indexTempTexture.Get(), 0, &box);

 

아까 백버퍼와 같은 원리로 indexRenderTarget에서 사용되는 indexTexture는 멀티샘플링 요소가 포함되어 있으므로

indexTempTexture의 형식(Texture2D)으로 ResolveSubresource를 통해 변환한다. 그리고 indexTempTexture에서 커서가 있는 위치만 1x1 텍스쳐로 잘라내 indexStagingTexture에 복사한다.

 

 

 D3D11_MAPPED_SUBRESOURCE ms;
    m_context->Map(m_indexStagingTexture.Get(), NULL, D3D11_MAP_READ, NULL,
                   &ms); // D3D11_MAP_READ 주의
    memcpy(m_pickColor, ms.pData, sizeof(uint8_t) * 4);
    m_context->Unmap(m_indexStagingTexture.Get(), NULL);

 

GPU의 indexStagingTexture을 CPU의 m_pickColor로 복사한다.

m_pickColor는 uint8_t[4] 형식이다.

 

이제 구와 큐브를 초기화 할때 PixelConstantBuffer.indexColor에서 원하는 색을 지정해주고 렌더링할 색을 결정하는

Update함수에서 pickColor[0] == 255 (pickColor가 빨간색)이라면 PixelConstantBuffer.material.diffuse를 빨간색으로

바꿔서 마우스가 구에 닿으면 빨간색으로 바뀌게 표현할 수 있다.