DXR프로그래밍을 진행하기 위해서는 Shader Binding Table,줄여서 SBT란 녀석을 만들어야 한다.
mesh별로 Draw콜을 할 수 있는 Rasterization Pipeline과 달리 DXR Ray Tracing은 화면을 DispatchRay콜로 한번에 그려야 한다.Rasterization의 Draw과정은 VS-Rasterization-PS-OM등의 일련의 과정이 있지만, Ray tracing은 과정이 정해져 있지 않다. 어떤 Ray는 바로 Miss Shader가 호출될 것이고 어떤 Ray는 MAX_DEPTH까지 계속 Closest Hit Shader가 호출될 수도 있다. 그래서 DXR API는 SBT를 따로 두어서 RAY와 Object가 충돌했을시 어떤 처리를 할 것인지를 결정하는 과정이 존재하고 그 과정에 쓰이는 Record들의 Table이 SBT이다. SBT의 Record에는 어떤 Shader가 호출될 것인지? 그리고 Register에는 어떤 자원을 Mapping해야 하는지?에 대한 정보가 저장된다.
위 그림은 Acceleration Structure와 Shader Table의 관계를 간단하게 나타낸 그림이다.위 그림처럼 TLAS의 Instance들은 어떤 Shader가 호출되어야 하는지에 대한 정보를 Shader Table의 Index형태로 가지고 있다.
DXR은 SBT를 위한 별도의 API함수를 제공하지 않는다. 그냥 쌩 GPU메모리에 다 할당하고
Ray Tracing Pipeline에 gpu 가상 주소, Stride정보를 제공해야한다. 공식 홈페이지에서는 프로그래머의 자율성을 보장한다는 식으로 서술되어 있는데, 개발해보고 느낀점은 그냥 마소 개발자가 귀찮아서 안만든것 같다..
아무튼 SBT는 다음과 같이 코딩하면 된다.
m_shaderRecordSize = align(shaderRecordSize, D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT);//32바이트 단위로 m_shaderRecords.reserve(numShaderRecords); UINT bufferSize = numShaderRecords * m_shaderRecordSize;//buffer사이즈 설정 CD3DX12_HEAP_PROPERTIES uploadHeapProperties(D3D12_HEAP_TYPE_UPLOAD); CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize); hr = pDevice->CreateCommittedResource( &uploadHeapProperties, D3D12_HEAP_FLAG_NONE, &bufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_resource) );
Shader Table은 UPLOAD타입의 버퍼로 만들어주면 된다. Size부분이 중요한데
32바이트단위로Align한 사이즈를 할당해야한다. (ex/ 실제 record사이즈:48바이트=> 할당해야하는 record사이즈:64바이트)
LocalRootArgument rootArgument = { .cb = { .world = XMMATRIX(), .albedo = XMFLOAT4(0.0f,1.0f,0.0f,1.0f), .hasTexture = 0, .reflectivity = 0.f }, .vbGPUAddress = D3D12_GPU_VIRTUAL_ADDRESS(), .ibGPUAddress = D3D12_GPU_VIRTUAL_ADDRESS(), .diffuseTextureDescriptorHandle = D3D12_GPU_DESCRIPTOR_HANDLE() }; for (UINT i = 0; i < renderables.size(); i++) { rootArgument.cb.world = XMMatrixTranspose(renderables[i]->GetWorldMatrix()); rootArgument.cb.albedo = renderables[i]->GetColor(); rootArgument.cb.reflectivity = renderables[i]->GetMaterial()->GetReflectivity(); rootArgument.vbGPUAddress = renderables[i]->GetVertexBuffer()->GetGPUVirtualAddress(); rootArgument.ibGPUAddress = renderables[i]->GetIndexBuffer()->GetGPUVirtualAddress(); rootArgument.cb.hasTexture = 0u; if (renderables[i]->GetMaterial()->HasDiffuseTexture()) { rootArgument.cb.hasTexture = 1u; rootArgument.diffuseTextureDescriptorHandle = renderables[i]->GetMaterial()->GetDiffuseTexture()->GetDescriptorHandle(); } for (UINT j = 0; j < RayType::Count; j++) { void* hitGroupIdentifier = stateObjectProperties->GetShaderIdentifier(HIT_GROUP_NAMES[j]); Push_back(ShaderRecord(hitGroupIdentifier, shaderIdentifierSize, &rootArgument, sizeof(rootArgument))); } }
위 코드는 내 DXR프로젝트에서 HitGroup SBT에 Record를 추가하는 부분의 코드이다.
Local Root Signature에서 정의한 Argument구조체를 알맞게 채워서 SBT에 Push_back해준다.
record == GPU주소
record == GPU주소 + Stride x (trace ray의 4번째 parameter + tlas Instance에 정의한 hit group index + (trace ray의 5번째 parameter) x (blas안에서의 geometry index))
record == GPU주소 + Stride x (trace ray의 6번째 parameter)
위 공식들을 완전히 이해해야지만 DXR을 무리없이 쓸 수 있다..