美文网首页计算机图形学学习
Vulkan:绘制多个贴图的不同物体

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

作者: 玄冰影 | 来源:发表于2019-06-21 20:03 被阅读0次

    玩具项目上传了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);
        }
    };
    

    来个效果图


    我是图

    相关文章

      网友评论

        本文标题:Vulkan:绘制多个贴图的不同物体

        本文链接:https://www.haomeiwen.com/subject/kibuqctx.html