Vulkan:用dynamic uniform绘制多个物体

vulkan代码按照这个教程的基础上进行拓展
https://vulkan-tutorial.com/

参考了知乎大佬的笔记
和github大佬的代码

需要将vulkan与游戏引擎向结合,最基础的就是要把每一个game object画出来
基础教程中只画了一个图形,而vulkan的draw又是用command buffer预先录制,所以不能采用OpenGL那种更新一次uniform再draw一次的方法,效率也不高。
Vulkan可以用dynamic uniform来做这件事情。

1.申请uniform buffer
定义Dynamic Uniform Buffer Object

struct DynamicUBO {
    glm::mat4 *model = nullptr;
};

新增一组空指针来保存地址, 并申明dynamic ubo变量,以及2个对齐变量,等等

std::vector dynamicUniformData;
size_t dynamicAlignment;
size_t normalUBOAlignment;
DynamicUBO uboDynamic;

vulkan 内存管理,最好申请一块buffer,用offset的形式来使用,但是Vertex, Index buffer和uniform buffer的usage和property flag不尽相同,而且按照上文vulkan的教程,uniform buffer要有多块与swapchain数量对应,所以这里为了方便就另外申请了


dynamic和普通的uniform buffer还是可以放一起的,用minUboAlignment计算出最小间隔,从而得出offset的值。申请完Buffer后马上将指针map上去。
这里的VK_MEMORY_PROPERTY_HOST_COHERENT_BIT加不加在我电脑上看不出区别,可能和显卡型号有关,下面的flush也是,因为就在我电脑上搞搞就先不纠结这些了。

//dynamic
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
size_t minUboAlignment = properties.limits.minUniformBufferOffsetAlignment;
dynamicAlignment = sizeof(glm::mat4);
if (minUboAlignment > 0) {
    dynamicAlignment = (dynamicAlignment + minUboAlignment - 1) & ~(minUboAlignment - 1);
}
VkDeviceSize bufferSize = LONG_SIZE * dynamicAlignment;
uboDynamic.model = (glm::mat4*)alignedAlloc(bufferSize, dynamicAlignment);

dynamicUniformData.resize(swapChainImages.size());

//normal
normalUBOAlignment = sizeof(UniformBufferObject);
if (minUboAlignment > 0) {
    normalUBOAlignment = (normalUBOAlignment + minUboAlignment - 1) & ~(minUboAlignment - 1);
}
bufferSize += normalUBOAlignment;

uniformBuffers.resize(swapChainImages.size());
uniformBuffersMemory.resize(swapChainImages.size());

for (size_t i = 0; i < swapChainImages.size(); ++i)
{
    createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
        VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);
    vkMapMemory(device, uniformBuffersMemory[i], 0, LONG_SIZE * dynamicAlignment, 0, &dynamicUniformData[i]);
}

shader里把model矩阵单独拿出来作为binding 1

layout(binding = 1) uniform UboInstance{
    mat4 model;
}uboInstance;

2.下面是descriptor 相关的一套操作。
Vulkan Shader Resource Binding

Pool 与 Set

在DescriptorSetLayout里新增一个dyanmic的binding

VkDescriptorSetLayoutBinding uboDynamicLayoutBinding = {};
uboDynamicLayoutBinding.binding = 1;
uboDynamicLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
uboDynamicLayoutBinding.descriptorCount = 1;
uboDynamicLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
uboDynamicLayoutBinding.pImmutableSamplers = nullptr;

3.修改DescriptorPool

poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
poolSizes[1].descriptorCount = static_cast(swapChainImages.size());

4.在DescriptorSet里新增一个VkWriteDescriptorSet
别忘了描述下在buffer里的位置

VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = uniformBuffers[i];
bufferInfo.offset = 0;
bufferInfo.range = normalUBOAlignment;

VkDescriptorBufferInfo dynamicBufferInfo = {};
dynamicBufferInfo.buffer = uniformBuffers[i];
dynamicBufferInfo.offset = normalUBOAlignment;
dynamicBufferInfo.range = dynamicAlignment;

加一个write

descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pBufferInfo = &dynamicBufferInfo;
descriptorWrites[1].pTexelBufferView = nullptr;

5.画之前更新数据
先更新视距矩阵的信息

void VulkanAPI::updateUniformBuffer(uint32_t currentImage)
{
    UniformBufferObject ubo = {};
    glm::vec4 view_dir = *cameraTransform * glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
    glm::vec4 view_up = *cameraTransform * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f);
    glm::vec4 view_pos = *cameraTransform * glm::vec4(cameraOffset, 1.0f);
    ubo.view = glm::lookAt(glm::vec3(view_pos), glm::vec3(view_pos + view_dir), glm::vec3(view_up));
    ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f);
    ubo.proj[1][1] *= -1;

    memcpy(dynamicUniformData[currentImage], &ubo, sizeof(UniformBufferObject));
}

用dynamic uniform更新transform,注意偏移量指针需要强制转换成size_t来对应偏移单位
(此处可以优化,transform里的matrix作为指针指向这个medelMat,增删物体的时候做对应的指针变换,即可节省下这个复制的过程)

void VulkanAPI::updateDynamicUniformBuffer(uint32_t currentImage)
{
    uint32_t index = 0;
    for (auto transID: ComponentManager::GetInstance()->activeComponents[TRANSFORM])
    {
        glm::mat4* modelMat = (glm::mat4*)(((uint64_t)uboDynamic.model + (index * dynamicAlignment)));
        *modelMat = ComponentManager::GetInstance()->mTransforms[transID].transMatrix;
        ++index;
    }

    void* data = reinterpret_cast(dynamicUniformData[currentImage]) + normalUBOAlignment / sizeof(size_t);
    memcpy(data, uboDynamic.model, ComponentManager::GetInstance()->activeComponents[TRANSFORM].size() * dynamicAlignment);

    // Flush to make changes visible to the host 
    //VkMappedMemoryRange memoryRange = {};
    //memoryRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
    //memoryRange.memory = dynamicUniformBuffersMemory[currentImage];
    //memoryRange.size = VK_WHOLE_SIZE;
    //vkFlushMappedMemoryRanges(device, 1, &memoryRange);
}

flush不知道有啥用,据说有些显卡必须要flush才会可见,我的机器都一样,所以上面都加了VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,试了下都去掉也不影响正常的uniform...

  1. command buffer里面逐一画出来
for (uint32_t j = 0; j < ComponentManager::GetInstance()->activeComponents[TRANSFORM].size(); ++j)
{
    uint32_t dynamicOffset = j * static_cast(dynamicAlignment);
    vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 1, &dynamicOffset);
    vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), INSTANCE_COUNT, 0, 0, 0);
}

我申请buffer的时候是按照物体最大数量的大小来申请的,但是画的时候只画实际生效的物体数量,因为数量是录在command buffer里的,所以一旦增删物体需要重新录制。

void VulkanAPI::flushCommandBuffer()
{
    vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data());
    createCommandBuffers();
}
效果

(暂时hard code了一下运动,之前还做了一下INSTANCE DRAW所以是几个方块重叠的)

你可能感兴趣的:(Vulkan:用dynamic uniform绘制多个物体)