如何将多个结构加载到单个 UBO 中?

How do I load multiple structs into a single UBO?

我正在学习以下教程:Here

我已经完成加载模型,所以我的代码与那一点类似。

我现在正尝试将另一个结构传递给统一缓冲区对象,方法与之前显示的类似。

我创建了另一个在应用程序外部定义的结构来存储数据,如下所示:

struct Light{
    alignas(16) glm::vec3 position;
    alignas(16) glm::vec3 colour;
};

完成此操作后,我按以下方式调整统一缓冲区大小:

    void createUniformBuffers() {
        VkDeviceSize bufferSize = sizeof(CameraUBO) + sizeof(Light);
    ...

接下来,在创建描述符集时,我在已经定义的 bufferInfo 下面添加了 lightBufferInfo,如下所示:

        ...
        for (size_t i = 0; i < swapChainImages.size(); i++) {
            VkDescriptorBufferInfo bufferInfo = {};
            bufferInfo.buffer = uniformBuffers[i];
            bufferInfo.offset = 0;
            bufferInfo.range = sizeof(UniformBufferObject);

            VkDescriptorBufferInfo lightBufferInfo = {};
            lightBufferInfo.buffer = uniformBuffers[i];
            lightBufferInfo.offset = 0;
            lightBufferInfo.range = sizeof(Light);

        ...

然后我将其添加到 descriptorWrites 数组中:

        ...
        descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        descriptorWrites[2].dstSet = descriptorSets[i];
        descriptorWrites[2].dstBinding = 2;
        descriptorWrites[2].dstArrayElement = 0;
        descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
        descriptorWrites[2].descriptorCount = 1;
        descriptorWrites[2].pBufferInfo = &lightBufferInfo;
        ...

现在与 UniformBufferObject 类似,我打算使用 updateUniformBuffer(uint32_t currentImage) 函数来更改灯的位置和颜色,但首先我只是尝试将位置设置为所需的值:

    void updateUniformBuffer(uint32_t currentImage) {
        ...
        ubo.proj[1][1] *= -1;

        Light light = {};
        light.position = glm::vec3(0, 10, 10);
        light.colour = glm::vec3(1, 1, 0);

        void* data;
        vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data);
        memcpy(data, &ubo, sizeof(ubo));
        vkUnmapMemory(device, uniformBuffersMemory[currentImage]);
    }

我不明白在尝试将两个对象传递到统一缓冲区时偏移是如何工作的,所以我不知道如何将灯光对象复制到 uniformBuffersMemory

如何定义偏移量才能使其正常工作?

进一步阅读前的注意事项:将单个 UBO 的数据拆分为两个不同的结构和描述符会使传递数据变得更加复杂,因为您的所有大小和写入都需要与 minUniformBufferAlignment 属性 你的设备,使你的代码有点复杂。如果您开始使用 Vulkan,您可能希望将数据拆分为两个 UBO(创建两个缓冲区),或者只是将所有值作为一个结构传递。

但是如果您想继续您在 post 中描述的方式:

首先,您需要适当调整数组的大小,因为您的副本需要与 minUniformBufferAlignment 对齐,您可能无法将光数据复制到紧跟在其他数据之后的区域。如果你的设备有一个 256 字节的 minUniformBufferAlignment 并且你想复制两个主机结构你的统一缓冲区大小需要至少 2 * 256 字节而不仅仅是 sizeof(matrices) + sizeof(lights).因此,您需要相应地调整 VkDeviceSize 结构中的 bufferSize

接下来你需要偏移你的 lightBufferInfo VkDescriptorBufferInfo:

lightBufferInfo.offset = std::max(sizeof(Light), minUniformBufferOffsetAlignment);

这将使您的顶点着色器知道从哪里开始为该绑定获取数据。

在大多数 NVidia GPU 上,例如,minUniformBufferOffsetAlignment 是 256 字节,而您的 Light 结构的大小是 32 字节。因此,要在这样的 GPU 上进行这项工作,您必须以 256 字节而不是 32 字节对齐。

在 RenderDoc 中检查您的设置应该与此类似:

请注意,对于更复杂的分配和场景,您需要根据数据结构的大小正确获得正确的对齐大小,而不是像上面那样使用简单的 max

现在更新统一缓冲区时,您也需要映射和复制到正确的偏移量:

void* mapped = nullptr;

// Copy matrix data to offset for binding 0
vkMapMemory(device, uniformBuffersMemory[currentImage].memory, 0, sizeof(ubo), 0, &mapped);
memcpy(mapped, &ubo, sizeof(ubo));
vkUnmapMemory(device, uniformBuffersMemory[currentImage].memory);

// Copy light data to offset for binding 1 
vkMapMemory(device, uniformBuffersMemory[currentImage].memory, std::max(sizeof(ubo), minUniformBufferOffsetAlignment), sizeof(Light), 0, &mapped);

memcpy(mapped, &uboLight, sizeof(Light));
vkUnmapMemory(device, uniformBuffersMemory[currentImage].memory);

请注意,出于性能原因,您可能希望在创建缓冲区后只映射一次,而不是在每次更新时都映射。只需将偏移指针存储在代码中的某处。