Vulkan 教程 Khronos Vulkan® Tutorial · 中文翻译 GitHub

接下来几章,我们会搭建一条图形管线,用来绘制第一个三角形。图形管线是一连串操作,把网格的顶点和纹理一路处理成渲染目标中的像素。下图是它的简化概览:

vulkan simplified pipeline

输入装配器(input assembler)从你指定的缓冲区中收集原始顶点数据,也可以借助索引缓冲区复用某些元素,而不必复制顶点数据本身。

顶点着色器针对每个顶点运行一次,通常会对顶点位置施加变换,把它从模型空间转换到屏幕空间。它还会把逐顶点数据沿管线向下传递。

曲面细分着色器(tessellation shader)让你按一定规则细分几何体,以提高网格质量。砖墙、楼梯这类表面离得较近时,常用它来让表面看起来不那么扁平。

几何着色器(geometry shader)对每个图元(三角形、直线、点)运行一次,可以把它丢弃,也可以输出比输入更多的图元。它和曲面细分着色器类似,但灵活得多。不过如今的应用程序很少用它,因为除了 Intel 的集成 GPU,多数显卡上它的性能都不太好。此外,几乎所有几何着色器的用例都能被更现代的 Mesh 着色器管线取代;这种管线与光线追踪一样是全新的管线方案,因此不在标准图形管线的搭建流程之内。我们会在后续教程中讨论这种管线。

光栅化阶段把图元拆成片元。片元就是图元在帧缓冲上所覆盖的像素元素。落在屏幕之外的片元会被丢弃,顶点着色器输出的属性则在片元之间做插值,如下图所示。通常,位于其他图元片元之后的片元也会在这一步因深度测试而被丢弃。

片元着色器对每一个保留下来的片元运行,决定片元写入哪个(哪些)帧缓冲,以及写入什么颜色和深度值。它可以借助顶点着色器传来的插值数据完成这些工作,其中包括纹理坐标、用于光照的法线等。

颜色混合阶段执行若干操作,把映射到帧缓冲同一像素的不同片元混合起来。片元可以直接互相覆盖、相加,或者依据透明度进行混合。

绿色的阶段称为固定功能阶段。它们的运作方式是预先定义好的,你只能通过参数调整其具体操作。

而橙色的阶段是 programmable,也就是可编程的,意味着你可以把自己的代码上传到显卡,精确执行你想要的操作。例如,你可以用片元着色器实现从纹理映射、光照一直到光线追踪器的各种功能。这些程序同时在许多 GPU 核心上运行,并行处理大量对象,例如顶点和片元。

如果你以前用过 OpenGL、Direct3D 这类较旧的 API,应该已经习惯用 glBlendFunc、OMSetBlendState 之类的调用随意更改管线设置。而 Vulkan 中的图形管线几乎完全不可变,所以只要想更换着色器、绑定不同的帧缓冲或改变混合函数,就必须从头重建整条管线。代价是,你得为渲染操作中用到的每种状态组合各建一条管线。但反过来说,由于管线中会执行的所有操作都是预先知道的,驱动可以做针对性优化,效果要好得多。

部分可编程阶段是可选的,取决于你的目标。例如,只绘制简单几何体时,可以禁用曲面细分和几何阶段。如果你只关心深度值,可以禁用片元着色器阶段,这在生成 阴影贴图时很有用。

下一章我们先创建把三角形画到屏幕上所需的两个可编程阶段:顶点着色器和片元着色器。混合模式、视口、光栅化这类固定功能配置会在再下一章设置。搭建 Vulkan 图形管线的最后一部分工作,是指定输入和输出所用的帧缓冲。

新建一个 createGraphicsPipeline 函数,在 initVulkan 中紧接 createImageViews 之后调用它。接下来几章都会围绕这个函数展开。

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
    createImageViews();
    createGraphicsPipeline();
}

...

void createGraphicsPipeline() {

}

下一章会讨论真正把东西显示到屏幕上所需的着色器模块。