设置渲染目标

多重采样是在一个离屏缓冲中进行的。这个离屏缓冲与我们之前进行渲染的一般图像对象略有不同。它需要对每个像素保存多个样本数据。多重采样缓冲的数据需要转换后,才能写入帧缓冲。和深度缓冲一样,我们只需要一个渲染目标用作多重采样缓冲即可,因为同时只会有一个绘制操作在进行。添加下面这些类成员变量:

...
VkImage colorImage;
VkDeviceMemory colorImageMemory;
VkImageView colorImageView;
        ...

我们需要修改createImage函数,添加numSamples参数用来指定采样个数:

void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
        ...
imageInfo.samples = numSamples;
        ...

现在,我们需要更新所有之前对createImage函数的调用,保证调用使用的参数正确:

createImage(swapChainExtent.width, swapChainExtent.height, 1, VK_SAMPLE_COUNT_1_BIT, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
        ...
createImage(texWidth, texHeight, 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, textureImage, textureImageMemory);

创建多重采样颜色缓冲。添加一个叫做createColorResources的函数,这里我们使用msaaSamples作为图像的采样个数。我们将图像的细化级别数目设置为1。Vulkan规范要求对于采样个数大于1的图像只能使用1个细化级别。实际上,对于多重采样颜色缓冲来说也只需要一个细化级别来存储原始图像数据,毕竟它不会被作为纹理贴图进行渲染。

void createColorResources() {
    VkFormat colorFormat = swapChainImageFormat;

    createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImage, colorImageMemory);
    colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);

    transitionImageLayout(colorImage, colorFormat, VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 1);
}

我们在创建深度缓冲资源的函数调用之前调用createColorResources函数创建多重采样缓冲:

void initVulkan() {
        ...
    createColorResources();
    createDepthResources();
        ...
}

读者可能已经注意到,我们新创建的用于多重采样的缓冲图像使用了一个新的布局变换过程:从VK_IMAGE_LAYOUT_UNDEFINED到VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL。我们需要修改transitionImageLayout函数来处理它:

void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) {
        ...
    else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
        barrier.srcAccessMask = 0;
        barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
        destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    }
    else {
        throw std::invalid_argument("unsupported layout transition!");
    }
        ...
}

现在我们需要修改createDepthResources函数,更新深度缓冲使用的采样个数:

void createDepthResources() {
        ...
    createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
        ...
}

最后,不要忘记在清除交换链时,清除我们创建的与多重采样缓冲相关的资源:

void cleanupSwapChain() {
    vkDestroyImageView(device, colorImageView, nullptr);
    vkDestroyImage(device, colorImage, nullptr);
    vkFreeMemory(device, colorImageMemory, nullptr);
        ...
}

修改recreateSwapChain函数,在窗口大小变化时重建多重采样缓冲:

void recreateSwapChain() {
        ...
    createGraphicsPipeline();
    createColorResources();
    createDepthResources();
        ...
}

至此,我们就完成了多重采样缓冲的设置,可以在图形管线中使用它了。