如何将多个结构加载到单个 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);
请注意,出于性能原因,您可能希望在创建缓冲区后只映射一次,而不是在每次更新时都映射。只需将偏移指针存储在代码中的某处。
我正在学习以下教程: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);
请注意,出于性能原因,您可能希望在创建缓冲区后只映射一次,而不是在每次更新时都映射。只需将偏移指针存储在代码中的某处。