Vulkan 学习笔记(十一) 图像视图(Image views)
介绍
要在渲染管线中使用任何 VkImage(包括交换链中的图像),我们必须创建一个 VkImageView 对象。图像视图从字面上看就是图像的一个"视角"。它描述了如何访问图像以及访问图像的哪一部分,例如是否应将其视为2D纹理、深度纹理,以及是否使用任何mipmapping级别。
在本章中,我们将编写一个 createImageViews 函数,为交换链中的每个图像创建一个基本的图像视图,以便我们稍后可以将它们用作颜色目标。
💡 术语解释:VkImage vs VkImageView
想象VkImage是一张原始的、未经处理的胶片底片,而VkImageView则是你用来观看这张底片的投影仪。底片本身包含了所有数据,但你需要投影仪来决定如何展示它——是放大、缩小、旋转,还是只展示其中的一部分。
在Vulkan的世界里,VkImage是数据的容器,存储着像素信息;而VkImageView则是告诉GPU"请按照这种方式来看待这些数据"的指令集。这种分离设计让Vulkan变得极其灵活——同一张"底片"可以通过不同的"投影仪"呈现出完全不同的效果!
创建图像视图
首先添加一个类成员来存储图像视图:
std::vector<VkImageView> swapChainImageViews;
创建 createImageViews 函数,并在交换链创建后立即调用它。
void initVulkan(){ createInstance(); setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); createSwapChain(); createImageViews();}void createImageViews(){}
我们需要做的第一件事是调整列表大小,以容纳我们将要创建的所有图像视图:
void createImageViews(){ swapChainImageViews.resize(swapChainImages.size());}
接下来,设置一个循环,遍历交换链中的所有图像。
for (size_t i = 0; i < swapChainImages.size(); i++) {}
💡 术语解释:Mipmapping
想象你在看一张高分辨率的照片。当你把它放在眼前时,你能看到所有细节;但当你把它举到远处时,很多细节就看不清了。Mipmapping就是为这种情况准备的"魔法相册"!
它预先为你的纹理创建了一系列越来越小的版本(就像相册中从小到大的照片)。当你在远处看物体时,GPU会自动选择较小的版本,这样不仅渲染更快,还能避免出现难看的闪烁和摩尔纹。这就像为不同距离准备了不同焦距的镜头,让画面始终清晰流畅!
在我们的交换链图像中,由于我们只在屏幕上显示一次,所以不需要这个"魔法相册",因此levelCount设置为1。
配置图像视图参数
图像视图创建的参数在 VkImageViewCreateInfo 结构体中指定。前几个参数很简单。
VkImageViewCreateInfo createInfo{};createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;createInfo.image = swapChainImages[i];
viewType 和 format 字段指定了如何解释图像数据。viewType 参数允许你将图像视为1D纹理、2D纹理、3D纹理和立方体贴图。
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;createInfo.format = swapChainImageFormat;
components 字段允许你重新排列颜色通道。例如,你可以将所有通道映射到红色通道,以创建单色纹理。你也可以将通道映射到常量值 0 和 1。在我们的例子中,我们将坚持使用默认映射。
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
💡 术语解释:组件Swizzle(颜色通道重排)
想象你有一个调色板,上面有红、绿、蓝、透明四个颜料槽。Swizzle就像是一个调皮的调色师,他可以任意交换这些颜料槽的位置!
比如,他可以把红色通道的颜料倒进蓝色槽,蓝色的倒进绿色槽,绿色的倒进红色槽——这样原本红色的物体就会变成蓝色!这种魔法在图像处理中非常有用:
在我们的例子中,我们让调色师保持"老实本分"(IDENTITY),不做任何交换,保持原始的颜色通道顺序。
subresourceRange 字段描述了图像的用途以及应该访问图像的哪一部分。我们的图像将用作颜色目标,没有任何mipmapping级别或多个层。
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;createInfo.subresourceRange.baseMipLevel = 0;createInfo.subresourceRange.levelCount = 1;createInfo.subresourceRange.baseArrayLayer = 0;createInfo.subresourceRange.layerCount = 1;
💡 术语解释:Aspect Mask(图像面罩)
想象一张特殊的3D贺卡,它有多个"面":正面印着彩色图案,背面可能藏着深度信息(就像浮雕),侧面可能还有模板信息(比如镂空的形状)。Aspect Mask就像是一个选择器,告诉你"我现在只想看这张贺卡的彩色正面"或者"我需要同时看彩色和深度信息"。
在Vulkan中,图像可以包含不同类型的数据:
- •
VK_IMAGE_ASPECT_COLOR_BIT:彩色数据(RGB/RGBA) - •
VK_IMAGE_ASPECT_DEPTH_BIT:深度数据(用于3D空间中的距离) - •
VK_IMAGE_ASPECT_STENCIL_BIT:模板数据(用于复杂的形状裁剪)
就像你不会用放大镜看贺卡的背面来欣赏正面的图案一样,GPU也需要知道你想要访问图像的哪个"面"。这不仅仅是性能优化,更是确保数据正确解释的关键!
如果你正在开发立体3D应用程序,那么你会创建一个具有多层的交换链。然后,你可以为每个图像创建多个图像视图,通过访问不同的层来表示左右眼的视图。
现在创建图像视图只需要调用 vkCreateImageView:
if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("创建图像视图失败!");}
清理图像视图
与图像不同,图像视图是我们显式创建的,所以我们需要在程序结束时添加一个类似的循环来销毁它们:
void cleanup(){ for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } ...}
图像视图足以开始将图像用作纹理,但它还不能直接用作渲染目标。这还需要一个额外的间接步骤,称为帧缓冲区。但首先我们必须设置图形管线。
[C++ 代码]https://vulkan-tutorial.com/code/07_image_views.cpp