深入理解Mesh Shader优化原理

文章对比了传统顶点着色器与MeshShader在GPU渲染中的性能和效率,指出MeshShader的优势在于能直接控制几何处理,提供更高的顶点重用和灵活性,适用于程序化几何、细分曲面等复杂场景。
摘要由CSDN通过智能技术生成

参照AMD官网文章和GDC中其分享内容https://gpuopen.com/learn/mesh_shaders/mesh_shaders-index/总结自用,大佬直接原文。

一、传统顶点着色器管线与Mesh 着色器对比

具体之前也研究过可参照:DX12_Mesh Shaders Render
这里主要针对之前忽略的一些知识点进行补充。

1.1 顶点着色器的弊端

我们先深入了解一下GPU 是如何使用顶点缓冲区和索引缓冲区来处理传统的绘制调用的(可参照:GPU架构与管线总结)。在传统的图形管线中,网格通常被定义为一组顶点,连续的三个顶点构成一个三角形。为了减少数据冗余,可以通过索引缓冲区将三角形定义为一组三个索引,每个索引引用一个不同的顶点。如下图所示:
在这里插入图片描述

常规流程如下:
在这里插入图片描述

对于索引缓冲区引用的每个顶点,GPU都需要运行一个vertex shader实例,将顶点从顶点缓冲区中的输入布局转换为裁剪空间(clip-space)中的位置。由于输入网格使用的是索引缓冲区,因此重复使用顶点的好处同样适用于此——减少不必要的顶点着色计算,从而降低图形处理成本。Input Assembler负责解析索引缓冲区并启动后续的顶点处理。

在Input Assembler中实现顶点重用的一种显而易见的方法是对顶点缓冲区中的每个顶点运行vertex shader,并存储转换后的顶点。然而这种方法具体实施起来却并不可行,因为存储数百万个转换后的顶点可能会超出GPU的内存。另外,这样的实现会延迟图元组装,导致着色必须要等到所有vertex shader实例执行完成之后才开始。

以要绘制上述四个三角形为例,我们需要向图形命令列表提交以下绘制调用:

graphicsCommandList->DrawIndexedInstanced(12 /* index count */,
1 /* instance count */,
0 /* start index location */,
0 /* base vertex location */,
0 /* base instance location */).

一旦提交了命令缓冲区,API 运行时和 GPU 驱动程序处理了绘制调用,就会向 GPU 发送绘制命令。该命令由以下各部分接收和处理:
在这里插入图片描述

  • Command Processor 从 GPU 驱动程序接收绘制、调度或其他命令,并根据当前命令设置其他组件,如Geometry
    Engine。
  • Geometry Engine 负责从索引缓冲区加载索引、设置并启动vertex shader实例。
  • Dual Compute Unit 执行vertex shader以及其他shader。Geometry Engine会指示计算单元每个线程处理哪个顶点。计算单元从一个或多个顶点缓冲区获取顶点信息,并将转换后的顶点信息存储到shader export中。
  • Shader Export 是一个内存块,用于接收和存储计算单元的顶点和图元信息。
  • Primitive Assembler 从shader export读取图元连接信息和转换后的顶点位置,将它们组装成独立的三角形,然后输出到rasterizer。
  • Rasterizer 接收组装好的三角形,并确定每个三角形需要进行着色的片段。

为了确定哪些顶点需要由vertex shader处理,geometry engine会从索引缓冲区中选择一个图元子集(primitive subset)。从概念上讲,一个图元子组(即重新索引的顶点索引和唯一顶点)会形成一个小网格,或者成为meshlet,具有自己的一组顶点和索引。由于这些索引并不直接引用原始输入顶点缓冲区中的顶点,而是引用唯一顶点列表中的顶点索引,因此我们将这些索引称为图元连接(primitive connectivity)。
在这里插入图片描述
在一个图元组中的顶点将一起进行处理,一旦顶点处理完成,图元连接将被用于把它们组装成三角形,以便进行光栅化处理。一个图元组形成并送去处理后,geometry engine会对索引缓冲区中的下一个图元子集重复相同的处理步骤,直到所有图元都渲染完毕。

为了最大限度地提高顶点重用率,理想情况下我们希望所有引用同一顶点的图元都在同一个图元子组中。换句话说,我们希望每个顶点只存在于一个图元子组中。否则 GPU 将为同一个顶点多次调用vertex shader,造成多余计算,影响整体的性能。由于图元子组受顶点最大数量或图元最大数量的限制,理想的顶点重用,即每个顶点只被处理一次,在一般情况下是不可能的。

其实狭长三角形光栅化性能消耗更大,底层原因也是如此。一个简单的demo示意可以上述所有啰嗦(顶点缓存优化)。
在这里插入图片描述

总结: 顶点管线无法直接控制vertex shader的调用和顶点的重用,所以说管线驱动层一定就存在一定的性能浪费。

1.2 Mesh Shader优势

Mesh shaders引入了一种新的、类似compute shaders的几何管线,让开发者能够直接将顶点和图元信息批量发送到rasterizer。这些集合通常被称为meshlet,由少量顶点和引用这些顶点的三角形列表组成。其可以用来表示较大网格的一小部分,但这些meshlet可以完全由用户定义,因此也可以用来渲染程序化几何图形(如地形)或细分曲面等网格几何图形。下边我们重点介绍如何使用mesh shader渲染三角形网格。

Mesh shader引入了两个新的 API shader阶段:

  • Task Shader(VK)/Amplification Shader(DX),决定要发起哪些以及多少个mesh shader线程组。
  • Mesh Shader 将上述顶点和图元输出到rasterizer。

这两个新的shader阶段取代了传统的几何管线,包括vertex shader、hull shader、domain shader和geometry shader,并且不能与这些阶段中的任何一个结合使用。

在这里插入图片描述
Mesh shader和amplification shader都采用了和compute shader类似的编程模型,因此更符合现代显卡架构的统一计算硬件发展趋势。在传统的图形渲染模型中,每个线程负责一个顶点、采样或图元,线程之间并不可见或通信。而mesh shaders以线程组的形式组织,每个线程组不再限定于特定的顶点或图元,可以指定并写入可变数量的顶点和图元。任何线程都可以处理任何顶点或图元。

由于mesh shader采用了类似compute shader的线程组组织方式,因此启动mesh shader的方式也与传统的绘制调用有所不同。使用 DispatchMesh 命令(在 Vulkan 中为 vkCmdDrawMeshTasksEXT)时,不再需要指定要处理的顶点或索引的数量。相反,该命令需要指定在compute shader所在的三维网格中启动的mesh shader线程组的数量:

graphicsCommandList->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ);

这种直接启动线程组的方式也意味着mesh shader跳过了Input Assembler阶段。开发者因此可以自定义从mesh shader导出哪些图元,输入数据的各种限制也消失了。就像compute shader一样,mesh shader可以读取甚至写入任何资源。另外,开发者还可以直接控制哪些图元进行光栅化,对单个图元进行剔除,或者和amplification shader配合使用,剔除整个meshlet。

Mesh shader绕过了传统的input assembler阶段,就需要将顶点重用计算移到预处理阶段。这就可以避免在每一帧或每个绘制调用中重复计算顶点重用信息,实现信息在多个绘制调用和帧之间共享。移除了input assembler也意味着它不会成为渲染管线的瓶颈。mesh shader也可以直接适应不同GPU配置。此外,放弃了传统的几何生成编程模型,如tessellation shader和geometry shader,意味着mesh shader中的几何生成可以更好地映射到计算硬件,并减少对固定功能硬件的依赖。

最后,mesh shader中线程组的组织方式允许线程通过wave intrinsics和组共享内存通信,共同计算一个或多个顶点或图元。

总之,mesh shader类似compute shader的编程模型让开发者能够克服传统顶点管线的关键瓶颈,同时还能更灵活地使用高级渲染技术,例如三角形剔除或实时几何解压缩。

Amplification shaders

Amplification Shader是可选的一个shader阶段,在mesh shader之前运行,可以控制mesh shader线程组的后续启动。每个amplification shader线程组可以启动不同数量的mesh shader线程组,从而增加在GPU上执行的工作负载。不过,这种放大与使用tessellation shader实现的放大不同。Amplification shader不是通过固定功能硬件来生成control patch内的图元数量,而是只指定要启动的mesh shader线程组的数量。不过,使用amplification shaders可以模仿固定函数tessellation shader生成的效果

为了启动mesh shader,amplification shader可以调用 DispatchMesh shader 内置函数,并指定要启动的三维网格的mesh shader线程组。这个内置函数的行为与 CPU 上的 DispatchMesh 命令相同。 CPU 可以在 DispatchMesh 调用之间对shader的根签名进行写入或修改,但amplification shader则不能。相反,amplification shaders可以通过传递用户定义的payload,将信息从amplification shader传递给所有随后启动的mesh shader

DispatchMesh(threadGroupCountX,
threadGroupCountY,
threadGroupCountZ,
payload);

从 CPU 启动amplification shader的方式与启动mesh shader的方式相同:如果图形管线状态指定了amplification shader,则 DispatchMesh 命令中指定的三维网格会直接指向要启动的amplification shader网格。需要注意的是,选择要启动的是mesh shader还是amplification shader需要通过设置不同的图形管线状态来实现。

在这里插入图片描述

但amplification shader还存在着一些关键的差异和限制:

  • Mesh shader图形管线只包含一个amplification shader,更重要的是只包含一个mesh shader,这意味着amplification shader直接与mesh shader绑定。amplification shader只能控制启动多少个mesh shader线程组,并不能控制使用哪个mesh shader或pixel shader。
  • Amplification shader只能调用mesh shader,不能调用其他amplification shader或自身。这意味着amplification shader仅提供一层work amplification。
  • Amplification shader内的 DispatchMesh 内置函数仅接受单一payload,供所有随后启动的mesh shader线程组使用。同样,amplification shader线程组只能调用一次 DispatchMesh。

尽管有这些局限,amplification shader也能扩展mesh shader管线的灵活性,完全在 GPU 上实现动态work amplification或work reduction。Amplification shader的应用实例包括:

  • Meshlet或实例剔除: amplification shader可用于每个meshlet或实例的剔除。每个线程都负责测试特定meshlet或实例的可见性,通常通过检查其bounding box(包围盒)是否在摄像机的视锥体内。
  • 动态LOD: 与上述实例剔除示例类似,每个网格实例都会启动一个amplification shader线程。然后,每个线程会确定其实例与摄像机的距离,并选择相应的LOD进行渲染。
  • 生成程序式几何图形: Amplification shader通过动态地将工作负载分配给多个mesh shader线程组,可以扩展mesh shader的输出能力,用于处理例如程序生成的几何图形或细分曲面。

有一点需要注意:在实例剔除和动态 LOD 的情况下,amplification shader线程组中的每个线程希望启动的mesh shader线程组数量在整个线程组中会有所不同。这可能会使将mesh shader线程组分配给payload中特定元素的过程更加复杂。

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值