计算机图形学学习

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

2019-06-21  本文已影响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);
    }
};

来个效果图


我是图
上一篇下一篇

猜你喜欢

热点阅读