张家口市网站建设_网站建设公司_会员系统_seo优化
2026/1/12 5:37:05 网站建设 项目流程

目录

Vulkan 的起源

绘制一个三角形需要做什么

步骤 1 - 实例与物理设备选择

步骤 2 - 逻辑设备与队列族

步骤 3 - 窗口表面与交换链

步骤 4 - 图像视图与帧缓冲

步骤 5 - 渲染通道

步骤 6 - 图形管线

步骤 7 - 命令池与命令缓冲区

步骤 8 - 主循环

总结

API 概念

编码规范

验证层


本章将首先介绍 Vulkan 及其要解决的问题。之后,我们会了解绘制第一个三角形所需的核心组件,帮助你建立整体认知,以便更好地理解后续章节的内容。最后,我们将详细说明 Vulkan API 的结构和通用使用模式。

Vulkan 的起源

与以往的图形 API 类似,Vulkan 旨在作为跨平台的 GPU 抽象层。但大多数早期 API 存在一个关键问题:它们的设计时代背景下,图形硬件大多局限于可配置的固定功能。开发者必须以标准格式提供顶点数据,而在光照和着色选项上只能受制于 GPU 制造商。

随着显卡架构的成熟,硬件开始支持越来越多的可编程功能。这些新功能不得不以某种方式与现有 API 整合,这导致了不够理想的抽象设计 —— 图形驱动需要大量推测开发者的意图,才能将其映射到现代图形架构上。这也是为什么显卡驱动需要频繁更新以提升游戏性能(有时提升幅度十分显著)的原因之一。此外,由于驱动的复杂性,应用开发者还需应对不同厂商间的兼容性问题,例如着色器支持的语法差异。

除了这些新功能带来的挑战,过去十年中移动设备的兴起也带来了新的需求。这些移动 GPU 基于能耗和空间限制采用了不同的架构,例如瓦片渲染(tiled rendering)—— 如果能让开发者对该功能拥有更多控制权,就能进一步提升性能。另外,早期 API 的另一个局限是对多线程支持不足,这可能导致 CPU 侧出现性能瓶颈。

Vulkan 通过为现代图形架构从头设计,解决了这些问题:

  1. 它采用更冗长但明确的 API,让开发者能够清晰地表达意图,从而减少驱动开销;
  2. 支持多线程并行创建和提交命令;
  3. 采用标准化的字节码格式和统一编译器,减少着色器编译的兼容性问题;
  4. 统一了图形和计算功能,充分发挥现代显卡的通用计算能力。

绘制一个三角形需要做什么

下面我们将概述一个规范的 Vulkan 程序中,渲染三角形所需的所有步骤。本节介绍的所有概念都会在后续章节中详细展开,此处仅为帮助你建立整体认知,理解各个组件之间的关联。

步骤 1 - 实例与物理设备选择

Vulkan 应用程序首先需要通过VkInstance初始化 Vulkan API。创建实例时,需描述应用程序信息以及将要使用的 API 扩展。实例创建完成后,可查询系统中支持 Vulkan 的硬件,并选择一个或多个VkPhysicalDevice用于后续操作。你可以通过查询设备属性(如显存大小、设备功能)来筛选符合需求的设备,例如优先选择独立显卡。

步骤 2 - 逻辑设备与队列族

选择合适的物理设备后,需要创建VkDevice(逻辑设备)。创建时需更具体地指定将要使用的VkPhysicalDeviceFeatures(如多视口渲染、64 位浮点数支持等),同时还要指定需要使用的队列族。

Vulkan 中的大多数操作(如绘制命令、内存操作)都是通过提交到VkQueue(队列)异步执行的。队列从队列族中分配,每个队列族支持的操作类型是固定的 —— 例如,可能存在专门用于图形操作、计算操作或内存传输操作的队列族。队列族的支持情况也可作为筛选物理设备的依据。理论上,支持 Vulkan 的设备可能不提供任何图形功能,但目前所有支持 Vulkan 的显卡通常都会支持我们所需的所有队列操作。

步骤 3 - 窗口表面与交换链

除非仅需离屏渲染,否则需要创建一个窗口来显示渲染结果。窗口可通过原生平台 API 或 GLFW、SDL 等库创建(本教程将使用 GLFW,后续章节会详细介绍)。

要向窗口渲染图像,还需要两个核心组件:窗口表面(VkSurfaceKHR)和交换链(VkSwapchainKHR)。注意后缀KHR表示这些对象属于 Vulkan 扩展 ——Vulkan API 本身完全与平台无关,因此需要通过标准化的 WSI(Window System Interface,窗口系统接口)扩展与窗口管理器交互。

窗口表面是对渲染目标窗口的跨平台抽象,通常通过传入原生窗口句柄(如 Windows 平台的HWND)初始化。幸运的是,GLFW 库提供了内置函数,可自动处理这些平台相关的细节。

交换链是一组渲染目标的集合,其核心作用是确保当前正在渲染的图像与屏幕上显示的图像是分离的,从而保证只有完整的图像才会被展示。每次绘制一帧时,需先向交换链请求一个用于渲染的图像;绘制完成后,将图像返回给交换链,由交换链在合适的时机显示到屏幕上。

交换链中渲染目标的数量以及图像展示的条件由呈现模式(present mode)决定,常见的呈现模式包括双缓冲(垂直同步,vsync)和三重缓冲。后续章节介绍交换链创建时会详细说明。

部分平台支持通过VK_KHR_displayVK_KHR_display_swapchain扩展直接渲染到显示器,无需与任何窗口管理器交互。例如,可通过这些扩展创建代表整个屏幕的表面,用于实现自定义窗口管理器。

步骤 4 - 图像视图与帧缓冲

要在从交换链获取的图像上进行绘制,需将其封装为VkImageView(图像视图)和VkFramebuffer(帧缓冲)。图像视图指定了图像中用于渲染的特定部分;帧缓冲则关联了用于颜色、深度和模板目标的图像视图。

由于交换链中可能包含多个图像,我们需要提前为每个图像创建对应的图像视图和帧缓冲,在绘制时选择对应的对象使用。

步骤 5 - 渲染通道

Vulkan 中的渲染通道(Render Pass)描述了渲染过程中使用的图像类型、使用方式以及内容处理规则。在初始的三角形渲染程序中,我们会通过渲染通道告知 Vulkan:将使用单个图像作为颜色目标,并在绘制前将其清除为纯色。需要注意的是,渲染通道仅描述图像的类型,而VkFramebuffer会将具体的图像绑定到这些类型对应的槽位上。

步骤 6 - 图形管线

图形管线通过创建VkPipeline对象配置,它描述了显卡的可配置状态(如视口大小、深度缓冲操作)和可编程状态(通过VkShaderModule对象实现)。VkShaderModule由着色器字节码创建,驱动还需要通过引用渲染通道来明确管线将使用的渲染目标。

Vulkan 与现有 API 最显著的区别之一是:图形管线的几乎所有配置都需要提前完成。这意味着,如果需要切换着色器或略微修改顶点布局,就必须重新创建整个图形管线。因此,你需要提前为渲染操作所需的所有配置组合创建对应的VkPipeline对象。只有部分基础配置(如视口大小、清除颜色)支持动态修改,所有状态都需要显式描述(例如,不存在默认的颜色混合状态)。

不过这也带来了优势:这种 “提前编译”(ahead-of-time compilation)相比 “即时编译”(just-in-time compilation),能为驱动提供更多优化空间,且运行时性能更可预测 —— 因为管线切换等大型状态变更被明确化了。

步骤 7 - 命令池与命令缓冲区

如前所述,Vulkan 中的许多操作(如绘制)需要提交到队列执行。这些操作必须先记录到VkCommandBuffer(命令缓冲区)中,才能提交到队列。命令缓冲区从与特定队列族关联的VkCommandPool(命令池)中分配。

要绘制一个简单的三角形,需要在命令缓冲区中记录以下操作:

  1. 开始渲染通道
  2. 绑定图形管线
  3. 绘制 3 个顶点
  4. 结束渲染通道

由于帧缓冲中的图像取决于交换链提供的具体图像,我们需要为每个可能的图像记录对应的命令缓冲区,在绘制时选择使用。另一种方案是每帧重新记录命令缓冲区,但效率较低。

步骤 8 - 主循环

将绘制命令封装到命令缓冲区后,主循环的逻辑就相对简洁了:

  1. 通过vkAcquireNextImageKHR从交换链获取图像;
  2. 选择该图像对应的命令缓冲区,通过vkQueueSubmit执行;
  3. 通过vkQueuePresentKHR将图像返回给交换链,用于屏幕显示。

提交到队列的操作是异步执行的,因此需要使用信号量(semaphore)等同步对象确保执行顺序正确:

  • 绘制命令缓冲区的执行必须等待图像获取完成,否则可能出现 “正在渲染的图像仍在被屏幕读取” 的问题;
  • 反之,vkQueuePresentKHR调用必须等待渲染完成,因此需要使用第二个信号量 —— 在渲染完成后发出信号。

总结

以上快速概述让你对绘制第一个三角形的流程有了基本了解。实际应用程序会包含更多步骤(如分配顶点缓冲区、创建统一缓冲区、上传纹理图像等),这些会在后续章节中介绍。我们先从简单场景入手 —— 因为 Vulkan 的学习曲线本就较为陡峭。

需要说明的是,为了简化初始步骤,我们会暂时将顶点坐标嵌入顶点着色器中,而非使用顶点缓冲区(管理顶点缓冲区需要先熟悉命令缓冲区的相关操作)。

简而言之,绘制第一个三角形需要完成以下工作:

  1. 创建VkInstance
  2. 选择支持 Vulkan 的显卡(VkPhysicalDevice);
  3. 创建用于绘制和呈现的VkDevice(逻辑设备)和VkQueue(队列);
  4. 创建窗口、窗口表面和交换链;
  5. 将交换链图像封装为VkImageView(图像视图);
  6. 创建指定渲染目标和使用规则的渲染通道;
  7. 为渲染通道创建帧缓冲;
  8. 配置图形管线;
  9. 为每个可能的交换链图像分配并记录包含绘制命令的命令缓冲区;
  10. 通过 “获取图像→提交命令缓冲区→返回图像” 的流程绘制帧。

虽然步骤较多,但后续章节会将每个步骤的目的和实现方式讲解得简洁明了。如果对单个步骤与整体程序的关联感到困惑,可以回顾本章内容。

API 概念

本章最后将简要介绍 Vulkan API 的底层结构。

编码规范

Vulkan 的所有函数、枚举和结构体都定义在vulkan.h头文件中(包含在 LunarG 开发的 Vulkan SDK 中,下一章将介绍 SDK 的安装)。

编码规范如下:

  • 函数:以小写vk为前缀(如vkCreateXXX);
  • 类型(枚举、结构体等):以Vk为前缀(如VkInstanceVkDevice);
  • 枚举值:以VK_为前缀(如VK_SUCCESS)。

API 大量使用结构体传递函数参数,例如对象创建通常遵循以下模式:

cpp

运行

VkXXXCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; createInfo.pNext = nullptr; createInfo.foo = ...; createInfo.bar = ...; VkXXX object; if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { std::cerr << "创建对象失败" << std::endl; return false; }

Vulkan 中的许多结构体要求在sType成员中显式指定结构体类型;pNext成员可指向扩展结构体,本教程中始终设为nullptr;创建或销毁对象的函数会包含VkAllocationCallbacks参数,用于自定义驱动内存分配器,本教程中也将其设为nullptr

几乎所有函数都会返回VkResult类型的值,可能是VK_SUCCESS(成功)或错误码。Vulkan 规范会明确每个函数可能返回的错误码及其含义。

验证层

如前所述,Vulkan 的设计目标是高性能和低驱动开销,因此默认情况下仅提供非常有限的错误检查和调试功能。如果操作不当,驱动通常会直接崩溃(而非返回错误码),更糟的情况是:程序在你的显卡上能正常运行,但在其他显卡上完全失效。

Vulkan 允许通过 “验证层”(validation layers)启用全面的检查功能。验证层是插入在 API 和图形驱动之间的代码片段,可执行额外的检查(如函数参数验证)和问题跟踪(如内存管理问题)。其优势在于:开发阶段可启用验证层调试,发布应用时可完全禁用,不会产生任何性能开销。

任何人都可以编写自定义验证层,但 LunarG 的 Vulkan SDK 提供了一套标准验证层,本教程将使用这些验证层。此外,还需要注册一个回调函数来接收验证层的调试信息。

由于 Vulkan 的每个操作都非常明确,且验证层功能强大,相比 OpenGL 和 Direct3D,Vulkan 中 “屏幕变黑” 等问题的排查反而可能更加简单!

在开始编写代码前,还需要完成最后一步:搭建开发环境。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询