现在我从不同的地方听到了所谓的 GPU 驱动渲染,它是一种全新的渲染范式,完全不需要绘制调用,并且它被新版本的 OpenGL 和 Vulkan API 支持。有人能够解释一下它在概念层面上是如何工作的,以及与传统方法的主要区别是什么吗?
为了渲染场景,需要进行多项操作。您需要遍历场景图以确定存在哪些对象。对于每个存在的对象,您现在需要确定它是否可见。对于每个可见的对象,您需要确定其几何形状存储在何处,将使用哪些纹理和缓冲区来渲染该对象,将使用哪些着色器来渲染该对象等等。然后您就可以渲染该对象。
处理此过程的“传统”方法是由 CPU 处理。场景图位于 CPU 可访问的内存中。CPU 对该场景图进行可见性剔除。CPU 获取可见对象并访问一些有关几何形状(OpenGL 缓冲区对象和纹理名称、Vulkan 描述符集和 VkBuffer
等)和着色器等方面的 CPU 数据,将其作为状态数据传输到 GPU。然后,CPU 发出 GPU 命令以使用该状态渲染该对象。
现在,如果我们回到更远的过去,最“传统”的方法根本不涉及 GPU。 CPU 只需获取这些网格和纹理数据,进行顶点变换、光栅化等操作,在 CPU 内存中生成图像。然而,我们开始将其中的一些内容转移到单独的处理器上。我们从光栅化开始(最早的图形芯片只是光栅化器;CPU 执行所有顶点 T&L)。然后,我们将顶点变换合并到 GPU 中。在这样做时,我们开始使用 GPU 可访问的内存存储顶点数据,以便 GPU 可以按自己的时间读取它。
我们之所以将所有这些内容转移至单独的处理器,有两个原因:GPU 的速度(大大)更快,而 CPU 现在可以将其时间用于其他事情。
GPU驱动渲染只是这个过程的下一阶段。我们从没有GPU到光栅化GPU,再到顶点GPU,现在到了场景图级别的GPU。"传统"方法将如何渲染卸载到GPU上;GPU驱动渲染则卸载了决定何时渲染。
现在,我们之所以一直没有这样做,是因为基本的渲染命令都需要来自CPU的数据。glDrawArrays/Elements
从CPU中获取许多参数。因此,即使我们使用GPU生成该数据,我们也需要完全的GPU/CPU同步,以便CPU可以读取数据...并将其立即返回给GPU。
那没用。
OpenGL 4为我们提供了各种形式的间接渲染。基本思想是,它们不是从函数调用中获取这些参数,而是存储在GPU内存中的数据。CPU仍然必须进行函数调用以启动渲染操作,但该调用的实际参数只是存储在GPU内存中的数据。
另一半需要GPU能够以间接渲染可读取的格式将数据写入GPU内存的能力。历史上,GPU上的数据只有一个方向:读取数据以将其转换为渲染目标中的像素。我们需要一种在GPU上从其他任意数据生成半任意数据的方法。
过去用于此目的的旧机制是滥用变换反馈,但现在我们只使用SSBOs或无法实现时使用image load/store。计算着色器也有所帮助,因为它们被设计为超出标准渲染管线之外,因此不受其限制。uniform
,我们该怎么办呢?如何在CPU渲染命令中有效地更改缓冲区绑定状态?嗯...你不能。所以你需要“作弊”。首先,你要意识到SSBO可以任意大。所以你不需要真正改变缓冲区绑定状态。你需要的是一个包含每个对象数据的单个SSBO。对于每个顶点,VS只需从巨大的数据列表中选择出正确的子绘制数据即可。这是通过一个特殊的顶点着色器输入完成的:gl_DrawID
。当你发出多次绘制命令时,VS会得到一个表示此子绘制操作在多次绘制命令中的索引的输入值。因此,你可以使用gl_DrawID
来索引一个每个对象数据表,以获取该特定对象的适当数据。这也意味着生成这个子绘制的计算着色器还需要使用该子绘制的索引来定义在数组中放置该子绘制的每个对象数据的位置。因此,编写子绘制的CS还需要负责设置与子绘制相匹配的每个对象数据。纹理OpenGL和Vulkan对绑定的纹理数量有相当严格的限制。实际上,相对于传统的渲染来说,这些限制是相当大的,但在GPU驱动的渲染领域,我们需要一个单个CPU渲染调用来潜在地访问任何纹理。这更难了。gl_DrawID
;结合上面提到的表格,我们可以检索每个对象的数据。那么:我们如何将其转换为纹理?gl_DrawID
从我们的SSBO中获取数组索引,该数组索引成为我们用于获取“自己”纹理的数组层。请注意,我们不直接使用gl_DrawID
,因为多个不同的子绘制可能会使用相同的纹理,并且设置绘制调用数组的GPU代码不能控制纹理在数组中出现的顺序。sampler
类型。因此,您可以使用gl_DrawID
从每个对象数据中获取64位句柄,然后将其转换为相应类型的sampler
并使用它。sampler
类型:uniform sampler2D my_2D_textures[6000];
。在OpenGL中,这将是一个编译错误,因为每个数组元素代表一个纹理的不同绑定点,您不能有6000个不同的绑定点。在Vulkan中,一个数组采样器只代表一个单一描述符,无论该数组中有多少元素。 Vulkan实现对此类数组中可以有多少元素有限制,但是支持该功能的硬件(shaderSampledImageArrayDynamicIndexing
)通常会提供一个慷慨的限制。gl_DrawID
从每个对象数据中获取索引。该索引通过从采样器数组中获取值来转换为sampler
。该数组描述符中纹理的唯一限制是它们必须都是相同类型和基本数据格式(sampler2D
的浮点二维,usamplerCube
的无符号整数立方体贴图等)。格式、纹理大小、mipmap计数等的具体细节都是无关紧要的。
如果您担心Vulkan的采样器数组与OpenGL的无绑定的成本差异,请不要担心; 无绑定实现只是在背后执行此操作。