Vulkan 学习笔记(十二) 图形管线基础(Graphics pipeline basics)
简介
在接下来的几章中,我们将设置一个图形管线,这个管线被配置为绘制我们的第一个三角形。图形管线是一系列操作的序列,这些操作将网格的顶点和纹理一路转换到渲染目标中的像素。下图显示了一个简化的概述:
Vulkan简化管线图💡 术语解释:渲染目标 (Render Targets)
想象你是一位画家,你的画布就是渲染目标。在传统绘画中,你只有一块画布;但在Vulkan的世界里,你可能有多个"魔法画布"同时工作!
渲染目标就像是GPU的"工作台",所有图形操作的最终结果都会被绘制到这些目标上。最常见的渲染目标就是我们屏幕上看到的交换链图像,但Vulkan还允许你将内容渲染到"离屏"目标(比如纹理),这就像先在草稿纸上画好,再誊抄到正式作品上。
有些高级技术会使用多个渲染目标同时进行不同计算,这就像是用分屏作画——左边画颜色,右边画深度,上边画法线,最后再把它们神奇地组合在一起!
图形管线概述
输入汇编器从你指定的缓冲区中收集原始顶点数据,并且还可以使用索引缓冲区来重复某些元素,而无需复制顶点数据本身。
顶点着色器为每个顶点运行,通常应用变换将顶点位置从模型空间转换到屏幕空间。它还将逐顶点数据传递到管线的后续阶段。
曲面细分着色器允许你根据某些规则细分几何体,以提高网格质量。这通常用于使附近的砖墙和楼梯等表面看起来不那么平坦。
几何着色器在每个图元(三角形、线、点)上运行,可以丢弃它或输出比输入更多的图元。这与曲面细分着色器类似,但更加灵活。然而,在当今的应用程序中它并不常用,因为在大多数显卡上性能不太好,除了Intel的集成GPU。
光栅化阶段将图元离散化为片段。这些是它们在帧缓冲区中填充的像素元素。任何落在屏幕外的片段都会被丢弃,并且顶点着色器输出的属性会在片段之间进行插值,如图所示。通常,由于深度测试,位于其他图元片段后面的片段也会在此处被丢弃。
💡 术语解释:深度测试 (Depth Testing)
想象你在一个拥挤的音乐厅里,每个人都在尝试看到舞台。深度测试就像是音乐会的"座位规则"——如果你前面有人坐着,你就看不到舞台!
在3D图形中,深度测试确保只有最靠近摄像机的物体才会被绘制。没有它,你会看到墙壁内部的家具,或者透过角色的身体看到背后的敌人——这就像X光透视,但在游戏中通常不是我们想要的效果!
Vulkan通过深度缓冲区(一个存储每个像素距离的"秘密账本")来实现这一功能。当GPU决定绘制一个像素时,它会先检查这个账本,只有当新像素更近时才会更新它。这就像是音乐会的座位管理员,确保每个人都能获得最佳视野!
片段着色器为每个存活下来的片段调用,并确定片段被写入哪个帧缓冲区以及使用什么颜色和深度值。它可以使用顶点着色器的插值数据,这些数据可以包括纹理坐标和用于光照的法线等信息。
颜色混合阶段应用操作来混合映射到帧缓冲区中同一像素的不同片段。片段可以简单地相互覆盖,相加,或者根据透明度进行混合。
带有绿色颜色的阶段被称为固定功能阶段。这些阶段允许你使用参数调整它们的操作,但它们的工作方式是预定义的。
另一方面,带有橙色颜色的阶段是可编程的,这意味着你可以将自己的代码上传到显卡,以应用你想要的确切操作。这允许你使用片段着色器来实现从纹理和光照到光线追踪器的任何内容。这些程序在许多GPU核心上同时运行,以并行处理许多对象,如顶点和片段。
图形管线的不可变性
如果你之前使用过像OpenGL和Direct3D这样的旧API,那么你习惯于可以随意更改任何管线设置,使用诸如glBlendFunc和OMSetBlendState之类的调用。Vulkan中的图形管线几乎完全不可变,因此如果你想更改着色器、绑定不同的帧缓冲区或更改混合函数,你必须从头开始重新创建管线。
缺点是你必须创建代表你希望在渲染操作中使用的所有不同状态组合的多个管线。然而,由于管线中你将要进行的所有操作都预先知道,驱动程序可以更好地对其进行优化。
💡 术语解释:管线状态组合 (Pipeline State Combinations)
想象你是一家披萨店的厨师,每种披萨都需要特定的制作流程:有的需要先烤再加酱,有的需要先加酱再烤;有的用高温短时间,有的用低温长时间。
在Vulkan中,每个"制作流程"就是一个管线状态组合。如果你想要10种不同的披萨(渲染效果),你就需要预先设置10条完整的生产线,而不是像老式API那样在一条生产线上临时调整参数。
这看似麻烦,但实际上更高效!就像专业厨房会为每种招牌菜设置专用工作站一样,Vulkan的这种设计让GPU可以预先优化每条"生产线",避免在运行时进行昂贵的状态切换。虽然准备时间稍长,但一旦开始"烹饪",速度会快得多!
某些可编程阶段根据你的意图是可选的。例如,如果你只是绘制简单的几何体,可以禁用曲面细分和几何阶段。如果你只对深度值感兴趣,那么可以禁用片段着色器阶段,这对于阴影映射生成很有用。
💡 术语解释:阴影映射 (Shadow Mapping)
想象你有一个手电筒和一堆乐高积木。当你打开手电筒时,积木会在墙上投下阴影。阴影映射就像是先记录下这些阴影的"形状",然后再把它们应用到3D场景中。
在3D图形中,阴影映射是通过从光源的角度渲染场景(只记录深度信息)来创建的。这个深度图就像是"阴影模板",在最终渲染时,GPU会检查每个像素是否在阴影中,就像检查乐高积木是否挡住了光线一样。
为什么这很重要?因为在真实世界中,没有阴影的场景看起来像纸片一样不真实。Vulkan允许你禁用片段着色器来专门生成这些阴影深度图,这就像只用黑白相机拍照来制作阴影模板,而不是用彩色相机浪费资源!
创建图形管线函数
在下一章中,我们将首先创建两个必需的可编程阶段,以将三角形放到屏幕上:顶点着色器和片段着色器。诸如混合模式、视口、光栅化等固定功能配置将在接下来的章节中设置。在Vulkan中设置图形管线的最后一部分涉及指定输入和输出帧缓冲区。
创建一个createGraphicsPipeline函数,在initVulkan中的createImageViews之后调用。我们将在接下来的章节中处理这个函数。
void initVulkan(){
createInstance();
setupDebugMessenger();
createSurface();
pickPhysicalDevice();
createLogicalDevice();
createSwapChain();
createImageViews();
createGraphicsPipeline();
}
...
void createGraphicsPipeline(){
}
[C++ 代码]https://vulkan-tutorial.com/code/08_graphics_pipeline.cpp