一、前言在 Vulkan 中如果我们想给 Shader 传递一些每帧、每个物体都会变化的数据最常见的方式之一就是使用 Uniform Buffer。例如在渲染 100 个物体时每个物体都有自己的模型矩阵model但它们共享同一个相机矩阵view和投影矩阵projection。如果用最朴素的方式我们可能会为每个物体创建一个独立的 Uniform Buffer或者为每个物体创建一套独立的 Descriptor Set。这种方式可以工作但并不优雅Descriptor Set 数量会变多Buffer 对象数量会变多CPU 更新和管理成本会上升渲染大量物体时绑定开销和资源管理复杂度会明显增加。因此 Vulkan 提供了 Dynamic Uniform Buffer也就是动态 uniform buffer。它允许我们把多个物体的 Uniform 数据放进一个大的 Buffer 中在绘制不同物体时只通过一个动态偏移量dynamic offset指向当前物体的数据。一句话概括Dynamic Uniform Buffer 的核心思想是“一个大 Uniform Buffer存放多个对象的数据绘制时通过动态 offset 选择其中某一段数据。”二、普通 Uniform Buffer 的问题假设我们有如下 UBO 结构struct ObjectUBO { glm::mat4 model; };每个物体都需要一个不同的model矩阵。如果场景中有 100 个物体普通做法可能是Object 0 - UniformBuffer 0 - DescriptorSet 0 Object 1 - UniformBuffer 1 - DescriptorSet 1 Object 2 - UniformBuffer 2 - DescriptorSet 2 ... Object 99 - UniformBuffer 99 - DescriptorSet 99这样做的问题很明显资源数量太多。更好的想法是一个大 Buffer ------------------ | Object 0 的 UBO | ------------------ | Object 1 的 UBO | ------------------ | Object 2 的 UBO | ------------------ | ... | ------------------ | Object 99 的 UBO | ------------------绘制第 0 个物体时Shader 读取 Buffer 的第 0 段。绘制第 1 个物体时Shader 读取 Buffer 的第 1 段。绘制第 99 个物体时Shader 读取 Buffer 的第 99 段。这就是 Dynamic Uniform Buffer 要解决的问题。三、Dynamic Uniform Buffer 是什么Dynamic Uniform Buffer 使用的 Descriptor 类型是VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC它和普通 Uniform Buffer 的区别在于普通 Uniform BufferVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER绑定 Descriptor Set 后Shader 读取的位置基本固定。动态 Uniform BufferVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC绑定 Descriptor Set 时可以额外传入一个动态偏移量vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, descriptorSet, 1, dynamicOffset );这里的dynamicOffset会告诉 Vulkan“这一次绘制时不要从 Buffer 开头读取而是从 Buffer 的某个偏移位置开始读取。”所以它适合这种场景同一个 Descriptor Set 同一个大 Buffer 不同 draw call 使用不同 dynamic offset四、Dynamic Uniform Buffer 的整体结构一个典型的 Dynamic Uniform Buffer 渲染流程如下CPU 端 1. 创建一个大的 VkBuffer 2. 按照对齐要求切分 Buffer 3. 把每个物体的 UBO 数据写入不同位置 Descriptor 端 4. Descriptor 类型设置为 VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC 5. Descriptor 指向整个大 Buffer或者指向其中一个逻辑范围 绘制端 6. 每绘制一个物体计算 dynamicOffset 7. 调用 vkCmdBindDescriptorSets 传入 dynamicOffset 8. 调用 vkCmdDraw / vkCmdDrawIndexed可以理解为VkBuffer: -------------------------- offset 0 * alignedSize | Object 0 UBO | -------------------------- offset 1 * alignedSize | Object 1 UBO | -------------------------- offset 2 * alignedSize | Object 2 UBO | -------------------------- | ... | -------------------------- 绘制 Object i dynamicOffset i * alignedSize五、为什么需要对齐Dynamic Uniform Buffer 最容易出错的地方就是对齐。Vulkan 设备会规定一个限制VkPhysicalDeviceLimits::minUniformBufferOffsetAlignment这个值表示 dynamic uniform buffer 的 offset 必须满足的最小对齐要求。例如某些 GPU 上minUniformBufferOffsetAlignment 256如果你的 UBO 结构大小是sizeof(ObjectUBO) 64你不能简单地让Object 0 offset 0 Object 1 offset 64 Object 2 offset 128 Object 3 offset 192因为 64、128、192 不一定满足设备要求。若设备要求 256 字节对齐那么合法布局应该是Object 0 offset 0 Object 1 offset 256 Object 2 offset 512 Object 3 offset 768虽然中间会浪费一些空间但这是 Vulkan 对动态 UBO 的硬性要求。六、计算对齐后的 UBO 大小通常我们会写一个函数来计算对齐后的大小VkDeviceSize getAlignedSize(VkDeviceSize originalSize, VkDeviceSize alignment) { if (alignment 0) { return originalSize; } return (originalSize alignment - 1) ~(alignment - 1); }使用方式VkPhysicalDeviceProperties deviceProperties{}; vkGetPhysicalDeviceProperties(physicalDevice, deviceProperties); VkDeviceSize minAlignment deviceProperties.limits.minUniformBufferOffsetAlignment; VkDeviceSize objectUBOSize sizeof(ObjectUBO); VkDeviceSize dynamicAlignment getAlignedSize(objectUBOSize, minAlignment);如果sizeof(ObjectUBO) 64 minUniformBufferOffsetAlignment 256那么dynamicAlignment 256最终大 Buffer 的总大小VkDeviceSize bufferSize objectCount * dynamicAlignment;七、Shader 中如何声明在 GLSL 中Dynamic Uniform Buffer 和普通 Uniform Buffer 的写法没有本质区别。例如顶点着色器#version 450 layout(location 0) in vec3 inPosition; layout(location 1) in vec3 inColor; layout(set 0, binding 0) uniform CameraUBO { mat4 view; mat4 proj; } cameraUBO; layout(set 0, binding 1) uniform ObjectUBO { mat4 model; } objectUBO; layout(location 0) out vec3 fragColor; void main() { gl_Position cameraUBO.proj * cameraUBO.view * objectUBO.model * vec4(inPosition, 1.0); fragColor inColor; }注意layout(set 0, binding 1) uniform ObjectUBO { mat4 model; } objectUBO;这段 Shader 并不知道自己读取的是普通 UBO 还是 Dynamic UBO。Dynamic 的概念主要发生在 Vulkan API 绑定 Descriptor 的时候。八、Descriptor Set Layout 配置创建 Descriptor Set Layout 时需要把某个 binding 设置为VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC例如VkDescriptorSetLayoutBinding cameraLayoutBinding{}; cameraLayoutBinding.binding 0; cameraLayoutBinding.descriptorType VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; cameraLayoutBinding.descriptorCount 1; cameraLayoutBinding.stageFlags VK_SHADER_STAGE_VERTEX_BIT; cameraLayoutBinding.pImmutableSamplers nullptr; VkDescriptorSetLayoutBinding objectLayoutBinding{}; objectLayoutBinding.binding 1; objectLayoutBinding.descriptorType VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; objectLayoutBinding.descriptorCount 1; objectLayoutBinding.stageFlags VK_SHADER_STAGE_VERTEX_BIT; objectLayoutBinding.pImmutableSamplers nullptr; std::arrayVkDescriptorSetLayoutBinding, 2 bindings { cameraLayoutBinding, objectLayoutBinding }; VkDescriptorSetLayoutCreateInfo layoutInfo{}; layoutInfo.sType VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount static_castuint32_t(bindings.size()); layoutInfo.pBindings bindings.data(); VkDescriptorSetLayout descriptorSetLayout; vkCreateDescriptorSetLayout( device, layoutInfo, nullptr, descriptorSetLayout );这里有两个 bindingbinding 0 - 普通 Uniform Buffer存放相机数据 binding 1 - Dynamic Uniform Buffer存放每个物体的数据通常相机数据每帧只需要一份而物体数据需要很多份所以物体矩阵更适合放进 Dynamic Uniform Buffer。九、创建 Dynamic Uniform Buffer首先定义每个物体的数据结构struct ObjectUBO { glm::mat4 model; };然后根据物体数量计算 Buffer 大小uint32_t objectCount 100; VkDeviceSize objectUBOSize sizeof(ObjectUBO); VkPhysicalDeviceProperties properties{}; vkGetPhysicalDeviceProperties(physicalDevice, properties); VkDeviceSize minAlignment properties.limits.minUniformBufferOffsetAlignment; VkDeviceSize dynamicAlignment getAlignedSize(objectUBOSize, minAlignment); VkDeviceSize bufferSize objectCount * dynamicAlignment;然后创建 BuffercreateBuffer( bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, dynamicUniformBuffer, dynamicUniformBufferMemory );这里为了简单演示使用了VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT VK_MEMORY_PROPERTY_HOST_COHERENT_BIT这表示 CPU 可以直接映射并写入这块内存。在性能要求更高的项目中也可以使用 staging buffer 或者 ring buffer 等方式进一步优化。十、写入多个物体的 UBO 数据因为每个物体的数据之间需要按照dynamicAlignment对齐所以不能直接使用普通数组下标写入。正确写法通常是void* data nullptr; vkMapMemory( device, dynamicUniformBufferMemory, 0, bufferSize, 0, data ); for (uint32_t i 0; i objectCount; i) { ObjectUBO objectUBO{}; objectUBO.model glm::mat4(1.0f); objectUBO.model glm::translate( objectUBO.model, glm::vec3(i * 2.0f, 0.0f, 0.0f) ); char* destination reinterpret_castchar*(data) i * dynamicAlignment; memcpy(destination, objectUBO, sizeof(ObjectUBO)); } vkUnmapMemory(device, dynamicUniformBufferMemory);关键是这一句char* destination reinterpret_castchar*(data) i * dynamicAlignment;它表示第i个物体的 UBO 数据写入到i * dynamicAlignment这个偏移位置。十一、更新 Descriptor SetDynamic Uniform Buffer 仍然需要写入 Descriptor Set。VkDescriptorBufferInfo cameraBufferInfo{}; cameraBufferInfo.buffer cameraUniformBuffer; cameraBufferInfo.offset 0; cameraBufferInfo.range sizeof(CameraUBO); VkDescriptorBufferInfo objectBufferInfo{}; objectBufferInfo.buffer dynamicUniformBuffer; objectBufferInfo.offset 0; objectBufferInfo.range sizeof(ObjectUBO);然后写入 Descriptorstd::arrayVkWriteDescriptorSet, 2 descriptorWrites{}; descriptorWrites[0].sType VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[0].dstSet descriptorSet; descriptorWrites[0].dstBinding 0; descriptorWrites[0].dstArrayElement 0; descriptorWrites[0].descriptorType VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorWrites[0].descriptorCount 1; descriptorWrites[0].pBufferInfo cameraBufferInfo; descriptorWrites[1].sType VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[1].dstSet descriptorSet; descriptorWrites[1].dstBinding 1; descriptorWrites[1].dstArrayElement 0; descriptorWrites[1].descriptorType VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; descriptorWrites[1].descriptorCount 1; descriptorWrites[1].pBufferInfo objectBufferInfo; vkUpdateDescriptorSets( device, static_castuint32_t(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr );这里需要注意objectBufferInfo.offset 0; objectBufferInfo.range sizeof(ObjectUBO);很多初学者会疑惑为什么 offset 不写成某个物体的偏移原因是Descriptor Set 只描述这个 Buffer 的基本绑定信息真正选择第几个物体的数据是在绘制时通过dynamicOffset完成的。十二、绘制时传入 dynamic offset绘制多个物体时核心代码如下for (uint32_t i 0; i objectCount; i) { uint32_t dynamicOffset static_castuint32_t(i * dynamicAlignment); vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, descriptorSet, 1, dynamicOffset ); vkCmdDrawIndexed( commandBuffer, indexCount, 1, 0, 0, 0 ); }重点是uint32_t dynamicOffset static_castuint32_t(i * dynamicAlignment);第 0 个物体dynamicOffset 0第 1 个物体dynamicOffset dynamicAlignment第 2 个物体dynamicOffset 2 * dynamicAlignment第 N 个物体dynamicOffset N * dynamicAlignment这样所有物体都使用同一个 Descriptor Set但每次 draw call 读取的 UBO 数据不同。十三、Dynamic Offset 的绑定顺序如果一个 Descriptor Set 中有多个 Dynamic Uniform Buffer或者同时使用 Dynamic Storage Buffer那么dynamicOffsets数组的顺序必须和 Descriptor Set Layout 中动态 descriptor 的顺序一致。例如set 0, binding 0 - 普通 UBO set 0, binding 1 - Dynamic UBO set 0, binding 2 - Dynamic UBO那么绑定时需要传入两个 dynamic offsetuint32_t dynamicOffsets[2] { offsetForBinding1, offsetForBinding2 };调用vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, descriptorSet, 2, dynamicOffsets );如果只有一个 Dynamic Uniform Buffer那么就只传一个 offset。十四、完整绘制逻辑示意整体渲染流程可以总结为初始化阶段 1. 获取 minUniformBufferOffsetAlignment 2. 计算 dynamicAlignment 3. 创建 objectCount * dynamicAlignment 大小的 VkBuffer 4. 创建 Descriptor Set Layout 5. binding 使用 VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC 6. 分配 Descriptor Set 7. vkUpdateDescriptorSets 写入 Buffer 信息 每帧更新阶段 1. 更新 CameraUBO 2. 遍历所有物体 3. 将每个 ObjectUBO 写入大 Buffer 的对应偏移位置 命令录制阶段 1. 绑定 Pipeline 2. 绑定 Vertex Buffer / Index Buffer 3. 遍历所有物体 4. 计算 dynamicOffset i * dynamicAlignment 5. vkCmdBindDescriptorSets 传入 dynamicOffset 6. vkCmdDrawIndexed十五、Dynamic Uniform Buffer 与 Push Constants 的区别Dynamic Uniform Buffer 和 Push Constants 都可以用于传递小规模数据但两者定位不同。1. Push ConstantsPush Constants 适合传递非常小、频繁变化的数据。例如struct PushConstantData { glm::mat4 model; };优点1. 使用简单 2. 不需要创建 Buffer 3. 更新开销低 4. 非常适合少量数据缺点1. 容量很小 2. 设备限制通常比较严格 3. 不适合大量物体数据2. Dynamic Uniform BufferDynamic Uniform Buffer 适合存储较多对象的 per-object 数据。优点1. 可以存放大量物体数据 2. Descriptor Set 数量少 3. 适合批量渲染多个对象 4. 资源管理比每物体一个 UBO 更整洁缺点1. 需要处理内存对齐 2. 需要手动计算 dynamic offset 3. 如果每个 draw call 都绑定 descriptor仍然存在一定 CPU 开销简单选择原则数据很小、对象数量少Push Constants 对象数量多、每个对象都有独立矩阵/材质参数Dynamic Uniform Buffer 数据量更大、结构更复杂Storage Buffer十六、Dynamic Uniform Buffer 与 Storage Buffer 的区别Dynamic Uniform Buffer 的数据通常只读并且受Uniform Buffer相关限制约束。Storage Buffer 使用VK_DESCRIPTOR_TYPE_STORAGE_BUFFER或者VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMICStorage Buffer 的容量通常更大也更加灵活可以在 Shader 中读写。对比Uniform Buffer 适合小规模、结构清晰、频繁读取的常量数据。 例如 view/proj/model、材质参数、灯光参数等。 Storage Buffer 适合大规模数组数据、实例数据、粒子数据、骨骼矩阵、GPU 计算结果等。如果只是传递每个物体的model matrixDynamic Uniform Buffer 是很合理的选择。如果要传递成千上万个实例的数据或者数据结构非常大Storage Buffer 往往更合适。十七、常见错误与排查方法错误一没有按照 minUniformBufferOffsetAlignment 对齐错误写法dynamicOffset i * sizeof(ObjectUBO);正确写法dynamicOffset i * dynamicAlignment;其中dynamicAlignment sizeof(ObjectUBO)并且满足设备对齐要求。错误二Descriptor 类型写错如果 Descriptor Set Layout 中写成了VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER但绑定时却传入 dynamic offset就会出现错误。Dynamic Uniform Buffer 必须使用VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC错误三dynamicOffset 数量不匹配如果 Descriptor Set 中有一个 dynamic descriptor那么dynamicOffsetCount 1如果有两个 dynamic descriptor那么dynamicOffsetCount 2不能多也不能少。错误四Buffer 总大小不足假设objectCount 100 dynamicAlignment 256那么 Buffer 至少需要100 * 256 25600 bytes如果只分配100 * sizeof(ObjectUBO)很容易越界或者造成渲染异常。错误五range 设置不合理DescriptorBufferInfo 中的objectBufferInfo.range通常设置为单个对象 UBO 的大小objectBufferInfo.range sizeof(ObjectUBO);也可以根据需求设置为更大的范围但需要确保 offset 和 range 访问不越界。对于初学者建议先使用offset 0 range sizeof(ObjectUBO)然后通过 dynamic offset 指向具体对象。错误六CPU 写入数据后 GPU 没有正确看到如果使用的内存不是HOST_COHERENT那么 CPU 写入后需要调用vkFlushMappedMemoryRanges如果使用VK_MEMORY_PROPERTY_HOST_COHERENT_BIT则通常不需要手动 flush。不过从工程角度看仍然要理解 Vulkan 的显式同步模型。Vulkan 不会自动帮你处理所有 CPU/GPU 数据可见性问题。十八、一个更完整的类设计思路可以将 Dynamic Uniform Buffer 封装成一个类class DynamicUniformBuffer { public: void create( VkPhysicalDevice physicalDevice, VkDevice device, uint32_t objectCount ); void updateObject(uint32_t index, const ObjectUBO ubo); VkBuffer getBuffer() const; VkDeviceSize getDynamicAlignment() const; VkDeviceSize getOffset(uint32_t index) const; private: VkDevice device VK_NULL_HANDLE; VkBuffer buffer VK_NULL_HANDLE; VkDeviceMemory memory VK_NULL_HANDLE; void* mapped nullptr; uint32_t objectCount 0; VkDeviceSize objectSize sizeof(ObjectUBO); VkDeviceSize dynamicAlignment 0; VkDeviceSize bufferSize 0; };更新某个对象void DynamicUniformBuffer::updateObject( uint32_t index, const ObjectUBO ubo ) { char* destination reinterpret_castchar*(mapped) index * dynamicAlignment; memcpy(destination, ubo, sizeof(ObjectUBO)); }获取 offsetVkDeviceSize DynamicUniformBuffer::getOffset(uint32_t index) const { return index * dynamicAlignment; }绘制时uint32_t dynamicOffset static_castuint32_t(dynamicUBO.getOffset(i)); vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, descriptorSet, 1, dynamicOffset );这样可以让主渲染逻辑更清晰。十九、适合使用 Dynamic Uniform Buffer 的场景Dynamic Uniform Buffer 特别适合以下场景1. 多个物体共享同一个 Pipeline 2. 多个物体共享同一个 Descriptor Set Layout 3. 每个物体都有不同的 model 矩阵 4. 每个物体有少量独立材质参数 5. 想减少 Descriptor Set 数量 6. 想把 per-object 数据集中管理 7. 不想为每个物体单独创建一个 Uniform Buffer。例如渲染 100 个立方体 渲染多个 glTF 节点 渲染多个模型实例 渲染多个带有不同材质参数的小物体 渲染场景中的多个 transform object。二十、不适合使用 Dynamic Uniform Buffer 的场景Dynamic Uniform Buffer 并不是万能的。以下场景可能不适合1. 数据量特别大 2. 每个对象的数据结构复杂且变化频繁 3. 需要在 Shader 中随机访问大量对象数据 4. 需要 GPU 端写入数据 5. 想做大规模 GPU-driven rendering 6. 每次 draw call 绑定 dynamic offset 成为 CPU 瓶颈。这些情况下可以考虑1. Storage Buffer 2. Instancing 3. Push Constants 4. Descriptor Indexing 5. GPU-driven rendering 6. Multi-draw indirect。二十一、Dynamic Uniform Buffer 和 glTF 模型渲染在 glTF 模型加载中Dynamic Uniform Buffer 很常见。一个 glTF 文件通常由多个 Node 组成每个 Node 都有自己的变换矩阵glTF Scene ├── Node 0 - model matrix 0 ├── Node 1 - model matrix 1 ├── Node 2 - model matrix 2 └── Node 3 - model matrix 3如果每个 Node 都创建一个单独的 UBO会让资源管理变复杂。更好的方式是CameraUBO 存放 view / projection Dynamic ObjectUBO 存放每个 node 的 model matrix MaterialUBO 或 StorageBuffer 存放材质参数绘制 glTF Node 时for (uint32_t nodeIndex 0; nodeIndex nodes.size(); nodeIndex) { uint32_t dynamicOffset static_castuint32_t(nodeIndex * dynamicAlignment); vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, descriptorSet, 1, dynamicOffset ); drawNode(commandBuffer, nodes[nodeIndex]); }这样每个 Node 使用同一个 Descriptor Set但是通过不同的 dynamic offset 读取不同的模型矩阵。二十二、性能分析Dynamic Uniform Buffer 的主要收益是减少资源绑定复杂度而不是完全消除 draw call 开销。它可以减少1. Descriptor Set 数量 2. Uniform Buffer 对象数量 3. Descriptor 更新次数 4. CPU 端资源管理复杂度。但是它不能减少1. draw call 数量 2. 每次 vkCmdBindDescriptorSets 的调用 3. Pipeline 切换成本 4. 顶点处理成本 5. 片元处理成本。如果你的场景中有大量相同 Mesh 的实例Instancing 可能更合适。如果你的场景中有大量不同 Mesh并且追求极限性能则可能需要进一步考虑1. Multi Draw Indirect 2. GPU Culling 3. Descriptor Indexing 4. Bindless Resource 5. Mesh Shader 6. GPU-driven Pipeline。Dynamic Uniform Buffer 更像是 Vulkan 初中级阶段非常实用的一种资源组织方法。二十三、推荐的资源组织方式对于一个基础 Vulkan Renderer可以这样设计 Descriptorset 0Frame / Camera 级别数据 binding 0CameraUBO binding 1LightUBO set 1Object 级别数据 binding 0Dynamic ObjectUBO set 2Material / Texture 级别数据 binding 0BaseColor Texture binding 1Normal Texture binding 2Sampler也可以简化成set 0 binding 0CameraUBO binding 1Dynamic ObjectUBO binding 2Sampler binding 3Texture对于初学项目后一种更容易实现。对于较大型引擎前一种分层方式更清晰。二十四、最小示例总结1. 定义 UBOstruct ObjectUBO { glm::mat4 model; };2. 获取对齐要求VkPhysicalDeviceProperties properties{}; vkGetPhysicalDeviceProperties(physicalDevice, properties); VkDeviceSize alignment properties.limits.minUniformBufferOffsetAlignment;3. 计算对齐大小VkDeviceSize dynamicAlignment getAlignedSize(sizeof(ObjectUBO), alignment);4. 创建大 BufferVkDeviceSize bufferSize objectCount * dynamicAlignment;5. Descriptor 类型descriptorType VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;6. 写 DescriptorbufferInfo.buffer dynamicUniformBuffer; bufferInfo.offset 0; bufferInfo.range sizeof(ObjectUBO);7. 绘制时传入 offsetuint32_t dynamicOffset i * dynamicAlignment; vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, descriptorSet, 1, dynamicOffset );二十五、结语Dynamic Uniform Buffer 是 Vulkan 中非常重要的资源管理技术。它的核心并不复杂把多个对象的 Uniform 数据放进一个大 Buffer 每个对象的数据按照设备要求对齐 绘制时通过 dynamic offset 指向当前对象的数据。它解决的是“多个对象如何高效共享一个 Descriptor Set 和一个 Uniform Buffer”的问题。对于 Vulkan 初学者来说掌握 Dynamic Uniform Buffer 具有很高的实践价值。它不仅能帮助我们理解 Vulkan 的 Descriptor 系统也能为后续学习 glTF 渲染、实例化渲染、材质系统、GPU-driven rendering 打下基础。可以这样记住它普通 Uniform Buffer绑定一次读取固定位置。 Dynamic Uniform Buffer绑定同一个 Buffer但每次绘制可以动态改变读取位置。在真实项目中Dynamic Uniform Buffer 常用于1. 每个物体的 model 矩阵 2. 每个物体的材质参数 3. 每个 glTF node 的变换数据 4. 多对象共享 Descriptor Set 的渲染系统。只要注意对齐、Buffer 大小、Descriptor 类型和 dynamic offset 的正确使用Dynamic Uniform Buffer 就是 Vulkan 中非常稳定且高效的一种资源组织方式。