…
加速结构(AS)通过空间层次组织场景,减少射线求交测试。Vulkan 将其分为两层:BLAS 组织几何,TLAS 组织实例。
示例使用 Vulkan-Hpp,省略初始化和错误检查。createBuffer()、destroyBuffer()、submitAndWait() 为应用封装;buffer 封装负责分配、绑定和对齐,提供 .buffer 与 .address。
使用 C++20 designated initializer,按成员声明顺序初始化。首次包含 Vulkan-Hpp 前关闭构造函数:
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
1. BLAS 与 TLAS
一个 BLAS 可以包含多个 geometry。TLAS 的每个 instance 引用一个 BLAS,并指定变换、可见性 mask 等信息。
TLAS
├── instance 0:BLAS A,变换 T0
├── instance 1:BLAS A,变换 T1
└── instance 2:BLAS B,变换 T2
多个实例可以共享 BLAS。只改变物体变换时处理 TLAS;几何形状变化时处理对应 BLAS。下面以三角形为例。
2. 构建 BLAS
流程:描述几何 → 查询大小 → 分配资源 → 执行构建。
描述输入几何
vk::AccelerationStructureGeometryKHR 描述几何类型,以及顶点、索引的地址和格式。

vk::AccelerationStructureGeometryTrianglesDataKHR triangles{
.vertexFormat = desc.vertex_format,
.vertexData = {.deviceAddress = vertex_buffer.address + vertex_offset},
.vertexStride = desc.vertex_stride,
.maxVertex = vertex_count - 1,
.indexType = desc.index_type,
.indexData = {.deviceAddress = index_buffer.address + index_offset},
.transformData = {.deviceAddress = 0}, // 不提供额外几何变换,使用单位变换
};
vk::AccelerationStructureGeometryKHR geometry{
.geometryType = vk::GeometryTypeKHR::eTriangles,
.geometry = {.triangles = triangles},
.flags = vk::GeometryFlagBitsKHR::eOpaque,
};
vk::AccelerationStructureBuildRangeInfoKHR range{
.primitiveCount = index_count / 3,
};
示例假设索引三角形列表非空,地址已包含数据偏移。maxVertex 是最大顶点索引,vertexStride 是顶点步长(字节)。
输入 buffer 需要 eAccelerationStructureBuildInputReadOnlyKHR、eShaderDeviceAddress 及设备地址分配支持。
eOpaque 用于不透明几何;使用 any-hit 做 alpha test 时,需要检查几何、实例和射线的不透明性标志。
查询构建大小
build_info 指定 AS 类型、构建模式、优化标志和输入几何。
vk::AccelerationStructureBuildGeometryInfoKHR build_info{
.type = vk::AccelerationStructureTypeKHR::eBottomLevel,
.flags = vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace,
.mode = vk::BuildAccelerationStructureModeKHR::eBuild,
.geometryCount = 1,
.pGeometries = &geometry,
};
vk::AccelerationStructureBuildSizesInfoKHR sizes_info{};
const uint32_t max_primitive_count = range.primitiveCount;
device.getAccelerationStructureBuildSizesKHR(
vk::AccelerationStructureBuildTypeKHR::eDevice,
&build_info, &max_primitive_count, &sizes_info);
多个 geometry 需要对应的 geometry、range 和最大图元数量数组。geometryCount 是几何数量,不是 buffer 数量。
查询返回三种大小:
| 字段 | 用途 |
|---|---|
accelerationStructureSize | AS 构建结果所需的存储空间 |
buildScratchSize | BUILD 操作所需的临时工作空间 |
updateScratchSize | UPDATE 操作所需的临时工作空间 |
AS 内部布局由驱动管理。常见的构建标志选择如下:
| 类型 | 常见选择 | 原因 |
|---|---|---|
| 静态 BLAS | ePreferFastTrace | 构建少、遍历多 |
| 动态 BLAS | ePreferFastBuild | 几何频繁变化 |
| TLAS | ePreferFastBuild | 实例通常频繁变化 |
实际选择取决于构建与遍历的总耗时。UPDATE 和 COMPACT 分别要求初始构建加入 eAllowUpdate、eAllowCompaction。
分配存储
auto as_buffer = createBuffer(
sizes_info.accelerationStructureSize,
vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR |
vk::BufferUsageFlagBits::eShaderDeviceAddress);
vk::AccelerationStructureCreateInfoKHR create_info{
.buffer = as_buffer.buffer,
.offset = 0,
.size = sizes_info.accelerationStructureSize,
.type = vk::AccelerationStructureTypeKHR::eBottomLevel,
};
vk::AccelerationStructureKHR blas =
device.createAccelerationStructureKHR(create_info);
const auto scratch_alignment =
as_properties.minAccelerationStructureScratchOffsetAlignment;
auto scratch_buffer = createBuffer(
sizes_info.buildScratchSize,
vk::BufferUsageFlagBits::eStorageBuffer |
vk::BufferUsageFlagBits::eShaderDeviceAddress,
scratch_alignment);
创建 AS 对象不等于完成构建。 AS buffer 保存结果,scratch 提供临时空间。最终 scratch 地址必须对齐;手动偏移地址时,要为偏移后的完整区间预留空间。
执行构建

build_info.dstAccelerationStructure = blas;
build_info.scratchData.deviceAddress = scratch_buffer.address;
const vk::AccelerationStructureBuildRangeInfoKHR* ranges[] = {&range};
// 在记录构建命令前,建立输入数据写入 -> AS 构建读取的依赖。
command_buffer.buildAccelerationStructuresKHR(1, &build_info, ranges);
// 后续使用 AS 前,还需要相应的构建写入 -> 读取依赖。
submitAndWait(command_buffer);
destroyBuffer(scratch_buffer);
命令提交并完成后才能回收 scratch。示例直接等待,实际渲染器可用 fence 或 timeline semaphore 延迟回收。
3. 构建 TLAS
TLAS 的构建流程与 BLAS 相同,输入换成 vk::AccelerationStructureInstanceKHR 数组。

描述实例
vk::AccelerationStructureInstanceKHR instance{
.transform = desc.transform_matrix,
.instanceCustomIndex = desc.mesh_index,
.mask = 0xFF,
.instanceShaderBindingTableRecordOffset = 0,
.flags = static_cast<VkGeometryInstanceFlagsKHR>(
vk::GeometryInstanceFlagBitsKHR::eTriangleFacingCullDisable),
.accelerationStructureReference =
device.getAccelerationStructureDeviceAddressKHR(
vk::AccelerationStructureDeviceAddressInfoKHR{
.accelerationStructure = blas,
}),
};
| 字段 | 含义 |
|---|---|
transform | BLAS 局部坐标到场景坐标的 3×4 矩阵,注意存储顺序 |
instanceCustomIndex | 应用定义的 24 位索引 |
mask | 与射线的 cull mask 按位与,结果为 0 时跳过实例 |
instanceShaderBindingTableRecordOffset | 命中组 SBT 索引计算中的记录偏移 |
accelerationStructureReference | BLAS 的 AS 地址,不是 buffer 地址 |
将实例上传到具备 eAccelerationStructureBuildInputReadOnlyKHR 和 eShaderDeviceAddress 用途的 buffer,并完成上传同步。
描述实例数组并构建
vk::AccelerationStructureGeometryKHR instances_geometry{
.geometryType = vk::GeometryTypeKHR::eInstances,
.geometry = {
.instances = {
.arrayOfPointers = false,
.data = {.deviceAddress = instances_buffer.address},
},
},
};
vk::AccelerationStructureBuildRangeInfoKHR tlas_range{
.primitiveCount = instance_count,
};
vk::AccelerationStructureBuildGeometryInfoKHR tlas_build_info{
.type = vk::AccelerationStructureTypeKHR::eTopLevel,
.flags = vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastBuild,
.mode = vk::BuildAccelerationStructureModeKHR::eBuild,
.geometryCount = 1,
.pGeometries = &instances_geometry,
};
TLAS 的 geometryCount = 1,primitiveCount 是实例数量,arrayOfPointers = false 表示连续数组。随后查询大小、分配 TLAS 和 scratch,再执行构建。
BLAS 构建与引用它的 TLAS 构建必须建立依赖,不能放在同一次 build 调用中。构建命令规则
4. UPDATE 与重建
初始构建包含 eAllowUpdate 时,可在满足兼容条件的前提下执行 UPDATE。更新输入数据后,沿用原有 geometry、range 和构建参数:
build_info.mode = vk::BuildAccelerationStructureModeKHR::eUpdate;
build_info.srcAccelerationStructure = blas;
build_info.dstAccelerationStructure = blas; // 原地更新
build_info.scratchData.deviceAddress = update_scratch_address;
const vk::AccelerationStructureBuildRangeInfoKHR* ranges[] = {&range};
command_buffer.buildAccelerationStructuresKHR(1, &build_info, ranges);
更新 scratch 仍需对齐,大小至少为 updateScratchSize。
| 场景变化 | 通常需要处理的层级 |
|---|---|
| 物体平移、旋转或缩放,几何不变 | 更新或重建 TLAS,复用 BLAS |
| 顶点位置改变,三角形连接关系不变 | 符合限制时 UPDATE BLAS;若实例包围范围变化,再更新或重建 TLAS |
| 三角形连接关系、图元数量或 geometry 数量改变 | 重新 BUILD 对应 BLAS,并处理受影响的 TLAS |
| 实例数量改变 | 重新 BUILD TLAS |
UPDATE 受格式、数量、拓扑和活动状态等兼容条件限制。大幅形变可能降低遍历效率,应同时评估重建成本与 trace 性能。
5. BLAS 压缩
初始构建包含 eAllowCompaction 后,可以查询压缩大小,再用 COMPACT copy 生成更紧凑的 BLAS:
构建 BLAS(ALLOW_COMPACTION)
→ 写入 compacted-size query
→ 等待并读取查询结果
→ 分配目标存储并创建新 BLAS
→ 记录 COMPACT copy
→ 完成复制并切换引用
→ 安全回收旧 BLAS 与旧存储
查询压缩后的大小
创建 eAccelerationStructureCompactedSizeKHR query pool,确保 BLAS 构建结果可见后查询:
command_buffer.resetQueryPool(compaction_query_pool, 0, 1);
command_buffer.writeAccelerationStructuresPropertiesKHR(
1, &blas,
vk::QueryType::eAccelerationStructureCompactedSizeKHR,
compaction_query_pool, 0);
submitAndWait(command_buffer);
vk::DeviceSize compacted_size = 0;
const vk::Result query_result = device.getQueryPoolResults(
compaction_query_pool, 0, 1,
sizeof(compacted_size), &compacted_size, sizeof(compacted_size),
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
检查查询结果,按 compacted_size 分配存储并创建 compacted_blas;收益不明显时可跳过压缩。
复制并切换引用
vk::CopyAccelerationStructureInfoKHR copy_info{
.src = blas,
.dst = compacted_blas,
.mode = vk::CopyAccelerationStructureModeKHR::eCompact,
};
command_buffer.copyAccelerationStructureKHR(copy_info);
submitAndWait(command_buffer);
COMPACT 是 AS 专用复制操作,不能用普通 buffer copy 代替。复制命令说明
压缩后 BLAS 地址改变。 最好先压缩,再构建 TLAS;已有 TLAS 则需换用新地址并更新或重建。复制和旧 AS 的在途操作结束后,才能回收旧存储。压缩期间新旧存储同时存在,需考虑显存峰值。
延伸阅读
- Vulkan 官方教程:Acceleration Structures:BLAS/TLAS 的基础构建流程。
- NVIDIA Vulkan Ray Tracing Tutorial:Acceleration Structures:结合 sample 理解构建与资源管理。
- nvpro_core2 acceleration_structures.hpp:构建、批处理与压缩的 helper 接口。
- NVIDIA RTXMU:面向光追加速结构的内存管理库。