这个系列是GAMES106-现代图形绘制流水线原理与实践(GAMES 106: Intro to Modern Rendering Pipeline)的同步课程笔记。课程内容涉及现代图形绘制流水线的基础原理,实践方法,以及优化技巧。本节课主要介绍Vulkan的基本架构和绘制流程。
Vulkan简介
Vulkan可以看做是新一代的OpenGL。作为图形API,Vulkan只提供了标准的接口而具体实现则依赖于硬件厂商。因此Vulkan的文档中很多功能的支持可能是不完整的,这对于核显和移动端的图形开发需要额外注意。
和传统RHI(render hardware interface)相比,Vulkan等现代RHI大多抛弃了状态机模型转而允许开发者更多地直接对GPU进行控制。这样的好处是理论上开发者可以摆脱API的限制写出更高性能的程序,但也使得Vulkan的学习曲线更加陡峭。
使用Vulkan的整体流程大致可以分为三个部分:
- 初始化:
- 创建显示窗口
- 初始化Vulkan库并连接GPU
- 创建Swapchain与连接显示设备
- 渲染主循环:
- 确定输入、输出以及渲染逻辑
- 更新CPU端业务逻辑
- 将CPU数据拷贝到GPU端
- 生成command buffer把渲染任务交给GPU并在屏幕上显示
- 退出程序:
- 释放Vulkan对象
- 关闭程序窗口
Vulkan初始化
创建窗口
使用Vulkan进行渲染的第一步是创建一个显示窗口。不过Vulkan并不直接提供创建窗口的功能,一般需要借助于SDL或是GLFW这些第三方库来实现。Vulkan对这些创建窗口的第三方库有着非常好的支持,使用它们甚至可以简化Vulkan的初始化过程。
Vulkan层次
整个Vulkan程序按照从上到下的顺序可以划分为应用程序Application
、实例VkInstance
、物理设备VkPhysicalDevice
、逻辑设备VkDevice
以及队列VkQueue
,如下图所示。在绝大多数情况下一个Vulkan程序只包括一个实例、一个物理设备、一个逻辑设备以及一个或多个队列,只要在同时需要多张显卡同时进行工作时才会涉及多个物理设备和逻辑设备的情况。
VkInstance
创建VkInstance
的目的是连接应用程序和Vukan库。这里应用程序不是直接和驱动进行连接,而是要穿过一系列的Vulkan Layer才能到达驱动。在创建VkInstance
时我们需要指定swapchain的类型、设置Vulkan对象的名称以及检查程序报错时的回调函数。
Vulkan Layer的功能类似于函数重载。当应用程序调用了某个函数时会按照层的顺序依次执行每一层的对应函数,并传递给下一层直到实际的
VkPhysicalDevice
创建好VkInstance
后应用程序需要和GPU取得联系。这里Vulkan允许我们查询计算机所有可以使用的GPU以及它们的计算能力,这样程序就可以根据自身的需求来选择合适的设备。
Vulkan中所有的命令都需要上传到命令队列VkQueue
中以备执行。不同类型的队列来自于相应的队列簇(Queue Family Properties),每个队列簇只允许部分类型的命令。
VkDevice
程序得到物理设备后需要创建一个逻辑设备VkDevice
和Vulkan进行交互。根据程序自身的需求,我们可以创建所需类型的队列簇而无需所有类型。创建好逻辑设备后就可以从逻辑设备获取命令队列了。
Swapchain
创建好VkDevice
后Vulkan和GPU的初始化就结束了,接下来需要和显示设备进行连接。这一过程需要创建swapchain并设置swapchain的各种属性。
绘制主循环
在Vulkan的绘制主循环中我们需要手动设置整个渲染流水线的所有流程。
顶点输入
绘制流水线的第一个阶段是把三角形顶点的数据输入到vertex buffer中。
除了顶点数据存储的vertex buffer外,我们还需要记录顶点连接关系的index buffer。这里的一个技巧在于把顶点属性和索引尽可能连续地布置在一起从而提高缓存利用率,避免索引跳转过大产生的性能影响。
当然Vulkan也支持三角形外其它类型的图元包括点和线等。
顶点着色器
得到顶点数据后可以通过顶点着色器(vertex shader)变换到屏幕空间。Vulkan支持HLSL以及GLSL等各种常用著色语言的编译。
需要注意的是Vulkan中NDC坐标轴的指向与其它RHI有所不同。
光栅化
在光栅化(Rasterization)阶段Vulkan需要指定三角形的填充方式(polygon mode)、剔除方式(cull mode)、正方向(front face)、线宽度、depth bias等属性。
这里需要注意窗口和viewport的区别。在Vulkan中viewport是真正需要绘制的区域,而窗口则是整个显示的区域。在大多数情况下视图和窗口是一致的,不过也有viewport与窗口不一致的情况。
在UI绘制中有时还需要考虑对viewport进行裁剪。
片段着色器
片段着色器(fragment shader)是整个渲染管线最核心的部分,它用来计算每个像素的着色。
片段输出
最后,fragment shader的计算结果会输出到fragment buffer中并进行混合。
总结一下,在Vulkan中的经典绘制流水线如下。