如何使用 C++ 在 Vulkan 中正确实现深度缓冲?

问题描述 投票:0回答:1

在 Vulkan 中实现分层纹理深度缓冲的问题

我正在学习 Vulkan 教程,并已经实现了包括纹理在内的所有内容。 然而,我并没有逐行实现教程中的每个示例,而是尝试将特定的执行区域分成独立的类。 虽然我正在设置教程要求的所有内容,并且代码编译和执行没有错误,但深度缓冲显然不起作用,它不是将一个带有纹理的方块渲染在另一个带有纹理的方块之上,而是将它们渲染在一起。 我添加了指向我的 github 页面的链接,其中维护了完整的代码库。 下面显示了错误渲染的方块的图片,以突出显示不正确的行为。 (注意正方形之间重叠区域的锯齿渲染。)

Image of incorrectly rendered texture squares

创建深度管理器

在本例中,我正在实现深度缓冲,以下类名为

DepthManager
。 下面是graphics.hpp 文件中的原型,下面是graphics.cpp 文件中的实现。 我应该说,我还使用 Vulkan Memory Allocator 库来管理内存,而不是内置分配器。

DepthManager 的原型和实现如下所示;

class DepthManager {
public:
    DepthManager(AllocatorManager& allocatorManager, VkDevice device, 
                 VkPhysicalDevice physicalDevice, VkExtent2D swapChainExtent);
// --------------------------------------------------------------------------------

    ~DepthManager();
// --------------------------------------------------------------------------------

    VkImageView getDepthImageView() const { return depthImageView; }
// --------------------------------------------------------------------------------

    void createDepthResources();
// --------------------------------------------------------------------------------

    VkFormat findDepthFormat();
// --------------------------------------------------------------------------------

    VkImageView getDepthImageView();
// ================================================================================
private:
    AllocatorManager& allocatorManager;
    VkDevice device;
    VkPhysicalDevice physicalDevice;
    VkExtent2D swapChainExtent;
    
    VkImage depthImage = VK_NULL_HANDLE;
    VmaAllocation depthImageMemory = VK_NULL_HANDLE;
    VkImageView depthImageView = VK_NULL_HANDLE;
// --------------------------------------------------------------------------------

    bool hasStencilComponent(VkFormat format);
// -------------------------------------------------------------------------------- 

    VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, 
                                 VkImageTiling tiling, VkFormatFeatureFlags features);
// --------------------------------------------------------------------------------

    void createImage(uint32_t width, uint32_t height, VkFormat format, 
                     VkImageTiling tiling, VkImageUsageFlags usage, 
                     VmaMemoryUsage memoryUsage, VkImage& image, 
                     VmaAllocation& imageMemory);
// --------------------------------------------------------------------------------

    VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags);
};

DepthManager 实现如下所示

DepthManager::DepthManager(AllocatorManager& allocatorManager, 
                           VkDevice device, 
                           VkPhysicalDevice physicalDevice, 
                           VkExtent2D swapChainExtent)
    : allocatorManager(allocatorManager),
      device(device),
      physicalDevice(physicalDevice),
      swapChainExtent(swapChainExtent) {
    createDepthResources();
}
// --------------------------------------------------------------------------------

DepthManager::~DepthManager() {
    if (depthImageView != VK_NULL_HANDLE) {
        vkDestroyImageView(device, depthImageView, nullptr);
        depthImageView = VK_NULL_HANDLE;
    }

    if (depthImage != VK_NULL_HANDLE && depthImageMemory != VK_NULL_HANDLE) {
        vmaDestroyImage(allocatorManager.getAllocator(), depthImage, depthImageMemory);
        depthImage = VK_NULL_HANDLE;
        depthImageMemory = VK_NULL_HANDLE;
    }
}
// --------------------------------------------------------------------------------

void DepthManager::createDepthResources() {
    VkFormat depthFormat = findDepthFormat();

    // Create depth image and verify initialization
    createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, 
                VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 
                VMA_MEMORY_USAGE_GPU_ONLY, depthImage, depthImageMemory);
    
    // Check if depthImage and depthImageMemory are valid
    if (depthImage == VK_NULL_HANDLE || depthImageMemory == VK_NULL_HANDLE) {
        throw std::runtime_error("DepthManager: Failed to initialize depth image or memory allocation.");
    }

    // Create depth image view and verify initialization
    depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT);

    // Check if depthImageView is valid
    if (depthImageView == VK_NULL_HANDLE) {
        throw std::runtime_error("DepthManager: Failed to initialize depth image view.");
    }
}
// --------------------------------------------------------------------------------

VkFormat DepthManager::findDepthFormat() {
    // Define the list of depth formats to check in order of preference
    std::vector<VkFormat> depthFormats = {
        VK_FORMAT_D32_SFLOAT, 
        VK_FORMAT_D32_SFLOAT_S8_UINT, 
        VK_FORMAT_D24_UNORM_S8_UINT
    };

    // Loop through each candidate format and check for support
    for (VkFormat format : depthFormats) {
        VkFormatProperties props;
        vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);

        // Check if this format supports optimal tiling for depth-stencil attachment
        if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
            return format;
        }
    }

    throw std::runtime_error("failed to find supported depth format!");
}
// --------------------------------------------------------------------------------

VkImageView DepthManager::getDepthImageView() {
    return depthImageView;
}
// ================================================================================

bool DepthManager::hasStencilComponent(VkFormat format) {
    return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
}
// --------------------------------------------------------------------------------

VkFormat DepthManager::findSupportedFormat(const std::vector<VkFormat>& candidates, 
                                           VkImageTiling tiling, VkFormatFeatureFlags features) {
    for (VkFormat format : candidates) {
        VkFormatProperties props;
        vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);

        if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
            return format;
        } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
            return format;
        }
    }

    throw std::runtime_error("failed to find supported format!");
}
// --------------------------------------------------------------------------------

void DepthManager::createImage(uint32_t width, uint32_t height, VkFormat format, 
                               VkImageTiling tiling, VkImageUsageFlags usage, 
                               VmaMemoryUsage memoryUsage, VkImage& image, 
                               VmaAllocation& imageMemory) {
    VkImageCreateInfo imageInfo{};
    imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageInfo.imageType = VK_IMAGE_TYPE_2D;
    imageInfo.extent.width = width;
    imageInfo.extent.height = height;
    imageInfo.extent.depth = 1;
    imageInfo.mipLevels = 1;
    imageInfo.arrayLayers = 1;
    imageInfo.format = format;
    imageInfo.tiling = tiling;
    imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    imageInfo.usage = usage;
    imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    VmaAllocationCreateInfo allocInfo{};
    allocInfo.usage = memoryUsage;

    if (vmaCreateImage(allocatorManager.getAllocator(), &imageInfo, &allocInfo, &image, &imageMemory, nullptr) != VK_SUCCESS) {
        throw std::runtime_error("failed to create image!");
    }
}
// --------------------------------------------------------------------------------

VkImageView DepthManager::createImageView(VkImage image, VkFormat format, 
                                          VkImageAspectFlags aspectFlags) {
    VkImageViewCreateInfo viewInfo{};
    viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    viewInfo.image = image;
    viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
    viewInfo.format = format;
    viewInfo.subresourceRange.aspectMask = aspectFlags;
    viewInfo.subresourceRange.baseMipLevel = 0;
    viewInfo.subresourceRange.levelCount = 1;
    viewInfo.subresourceRange.baseArrayLayer = 0;
    viewInfo.subresourceRange.layerCount = 1;

    VkImageView imageView;
    if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
        throw std::runtime_error("failed to create texture image view!");
    }

    return imageView;
}

管理图形管道的深度

本教程还要求我更新 createRenderPass 和 createFrameBuffer 方法,它们是图形管道的一部分,为了完整起见,我将在下面显示这些方法的更新。

void GraphicsPipeline::createRenderPass(VkFormat swapChainImageFormat) {
    VkAttachmentDescription colorAttachment{};
    colorAttachment.format = swapChainImageFormat;
    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    VkAttachmentDescription depthAttachment{};
    depthAttachment.format = depthManager.findDepthFormat();
    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 colorAttachmentRef{};
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

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

    VkSubpassDescription subpass{};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;
    subpass.pDepthStencilAttachment = &depthAttachmentRef;

    VkSubpassDependency dependency{};
    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
    dependency.srcAccessMask = 0;
    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;

    // Correct the attachments array and count
    std::array<VkAttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};

    VkRenderPassCreateInfo renderPassInfo{};
    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());  // Set to 2
    renderPassInfo.pAttachments = attachments.data();  // Point to both color and depth attachments
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpass;
    renderPassInfo.dependencyCount = 1;
    renderPassInfo.pDependencies = &dependency;

    if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
        throw std::runtime_error("failed to create render pass!");
    }
}

void GraphicsPipeline::createFrameBuffers(const std::vector<VkImageView>& swapChainImageViews, 
                                          VkExtent2D swapChainExtent) {
    // Verify depthImageView is initialized
    VkImageView depthImageView = depthManager.getDepthImageView();
    if (depthImageView == VK_NULL_HANDLE) {
        throw std::runtime_error("DepthManager depthImageView is not initialized.");
    }
    framebuffers.resize(swapChainImageViews.size());

    for (size_t i = 0; i < swapChainImageViews.size(); i++) {
            std::array<VkImageView, 2> attachments = {
                swapChainImageViews[i],
                depthImageView
            };

        VkFramebufferCreateInfo framebufferInfo{};
        framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
        framebufferInfo.renderPass = renderPass;
        framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
        framebufferInfo.pAttachments = attachments.data();
        framebufferInfo.width = swapChainExtent.width;
        framebufferInfo.height = swapChainExtent.height;
        framebufferInfo.layers = 1;

        if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &framebuffers[i]) != VK_SUCCESS) {
            throw std::runtime_error("failed to create framebuffer!");
        }
    }
}

使用深度缓冲调用 Vulkan 实例

问题可能与我实例化类的顺序有关,因此为了完整起见,我还显示了下面组织整个应用程序的

VulkanApplication
类的构造函数。 主要功能如下所示。

GLFWwindow* create_window(uint32_t h, uint32_t w, const std::string& screen_title, bool full_screen) {
    if (!glfwInit()) {
        throw std::runtime_error("GLFW Initialization Failed!\n");
    }

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

    GLFWmonitor* monitor = full_screen ? glfwGetPrimaryMonitor() : nullptr;

    GLFWwindow* window = glfwCreateWindow(w, h, screen_title.c_str(), monitor, nullptr);

    if (!window) {
        glfwTerminate();
        throw std::runtime_error("GLFW Instantiation failed!\n");
    }
    return window;
}
// ================================================================================
// ================================================================================ 


// Begin code
int main(int argc, const char * argv[]) {

    // Define vertices for two connected triangles  
    const std::vector<Vertex> vertices = {
        // First square, slightly offset along the x-axis and closer along the z-axis
        {{-0.4f, -0.4f, -0.25f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}},
        {{0.4f, -0.4f, -0.25f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}},
        {{0.4f, 0.4f, -0.25f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}},
        {{-0.4f, 0.4f, -0.25f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}},

        // Second square, further along z-axis and offset along x-axis
        {{-0.5f + 0.5f, -0.5f, -0.75f}, {1.0f, 0.5f, 0.0f}, {1.0f, 0.0f}},
        {{0.5f + 0.5f, -0.5f, -0.75f}, {0.5f, 1.0f, 0.5f}, {0.0f, 0.0f}},
        {{0.5f + 0.5f, 0.5f, -0.75f}, {0.0f, 0.5f, 1.0f}, {0.0f, 1.0f}},
        {{-0.5f + 0.5f, 0.5f, -0.75f}, {0.5f, 1.0f, 1.0f}, {1.0f, 1.0f}}
    };
    const std::vector<uint16_t> indices = {
        0, 1, 2, 2, 3, 0,
        4, 5, 6, 6, 7, 4
    };
    
    // Call Application 
    try {
        GLFWwindow* window = create_window(1050, 1200, "Vulkan Application", false);
        VulkanApplication triangle(window, vertices, indices);

        triangle.run();

         // Clean up the GLFW window
        glfwDestroyWindow(window);

        // Terminate GLFW and clean up resources
        glfwTerminate();
    } catch(const std::exception& e) {
        std::cerr << e.what() <<  "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

main函数实例化如下所示的

VulkanApplication
类。

VulkanApplication::VulkanApplication(GLFWwindow* window, 
                                     const std::vector<Vertex>& vertices,
                                     const std::vector<uint16_t>& indices)
    : windowInstance(std::move(window)),
      vertices(vertices),
      indices(indices){
    // Instantiate related classes
    glfwSetWindowUserPointer(windowInstance, this);

    validationLayers = std::make_unique<ValidationLayers>();
    vulkanInstanceCreator = std::make_unique<VulkanInstance>(this->windowInstance, 
                                                             *validationLayers.get());
    vulkanPhysicalDevice = std::make_unique<VulkanPhysicalDevice>(*this->vulkanInstanceCreator->getInstance(),
                                                                  this->vulkanInstanceCreator->getSurface());
    vulkanLogicalDevice = std::make_unique<VulkanLogicalDevice>(vulkanPhysicalDevice->getDevice(),
                                                                validationLayers->getValidationLayers(),
                                                                vulkanInstanceCreator->getSurface(),
                                                                deviceExtensions);
    allocatorManager = std::make_unique<AllocatorManager>(
        vulkanPhysicalDevice->getDevice(),
        vulkanLogicalDevice->getDevice(),
        *vulkanInstanceCreator->getInstance());

    swapChain = std::make_unique<SwapChain>(vulkanLogicalDevice->getDevice(),
                                            vulkanInstanceCreator->getSurface(),
                                            vulkanPhysicalDevice->getDevice(),
                                            this->windowInstance);
    depthManager = std::make_unique<DepthManager>(
        *allocatorManager,                              // Dereference unique_ptr
        vulkanLogicalDevice->getDevice(),
        vulkanPhysicalDevice->getDevice(),
        swapChain->getSwapChainExtent()
    );
    //depthManager->createDepthResources();
    commandBufferManager = std::make_unique<CommandBufferManager>(vulkanLogicalDevice->getDevice(),
                                                                  indices,
                                                                  vulkanPhysicalDevice->getDevice(),
                                                                  vulkanInstanceCreator->getSurface());
    samplerManager = std::make_unique<SamplerManager>(
            vulkanLogicalDevice->getDevice(),
            vulkanPhysicalDevice->getDevice()
    );
    samplerManager->createSampler("default");
    textureManager = std::make_unique<TextureManager>(
        *allocatorManager,                              // Dereference unique_ptr
        vulkanLogicalDevice->getDevice(),
        vulkanPhysicalDevice->getDevice(),
        *commandBufferManager,                          // Dereference unique_ptr
        vulkanLogicalDevice->getGraphicsQueue(),
        "../../../data/texture.jpg",
        *samplerManager
    );
    bufferManager = std::make_unique<BufferManager>(vertices,
                                                    indices,
                                                    *allocatorManager,
                                                    *commandBufferManager.get(),
                                                    vulkanLogicalDevice->getGraphicsQueue());
    descriptorManager = std::make_unique<DescriptorManager>(vulkanLogicalDevice->getDevice());
    descriptorManager->createDescriptorSets(bufferManager->getUniformBuffers(),
                                            textureManager->getTextureImageView(),
                                            samplerManager->getSampler("default"));
    graphicsPipeline = std::make_unique<GraphicsPipeline>(vulkanLogicalDevice->getDevice(),
                                                          *swapChain.get(),
                                                          *commandBufferManager.get(),
                                                          *bufferManager.get(),
                                                          *descriptorManager.get(),
                                                          indices,
                                                          vulkanPhysicalDevice->getDevice(),
                                                          std::string("../../shaders/shader.vert.spv"),
                                                          std::string("../../shaders/shader.frag.spv"),
                                                          *depthManager);
    graphicsPipeline->createFrameBuffers(swapChain->getSwapChainImageViews(), 
                                         swapChain->getSwapChainExtent());
    graphicsQueue = this->vulkanLogicalDevice->getGraphicsQueue();
    presentQueue = this->vulkanLogicalDevice->getPresentQueue();
}

正如我之前所说,应用程序可以正常工作,只是没有在两个方块之间正确实现 DepthBuffering,我不知道为什么!我认为问题在于如何

DepthBuffer`` class is written, or how it is used to support the 
createRenderPass
or
createFrameBuffer
functions, but is may also be in how it is called in the
VulkanApplication` 构造函数,所以我也分享这些实现。 从本质上讲,在比我更了解的人指出错误之前,我会陷入僵局,并且非常感谢我能得到的任何帮助。 如果您认为问题出在其他地方,请随意筛选顶部显示的 gitub 地址。

c++ glfw vulkan
1个回答
0
投票

调试如此简单的东西花了一些时间,但问题是由我的

Vertex
类引起的。
getAttributeDescriptons()
功能设置为接收 2D 信息而不是 3D 信息。 正确的功能如下所示。

static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
        std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};

        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[0].offset = offsetof(Vertex, pos);

        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
        attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[1].offset = offsetof(Vertex, color);

        attributeDescriptions[2].binding = 0;
        attributeDescriptions[2].location = 2;
        attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[2].offset = offsetof(Vertex, texCoord);

        return attributeDescriptions;
    }
© www.soinside.com 2019 - 2024. All rights reserved.