Direct3d와 게임 엔진 등 대부분 RayTracing이 아니라, Rasterization방식을 이용합니다. 여기서 물체를 클릭하고 싶을 때, 어떻게 할까요?
방법은 두 가지가 있습니다.
1. 월드좌표계의 물체에 eye에서 마우스 위치 방향으로 ray를 쏘기.
2. 스크린 좌표계에 투영된 물체를 이용하여 클릭된 물체 찾기
첫번째 방식은 ray를 쏘는 방식입니다. RayTracing방식이나 Rasterization방식이나 많이 이용합니다. 하지만 Gpu 자료를 Cpu로 가져오는 연습을 하기위해 두번째 방식을 공부해봅시다.
두번째 방식을 좀 더 자세히 알아보겠습니다.
순서는 다음과 같습니다.
1) 각 물체마다 index를 정하고 물체마다 고유 색깔을 지정합니다.
2) 렌더 타겟을 두개 준비합니다.
첫번째 렌더타겟은 백버퍼에 그려질 상입니다.
두번째 렌더타겟은 백버퍼가 아닌 임의의 렌더 타겟에 그릴 것이고, 최종적으로 그려질 상이 아니라, 물체의 고유 색깔만 그리는 방식입니다.
3) 두번째 렌더타겟에서 현재 마우스의 위치에 해당하는 픽셀의 색상 값을 가져옵니다
4) 그 픽셀의 색상 값이 물체의 색상 값과 같다면 물체를 클릭한 것으로 판정합니다.
멀티 렌더 타겟을 설정하는 방법을 간단히 코드로 보여드리겠습니다.
//렌더링 할 때의 코드
ID3D11RenderTargetView *targets[] = {m_renderTargetView.Get(),
m_indexRenderTargetView.Get()};
m_context->OMSetRenderTargets(2, targets, m_depthStencilView.Get());
m_context->OMSetDepthStencilState(m_depthStencilState.Get(), 0);
//픽셀 쉐이더의 코드
struct PixelShaderOutput
{
float4 pixelColor : SV_Target0;
float4 indexColor : SV_Target1;
};
PixelShaderOutput main(PixelShaderInput input)
{
..
PixelShaderOutput output;
output.pixelColor = m_pickColor ? indexColor : diffuse + specular;
output.indexColor = indexColor;
return output;
}
이 때 m_renderTargetView는 백버퍼에 연결되어있습니다.
m_indexRenderTargetView는 m_indexTexture라는 빈 텍스춰 공간에 연결되어 있습니다. 그 코드는 다음과 같습니다.
ComPtr<ID3D11Texture2D> backBuffer;
m_swapChain->GetBuffer(0, IID_PPV_ARGS(backBuffer.GetAddressOf()));
if (backBuffer) {
m_device->CreateRenderTargetView(backBuffer.Get(), nullptr,
m_renderTargetView.GetAddressOf());
// 마우스 피킹에 사용할 인덱스 색을 렌더링할 텍스춰와 렌더타겟 생성
backBuffer->GetDesc(&desc); // BackBuffer와 동일한 설정
if (FAILED(m_device->CreateTexture2D(&desc, nullptr,
m_indexTexture.GetAddressOf()))) {
cout << "Failed()" << endl;
}
m_device->CreateRenderTargetView(
m_indexTexture.Get(), nullptr,
m_indexRenderTargetView.GetAddressOf());
}
자 이제 여기서 어떻게 해야할까요?
m_indexRenderTargetView가 m_indexTexture를 타겟으로 해서 그리고 있죠? 그럼 m_indexTexture에서 값을 가져오면 될겁니다. 하지만 여기서 가장 중요한 개념이 필요합니다. m_indexTexture는 gpu에서 그려집니다. 하지만 우리가 픽셀의 값을 가져와서 물체인지 아닌지 판단하는 부분은 cpu에서 판단해야 하는 것이죠.
gpu에서 cpu로 가져오는 과정을 살펴봅시다. 그 과정에 필요한 텍스춰를 스테이징 텍스춰 라고 합니다.
단계는 다음과 같습니다.
1) m_indexTexture에서 m_indexTempTexture로 멀티샘플링에서 단일샘플링으로 변환해서 값을 할당해줍니다.
=> m_indexTexture는 멀티샘플 렌더 타겟이라 바로 읽어올 수 없습니다. 따라서 단일샘플링 변화가 필요합니다.
2) m_indexTempTexture에서 필요한 부분을 m_indexStagingTexture로 할당해줍니다.
3) m_indexStagingTexture는 아직 gpu에 있는 텍스춰라 이제 cpu의 픽셀 값으로 옮겨줍니다.
1) 의 코드는 다음과 같습니다.
//ResolveSubresource는 멀티샘플에서 단일샘플로 변환해주는 함수입니다
m_context->ResolveSubresource(m_indexTempTexture.Get(), 0,
m_indexTexture.Get(), 0,
DXGI_FORMAT_R8G8B8A8_UNORM);
2)의 코드는 다음과 같습니다.
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);
여기서 m_indexStaingTexture는 첫 초기화시 다음과 같이 초기화 해야합니다.
// 1x1 작은 스테이징 텍스춰 만들기
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;
}
즉, desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.Usage = D3D11_USAGE_STAGING;로 읽기모드와, 스테이징 할 거란 표시를 해줘야한다는 의미죠.
1*1는 한 픽셀의 크기를 의미합니다. m_indexStaingTexture에 모든 텍스춰를 가져오는 것이 아니라, 픽셀 한 부분만 가져오겠다는 뜻이죠. 가져올 위치는 box를 이용해서 가져옵니다.
3) 의 코드는 다음과 같습니다
D3D11_MAPPED_SUBRESOURCE ms;
m_context->Map(m_indexStagingTexture.Get(), NULL, D3D11_MAP_READ, NULL,
&ms); // D3D11_MAP_READ 주의
uint8_t *pData = (uint8_t *)ms.pData;
//indexPixel은 최종적으로 cpu에서 활용할 Pixel입니다
memcpy(&indexPixel[0], &pData[0],
sizeof(uint8_t) * 4);
m_context->Unmap(m_indexStagingTexture.Get(), NULL);
이렇게 indexPixel의 값을 이용해서 물체가 클릭된 것인지 아닌지 판단을 하면 됩니다.
'그래픽스 기술' 카테고리의 다른 글
[Tessellation] quad tesselation순서 및 patch control point 계산 (0) | 2025.01.06 |
---|---|
[PBR] 텍스춰 노멀매핑 (1) | 2025.01.05 |
[Rendering Pipeline] 후처리 시 스왑체인과 백버퍼에 대한 이해 (0) | 2024.12.12 |
[렌더링파이프라인] 구 Mapping의 원리 (0) | 2024.12.05 |
[렌더링 파이프라인] Subdivision의 개념 (1) | 2024.12.04 |