大多数浏览器和
Developer App 均支持流媒体播放。
-
利用 Metal 进行现代化渲染
Metal 是 GPU 加速图形和计算框架,可帮助开发者打造从专业 app 到控制台风格游戏的一切内容。了解如何利用 Metal 功能来实现现代图形技术,包括延迟渲染和分块前向渲染。了解 GPU 驱动功能如何让 Metal 自行安排工作负载,从而在几乎没有 CPU 交互的情况下构建和执行完整的场景及计算工作负载。了解 GPU 系列如何帮助您更加轻松地构建适用于所有 Apple 平台的 app,并探索 Metal 如何让您的 app 和游戏在性能和功能方面更上一层楼。
资源
相关视频
WWDC21
WWDC19
Tech Talks
-
下载
(用Metal进行现代化渲染)
大家好 我是Jaap van Muijden 欢迎参加这场关于 用Metal进行现代化渲染的演讲
在演讲的第一部分 我会讲一些更高级的渲染技巧 你如今可以在你的app中使用了
然后我的同事 Srinivas Dasari 会讲如何把你的CPU渲染循环移到 更多GPU驱动的管道上
最后我们讲如何使用新的GPU家族 来写跨平台的代码
无论你是从零开始 或是想改善现有的 Metal app
或你有一个很棒的渲染引擎 你想把它移到 Metal平台上 我们会告诉你如何通过渲染技巧
最大限度地利用可用硬件 来满足你的需要
我们从如今的游戏和app使用的 一些渲染技巧开始讲
从基本延迟渲染开始 这是最常用的渲染技巧 游戏和图形app在所有平台上 都能使用它 我们会讨论经典的双通道设置
我们会告诉你如何在Metal上 实施双通道设置 以及如何针对iOS平台 对此进行优化
然后我们继续讲平铺延迟 它扩展了延迟渲染的照明通道 如果需要复杂的照明设置的话 它是个非常完美的功能
然后我们会了解前向渲染 这对于Metal app来说 是个很好的替换方案 要求复杂的材料、抗锯齿、透明度 或特殊的性能考虑
我们要讲的最后一个技巧是 可见度缓冲区渲染 它推迟集合逻辑 一直推迟到照明通道 现在在Metal 3中 它的实施比以前简单多了
但在此之前 先让我们从延迟渲染开始讲
延迟渲染把场景渲染分为两个通道
一个是几何通道 你可以把你的整个场景渲染到 过渡几何图形中 或叫做GBuffer 这个缓冲区中的纹理 你在写入模型或后处理管道中 所需要的所有法线、反照率、 粗糙度和任意一种表面或材料属性
然后在第二通道中 照明通道渲染场景的光照体积 并在一个累积纹理中创建 最终光照场景
延迟的光照着色器将把GBuffer中的 所有纹理绑定到一起 从而计算对最终光照表面颜色的影响
让我们先定义这个技巧的数据流 然后再讲Metal实施
在这里我们有两个渲染通道 我们要在GPU上相继地运行 这两个渲染通道
在我们的几何通道中 我们需要写出深度 深度用于执行在几何通道过程中的 深度调用 但它可以用于给照明通道 计算像素位置和世界空间
并且我们还输出 我们的GBuffer纹理 在我们的例子中 我们使用了法线、 反照率和粗糙度纹理
然后在我们的第二通道 照明通道中 我们读回GBuffer纹理 然后我们绘制光照体积并把它们 累积到我们的输出纹理中
让我们看一下如何在Metal中 构造这个数据流
要在Metal中设置渲染通道 你首先要有一个渲染通道描述符
渲染通道描述符最重要的部分 就是它的输出 在Metal中 这些是用附件定义的
每个渲染通道都可以拥有 单一深度附件 和多颜色附件
对于每个附件来说 我们必须要定义它的纹理 纹理要指向存储我们附件数据的数据
我们需要定义我们的加载动作 那会告诉我们如何从纹理中加载 现有数据 并且存储动作就是如何把渲染结果 存回纹理中 当你定义好所有附件的这些属性之后 你就可以创建渲染命令编码器 然后你就可以最终在你的渲染通道中 绘制Objection了
让我们看一下如何在Metal中 创建这个 从设置代码开始看
这是我们的设置功能 我们要从创建渲染通道描述符开始 现在我们刚开始填充所有附件 我们先从深度附件开始 因为我们使用深度附件来执行 深度调用 我们要确保在开始渲染场景之前 清除它 因此我们把加载动作设置为清除 当然 我们想存储深度用于第二通道 因此我们把存储类型设置为存储
现在我们转到我们的颜色附件 颜色附件 我们需要一个颜色附件 应用于GBuffer中每一个纹理 因为所有这些纹理都将以同样的方式 进行处理 我们只讲一下反照率
因为我们在渲染过程中很可能会使用 比如天空盒或背景 因此我们非常确定我们会覆盖 我们的每一个帧中的每一个像素 那意味着我们并不真正关心 我们的GBuffer纹理中 之前的任何值 因此我们可以把加载动作设为不关心
当然 我们想将存储 GBuffer的结果 因此我们把存储动作设为存储
现在我们可以创建照明通道描述符了 我们创建另一个描述符对象 然后定义附件 用于累积缓冲区 因为我们累积数据 我们需要在开始之前清除它 因此我们把加载动作设置为清除
当然 我们想保存我们的最终图片 因此我们的存储动作将是存储
现在让我们看一下 当我们使用这些渲染通道 来实际绘制我们的场景时的渲染循环 我们再次从几何通道开始看 我们使用我们的描述符 创建渲染命令编码器 然后我们开始迭代场景中的所有度量 这是渲染场景的一种非常简单的方式
我的同事Srinivas 将在本场演讲的第二部分 讲如何把基础的CPU渲染循环 移到更多GPU驱动的管道中 通过各种剔除和LOD选择来实现
好的 现在我们已经编写好 整个几何缓冲区 抱歉 整个几何通道 我们要接着创建照明通道 我们创建另一个渲染命令编码器 现在我们开始迭代 照明通道中的所有光照 每个光照 每个延迟光照着色器 都将绑定这些来自GBuffer 的纹理 从而计算它的最终光照颜色
嗯 这两个通道系统在macOS 和iOS的 所有平台上用起来都非常顺畅 各种类型的硬件都支持 这两个通道系统 但我们还可以采取一些措施 在iOS上进一步优化我们的实施 让我们从代码中退回到我们的概览中
你可以看到在两个渲染通道之间的 这个大缓冲区 几何通道把它的所有数据 都存储在这些GBuffer纹理中 然后照明通道又把它们都带回来了 如果我们在单个像素上有多个光照点 我们会多次执行这种读回
通过在Metal中使用一种叫做 可编程混合的技巧 我们可以避免把这个过渡负荷存储到 设备内存中 通过利用iOS设备的平铺架构实现 我们要如何利用这种技巧呢?
嗯 要启动可编程混合 我们要合并几何通道和照明通道 并按几何和光照规则创建一个 渲染编码器
因此 由于iOS架构的本质 在编码器的整个持续时间内 附件总是处于平铺内存中
这意味着我们不仅可以写入附件中 但我们实际上还会把它们读回
我们可以读回我们写入的 同一个像素的值 并且这就是我们想要实现的操作 当我们计算照明通道中的光照时 我们想检索 同一个像素的GBuffer附件
让我们看看这会如何影响我们的 光照着色器
在这里我们有来自照明通道的 光照片段着色器 你可能知道 你只需要把所有 你需要的纹理绑定到一起 就能得到GBuffer数据 然后你在所有GBuffer纹理中 读取所有这些纹理 从而获取所有材料和表面信息 只有这样你才可以把材料 和表面信息推到照明模型中 从而获得最终光照颜色
现在让我们看一下如果我们使用 可编程混合会怎么样
我们不绑定所有纹理 而是绑定所有颜色附件
我们可以直接在照明模型中 使用这些值
你可以看到我们已给GBuffer 创建了一个新的线性深度颜色附件 这是因为当你使用可编程混合时 你不能访问深度附件
现在我们不再绑定或取样任何纹理 让我们看一下如何使用这个来进一步 优化我们的内存布局
当使用可编程混合时 我们不再从GBuffer纹理中 写入或读取 我们可以把颜色附件的存储动作 设置为不关心
好的 这解决了带宽问题 但我们仍然有这些 Metal纹理对象 占用我们设备上的空间 我们需要告诉Metal 我们不再需要 为GBuffer纹理 提供任何物理内存了
我们通过把纹理的存储模式设为 不耗内存实现 我们告诉Metal我们不再对纹理 实施任何存储动作了 因此我们实际上不需要分配内存
通过这些步骤 我们现在得到了一个iOS实施 具有GBuffer所有的优点 但却没有内存或带宽负担
在讲平铺延迟之前 让我们总结一下 几何通道和照明通道的分离 使得可编程混合成为一个 非常多用途的技巧 它可以很好地处理 复杂的几何和照明通道 并且GBuffer可用于促进 非常深入的后处理管道 整个管道可使用这个可编程混合方法 排成一行
在macOS上 你仍然要处理GBuffer、内存 和带宽消耗
现在让我们讲一下平铺照明方案 这适用于想要渲染最大光照体积 但仍想减少光照通道消耗的情况
平铺延迟渲染技巧尝试解决 渲染大体积光照而导致的性能问题 在经典延迟中 我们独立渲染每个光照 这由于重叠光照 而导致了大量GBuffer消耗
平铺延迟渲染通过一个额外的计算 预通道延伸了照明 那就允许我们进行着色 但不是按光照层级着色 而是按平铺层级着色
预通道首先把我们的屏幕分成 照明平铺的2D网格 并给每个平铺生成一个光照列表
然后第二步就是照明 然后通过使用单一光照碎片着色器 这些光照用于有效地照亮平铺 但光照是光照列表中的光照 在我们深入实施细节之前 先让我们快速了解一下 如何生成这些光照列表
嗯 我们首先把视椎分成 这些小的子椎 每个平铺一个子椎
然后使用计算着色器 进一步调整子椎 通过使用平铺的位置 以及平铺的深度边界实现 我们可以这样做 因为我们已经运行了 几何通道 因此深度缓冲区已经填充好了
当我们调整这些子椎时 我们可以 测试所有视椎的光照体积 并向光照列表中添加任意交集
这整个过程可以在所有平铺中 并行执行 非常适合计算内核
我们要如何把这个整合到 我们刚才见到过的延迟管道中呢?
嗯 要在我们设置 这个双通道延迟之前
现在我们已经把计算通道 添加到它中间了
那会为我们创建光照列表 我们需要在一个光照列表缓冲区中 存储这些光照列表 并存储在设备内存中
再一次 这个方案适用于所有平台 我们只需要创建额外的计算 并把光照逻辑 从每个着色器一个光照 移到照明着色器中的迭代循环中 就像之前的渲染器一样 我们现在可以利用 iOS上的硬件平铺 来进一步进行优化 让我们看一下这在我们的单一编码器 iOS实施中怎么样
这是我们刚才讲过的单通道方案 我们需要在那儿使用这个计算 但我们需要留在单渲染命令编码器中 从而使用可编程混合
Metal提供一种有效的方式 即使用基于平铺的硬件架构 来渲染我们所光栅化的 每个平铺的计算
为了iOS上的这个目的 渲染命令编码器可以编码 平铺着色器管道 使其运行计算函数
这非常适合我们的平铺照明 因为我们现在获取了照明平铺概念 并把它直接映像到我们的硬件平铺上
因此现在我们的光照调用预通道可以 直接在我们的硬件平铺上运行 我们可以使用第二个Metal功能 叫做持久线程组内存 用于在平铺内存中存储所生成的 光照列表以及附件
然后与附件类似 我们可以把它们读回 但渲染命令编码器中的所有绘制 在我们的例子中是按光照绘制
我们现在移动了照明后端 使其与我们的图形一致 完全在平铺内存内执行
那么这在Metal中 看起来是什么样的? 让我们返回到设置代码中
创建平铺着色器与设置 普通的渲染管道状态非常相似 我们创建描述符 我们设置所有的颜色附件
然后设置我们想要执行的计算函数 然后创建管道状态 因为我们使用了持久线程组内存 我们需要在平铺中保留一点儿内存 因此我们返回到渲染通道描述符 然后保留足够的数据 来存储我们的光照列表
现在让我们转到渲染循环 看一下分派尺寸
我们的渲染循环这一次是从 单一渲染命令编码器开始 然后我们再一次循环场景中的 所有网格
然后我们不直接进入照明通道 而是首先执行平铺着色器 我们设置了管道状态 我们设置了一个缓冲区 存储我们场景中的所有光照 然后我们把线程组内存缓冲区 绑定到我们的平铺内存中 然后我们分派我们的平铺着色器
现在我们已经执行了平铺着色器 线程组内存会保存光照列表 然后我们就可以在照明绘制中使用了 我们可以让每个像素都能访问它 它使用了持久线程组内存的 平铺光照列表 现在可以非常有效率地给它的像素 进行着色了
最后 设置好这一切之后 让我们看看 这在着色器中看起来怎么样
在这里我们有两个着色器 顶部那个是平铺着色器 它把输出光照列表绑定到 一个持久线程组内存缓冲区中
然后它会以某种方式循环所有光照 并把光罩输出到持久线程组内存中
然后由第二着色器读回 第二着色器是我们真实的照明着色器
它会写入它平铺内的所有可见光照 并给像素着色
现在我们已经了解对平铺延迟技巧 实施平铺照明技巧的所有要点 让我们看一下如何使用这个原理 来扩展我们的渲染器 使其有效地创建一个额外的前向通道
因为我们已经在持久线程组内存中 设置了光照列表 我们可以使用同样的数据来促进一个 有效的平铺前向通道
无论何时当我们在前向通道中 对前向几何图形进行着色时 我们都可以使用同一个 持久线程组内存读取平铺光照列表 并使用在延迟照明中所使用的同一个 光照循环 来非常有效率地对前向像素进行着色
这个前向通道真的增强了渲染功能 并接受透明度、 特效和其它复杂的着色 如果只有延迟通道 那通常是不可能实现的
然而延迟管道总有一些限制 抗锯齿
复杂的材料表达仍然是个问题 因为有过渡的GBuffer表示 使用这个平铺技巧我们看到 我们可以非常有效地加强前向渲染 使用平铺照明技巧
让我们往回退一步 只看前向通道 因为除平铺照明之外 它凭借自身力量 成为了一个可行性方案
要创建只进行前向渲染的渲染器 我们只需要移除延迟几何图形 和照明通道即可
然而我们的照明剔除技巧 需要那个深度来调整它的子椎
因此我们需要用深度预通道 替换几何图形通道 来填充这个深度缓冲区
如果你的引擎已经拥有这样一个 深度预通道 那这对于你来说 是一个非常完美的方案
如果你有过度绘制、优化、遮挡剔除 或自我混合 那这个方案可以满足你的需要
然而在iOS硬件上 这种通道通常是不必要的 对于那些情况而言 还有另一种照明方案 叫做集群照明 可能更适合你
这个集群方案以不同的方式 创建光照列表 而不需要任何深度
因为对于集群光照来说 我们不会给平铺创建任何深度边界 但我们只是再细分深度轴上的椎
然后我们发出一个3D光照列表贴图 而不是2D光照贴图
这可能不如平铺照明中的子椎 那么有效率 但它将大大提高照明性能 因为仅通过本地光照列表 对每一个像素进行着色
使用集群剔除 与平铺着色 和持久线程组内存一起 这将为我们提供一个非常优化的 前向渲染器
我们现在已经了解了一些 最流行的管道 以及如何在Metal上 对它们进行渲染
现在我们要了解一下 可见度缓冲区渲染技巧 它以一种不同方式 处理GBuffer消耗 从而更适合不支持硬件平铺的老硬件
让我们一直退回到延迟渲染器中 我们目前所了解的大部分优化 只能在iOS架构上使用
可见度缓冲区技巧尝试以另一种方式 把过渡缓冲区瓶颈最小化 也就是在那个缓冲区中存储 绝对最小量的数据
我们不按像素存储所有表面 和材料属性 我们只存储原始标识符和重心坐标
这个数据不直接用于给整个场景着色 但它可用于 重构和插入原始几何图形 然后在本地在照明着色器内 运行整个材料逻辑
因为这个重构步骤消耗太大 但它在平铺照明技巧中用起来 效果很好 因为它保证只对每个像素 进行一次重构
当我们实施这个技巧时 通常最大的问题就是 如何创建原始索引以及如何创建 重心坐标
而不需要大量额外的处理
我们现在很高兴地告诉你们 在Metal 3中 你现在可以在片段着色器内 使用这两个新属性 检索当前原始索引 和当前像素的重心坐标的索引了
生成几何图形着色器现在非常简单 Metal 3使几何通道 比以前更快了 实施也比以前更简单了
我们现在学习了 你可以在Metal中 用于场景渲染的所有这些不同方案 现在让我们看一个小演示 演示一下其中一些渲染技巧
在这里我们有测试场景 有一些非常复杂的几何图形 和设置PBR材料 以及一组不同的材料着色器 我们可以在任意设备上使用延迟 或平铺延迟或甚至是前向渲染器 来渲染这个场景 让我们从普通的延迟渲染器开始
延迟渲染器有两个通道 我们之前看到过 第一个通道现在 通过这些过渡 GBuffers渲染一切 现在让我们看其中一些 GBuffer纹理
在这里我们有反照率
我们有法线
我们有GBuffer的粗糙度纹理 如果你有暂时的抗锯齿 或更复杂的照明模型 你很可能需要在GBuffer中 存储更多东西
我们现在看到的场景 是由第二照明通道进行照明的 因此让我们进入夜间场景 来更好地呈现我们的光照
现在在这个场景中 要获得这样的照明效果 我们需要渲染许多光照 我们可以在这里看到 在普通延迟中 我们应该每次渲染一个光照 那样效率很低 并且你可以看到 在不同的光照之间有许多重叠 因此让我们改用平铺延迟照明
在这里我们有同一个场景 渲染 使用平铺延迟渲染器
我们想在这里展示的是我们所拥有的 所有可能的可视化效果 关于不同的平铺如何给你呈现 在每一个不同的平铺中所渲染的 不同的光照体积 你可以看到使用这些平铺细分 相对于同时照亮所有像素来说 效果真的不一样
现在我们已经演示了一些 你可以用于渲染场景的可能的 渲染技巧 接下来我的同事 Srinivas会讲 如何把CPU繁重的渲染循环 放到GPU驱动的管道中去
谢谢Jaap GPU驱动的管道 在Metal 2中我们引入了 GPU驱动的管道 由增强缓冲区 和非直接命令缓冲区组成 通过这些你现在可以 把基于CPU的渲染操作 移到GPU上来 我同事Jaap刚讲了 如何用Metal实施 各种高级渲染技巧 我要讲的是如何 把整个基于CPU的渲染循环 移到GPU上来 这不仅会让渲染循环变得更有效率 它还允许你释放CPU用于执行 你可能想要执行的其它处理 比如复杂的物理模拟REI 在深入讲细节之前 先让我们看一下渲染循环中 通常会执行哪些操作
大场景要求复杂的渲染操作 通常你会执行一系列操作 来有效地渲染场景 你要做的第一件事就是椎剔除 移除落在视椎外的对象 我们只讲绘制调用 接下来是遮挡剔除 在这里你排除由其它较大对象 所遮挡的对象 通常要做的另一件事是细节层次选择 根据模型到摄像机的距离 从一系列的模型细节层次中进行选择 那么带有所有这些操作的 基于CPU的渲染循环 一般看起来就像这样
从根本上说 你首先会 把被遮挡的绘制和遮挡测试 编码到命令缓冲区中 然后在GPU上的渲染通道中执行它 从而为下一帧生成遮挡数据 接下来你执行椎剔除 从而排除视椎外的对象 并做LOD选择 从而为模型选择一个细节层次 然后是遮挡剔除 从而排除 被较大对象遮挡的对象 那么最后你要编码可见对象的绘制 并在渲染通道中执行它 从而生成场景 现在这个流程用起来不错 但这里有一些功能效率很低 首先让我们看一下遮挡剔除 要执行遮挡剔除 你需要当前帧的遮挡数据 但因为你不想减少 当前帧中的任何同步性 你通常依赖于 上一帧的遮挡数据来实现 你通常得到的数据分辨率较低 因此它是近似数据 它可能会导致虚假遮挡 因此你很可能需要在游戏中 采取一些纠正措施 第二 这里有些操作很有麻痹性 比如椎剔除
在单CPU线程上 它是这样的 你要对每个对象执行椎剔除 一个接一个 现在你可以明确地把这个进程 分配到多个CPU线程上 但只有几个CPU线程可用 并且如果你包含你想要对每个对象 执行的全部操作 你很可能正在做这样的事 但这些操作是否很有麻痹性? 如果你有更多的线程 你当然可以并行处理 所有场景 处理场景中的所有对象 但一般来说场景中有成千上万个对象 因此要麻痹全部对象 你就需要成千上万个线程 因此执行这些操作的 最完美的选择是GPU
GPU是一个大规模的并行处理器 有成千上万个线程可用 用于安排操作的执行 可以把一个对象分配给一个 专用线程并执行 我们想要在那个对象上执行的 所有操作 通过成千上万个线程 你可以并行处理成千上万个对象 因此如果你把它从CPU移到GPU 渲染循环效率会很高 并且正如我之前提到过的那样 它还会释放你的CPU 用于执行其它想要执行的处理 如何把全部这些操作 移到GPU上呢? 你可以通过在GPU上 合并计算和渲染通道实现 从而我们可以在GPU上驱动 整个渲染循环 而不需要CPU参与 这个想法 我的意思是 这就是我们所需要的 这里的整个渲染循环都在GPU上 它完全由GPU驱动 现在让我们看一下这些通道 看这个GPU驱动的渲染循环 实际是如何运作的
现在我们需要的是 用于遮挡剔除的遮挡数据 那么首先我们有一个计算通道 获取场景数据 执行包围盒的椎剔除 并编码渲染包围盒的命令 现在这些编码的遮挡绘制命令 在一个渲染通道中执行 因此我们生成所有必要的遮挡数据 这个遮挡数据可以是多种不同的形式 取决于它的生成方式 那么你可能想进一步处理那个数据 为此我们有另一个计算通道 在这个通道中 包围盒数据可以转化为一种 更适用于遮挡剔除的形式 我们还需要一个计算通道 来执行我们所讲过的操作 也就是剔除、细节层次选择、 和编码场景绘制命令 这里有一件事要了解 即这里的遮挡剔除 已经不再依赖以前帧的数据了 在我们刚讲过的前两个通道中 为当前帧生成所要求的包围盒数据 同时因为我们生成当前帧的数据 数据也更精确了
最后我们有另一个渲染通道执行 场景绘制命令 用于渲染场景 在这个GPU驱动的渲染循环中 一切都发生在GPU上 无论何处都没CPU-GPU 同步操作 也不会依赖上一帧的数据 我们该如何创建这个GPU驱动的 管道呢? 现在很明确 要在GPU上创建这个渲染循环 我们至少需要两个东西 第一个是绘制命令
我们需要一种在GPU上编码 绘制命令的方式 从而计算通道 可以为渲染通道编码命令 Metal所提供的支持这个功能的 building block 是非直接命令缓冲区 我们还需要场景数据 我们应该能通过帧在GPU上访问 所编码的场景数据 在任何需要的情况下 通过这个场景数据 我们应该可以几乎描述 整个场景了 比如几何图形、 共享参数、材料等等 在Metal提供的支持这个功能的 building block中 是参数缓冲区 现在让我们深入看一下这两个 building block
现在参数缓冲区可以让你描述 整个场景数据 有复杂的数据结构 它们允许你在渲染循环的任何位置 访问场景数据 而非直接命令缓冲区 允许你在GPU上创建绘制调用 从根本上来说 它在GPU上支持 大量命令并行生成 现在让我们进一步看一下参数缓冲区 通过一个示例场景对象模型来看
我们所需要的第一个东西是 对场景数据的访问 那么场景数据通常都包含什么? 首先是网格 这就是网格 它是网格对象的一个区域 每个网格对象都描述了它的几何图形 还有材料 它是材料对象的一个区域 每个材料对象都有一组材料属性、 它所需要的任何纹理 以及描述阴影管道的管道操纵对象 场景还包含模型区域 在此每个模型都可以拥有一个LOD 因此在这个例子中 每个LOD一个模型 每个模型都包含网格区和材料区 最后我们有场景对象 关联网格、材料和模型 那些都是我们场景的一部分 让我们看一下这个对象模型 如何通过参数缓冲区进行表达
它是一个非常简单的一对一映像 从我们的对象模型映像到参数缓冲区 比如这里的场景参数缓冲区包含 我们在对象模型中描述的对象 就是网格区、材料区和模型区 从根本上来说 整个场景 现在可以通过参数缓冲区来描述 现在让我们看一下 如何在着色器中构造并访问它
现在我们所讨论的每个参数缓冲区 都用结构表示 那包含我们在对象模型中 所描述的元素 因为每个参数缓冲区都是一个 绝对灵活的结构 你可以添加比如数组、指针、 甚至是指向其它参数缓冲区的指针 比如这是一个Metal参数缓冲区 它包含Metal常量、 它所需要的任何纹理 当然了 还有描述阴影管道的管道专用对象 材料所需要的一切都在一个 参数缓冲区中 场景参数缓冲区 正是我们在对象模型中 所描述的样子 因此用参数缓冲区构造对象模型 非常简单 现在让我们看一下如何访问 共享中的这些参数缓冲区
我们刚讲过有一个计算内核 可以进行椎剔除 它编码绘制命令 把可见对象 绘制到非直接命令缓冲区中 每个线程都执行这个内核的一个实例 处理一个对象 并编码一个单一绘制调用 如果它决定那个对象可见的话 让我们看看它是如何实现的
首先我们给着色器传入 我们的高层级场景参数缓冲区 现在一旦我们可以访问着色器 可以访问场景 访问我们所需要的其它东西 就非常简单了 这里的命令R包含对我们想要编码的 非直接命令缓冲区的引用
我们首先根据线程ID 从场景中创建了这个模型 请注意这个计算内核的所有线程 是并行执行的 每个线程操作一个特定的对象 然后我们执行椎剔除 查看对象是否落在视椎外 一旦我们决定对象可见 我们就根据它到摄像机的距离 计算它的LOD 一旦我们有了LOD 读取它相应的网格和材料参数、 应用到那个LOD上的参数缓冲区 就非常直截了当了 之所以直截了当主要是因为 参数缓冲区 关联我们在场景中所需要的 资源的方式
我们已经得到我们所需的全部信息 现在是时候进行编码了 让我们看一下编码到非参数缓冲区 和非直接命令缓冲区到底是什么意思
非直接命令缓冲区 是渲染命令的一个区域 每个命令可以有不同的属性 一个命令可以包含一个管道专用对象 管道专用对象描述一个共享的管道 和绘制调用所需要的任意漩涡 和碎片缓冲区 还有绘制调用自己 从根本上说 编码的意思是 一旦我们决定一个对象可见 我们就读取它和它所有的属性 并把这些编码到非直接命令缓冲区中 现在凡是正在处理对象的线程 都可以编码到 这个非直接命令缓冲区的一个 特定的编码槽中 因为所有线程都是并行运行的 可以同时进行命令编码 现在让我们继续看剔除内核例子 看一个真实的编码示例
我们首先需要在命令缓冲区中 有一个位置 来编码绘制命令 因此我们使用绘制ID为我们自己 在非直接命令缓冲区中获取编码槽 就像我们所讨论的那样 我们需要设置绘制调用所需要的参数 现在我们刚获得的 材料和网格参数缓冲区 已经有我们要设置参数 所需要的全部信息 那么比如 从材料中我们可以设置 我们需要设置的管道专用对象 从网格对象中我们可以设置 我们需要设置的任何漩涡缓冲区 或漩涡一致性 当然了 碎片需要材料 因此我们也要设置它 最后 这就是如何编码绘制 就是这样 编码绘制调用非常简单和简便
现在让我们看看 你该如何在你的游戏中设置通道
现在我们首先需要一个 非直接命令缓冲区 来编码包围盒绘制命令 因为这是当我们讨论 GPU驱动的渲染器循环时 所讲到的第一个东西 要渲染包围盒 我们要启动一个计算分派 执行包围盒的椎剔除 并编码包围盒绘制命令 因为每个线程都是独立编码绘制的 因此在非直接命令缓冲区中 可以有多个状态设置、 写入和状态设置 我们可以根据需要 优化非直接命令缓冲区 从而移除任何驱动端阶段设置 现在这是一个随机通道 在非直接命令缓冲区中 执行包围盒绘制 类似地 其余通道的设置也很简单 比如这是我们的主计算分派 启动我们刚才讲过的剔除内核 它执行剔除测试、LOD选择 并编码绘制命令 我们已经准备好启动最后一个 渲染通道了 它会在非直接命令缓冲区中执行命令 那么就是这些 这就是绘制场景所要做的一切操作 现在让我们看一下在编码绘制命令后 非直接命令缓冲区看起来 是什么样子的
现在它可能很稀疏 有很多洞 这主要是因为正如我们在剔除内核 例子中所看到的那样 正在处理对象的线程 不会编码绘制命令 如果它发现那个对象不可见的话 比如这个例子中的对象一和对象三 那意味着非直接命令缓冲区中的 这些编码槽是空的 因此如果你把这个命令缓冲区 提交到GPU 它将会执行 一大堆空命令 那样效率很低 因此理想情况是像这样 把命令紧密地打包到一起 也就是当我们编码绘制时 我们需要一种打包命令的方式
为此我们有非直接原则
通过非直接范围 你可以告诉有执行调用的GPU 去哪里获取要执行的命令范围 从根本上说 你可以有非直接范围缓冲区 它有一个起始位置 和一堆要执行的命令 并且这个缓冲区 可以在你执行绘制命令的编码时 在GPU上进行填充 执行调用将从这个缓冲区中 选择起始位置 和要执行的命令的数量
它可用于打包 也可用于给出范围 现在让我们看一个例子 看这个实际是如何运作的
这是我们刚才讨论过的剔除内核 我们对它进行了修改 以使用非直接范围缓冲区 让我们看一下这个内核 如何打包绘制命令
我们首先传入指向 非直接范围缓冲区的长度元素的指针 当我们检索要编码的命令时 我们可以自动增加长度 现在每个线程都自动增加长度 当计算工作完成后 长度会被自动设置到 非直接范围缓冲区中 同时绘制命令也被打包好了 因为在这段代码中 由这个自动指令所返回的索引 是之前的长度值 因此比如 如果你从零开始 使用零号编码槽的线程 把长度增加到一 使用第一个编码槽的线程 把长度增加到二 以此类推 这很棒 因为现在我们不仅打包了命令 我们同时还更新了范围
现在让我们看一下如何在app中 设置非直接范围缓冲区
首先你要给计算通道创建一个 范围缓冲区 用于更新范围 接下来你要把范围缓冲区设置为 剔除计算内核的内核参数 然后我们执行启动了首先执行对象的 剔除内核的计算通道 同时自动更新范围 最后你通过非直接范围API 安排缓冲区中有执行命令的通道 现在这个调用将选择起始位置 和要从这个非直接范围缓冲区中 执行的命令的数量 通过非直接范围 你可以获得更多 非直接命令缓冲区的有效执行
目前在我们的GPU驱动的管道中 所有这些绘制命令 都创建在GPU上的计算通道中 并且这些计算通道是你的游戏中 发生常规分派的地方 那么有一个问题 就是在GPU上创建计算分派 我们是否可以把计算分派 编码到非直接命令缓冲区中? 我很高兴地告诉你们 我们在 Metal 3中新添加了一个功能 支持编码计算分派
现在你也可以 在GPU上创建计算分派了 关于功能性 计算非直接命令缓冲区 就跟渲染一样 它们也可以创建一次之后 就能一次又一次地重复使用 它们还可以帮助节约CPU周期 最棒的是渲染和计算现在都可以 在GPU上进行 非常棒 因为现在你可以创建更灵活的 GPU驱动的管道了 现在让我们通过一个用例看一个例子
每个补丁曲面细分因素 假如我们有一个网格 由一堆补丁组成 我们想给每个补丁 都生成曲面细分因素 我们当然可以在剔除计算内核中 实现 就是我们讲过的那个执行剔除任务 和编码绘制命令的剔除计算内核 它是GPU线程 处理对象的方式是 进入对象的每一个补丁 并生成曲面细分因素 但这样真的效率太低了 因为生成曲面细分因素自身 也是一个很有麻痹性的操作 因此如果按操作在多线程中进行分配 会很有效率 那样可以并行处理所有补丁 也就是说 正在处理对象的 剔除计算分派的每个线程 都可以编码计算分派 用于测试因素生成 并且这些分配 可以在另一个计算通道上执行 从而麻痹操作 那么通过GPU驱动的分派 我们现在可以实现了 让我们看一下 如何把GPU驱动的管道 交换为适应这种处理
这是我们之前讨论过的主计算通道 它执行剔除任务、LOD选择 并编码绘制命令 我们现在可以让这个通道 同时也编码分派 用于生成测试因素 比如在某线程决定某对象可见后 它可以编码分派 用于在非直接命令缓冲区中 生成测试因素 然后那些命令可以在主渲染通道之前 在另一个计算通道上执行 GPU驱动的分派 与GPU驱动的绘制合并 可以让我们创建更灵活的 GPU驱动的管道
我们要创建一个示例 为你实际演示一下我们所讲的内容 让我们看一下
这里是你之前见过的小酒馆场景 这个 我们实际上正在 低空飞过这里的街道 这个场景大约由280万个多边形 和接近8000个绘制调用构成 这只是一个视图的 如果你考虑这里使用的 用于阴影处理的阴影瀑布 那么这个渲染器正在处理 四个这样的视图 因此如果这场景在CPU上进行渲染 会有相当多的API调用 但在这个例子中 我们使用了非直接命令缓冲区 因此一切都在GPU上执行 它完全是由GPU驱动的 整个渲染循环都在GPU上 因此它节约了许多CPU
让我们再看一个视图 现在我们看的是同一个视图 同一个低空飞行 但我们正在看 摄像机穿过这里的街道 为了能让大家看清楚 我们把摄像机显示为一个白色物体 那就是摄像机
我们显示了几何图形 那个洋红色的几何图形正在下落 这个几何图形落在了摄像机的视椎外 因此你可以看到 随着摄像机穿过街道 有不少几何图形都落在了 摄像机的视椎外 我们的剔除计算分派在GPU上 执行椎剔除 它决定这个几何图形 这个有色的几何图形为不可见对象 因此在GPU上不会处理或渲染 这个几何图形 节约了许多渲染成本
让我们再看最后一个视图
这是另一个视图 在这里我们显示了工作中的 椎剔除和遮挡剔除 我们 我们把被遮挡住的几何图形 染成了蓝绿色 而洋红色的几何图形处于视椎外 你可以看到右边有不少几何图形 都被小酒馆遮挡住了 因此它们是蓝绿色的 你可以看到这里有许多几何图形 无论是在视椎外或是被遮挡住了 再一次 我们的剔除计算内核 在GPU上既执行椎剔除 又执行遮挡剔除 那就决定了这些几何图形是不可见的 因此我们不会在GPU上处理或渲染 这个染色的几何图形 这节约了许多渲染成本并提升了性能
较为简单的GPU家族 在我们结束这场演讲之前 我想再给大家介绍一个东西 我要讲一下我们如何把 在Metal核上写交叉引用 变得比以前更简单了 我还要讲一下如何更容易地瞄准 iOS、tvOS和 macOS上的特定功能 在此之前 先让我们看一下 现在各平台上可用的Metal功能
现在Metal在iOS和 tvOS平台上有一些新功能 在演讲的上半场我们讲了 如何在非直接命令缓冲区中 设置管道状态从而帮助你充分利用 GPU驱动的管道 我们还讲了非直接范围 如何让你更简单、 更有效地打包和执行非直接命令 最后我们还向 iOS和tvOS引入了 16位深度纹理支持
这一直都是一个很流行的请求 可以帮助优化阴影贴图渲染 Metal在macOS上 也有一些重要的新功能
如果你需要向内存缓冲区中 执行更灵活的输出的话 我们现在可以实现不带附件渲染了 你可以查询你的命令缓冲器 在GPU上的所花的时间 从而可以动态地调整表示区间 最后MacOS现在支持sRGB 和非sRGB视图之间的投射 从而更好地适应线性和非线性照明
现在让我们看一下 新GPU家族API
你之前使用Metal功能 根据可用功能和限制 设置查询使你的app适应 但功能的数量 功能集合增长了 它们当前的数量 以打为单位 GPU家族查询替换了功能集合 使得系统的查询功能更简单了 首先我们把它们合并为四个家族 并把它们组织起来 简化跨平台开发过程 第二 每个家族都支持 一系列等级的功能 它们被组织到一个或多个实例中 因此支持一个实例意味着 支持所有更早以前的实例 第三 新API把Metal 软件版查询独立出来 用于追踪指定家族的实例 如何改变软件的提交 最后一个GPU家族 定义了一小套设备查询 有一些可选功能 并未放到家族中 通过这些信息 让我们具体看一下 新的GPU家族定义
现在所有iOS和tvOS功能 都被组织到各自的家族中 一个家族有五个实例 每个实例支持之前实例内的所有功能 我不打算在这里列举所有功能 但这场演讲的资源部分有一个表 表中列出了家族和实例的功能 Mac功能只围绕两个实例组织 Mac 2支持来自Mac 1的 所有功能
现在查询这些功能 这些家族很大程度上简化了 扁平的、非特定的代码的书写 但当你想瞄准 所有平台时会怎么样? 为此我们有新的通用家族 通用家族把Metal功能 组织到跨平台等级中 所有Metal GPU 都普遍支持通用1 对于只稍微使用了Metal的 app来说是个不错的选择
通用2提供所有创建优秀游戏开发 必要的building block 比如非直接绘制、统计遮挡查询、 曲面细分 和Metal性能阴影支持 通用3提供高级app所需要的 所有功能 比如非直接命令缓冲区、分层渲染、 立方体贴图数组 以及漩涡位置不变量 最后Metal 3为iPad app提供一个 专用家族 使它们能引入到Mac中 也就是为那种体验进行的调整
两个iOS Mac实例 支持功能合并 这对于在Mac上获得强大性能来说 至关重要 特别是它们使得 Mac专用的区块压缩像素格式 和管理文本模式可用 可以在另外的 完全是iOS的app内使用 现在iOS Mac 1 支持通用2的所有功能 外加来自通用3的一些功能 除了BC像素格式和所管理的纹理 它支持立方体纹理数组、读写纹理、 分层渲染、 多视图端口渲染和非直接曲面细分
iOS Mac 2 支持通用3的全部功能 除BC像素格式和所管理的纹理之外 那么这就是四个新家族 现在让我们看一下 在实践中如何使用新QD API
在这个例子中 我们会检查Mac 2功能是否可用
我们先检查OS 是否支持新家族API 如果新家族API可用 我们就用它来检查 Metal 3功能是否可用 因为Metal 3是新发布的 你不需要对它进行严格的检查 但这是一个很好的做法
如果Metal 3可用 我们就检查我们想要使用的家族 这里的跨平台app 检查其中一个通用家族 以及一个或多个 Apple或Mac专用家族
如果API或版本号有一个不可用 我们就返回 较老的功能集API 或较老的Metal版本
现在让我们看一下 你可以查询的设置选项功能
当某家族在家族内指定了 GPU的通用行为后 但某些重要的功能和限制在家族内 并不是统一地都支持 比如深度24模板8像素格式 以及一个像素中MSA样本的数量 为了处理这些问题 Metal设备提供了一个API 可以直接查询其中每一个功能 但你可以看到 这个类中并没有太多功能
那么要结束这场演讲 让我们看一下目前我们讲过的技巧 有多少被新GPU家族所支持
经典的延迟着色在所有平台上都支持 可编程混合在所有 Apple GPU上都支持 它是开发游戏的一个很好的默认选择 平铺延迟和前向渲染也通过 Apple专用优化被广泛支持 要求有更先进的硬件条件 最后可见度缓冲区技巧 只由Mac家族支持 它恰好有非常苛刻的分辨率要求
现在让我们看一下这些家族 如何支持GPU驱动的管道功能 然后就结束我们的演讲
现在有些功能要求广泛的支持 成为渲染引擎的核心部分 我们相信GPU驱动的管道 也要求那种支持 因此我们非常高兴地告诉你们 现在通用家族2及之后的家族都支持 用于图形和计算的参数缓冲区 和非直接命令缓冲区
然后我们就要结束这场演讲了 用Metal进行现代化渲染 我们希望你们可以在你们的游戏 和app中应用所有这些技巧 让我们快速回顾一下
我同事Jaap讲了 如何用Metal 实施更高级的渲染技巧 比如延迟着色、平铺前向渲染 与iOS非常相配 当合并和优化可编程混合 和平铺共享时 在Mac上 你可以使用新的中心坐标 和查询LOD来实施 可见度缓冲区技巧 并以高分辨率进行渲染 但无论你选择使用哪种技巧 你都可以把整个渲染循环 移到GPU上 椎剔除、遮挡剔除、LOD选择 都可以在通过参数缓冲区 和非直接命令缓冲区GPU上实现 现在你还可以把计算分派编码到 GPU上的非直接命令缓冲区中
无论你是想瞄准iOS或 macOS上的大范围硬件 或想使用一些高级Metal功能 你现在都可以使用新的、 重新设计的GPU家族API 在运行时检查可用的功能
现在请访问我们的演讲网站了解 关于Metal功能 和GPU驱动的管道的更多信息 我们将发布 我们在这场演讲中使用的示例app 你可以探索这些技巧 并把它们整合到你的app 和游戏中 请参加我们的演讲 事实上这场演讲结束之后 就有一场演讲
谢谢 祝你们度过愉快的会议时光 谢谢
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。