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<void*> 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<uint32_t>(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<size_t*>(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<uint32_t>(dynamicAlignment);
    vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 1, &dynamicOffset);
    vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), INSTANCE_COUNT, 0, 0, 0);
}

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342