大多数浏览器和
Developer App 均支持流媒体播放。
-
探索 Metal 中的 Bindless 渲染
通过添加参数缓冲区来采用 Bindless 纹理渲染,释放着色器的全部潜力,实施现代渲染技术。了解如何让您的整个场景和资源对 GPU 可用,以充分利用光线追踪和光栅化管线。
资源
- Accelerating ray tracing using Metal
- Applying realistic material and lighting effects to entities
- Managing groups of resources with argument buffers
- Metal
- Metal Feature Set Tables
- Metal Shading Language Specification
- Rendering reflections in real time using ray tracing
相关视频
WWDC22
WWDC21
-
下载
♪ (探索Metal中的无绑定渲染) 欢迎来到WWDC! 我是阿勒·塞戈维亚·安札皮安 是Apple的一名 GPU软件工程师 在本视频中 我们将探索Metal中 的无绑定渲染 无绑定是一种新式的资源绑定模型 让资源组可以 对GPU可用 以实现新的渲染技术 首先 我们将了解 无绑定概念背后的需求 然后 我们将介绍无绑定模型 并说明它如何提供必要的灵活性 来解决传统绑定模型面临的挑战 我们将回顾把场景资源编码 并利用参数缓冲区 使场景资源对Metal可用的 各种机制 以及如何从着色器导航GPU结构 让我们开始吧! 无绑定渲染使所有的场景资源对 着色器可用 为图形技术带来不可思议的灵活性 让我们来看一个示例 假设我们有一个光线跟踪内核 它寻找与加速结构的交点 对于某些光线效果 例如光线跟踪阴影 这个算法非常自然 我们希望找出 交点和光源之间的任何对象 在跟踪阴影光线时 我们只需要 一个位置和朝向光源的方向 除了交点的世界空间位置以外 不需要任何对象属性 或Metal资源 我们可以从光线和交点的参数 推导出交点的位置 但对于其他效果 例如反射 情况就会变得更加复杂 让我们看看Metal Shading Language 中的一个光线跟踪反射着色器 在这个新的示例中 我们刚刚找到了一个交点 我们试图用正确的反射色 来为这个像素着色 如果我们在找到交点后用纯色来着色 则地面上的反射就会显得不真实 为营造出真实的效果 我们需要确定 每个已知反射点的属性 并计算其像素的正确着色 同样的问题也适用于 其他的光线跟踪效果 例如漫射的全局照明 和某些情况下的环境光遮蔽 难点在于 当我们跟踪光线时 光线可能击中加速结构中的任何对象 这意味着光线跟踪着色器 可能需要访问场景中的 任何Metal资源 包括顶点数据—— 与相交的网格有关—— 及其材料 我们不可能将这么多的资源 直接绑定到流水线 这便是无绑定绑定模型的用武之地 无绑定背后的想法是汇总我们的资源 并将它们链接在一起 这使我们可以将单个 缓冲区绑定到流水线 并通过导航 使所有被引用的资源可用 在Metal中 参数缓冲区 可以让我们可以做到这一点 特别是就无绑定而言 二级参数缓冲区必不可少 Apple6和Mac2 GPU 系列都拥有二级参数缓冲区 Metal中的所有着色器类型 都可以使用参数缓冲区 这意味着您可以为光线跟踪和光栅化 使用参数缓冲区 正如我们所见 对于某些光线跟踪效果 使用无绑定是强制的 以便获得出色的视觉效果 对于光栅化 使用无绑定是可选的 但它拥有直接绑定模型不具备的优势 特别是 无绑定在视觉上消除了 任何给定draw调用 可绑定资源数量 的槽限制 它还提供了很好的优化机会 我们稍后将在本视频中对此进行讨论 Metal 2引入了参数 缓冲区作为一种机制 使您可以在单个调用中 将常量数据和资源 同时绑定到Metal API 参数缓冲区非常灵活 甚至可以引用其他缓冲区 无绑定模型背后的想法 是利用这种能力 将所有的场景资源链接在一起 这可以使它们同时 对GPU可用 让我们举例说明 如何将场景资源与 参数缓冲区链接起来 比如说我们想要渲染这个消防车模型 该模型由纹理 顶点数据和索引数据组成 这些是您在传统的绑定模型中 需要为每个draw调用 一一绑定的典型资源 但在我们的例子中 由于我们希望使场景的所有纹理 顶点数据和索引数据同时可用 我们需要汇总这些资源 以下是实现方法 先创建一个网格参数缓冲区 其中包含所有的网格或子网格 具体取决于资产的组织方式 这个参数缓冲区允许引用 场景中的顶点和索引数组 我们也可以采取同样的做法 把材质编码到 一个参数缓冲区 每种材质都能引用其纹理 并包含内联常量数据 现在 所有网格和材质 已经对GPU可用 那么我们如何将它们链接在一起呢? 比如 我们可以创建一个实例对象 并把它在一个参数缓冲区里 一个实例可以引用一个网格 和一个相关材质 这也是将 一个模型变换矩阵储存为 内联常量数据的好地方 但我们不会止步于此 既然我们可以储存一个实例 何妨更进一步 把所有实例作为一个数组 编码到这个参数缓冲区中 让我们简化这个图表 并添加更多的消防车实例 每个实例都有自己的材质 正如我们所见 通过这种方法 现在整个场景及其资源 被编码并与参数缓冲区链接 以后 当我们想要从着色器引用 这些资源时 我们只需要一个 指向实例缓冲区的指针 我们可以直接传递它 并把这个缓冲区解释为一个数组 或者通过另一个场景 参数缓冲区来传递指针 现在让我们看看间接访问资源 的驻留问题 由于我们只把指向场景的指针 传递到流水线 Metal将知道这个缓冲区引用 但不知道间接访问的资源 该App负责声明 所有间接访问资源的驻留 使资源驻留意味着向 驱动程序发出信号 使其内存对GPU可用 这是必要的 这样我们就可以 从着色器引用它们 方法是调用用于计算编码器的 useResource: usage: API 以及用于渲染命令编码器的use Resource:usage :stages: API 访问非驻留资源是GPU重启 和命令缓冲区失败的常见原因 原因是如果我们忘记调用该API 其内存页可能不存在 所以必须向Metal声明 每个间接访问资源 现在 另一种简便的方法 是借助useHeap API 通过单个调用就使 从MTLHeap分配的资源 实现驻留 如果您已经从堆中再分配 或打算再分配资源 这将是一个好选项 堆是Metal API的 一个重要组成部分 我们建议您使用它们 来获得最佳的资源创建性能 和内存节省机会 但想要有效地使用它们 有几点需要注意 首先是思考 再分配的所有资源是只读的吗? 我们可能需要写入资源的情况 包括来自计算着色器的网格蒙皮 和动态纹理等等 在这些情况下 如果GPU需要 写入任何资源 则资源必须利用写入使用标记 来分别声明驻留 另外 如果我们现在想要读取 可能被修改过的资源 则这些资源仍然需要 useResource调用 这是为了让Metal框架 可以为您处理资源转换 刷新GPU缓存 以及调整内部内存布局 需要思考的第二件事是 堆跟踪是否再分配资源依赖项? 如果我们读取和写入 来自于同一个堆的资源 则这个问题将显得特别重要 Metal擅长通过依赖项跟踪 来避免同步问题 自Metal 2.3开始 堆可以配置为 在访问其资源时跟踪危险 不过 由于堆是 Metal的单一资源 同步问题是在堆层级 而不是在再分配层级进行处理 这可能会使再分配资源 面临错误共享的问题 让我们来看一看 假设有两个渲染通道 A和B 访问同一个堆的资源 渲染通道A渲染从一个被跟踪的堆中 分配的一个渲染纹理 渲染通道B从一个 不相关的缓冲区读取资源 该缓冲区是从同一个堆再分配的 根据不同的条件 渲染通道A和B可能有资格 被GPU并行执行 不过 由于从同一个堆 读取和写入资源 所带来的潜在危险 Metal必须对访问 进行序列化处理 来确保没有竞争条件 这可能会增加GPU工作负载的 执行时间 但在我们的例子中 如果我们知道单个资源是独立的 就能避开这个栅栏 有两种方法可以做到这一点 一种方法是将堆中的可更新资源 单独再分配到用于静态资源的堆 而如果我们希望把 所有资源都绑在一起 另一个方法是确保堆被配置为 不跟踪其再分配资源 这在Metal中是默认行为 意味着程序员需要自己解决 同步问题 在这个图表中 我将情况简化 以说明错误共享的问题 在实践中 重叠发生在着色阶段层级 而不是在渲染通道层级 因此 Metal允许我们 在阶段粒度指定栅栏 这非常不错 因为这使我们可以 同时运行流水线的多个阶段 例如顶点阶段和光栅化 只有在碰巧依赖于 前一个通道的片段阶段输出时 才会在片段阶段阻断运行 我们建议您在允许的情况始终这么做 以获取最大性能 这需要记很多东西 如果您只能记住其中的一件事 那么请记住这一点:只读数据 例如静态纹理和网格 是最容易处理的 预先确定总的分配大小 和对齐要求 并在App启动或加载期间 将这些资源放在堆中 这样一来 以后您可以利用 单个调用使资源驻留 并且最大程度减少 在关键路径上的开销 现在我们了解了无绑定绑定模型 接下来看看如何编码我们的资源 并投入实践应用 以及如何利用参数缓冲区使完整场景 对GPU可用 假设我们希望编码我们的实例缓冲区 记住 该缓冲区由一个实例数组组成 我们知道 实例引用网格 材质 并包含一个内联的常量4x4矩阵 它描述了从局部空间 到世界空间的转换 编码通过参数缓冲区编码器进行 有两种不同的方法可以在 Metal中创建编码器 您也许熟悉通过反射进行编码 如果参数缓冲区作为直接参数 被传递到着色器函数 我们可以要求 MTLFunction对象 为我们创建一个编码器 该机制很不错 但当我们将整个场景编码到 参数缓冲区中时 并非所有的编码器都能被反射 尤其是MTLFunction签名 不知道间接引用的缓冲区 在其他某些情况下 从MTLFunction 创建编码器 并不方便 例如引擎架构 处理参数缓冲区的创建和资源加载 而不是流水线状态的创建 另外 如果函数将传递一个数组时 我们也无法反射编码器 在这些情况下 我们该怎么做? 对于这些情况 Metal 提供了一个 方便的第二机制 通过MTLArgument Descriptor来创建编码器 MTLArgument Descriptor允许向 Metal描述结构成员 然后创建一个编码器 不需要MTLFunction 我们必须先为每个成员 创建一个描述符 指定数据类型和绑定索引 然后 我们将描述符 直接传递到MTLDevice 以创建编码器 因此 我们得到了编码器对象 让我们看看这在代码中是怎样的 对于每个成员 我们需要创建一个 MTLArgument Descriptor 我们指定绑定索引 对应于结构成员的 ID属性 我们指定MTLDataType 和潜在访问 最后 在声明所有成员后 我们可以直接从设备创建编码器 传递一个带有所有描述符的数组 一旦有了编码器 将数据记录进缓冲区就很简单了 我们在编码器上设置参数缓冲区 指向缓冲区的起始处 然后设置我们想要储存的数据 编码一个数组也很简单 我们只需要 让编码器的参数缓冲区记录点被 encodedLength偏移 我们可以方便地从编码器检索 encodedLength 对于下一个实例 我们把 encodedLength 第二次添加到我们的偏移量 实际上 需要记录的 每个位置的偏移量 就是索引乘以 encodedLength 借助这一机制可以很容易地 对结构的数组进行编码 现在 需要指出的是 着色器不需要进行特殊处理 就可以索引到这些数组 着色器不需要知道 缓冲区的长度 并且能够自由地索引到 数组中的任何位置 就是这么方便! 好了 我们已经编码了无绑定场景 让我们看看导航 就光线跟踪而言 导航效果非常自然 首先 我们将包含 无绑定场景根的缓冲区 绑定到光线跟踪流水线 我们可以从这个参数缓冲区 访问其他所有的参数缓冲区 接下来 我们照例从内核 处理光线跟踪交点 在发现交点后 交点结果对象描述导航 我们可为instance_id geometry_id 和primitive_id 查询该对象 这些成员旨在 导航我们的加速结构 因此 用来构建无绑定场景的结构 必须反映加速结构 例如之前展示的那个结构 让我们再来看看 记住 这只是如何组织场景的 一个例子 我将根据组织方式来导航 对于您的场景 具体细节可能有所不同 这取决于您决定如何组织 您的参数缓冲区 首先 我们需要找到一个交点 找到交点后 由于我们有策略性地组织无绑定场景 给定instance_id 我们现在可以跟踪 指向实例缓冲区的指针 并确定我们击中了哪个实例 接下来 实例知道其网格和材质 于是 我们可以使用 geometry_id 来确定我们击中了 被引用缓冲区中的哪个几何形状 最后 如果我们让每个网格 知道它的索引缓冲区 我们可以使用 primitive_id 来确定我们击中的原始类型 例如 就三角形而言 我们可以从这个数组中提取三个下标 用它们来检索顶点数据 这是该导航在 Metal Shading Language中的样子 我们可以从交点对象 检索instance_id 用它来动态地索引到实例数组 并检索我们击中的实例 有了实例后 我们使用 geometry_id 来确定哪个几何形状或子网格被击中 一旦确定了几何形状 我们可以直接从索引缓冲区提取下标 就三角形而言 我们一个接一个地 提取三个下标 我们使用这些下标 来访问顶点数据数组 并检索我们需要的任何属性 例如 我们可以检索 对应于每个顶点的法线 最后 利用该点的质心坐标 我们手动插入顶点法线 以便在交点处得到正确的法线 有了这些改动 再来看茶壶的例子 现在我们可以计算 交点处的法线 可以正确地为反射着色 我们更新了代码以找到 交点处的正确属性 效果在视觉上是正确的 我们可以继续在这个框架上进行构建 以计算我们需要的其他所有属性 例如用来应用纹理的纹理坐标 或者用来实现法线贴图的切向量 现在我们知道了如何 导航我们的无绑定场景 以检索顶点数据 手动插入它 以及用它来为已发现的 所有交点进行正确着色 为帮助您将这些概念融入您的引擎 我们将发布一个配套代码示例 来展示所有这些内容的具体实现 这是个混合渲染示例 它计算使用Model I/O 框架加载的 场景的光线跟踪反射 该示例不仅展示了如何编码一个 匹配光线追踪加速结构的无绑定场景 还展示了如何找到交点 以及如何直接从光线跟踪着色器 为交点相关像素进行正确着色 正如我们在这里看到的那样 该示例也允许在光线与 消防车的交点处 将反射光线跟踪着色器的输出 直接可视化 这对于反复试验 反射算法非常有用 现在 我们已经讲了很多内容 到目前为止 我们的大部分讨论 都聚焦于光线跟踪的情境 但正如我之前所说 我们可以在光栅化的情境中 运用同样的原理来适当地为像素着色 基于物理的渲染是一个很好的选择 在PBR中 我们的片段着色器需要 来自于几种纹理的信息 例如反照率 粗糙度 金属性和环境遮挡 在直接绑定模型中 我们需要分别绑定每个槽 然后发出每个draw调用 无绑定模型大大简化了这方面的工作 一旦对参数缓冲区进行了编码 我们就可以直接绑定场景 导航到与draw调用对应的材质 并间接访问所有纹理 实际上 由于我们只需要 一次绑定一个缓冲区 该架构可以 减少draw调用的数量 从而进一步优化引擎 并使用实例渲染 要记住使我们打算 访问的所有纹理驻留 这里有一个典型PBR着色器的例子 在传统模型中 每个被引用的纹理 都需要被分别绑定 然后发出这个draw调用 如果随后的draw调用 需要不同的纹理集 则所有这些资源也需要一一绑定 在使用无绑定模型时 我们只需要传递根参数缓冲区 并直接从其引用结构中 检索材质 就像以前所做的那样 首先 我们检索实例 这可能在顶点着色阶段确定 然后检索其材质 并使用其引用纹理和常量数据 来计算适当的着色 最后返回颜色 好了! 关于如何在Metal中有效实现 无绑定渲染的讨论到此结束! 让我们来复习一下 我们探索了Metal无绑定模型 看到了它有多么灵活 使您的场景能以 您希望的任何方式呈现 我的建议是针对特定的渲染器 设计和构建易于导航的结构 这样一来 导航就会变得非常自然 您甚至可以为光线跟踪和光栅化 使用相同的缓冲区 无绑定彻底改变了规则 让GPU获得 您实现新式渲染技术所需的所有数据 您甚至可以更进一步 用这种架构 来让GPU处于支配地位 并通过间接命令缓冲区和GPU剔除 来实现间接流水线 我们迫不及待地想要 看到您将这一切付诸实践 开发出下一代的 图形应用和游戏 谢谢大家 敬请观看 WWDC 2021的其他内容! ♪
-
-
0:07 - Simple Intersection Kernel 2
if(i.type == intersection_type::triangle) { constant Instance& inst = get_instance(i); constant Mesh& mesh = get_mesh(inst, i); constant Material& material = get_material(inst, i); color = shade_pixel(mesh, material, i); } outImage.write(color, tid);
-
0:08 - PBR fragment shading requires several textures
fragment half4 pbrFragment(ColorInOut in [[stage_in]], texture2d< float > albedo [[texture(0)]], texture2d< float > roughness [[texture(1)]], texture2d< float > metallic [[texture(2)]], texture2d< float > occlusion [[texture(3)]]) { half4 color = calculateShading(in, albedo, roughness, metallic, occlusion); return color; }
-
0:09 - Bindless makes all textures available via AB navigation
fragment half4 pbrFragmentBindless(ColorInOut in [[stage_in]], device const Scene* pScene [[buffer(0)]]) { device const Instance& instance = pScene->instances[in.instance_id]; device const Material& material = pScene->materials[instance.material_id]; half4 color = calculateShading(in, material); return color; }
-
1:48 - Simple Intersection Kernel 1
if (intersection.type == intersection_type::triangle) { // solid blue triangle color = float4(0.0f, 0.0f, 1.0f, 1.0f); } outImage.write(color, tid);
-
11:33 - Encoder creation
struct Instance { constant Mesh* pMesh [[id(0)]]; constant Material* pMaterial [[id(1)]]; constant float4x4 modelTransform [[id(2)]]; };
-
11:50 - Encoder via reflection
// Shader code references scene kernel void RTReflections( constant Scene* pScene [[buffer(0)]] );
-
13:08 - Argument Buffers referenced indirectly
MTLArgumentDescriptor* meshArg = [MTLArgumentDescriptor argumentDescriptor]; meshArg.index = 0; meshArg.dataType = MTLDataTypePointer; meshArg.access = MTLArgumentAccessReadOnly; // Declare all other arguments (material and transform) id<MTLArgumentEncoder> instanceEncoder = [device newArgumentEncoderWithArguments:@[meshArg, materialArg, transformArg]];
-
16:10 - Navigation 1
// Instance and Mesh constant Instance& instance = pScene->instances[intersection.instance_id]; constant Mesh& mesh = instance.mesh[intersection.geometry_id]; // Primitive indices ushort3 indices; // assuming 16-bit indices, use uint3 for 32-bit indices.x = mesh.indices[ intersection.primitive_id * 3 + 0 ]; indices.y = mesh.indices[ intersection.primitive_id * 3 + 1 ]; indices.z = mesh.indices[ intersection.primitive_id * 3 + 2 ];
-
16:43 - Navigation 2
// Vertex data packed_float3 n0 = mesh.normals[ indices.x ]; packed_float3 n1 = mesh.normals[ indices.y ]; packed_float3 n2 = mesh.normals[ indices.z ]; // Interpolate attributes float3 barycentrics = calculateBarycentrics(intersection); float3 normal = weightedSum(n0, n1, n2, barycentrics);
-
17:15 - Simple Intersection Kernel
if(i.type == intersection_type::triangle) { constant Instance& inst = get_instance(i); constant Mesh& mesh = get_mesh(inst, i); constant Material& material = get_material(inst, i); color = shade_pixel(mesh, material, i); } outImage.write(color, tid);
-
19:30 - PBR fragment shading requires several textures
fragment half4 pbrFragment(ColorInOut in [[stage_in]], texture2d< float > albedo [[texture(0)]], texture2d< float > roughness [[texture(1)]], texture2d< float > metallic [[texture(2)]], texture2d< float > occlusion [[texture(3)]]) { half4 color = calculateShading(in, albedo, roughness, metallic, occlusion); return color; }
-
19:48 - Bindless makes all textures available via AB navigation
fragment half4 pbrFragmentBindless(ColorInOut in [[stage_in]], device const Scene* pScene [[buffer(0)]]) { device const Instance& instance = pScene->instances[in.instance_id]; device const Material& material = pScene->materials[instance.material_id]; half4 color = calculateShading(in, material); return color; }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。