Vulkan:绘制多个贴图的不同物体

玩具项目上传了Github: vulkanTest

vulkan比之前的图形api更注重预处理,既要做什么事必须预先录制好command buffer,
在draw的时候只是submit这个command buffer,
所以换texture不能像openGL那样在draw之前bind texture,
但是在command buffer里面可以在每个draw index之前Bind DescriptorSets
这就要把DescriptorSet里面的sampler给拆出来,然后根据每个物体的texture不同,换上不同的DescriptorSet。

先在原来的程序基础上把sampler拆出来看看。
思路是这样:
①拆DescriptorSetLayout,因为是不同的set了,所以binding可以从0开始

// 这部分不变
VkDescriptorSetLayoutBinding samplerLayoutBinding = {};
samplerLayoutBinding.binding = 0;
samplerLayoutBinding.descriptorCount = 1;
samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerLayoutBinding.pImmutableSamplers = nullptr;
samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

std::array<VkDescriptorSetLayoutBinding, 1> textureBindings = { samplerLayoutBinding };
layoutInfo.bindingCount = static_cast<uint32_t>(textureBindings.size());
layoutInfo.pBindings = textureBindings.data();
// 新增一个texture的layout
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &textureDescriptorSetLayout) != VK_SUCCESS)
{
    throw std::runtime_error("failed to create descriptor set layout!");
}

② 拆pool, 这里要注意,pool size和maxSets是今后总共要从这个pool里出多少个descriptor set的数量,所以应该是这个关卡要用到的所有贴图的数量的数字,我这里暂时用一个hard coded数字代替。

void VulkanAPI::createTextureDescriptorPool()
{
    std::array<VkDescriptorPoolSize, 1> texturePoolSizes = {};
    texturePoolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    texturePoolSizes[0].descriptorCount = TEXTURE_COUNT;

    VkDescriptorPoolCreateInfo poolInfo = {};
    poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    poolInfo.poolSizeCount = static_cast<uint32_t>(texturePoolSizes.size());;
    poolInfo.pPoolSizes = texturePoolSizes.data();
    poolInfo.maxSets = TEXTURE_COUNT;

    if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &textureDescriptorPool) != VK_SUCCESS)
    {
        throw std::runtime_error("failed to create descriptor pool!");
    }
}

texture不用每帧都变,所以不用和swapchain的挂钩,单独开一个函数就好了。

③ 从新的pool里出新的DescriptorSet,然后绑定图片
要注意的是因为拆了2个DescriptorSet,所以在shader里要标注好set=0, set=1

layout(set = 0, binding = 0) uniform UniformBufferObject{
    mat4 view;
    mat4 proj;
} ubo;
layout(set = 1, binding = 0) uniform sampler2D texSampler;

这里的set 的序号由之前做pipeline时候填的VkDescriptorSetLayout顺序决定

VkDescriptorSetLayout descriptorSetLayouts[2] = { descriptorSetLayout, textureDescriptorSetLayout };
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 2;
pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = push_constant_ranges;

command buffer里的vkCmdBindDescriptorSets的顺序也不能错

VkDescriptorSet sets[2] = { descriptorSets[i] , textureDescriptorSet };
vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 2, sets, 1, &dynamicOffset);

实验下来结果和原来一样,既代表这个思路是行得通的。
和游戏引擎结合,在vulkan里开一个struct,里面存了一个image的所有相关信息,因为sampler暂时只和mipLevel有关,所以就单独存了一个hash表,用miplevel的数字去索引得到。

struct TextureInfo
{
    VkImage textureImage;
    uint32_t mipLevels;
    VkDeviceMemory textureImageMemory;
    VkImageView textureImageView;
    VkSampler* textureSampler;
    VkDescriptorSet descriptorSet;
};

std::vector<TextureInfo> textureInfos;
std::unordered_map<uint32_t, VkSampler> textureSamplers;

在Resource Manager里面存了一个hash表,如果之前没有读取过的图片地址,就到vulkan里面上传一张图片到GPU,返回一个uint32的数字索引,下次要画什么texture就到textureInfos里面去找。

uint32_t VulkanAPI::UploadTexture(std::string path)
{
    TextureInfo newTexture = {};
    int texWidth, texHeight, texChannels;
    stbi_uc* pixels = stbi_load(path.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    if (!pixels) {
        throw std::runtime_error("failed to load texture image!");
    }
    VkDeviceSize imageSize = texWidth * texHeight * 4;

    newTexture.mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;

    createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        stagingBuffer, stagingBufferMemory);
    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
    memcpy(data, pixels, static_cast<size_t>(imageSize));
    vkUnmapMemory(device, stagingBufferMemory);

    stbi_image_free(pixels);

    createImage(texWidth, texHeight, newTexture.mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
        VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, newTexture.textureImage, newTexture.textureImageMemory);

    transitionImageLayout(newTexture.textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, newTexture.mipLevels);
    copyBufferToImage(stagingBuffer, newTexture.textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));

    vkDestroyBuffer(device, stagingBuffer, nullptr);
    vkFreeMemory(device, stagingBufferMemory, nullptr);

    generateMipmaps(newTexture.textureImage, VK_FORMAT_R8G8B8A8_UNORM, texWidth, texHeight, newTexture.mipLevels);

    newTexture.textureImageView = createImageView(newTexture.textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, newTexture.mipLevels);
    newTexture.textureSampler = createTextureSampler(newTexture.mipLevels);

    // texture descriptorSet
    VkDescriptorSetAllocateInfo textureAllocInfo = {};
    textureAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    textureAllocInfo.descriptorPool = textureDescriptorPool;
    textureAllocInfo.descriptorSetCount = 1;
    textureAllocInfo.pSetLayouts = &textureDescriptorSetLayout;

    if (vkAllocateDescriptorSets(device, &textureAllocInfo, &newTexture.descriptorSet) != VK_SUCCESS)
    {
        throw std::runtime_error("failed to allocate descriptor sets");
    }

    VkDescriptorImageInfo imageInfo = {};
    imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    imageInfo.imageView = newTexture.textureImageView;
    imageInfo.sampler = *newTexture.textureSampler;

    VkWriteDescriptorSet texturedescriptorWrites = {};
    texturedescriptorWrites.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    texturedescriptorWrites.dstSet = newTexture.descriptorSet;
    texturedescriptorWrites.dstBinding = 0;
    texturedescriptorWrites.dstArrayElement = 0;
    texturedescriptorWrites.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    texturedescriptorWrites.descriptorCount = 1;
    texturedescriptorWrites.pImageInfo = &imageInfo;
    texturedescriptorWrites.pTexelBufferView = nullptr;

    vkUpdateDescriptorSets(device, 1, &texturedescriptorWrites, 0, nullptr);

    textureInfos.push_back(newTexture);
    return uint32_t(textureInfos.size()-1);
}

transform经过我的优化后变成这样:
1.Resource Manager读取这个关卡要用的所有mesh数据
1.Vulkan初始化的时候拿到mesh的数量,给每个mesh单独开一个model的数组,当然这些model还是在同一个uniformbuffer上的
3.每个有mesh的game object会拿到一个meshData的指针,用这个指针去找到这个mesh的model的空余位置,在把那个mat4的指针交给transform里面,这样transform的更新直接就修改那个model的数据,每次draw之前,把所有的model按照mesh种类的大的offset用多少复制上去多少

uint32_t index = 0;
for (auto &dynamicUBO : uboDynamic)
{
    void* data = reinterpret_cast<size_t*>(dynamicUniformData[currentImage]) + normalUBOAlignment / sizeof(size_t) + index* (LONG_SIZE * dynamicAlignment / sizeof(size_t));
    memcpy(data, dynamicUBO.second.model, dynamicUBO.second.inversePtr.size() * dynamicAlignment);
    ++index;
}

vertex index的Buffer也根据mesh的种类对应申请

最后的drawCall长这样

uint32_t k = 0;
for (auto &dubo : uboDynamic) {
    VkDeviceSize offsets[] = { 0 };
    vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, &vertexIndexBuffers[k], offsets);
    vkCmdBindVertexBuffers(commandBuffers[i], 1, 1, &instanceBuffer, offsets);
    vkCmdBindIndexBuffer(commandBuffers[i], vertexIndexBuffers[k], sizeof((*dubo.first).vertices[0])*(*dubo.first).vertices.size(), VK_INDEX_TYPE_UINT32);

    for (uint32_t j = 0; j < dubo.second.inversePtr.size(); ++j)
    {
        uint32_t dynamicOffset = k* LONG_SIZE * static_cast<uint32_t>(dynamicAlignment) + j * static_cast<uint32_t>(dynamicAlignment);
        VkDescriptorSet sets[2] = { descriptorSets[i] , textureInfos[dubo.second.inversePtr[j]->textureID].descriptorSet };
        vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 2, sets, 1, &dynamicOffset);
        vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>((*dubo.first).indices.size()), INSTANCE_COUNT, 0, 0, 0);
    }
    ++k;
}

增删model指针其实很简单,为了保持结构紧凑,增就拿最后一个的后一个,删就把最后一个和删除的那个交换,然后总数量-1

struct DynamicUBO {
    glm::mat4 *model = nullptr;
    static size_t DU_Alignment;
    std::vector<Transform*> inversePtr;

    glm::mat4* GetAvailableModel(Transform* pTr)
    {
        glm::mat4* m = (glm::mat4*)(((uint64_t)model + (inversePtr.size() * DU_Alignment)));
        inversePtr.push_back(pTr);
        return m;
    }

    void DeleteModel(glm::mat4* m)
    {
        glm::mat4* lastModel = (glm::mat4*)(((uint64_t)model + ((inversePtr .size()-1) * DU_Alignment)));
        size_t index = ((uint64_t)m - (uint64_t)model) / DU_Alignment;

        if (m != lastModel) {
            *m = *lastModel;
            inversePtr.back()->transMatrix = m;
            inversePtr[index] = inversePtr.back();
        }
        inversePtr.resize(inversePtr.size() - 1);
    }
};

来个效果图


我是图
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容