关注专栏 写文章
第 11 章. Drawing Textures 绘制纹理

第 11 章. Drawing Textures 绘制纹理

4 人 赞同了该文章

第 11 章. Drawing Textures 绘制纹理

在前一章中,我们学习了如何更新资源的内容并使用描述符在着色器阶段读取它们。 我们还介绍了 push 常量,它是在着色器阶段使用命令缓冲区更新常量数据的优化方式。 另外,通过使用描述符,我们对渲染的图元添加了 3D 变换,并且还演示了一个示例来学习 push 常量。

在本章中,我们会学习和实现纹理;我们会将它们缠绕在几何表面上,以便增强现实感。 纹理是使用 Vulkan 的图像资源创建的;其数据可以以线性或最佳布局存储。 我们将实现这两种布局 - 后者布局使用暂存。 在暂存中,物理的分配过程使用了两个不同的存储区域。 主机可能无法看到资源的理想内存布局。 在这种情况下,应用程序必须首先将资源填充到主机可见的暂存缓冲区中,然后将其转移到理想的位置。

在本章中,我们会介绍以下主题:

  • 图像资源 Image resource - 快速回顾
  • 纹理绘制 texture drawing 的先决条件
  • 用线性平铺 linear tiling 实现图像资源
  • 用优化平铺 optimal tiling 实现图像资源
  • 在图像和缓冲区之间复制数据内容
  • 更新描述符集 descriptor set

Image resource - a quick recap

图像资源 - 快速回顾

图像是以 1D,2D 或 3D 形式存储的连续字节数组。 与缓冲区资源不同,图像是存储在内存中格式化的信息。

Vulkan 中的图像资源由 VkImage 对象表示,并使用 vkCreateImage API 创建。 这个对象的创建还没有实际的图像内容作为后端支持。 这必须单独完成,其中需要分配设备内存并将图像内容存储到其中。 然后把该内存绑定到创建的对象。

为了在着色器阶段使用创建的图像对象,必须将它们转换为图像视图 -VkImageView。 在将图像转换为图像视图之前,必须使用图像布局使其与底层实现兼容。

使用 VkImageLayout 将图像转换为实现相关的布局。 对于给定的图像资源,可以创建并使用多个图像布局。 不同的布局可能会暴露不同的性能特征,因为它们非常专注于 usage 类型。 因此指明正确的用法 usage 可让驱动程序选择一个特定的存储位置或部分区域,适合用来提供最佳的性能。

如果您想了解图像资源的详细介绍,请参阅第 6 章“分配图像资源以及使用 WSI 创建交换链接”中的第一部分,即“图像资源入门”。 在同一章节中,您可以参考“了解图像资源”部分以获取有关图像,图像视图和图像布局的详细信息。

创建一个图像资源很简单,由以下步骤组成:

  1. 图像对象的创建:Image object creation,首先,创建 VkImage 对象。 此对象不包含图像数据,但它存储了图像资源各种重要的对象状态,例如格式,尺寸,图像类型,图像的 usage 类型,平铺样式等。 一个给定的图像可以具有多个子图像资源,例如 mipmap。 以下是创建图像对象的步骤:
    1. 平铺:Tiling,可以指定图像平铺的两种方式:线性和最佳。 在线性布局中,图像数据会被映射到设备上的连续内存,以线性方式排列。 然而,在最佳布局中,图像以贴片的形式存储,并且每个贴片内的纹理元素可以以线性或某种专有格式排列以提供最佳的性能。 有关线性和最佳布局的详细介绍,请参阅第 6 章“分配图像资源以及使用 WSI 创建交换链”中的“平铺简介”一节。
    2. 分配和赋值图像数据:Allocating and assigning image data,读取图像内容并将所需的内存分配给图像资源。 使用图像通道的内容填充分配的设备内存。
    3. 设置正确的布局:Setting the correct layout,创建一个实现兼容的图像布局。 单个图像及其子资源可以用多个布局来指定。

  2. 图像采样器:Image sampler,创建采样器(VkSampler)来控制纹理的过滤。
  3. 图像视图创建:Image view creation,图像资源只能在着色器中以图像视图(VkImageView)的形式访问。

Prerequisites for texture drawing

纹理绘制的先决条件

实现纹理很简单,只需要几个步骤。 我们首先快速浏览一下,然后我们会对其进行深入的探讨:

  1. 纹理坐标:Texture coordinates,使用纹理坐标将纹理粘贴到几何表面。 对于每个顶点,都附有相应的纹理坐标。 在我们的实现中,我们以交错的形式指定了顶点和纹理坐标。
  2. 着色器阶段:The shader stage,修改顶点和片段着色器以便约束纹理资源。 着色器阶段允许片段着色器访问纹理资源以及绘制片段。 在着色器阶段,纹理会以采样器的形式共享。
  3. 加载图像文件:Loading the image files,解析图像文件并将原始图像数据加载到本地的数据结构中。 这将有助于生成 Vulkan 图像资源并在着色器阶段共享它们。
  4. 本地图像数据结构:Local image data structure,TextureData 本地数据结构存储了所有图像特定的属性。

Specifying the texture coordinates 指定纹理坐标

几何坐标(x,y,z,w)与纹理坐标(u,v)以交错的形式定义在 MeshData.h 文件内的 VertexWithUV 结构中:

struct VertexWithUV
{
float x, y, z, w; // Vertex Position
float u, v; // Texture format U,V
};

在本示例中,我们会渲染用纹理面绘制的立方体。 以下代码显示了立方体的一个面,具有四个顶点位置,后面跟着两个纹理坐标。 完整的代码请参考 MeshData.h:

static const VertexWithUV geometryData[] = {
{ -1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 1.0f },  // - X side
{ -1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f },
{ -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f },
{ -1.0f, 1.0f,-1.0f, 1.0f, 0.0f, 0.0f },
{ -1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 1.0f },
. . . .
// Similar, specify +X, - Y, +Y, - Z, +Z faces
}

Updating the shader program 更新着色器程序

除了顶点坐标之外,现在我们的顶点着色器也会考虑纹理坐标。 输入的纹理坐标在布局位置 1 的 inUV 属性中进行接收。 然后这些坐标会被传递到片段着色器阶段并被接收到 outUV 中。 以下代码以粗体显示在现有顶点着色器中所做的修改:

// Vertex Shader
#version 450
layout (std140, binding = 0) uniform bufferVals { mat4 mvp;
} myBufferVals;