Part 3. Vulkan中的深度测试和Uniform缓冲区

转载自最早发布在formu.ziyuesinicization.site的文章

晨魔(Morning Demon)

我首先需要创建深度图像并为他分配内存,,之后我们需要创建深度图像视图:


    VkFormat depthFormat = VK_FORMAT_D32_SFLOAT;  

    VkImage depthImage;
    VkImageCreateInfo depthImgInfo{};
    depthImgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    depthImgInfo.imageType = VK_IMAGE_TYPE_2D;
    depthImgInfo.extent.width = spExtent.width;
    depthImgInfo.extent.height = spExtent.height;
    depthImgInfo.extent.depth = 1;
    depthImgInfo.mipLevels = 1;
    depthImgInfo.arrayLayers = 1;
    depthImgInfo.format = depthFormat;
    depthImgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
    depthImgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    depthImgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
    depthImgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    depthImgInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    vkCreateImage(device, &depthImgInfo, nullptr, &depthImage);

    VkMemoryRequirements depthMemReqs;
    vkGetImageMemoryRequirements(device, depthImage, &depthMemReqs);

    VkMemoryAllocateInfo depthAllocInfo{};
    depthAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    depthAllocInfo.allocationSize = depthMemReqs.size;
    depthAllocInfo.memoryTypeIndex = FindMemoryType(physicalDevice, depthMemReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    VkDeviceMemory depthImageMem;
    vkAllocateMemory(device, &depthAllocInfo, nullptr, &depthImageMem);
    vkBindImageMemory(device, depthImage, depthImageMem, 0);

    VkImageView depthImageView;
    VkImageViewCreateInfo depthViewInfo{};
    depthViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    depthViewInfo.image = depthImage;
    depthViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
    depthViewInfo.format = depthFormat;
    depthViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
    depthViewInfo.subresourceRange.baseMipLevel = 0;
    depthViewInfo.subresourceRange.levelCount = 1;
    depthViewInfo.subresourceRange.baseArrayLayer = 0;
    depthViewInfo.subresourceRange.layerCount = 1;
    vkCreateImageView(device, &depthViewInfo, nullptr, &depthImageView);
                                

下一步,我们创建深度附件和深度附件参考,并添加到渲染通道(Render Pass):


    VkAttachmentDescription depthAttachment{};
    depthAttachment.format = depthFormat;
    depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    VkAttachmentReference depthAttachRef{};
    depthAttachRef.attachment = 1; 
    depthAttachRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    VkAttachmentDescription attachments[2] = {colorAttachment, depthAttachment};

    VkSubpassDescription spDes = {};
    spDes.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    spDes.colorAttachmentCount = 1;
    spDes.pColorAttachments = &colorAttachRef;
    spDes.pDepthStencilAttachment = &depthAttachRef;                                 
                                

接着,我们给帧缓冲加上深度图像视图:


        VkImageView attachments[] = {
            spImagesView[i],
            depthImageView
        };

        VkFramebufferCreateInfo framebufferInfo{};
        framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
        framebufferInfo.renderPass = renderPass;
        framebufferInfo.attachmentCount = 2;
        framebufferInfo.pAttachments = attachments;
        framebufferInfo.width = spExtent.width;
        framebufferInfo.height = spExtent.height;
        framebufferInfo.layers = 1;
                                

最后在渲染管线开启深度测试即可:


VkPipelineDepthStencilStateCreateInfo depthStencil{};
    depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
    depthStencil.depthTestEnable = VK_TRUE;   
    depthStencil.depthWriteEnable = VK_TRUE;    
    depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
    depthStencil.depthBoundsTestEnable = VK_FALSE;
    depthStencil.stencilTestEnable = VK_FALSE;

pipelineInfo.pDepthStencilState = &depthStencil;          
                                

在循环中添加深度清除:


VkClearValue clearValues[2];
clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};  // 颜色清除
clearValues[1].depthStencil = {1.0f, 0};             // 1.0 表示最远

renderPassInfo.clearValueCount = 2;
renderPassInfo.pClearValues = clearValues;
                                

最后别忘了释放资源:


vkDestroyImageView(device, depthImageView, nullptr);
vkDestroyImage(device, depthImage, nullptr);
vkFreeMemory(device, depthImageMem, nullptr);
                                

接着我们来讨论一下Uniform缓冲。我们要从CPU端上传矩阵到GPU,先编写着色器,以HLSL为例:


#include "IO.hlsl"

cbuffer Buffer_0_s : register(b0) {
    float4x4 transform;
};

VS_OUTPUT main(VS_INPUT input, uint vertexID : SV_VertexID)
{
    VS_OUTPUT output;

    output.position = mul(transform, float4(input.position, 1.0));
    output.color = input.color;

    return output;
}                                   
                                

我们在CPU端写好矩阵:


Matrix transform;                                    
                                

按照上一篇文章的方法,先创建并分配内存,拷贝数据给我们的Uniform缓冲:


    VkBuffer uniBuffer;
    VkDeviceMemory uniBufferMem;

    CreateAndAllocateBuffer(device, physicalDevice, sizeof(Matrix), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &uniBuffer, &uniBufferMem);

    vkMapMemory(device, uniBufferMem, 0, sizeof(Matrix), 0, &data);
    memcpy(data, &transform.data[0][0], sizeof(Matrix));
    vkUnmapMemory(device, uniBufferMem);                                   
                                

我们先制作一个模板,并绑定为零,对应这刚刚着色器中的register(b0):


    VkDescriptorSetLayoutBinding layoutBinding = {};
    layoutBinding.binding = 0;
    layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    layoutBinding.descriptorCount = 1;
    layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

    VkDescriptorSetLayout descLayout;

    VkDescriptorSetLayoutCreateInfo layoutCreateInfo = {};
    layoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    layoutCreateInfo.bindingCount = 1;
    layoutCreateInfo.pBindings = &layoutBinding;  

    vkCreateDescriptorSetLayout(device, &layoutCreateInfo, nullptr, &descLayout);                        
                                

接着,我们需要一个池子存放:


    VkDescriptorPoolSize poolSize = {};
    poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    poolSize.descriptorCount = 1;

    VkDescriptorPoolCreateInfo poolInfo = {};
    poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    poolInfo.poolSizeCount = 1;
    poolInfo.pPoolSizes = &poolSize;    
    poolInfo.maxSets = 1;

    VkDescriptorPool descPool;
    vkCreateDescriptorPool(device, &poolInfo, nullptr, &descPool);
                                

然后开始分配:


    VkDescriptorSetAllocateInfo _allocInfo = {};
    _allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    _allocInfo.descriptorPool = descPool;       
    _allocInfo.descriptorSetCount = 1;          
    _allocInfo.pSetLayouts = &descLayout;     

    VkDescriptorSet descSet;
    vkAllocateDescriptorSets(device, &_allocInfo, &descSet);               
                                

最后就是运送数据了:


    VkDescriptorBufferInfo bufferInfo = {};
    bufferInfo.buffer = uniBuffer;
    bufferInfo.offset = 0;
    bufferInfo.range = sizeof(Matrix);

    VkWriteDescriptorSet write = {};
    write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    write.dstSet = descSet;       
    write.dstBinding = 0;      
    write.descriptorCount = 1; 
    write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    write.pBufferInfo = &bufferInfo;

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

接着,修改管线布局,把你刚刚描述集(descSet)加到管线布局上面:


pipelineLayoutInfo.pSetLayouts = &descLayout;                                   
                                

最后一步,在循环中绑定描述集即可:


vkCmdBindDescriptorSets(commandBuffer, 
                        VK_PIPELINE_BIND_POINT_GRAPHICS,
                        pipelineLayout,
                        0,             
                        1, &descSet,
                        0, nullptr);