Computer Graphics/VulkanPT

Vulkan에 Ray Tracing 도입 하기 (1)

surkim 2025. 4. 11. 15:42

3. Mesh를 돌며 BLAS 생성

for (auto& mesh : m_meshList) {
	std::cout << "create blas" << std::endl;
	auto blas = BottomLevelAS::createBottomLevelAS(m_context.get(), mesh.get());
	m_blasList.push_back(std::move(blas));
}

 

만드는 코드는 너무 길어 생략하겠습니다.

https://github.com/ksro0128/vulkanRT/blob/main/src/AccelerationStructure.cpp

 

중요한건 생성할 때 Mesh(vertices, indices)가 필요하다는 점과

 

제 엔진의 구조적으로 중요한 점은

각 메시와 BLAS가 1:1 대응 관계에 있다는 것입니다.

제 경우에는 인덱스가 일치시켰는데, 이렇게 함으로써 TLAS 생성 시

오브젝트 인스턴스와 해당 BLAS를 쉽게 참조할 수 있게 됩니다.

 

4. BLAS와 Scene 정보를 받아 TLAS 생성

m_tlas.resize(MAX_FRAMES_IN_FLIGHT);
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
	m_tlas[i] = TopLevelAS::createTopLevelAS(m_context.get(), m_blasList, m_modelList, modelToMatrixIndices, modelBuffers, m_scene->getObjects());
}

마찬가지로 내부 코드는
https://github.com/ksro0128/vulkanRT/blob/main/src/AccelerationStructure.cpp

BLAS와 동일한 위치에 있습니다.

 

TLAS는 기본적으로

씬의 인스턴스 정보(Object + transform)를 받습니다. 

매번 씬의 인스턴스 정보는 갱신될 수 있기 때문에

매프레임마다 재생성 됩니다.

 

5. Descriptorset 생성

레이트레이싱 셰이더에 들어갈 리소스를 DesciptorSet에 바인딩합니다.

위에서 만든 이미지와 TLAS가 들어갑니다.

이 디스크립터의 생명 주기는 이미지가 재생성 될 때

즉 화면의 크기가 바뀔 때 이고

현재 매 프레임 TLAS를 재생성해주고 있기 때문에

마찬가지로 매 프레임마다 디스크립터 셋도 업데이트 해줘야합니다.

 

6. Pipeline + SBT (Shader Binding Table)생성 

Pipeline은 잘 알지만 SBT는 처음입니다.

SBT에 생성에 대해 중심적으로 기록하겠습니다.

 

Ray Tracing 파이프라인에서는 레이 트레이싱 셰이더 그룹(rgen, rmiss, rchit)을 바인딩하기 위해

Shader Binding Table(SBT)을 사용합니다.

 

SBT는 Ray Tracing에서 레이 트레이싱 셰이더 그룹을 바인딩하는 데 사용되는 중요한 구조입니다.

SBT는 각 셰이더 그룹(예: raygen, miss, hit)을 고유하게 식별할 수 있는 핸들로 구성되어 있으며,

이를 통해 Ray Tracing 작업을 효율적으로 처리할 수 있습니다.

 

핸들을 메모리에 저장할 때 중요한 점은 핸들의 정렬입니다.

Vulkan에서는 메모리에서 핸들이 배치될 정렬 크기(alignment)를 반드시 맞춰야 하며,

그렇지 않으면 성능이 크게 저하될 수 있습니다.

 

shaderGroupHandleSize는 각 셰이더 그룹을 식별하는 데 필요한 메모리 크기이고,

shaderGroupBaseAlignment는 메모리에서 핸들이 배치될 정렬 크기입니다.

이 값을 바탕으로 정렬된 핸들 크기(handleSizeAligned)를 계산하고,

이 값을 사용하여 버퍼 크기를 설정합니다.

const uint32_t handleSize = rtProps.shaderGroupHandleSize;
const uint32_t baseAlignment = rtProps.shaderGroupBaseAlignment;
const uint32_t handleSizeAligned = (handleSize + baseAlignment - 1) & ~(baseAlignment - 1);

 

SBT 버퍼는 각 셰이더 그룹 핸들 정보를 저장하는데 사용되며,

이 정보는 GPU에서 레이 트레이싱 셰이더가 효율적으로 참조하고 작업을 진행할 수 있게 해줍니다.

SBT 버퍼는 버퍼 생성과 복사를 위한 staging 버퍼를 활용하여 메모리에 핸들을 작성하고,

최종적으로 GPU에 전달됩니다. 이 과정에서 GPU의 메모리 정렬과 핸들 정렬이 매우 중요합니다.

std::vector<uint8_t> handles(groupCount * handleSize);
if (g_vkGetRayTracingShaderGroupHandlesKHR(context->getDevice(), m_pipeline, 0, groupCount, handles.size(), handles.data()) != VK_SUCCESS) {
	throw std::runtime_error("failed to get shader group handles!");
}

 

핸들을 복사하는 과정에서 staging 버퍼를 사용하여 호스트 메모리에 먼저 저장하고, 이를 GPU 버퍼로 복사하여 최종적으로 SBT에 저장합니다.

이 버퍼는 GPU에서 처리할 수 있는 형태로 셰이더 그룹 핸들을 전달하는 효율적인 방법입니다.

 

SBT생성 전체 코드

const uint32_t groupCount = static_cast<uint32_t>(shaderGroups.size());

VkPhysicalDeviceRayTracingPipelinePropertiesKHR rtProps{};
rtProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;

VkPhysicalDeviceProperties2 props2{};
props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
props2.pNext = &rtProps;

vkGetPhysicalDeviceProperties2(context->getPhysicalDevice(), &props2);

const uint32_t handleSize = rtProps.shaderGroupHandleSize;
const uint32_t baseAlignment = rtProps.shaderGroupBaseAlignment;
const uint32_t handleSizeAligned = (handleSize + baseAlignment - 1) & ~(baseAlignment - 1);


std::vector<uint8_t> handles(groupCount * handleSize);
if (g_vkGetRayTracingShaderGroupHandlesKHR(context->getDevice(), m_pipeline, 0, groupCount, handles.size(), handles.data()) != VK_SUCCESS) {
	throw std::runtime_error("failed to get shader group handles!");
}

const uint32_t sbtSize = groupCount * handleSizeAligned;

VulkanUtil::createBuffer(
	context,
	sbtSize,
	VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
	VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
	m_sbtBuffer,
	m_sbtMemory
);

VkBuffer stagingBuffer;
VkDeviceMemory stagingMemory;
VulkanUtil::createBuffer(
	context,
	sbtSize,
	VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
	VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
	stagingBuffer,
	stagingMemory
);

void* mapped;
vkMapMemory(context->getDevice(), stagingMemory, 0, sbtSize, 0, &mapped);
for (uint32_t i = 0; i < groupCount; ++i) {
	memcpy(reinterpret_cast<uint8_t*>(mapped) + i * handleSizeAligned,
		handles.data() + i * handleSize,
		handleSize);
}
vkUnmapMemory(context->getDevice(), stagingMemory);

VulkanUtil::copyBuffer(context, stagingBuffer, m_sbtBuffer, sbtSize);

vkDestroyBuffer(context->getDevice(), stagingBuffer, nullptr);
vkFreeMemory(context->getDevice(), stagingMemory, nullptr);

VkDeviceAddress sbtAddress = VulkanUtil::getDeviceAddress(context, m_sbtBuffer);

m_raygenRegion = {
	sbtAddress + 0 * handleSizeAligned,
	handleSizeAligned,
	handleSizeAligned
};
m_missRegion = {
	sbtAddress + 1 * handleSizeAligned,
	handleSizeAligned,
	handleSizeAligned
};
m_hitRegion = {
	sbtAddress + 2 * handleSizeAligned,
	handleSizeAligned,
	handleSizeAligned
};

 

 

'Computer Graphics > VulkanPT' 카테고리의 다른 글

pt - 0. path tracing 렌더러 시작  (2) 2025.04.25
BRDF 정리  (1) 2025.04.22
The Rendering Equation 정리  (0) 2025.04.21
Vulkan에 Ray Tracing 도입 하기 (2)  (1) 2025.04.11
Vulkan에 Ray Tracing 도입 하기 (0)  (0) 2025.04.11