深度图像和视图

深度附着和颜色附着一样都是基于图像对象。区别是,交换链不会自动地为我们创建深度附着使用的深度图像对象。我们需要自己创建深度图像对象。使用深度图像需要图像、内存和图像视图对象这三种资源。

VkImage depthImage;
VkDeviceMemory depthImageMemory;
VkImageView depthImageView;

添加一个叫做createDepthResources的函数来配置深度图像需要的资源:

void initVulkan() {
        ...
    createCommandPool();
    createDepthResources();
    createTextureImage();
        ...
}

        ...

void createDepthResources() {

}

深度图像的创建方法相当简单。设置和颜色附着完全一样的图像分辨率,以及用于深度附着的使用标记,优化的tiling模式和设备内存。对于深度图像,只需要使用一个颜色通道。

和纹理图像不同,深度图像不需要特定的图像数据格式,这是因为实际上我们不需要直接访问深度图像。只需要保证深度数据能够有一个合理的精度即可,通常这一合理精度是至少为深度数据提供24位的位宽,下面这些值满足24位的需求:

  • VK_FORMAT_D32_SFLOAT:32位浮点深度值

  • VK_FORMAT_D32_SFLOAT_S8_UINT:32位浮点深度值和8位模板值

  • VK_FORMAT_D24_UNORM_S8_UINT:24位浮点深度值和8位模板值

模板值用于模板测试,我们会在之后的章节介绍它。

我们可以直接使用VK_FORMAT_D32_SFLOAT作为图像数据格式,目前这一图像数据格式已经被广泛支持。但我们最好还是对设备是否支持这一图像数据格式进行检测。为此我们可以编写一个findSupportedFormat函数查找一个既符合我们需求又被设备支持的图像数据格式:

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

}

可用的深度图像数据格式还依赖于tiling模式和使用标记,所以我们需要将这些信息作为函数参数,通过调用vkGetPhysicalDeviceFormatProperties函数查询可用的深度图像数据格式:

for (VkFormat format : candidates) {
    VkFormatProperties props;
    vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
}

上面代码中的VkFormatProperties结构体包含了下面这些成员变量:

  • linearTilingFeatures:数据格式支持线性tiling模式

  • optimalTilingFeatures:数据格式支持优化tiling模式

  • bufferFeatures:数据格式支持缓冲

目前我们只用到上面前两个成员变量,通过它们对应的tiling模式检测数据格式是否被支持:

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;
}

如果没有合适的格式可用,我们可以返回一个特殊值或直接抛出一个异常:

VkFormat 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!");
}

我们使用findSupportedFormat函数创建一个叫做findDepthFormat的辅助函数来查找适合作为深度附着的图像数据格式:

VkFormat findDepthFormat() {
    return findSupportedFormat( {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
}

需要注意这里使用的是VK_FORMAT_FEATURE_ 类的标记而不是VK_IMAGE_USAGE_ 类标记,这一类标记的格式都包含了深度颜色通道,有一些还包含了模板颜色通道,但在这里,我们没有用到模板颜色通道。但如果使用的格式包含模板颜色通道,在进行图像布局变换时就需要考虑这一点。这里我们添加一个可以用于检测格式是否包含模板颜色通道的函数:

bool hasStencilComponent(VkFormat format) {
    return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
}

我们在createDepthResources函数中调用findDepthFormat函数来查找一个可用的深度图像数据格式:

VkFormat depthFormat = findDepthFormat();

至此,我们已经具有足够的信息来创建深度图像对象,可以开始调用createImage和createImageView函数来创建图像资源:

createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
depthImageView = createImageView(depthImage, depthFormat);

但我们的createImageView函数目前假设aspectFlags的设置总是VK_IMAGE_ASPECT_COLOR_BIT,不符合我们的需求,我们需要进行修改将其作为一个函数参数:

VkImageView createImageView(VkImage image, VkFormat format,
VkImageAspectFlags aspectFlags) {
        ...
    viewInfo.subresourceRange.aspectMask = aspectFlags;
        ...
}

更新所有使用createImageView函数的地方,使用正确的aspectFlags标记:

swapChainImageViews[i] = createImageView(swapChainImages[i],
swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT);
        ...
depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT);
        ...
textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);

接着,就是创建深度图像。通常,我们在渲染流程开始后首先会清除深度附着中的数据,不需要复制数据到深度图像中。我们需要对图像布局进行变换,来让它适合作为深度附着使用。由于这一图像变换只需要进行一次,这里我们使用管线障碍来同步图像变换:

transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);

因为我们不需要深度图像之前的数据,所以我们使用VK_IMAGE_LAYOUT_UNDEFINED作为深度图像的初始布局。现在我们需要修改transitionImageLayout函数的部分代码来使用正确的subresource aspect:

if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;

    if (hasStencilComponent(format)) {
        barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
    }
} else {
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}

尽管这里我们没有使用模板颜色通道,我们还是要对它进行变换处理。

最后,设置正确的访问掩码和管线阶段。

if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
    barrier.srcAccessMask = 0;
    barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;

    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

    sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
    barrier.srcAccessMask = 0;
    barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;

    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
} else {
    throw std::invalid_argument("unsupported layout transition!");
}

深度缓冲数据会在进行深度测试时被读取,用来检测片段是否可以覆盖之前的片段。这一读取过程发生在VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,如果片段可以覆盖之前的片段,新的深度缓冲数据会在VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT写入。我们应该使用与要进行的操作相匹配的管线阶段进行同步操作。