大多数浏览器和
Developer App 均支持流媒体播放。
-
探索如何使用 Metal 光线追踪实现混合渲染
发现如何将光线追踪与您的光栅化引擎整合,实施简化图形技巧并提升您的 app 或游戏的视觉效果。我们将探索如何使用自然算法准确模拟光线的相互作用,还将学习如何利用 Xcode 中的最新工具来捕捉、检查和调试光线追踪场景。
资源
- Accelerating ray tracing using Metal
- Applying realistic material and lighting effects to entities
- Managing groups of resources with argument buffers
- Metal
- Metal Shading Language Specification
- Rendering reflections in real time using ray tracing
相关视频
WWDC23
WWDC22
WWDC21
-
下载
♪ (Metal光线追踪 混合渲染的探索之旅) 欢迎来到2021年度WWDC 我是阿里·德·容 是Apple的GPU软件工程师 今天 我将和我的同事 大卫·努涅斯·鲁比奥 带领大家一同探索 Metal光线追踪混合渲染技术 我们将先为您展示 光线追踪对视觉效果带来的显著提升 然后讨论如何利用 “混合渲染”技术 将光线追踪通道光栅化 之后 大卫将为我们介绍 用于实现光线追踪的新工具 首先 让我们先来看看 光线追踪的一些优秀使用案例 游戏和电影不断追求 真实感的提升 多年来 它们一直利用光栅化提升图像质量 光栅化可以很好的将精美图片 以实时速率呈现 然而可实现的效果有限 光线追踪这一机制 有助于改变着色器的陈旧现状 开启通往激动人心的新科技的大门 而且 将光线追踪与光栅化结合 还可以显著提升视觉效果 下面来看一些实例 光栅化始终存在的难题 是光线反射 这是由于对单独光栅化像素着色时 没有其余场景部分作为上下文 无法进行准确反射 我们必须额外生成信息 而光线追踪是通过 追踪被着色像素的阵列 推导出外部世界 更棒的是 我们可以递归地应用这一过程 来准确的表现阴影 甚至实现反射中的反射 光线追踪的另一优势在于 对阴影的处理 图中使用光栅化技术处理后 摩托车的弯曲表面 出现了阴影的总体模糊感 以及由阴影贴图分辨率引起的混叠 使用光线追踪后 阴影更加清晰 不需要人为调节阴影偏差等参数 即可解决混叠问题 柔和阴影也可得到更精确的体现 我们可以使阴影自然地变锐利或柔和 这取决于遮挡物 到着色点的距离 利用光栅化 我们需要在采样时 对阴影贴图进行过滤 而利用光线追踪 只需简单地在圆锥体中追踪光线 即可达到目的 最后 光线追踪 对视觉效果的提升还体现在透明度上 对于光栅化技术而言 在传统意义上很难准确处理透明度 在这张图片中 可以注意到阳光如何 透过窗户照射进来 然而玻璃上的不透明字母 没有产生阴影 传统的阴影贴图技术 在处理透明物体时 往往会出现很多问题 使用光线追踪时 可以创建一个自定义相交函数 用于透明材料 这样 我们可以定义 哪些光线可以穿过材料 哪些不可以 从而自然地产生投射阴影 例如半身像头部的字母阴影 当然 整体阴影看起来也更清晰 那么为什么光线追踪 可以如此神奇的提升视觉效果呢 为了便于理解 先来看一下传统光栅化的工作原理 在光栅化过程中 网格传输到Metal进行渲染 它们被顶点着色器放置在 镜头前 光栅器将这些基元 按像素或片段排列 然后这些像素被片段着色器着色 并将结果混合后输出图像 大家都知道 每个像素可独立着色 并行操作 这正是GPU在光栅化过程中的优势 然而 代价是 在生成阴影的过程中 完全丢失了 其余场景的上下文 我们不知道这个像素周围 是否存在其他物体 先进的游戏引擎 通过增加额外的渲染通道 产生中间信息来解决这个问题 片段着色器可以利用这些数据 来近似地模拟 该像素周围的几何背景 再来看一些工作原理的细节 对于这项技术 将场景的几何信息光栅化后 会产生中间纹理 而非直接投射到屏幕上 这些纹理包括反照率 深度或法线等 通常称为几何缓冲区通道 或简称G-Buffer通道 中间纹理被用来 输入光线近似通道 利用智能手段在屏幕上模拟 光线与物体的互动 例如屏幕空间环境遮蔽 和屏幕空间反射 最后一步对中间附件 图像去噪或轻微模糊 使图像更流畅自然 所有这些结合起来 产生最终图像 尽管这些复杂技术 可以提升图像质量 但仍然只是近似 相反 光线追踪采取完全不同的方式 使得视觉效果更加精确 视觉技术更加简化 不再是一次处理一个网格 在光线追踪时 建立一个加速结构 可以覆盖整个场景 有了这个结构 可以让GPU从某个点 朝既定方向追踪光线 并找到交叉点 这可以让我们 获得全部上下文场景信息 由于光线追踪建立了光线交互模型 除渲染外 还可应用于其他领域 它可以用于音频和物理学模拟 碰撞检测 或人工智能和寻路 光线追踪功能强大 将其与光栅化结合 可以各取所长 为达到该目的 我们使用 名为“混合渲染”的技术 接下来看看如何搭建 一个混合渲染框架 以及该技术的一些使用案例 先从光栅化框架图开始 我们可以使用光线追踪 取代部分或全部 光线近似通道 我们仍然对G-Buffer 进行光栅化处理 将其作为主光线 然后利用光线追踪 查询场景其他部分 从而更真实地模拟光线特性 仍需要进行去噪处理 并建立光线构成通道 对比场景数据所得结果 比之前要精确的多 这种框架结构提供了良好基础 可用来探索更多混合渲染技术 下面介绍如何利用Metal 对这类框架进行解码 首先从填充G-Buffer开始 创建一个渲染通道 填充G-Buffer 并将其纹理设置为通道附件 确保图像储存在存储器中 这样 渲染的内容可用于后续通道 开启通道 对渲染进行解码 然后关闭渲染通道 接下来 在此处添加 一个光线追踪计算调度 创建中间纹理之后 对光线追踪通道进行解码 在同一个命令缓冲区创建计算通道 设置G-Buffer纹理为输入 在默认情况下 Metal会跟踪写读依赖 可以让我们专注于算法 而不用太担心同步的问题 对这个算法 设置输出纹理 写出光线追踪的工作结果 为光线追踪技术设置 PipelineState物体 通过计算着色器中的每个线程 可以算出一个像素或区域的 光线追踪结果 最后调用2D网格并关闭该通道 在这个通道被编码后 可以继续编码更多 例如光线积累通道 或提交命令缓冲区 以便GPU开始对其进行工作 同时 还可以解码剩余框架 由于我们的编码分为两个通道 这需要将中间渲染附件 保存到系统内存 以便于两通道交互 这个方法很有效 但在 Apple Silicon 和IOS设备上还有更好的办法 在Apple GPU上 硬件利用区块内存 来缓存工作中的像素数据 在通道结尾 区块内存转为系统内存 并在下一通道起始处重新加载 理想情况下 可以使计算通道 直接在区块内存上工作 避免与系统内存的重复交互 今年 我很高兴能跟大家分享 我们通过调度光线追踪渲染管道 实现了这一功能 区块着色器在单一通道中 混合渲染和计算 可以利用区块内存进行光线追踪 这将减少带宽的使用和内存的消耗 降低用户设备运行时的温度 请回顾2019年的 《基于Metal的现代渲染技术》 课程 了解如何有效地混合渲染和计算 并将其应用于光线追踪 还有今年的课程 《使用Metal光线追踪 增强应用程序》 可以帮助您了解Metal光线追踪 带来的其他改善 了解如何编码混合渲染工作负载后 现在来回顾一下 光线追踪可以对哪些技术带来提升 我们将重点讨论阴影 环境遮蔽 以及光线反射 先从阴影开始 阴影有助于传达 场景中物体之间的距离 这对光栅化来说是个挑战 因为我们在着色时 丢失了场景的上下文 阴影贴图 可以补充这类信息 但需要从每个光源额外渲染 光栅化技术先从每个光源 渲染场景 这将产生一系列深度图 和每个光源的变换矩阵 都需要被存储 然后从主镜头视角进行渲染 为了对每个像素点进行着色 需要将该点转换为 光线的坐标 从深度图中提取深度 最终比较这些深度值 以确定对每个光源来说 该点是处于光照下还是阴影中 这项技术有几个缺点 第一 必须从每个光源来渲染场景 这意味着需要对场景多次处理 第二 阴影贴图有预先确定的分辨率 这意味着阴影会受到混叠影响 更糟的是不能得到 图像外的像素点信息 与光线追踪阴影进行比较 为计算光线追踪的阴影 我们可以简单地追踪一条光线 从一个点朝光源方向 并确定是否有任何物体挡住路径 如果没有 意味着该点应该采用这个光源 来产生阴影 如果有物体阻挡路径 就可在照明过程中 排除该光源 请注意图中与遮挡物体的轮廓相对应 产生了一个自然的阴影 更棒的是 我们不再局限于 存储在深度图中的信息 对于光圈或镜头视野之外的点 我们也可以确定阴影 下面看看光线追踪 如何简化阴影技术 从渲染主镜头开始 接下来搭建加速结构 由镜头位置渲染深度图 将其传送到光线追踪内核 计算像素位置 在光线方向只需追踪一条光线 由此可以判断 该点在光照下还是阴影中 取决于在有遮挡物的情况下 是否发现了交叉点 这个过程产生了阴影纹理 可将其与渲染通道结果相结合 得到最终成像 下面介绍如何编写Metal着色器 在着色器代码中 首先计算位置 每个线程都要根据深度 和线程ID进行处理 从计算出的位置创建阴影光线 并将其设置为追踪光线方向 对于大多数光线类型 例如点光源 聚光源和区域光源 设置最小和最大值来追踪 点到光源的所有路径 对于方向性阴影 将最大值设置为“无穷大” 此外 如果想实现圆锥光线追踪 来使阴影更柔和 那么在阴影光线中 加入抖动是个好方法 接着创建一个交叉点物体 若发现任何交叉点则该物体在阴影中 因此配置该区间接收任何交叉点 最终与加速结构相交 根据相交结果 无论该点是否被照亮 都可创建出对于场景来说 更精确的阴影纹理 应用该阴影纹理后 可以看出我们得到了更真实的阴影 并且没有混叠 利用光线追踪 所确定的阴影非常自然 只需追踪一条光线 看是否有物体遮挡该点光源 不再需要中间深度图 可以避免对每条光线 多次额外渲染的过程 这种技术很容易在 递延或前向渲染器中实现 因为它只取决于深度 对于半透明材料 则允许使用自定义相交函数 接下来 来看看环境遮蔽 理论上来讲 一个点周围的几何物体越多 接收到大量环境光线的可能性越小 环境遮蔽是指根据一个点的周围环境 来减弱该点所接受的环境光 使得缝隙自然变暗 最终成像更深 光栅化技术为实现这一目标 需要对点附近的深度和法线 进行采样 以确定周围是否有物体 有可能遮挡它 根据发现的附近物体数量 计算出衰减系数 来减弱环境光 并创建一个纹理用于成像 然而 对于不可见的遮挡物 以及图像边界外的物体 如深度缓冲区和表面法线 依靠屏幕空间信息是无法得到的 而利用光线追踪技术 我们依靠的不再是屏幕空间信息 而是实际场景的几何数据 原理是对每个像素着色 在一个半球体中生成随机光线 然后搜索对应物体交叉点 如果发现有任何交叉点 就把该像素纳入 环境遮蔽因子的考虑范围 我们还是从加速结构重新开始 这项技术需要法线数据以及深度 因此在G-Buffer通道中 采集这些数据 深度和法线用于 生成半球体中的随机光线 下一步 追踪光线 计算衰减系数 生成缝隙自动变暗的图像 产生这种效果 再来看一下Metal着色器 如何应用于环境遮蔽 第一步生成随机光线 在这个案例中沿着每条线的法线 使用余弦加权光线 设置最大距离为一个较小数字 因为我们只关心相邻小范围 下一步 创建区间 与加速结构相交 根据结果 得出衰减系数 这里是并列对比 可以立即看出 光线追踪的表现要好得多 我想着重介绍几处真正显示 屏幕空间效果局限性的地方 这里是一个例子 由于屏幕空间信息有限 周边物体被扭曲 这是因为实际几何形状 与镜头几乎垂直 因此这个角度并不在深度缓冲区 同样的问题出现在画面各处 特别是摩托车下面 从这个角度来看 摩托车底部 深度缓冲区缺失 因此屏幕空间技术 完全忽略衰减 而另一边 经过光线追踪 准确的找出了 摩托车底部交叉点 作为地面像素 下面是一个很好的例子 显示了屏幕边界的局限性 负责遮挡的几何体在屏幕之外 因此它在屏幕空间技术中不起作用 而在光线追踪下凸显出来 正如我们所看到的 混合渲染提供了 显著的图像质量改善 利用场景中的实际几何图形 摆脱了屏幕空间信息 对技术的限制 最后 来看一下反射 对于光栅化来说 反射一直是难点 反射探针可以起到很好的作用 但对分辨率有限制 需要经过过滤 并且它和动态几何体冲突 屏幕空间反射技术 受到屏幕空间信息的限制 反射探针需要 沿着整个场景针对性的放置摄像头 来捕捉周边色彩信息 利用反射探针 可以从场景不同方位 捕捉到立方体图像 本质上是从同一个点的六个方向 进行渲染 当着色一个像素时计算与探头的联系 并对立方体图像进行采样 来生成反射阴影 为了取得真实效果 通常需要将大量探针散布在场景中 而当动态物体在场景中移动时 着色器需要从多个立方体图像中取样 并且手动修改反射色彩 立方体图像也需要进行预过滤 来准确呈现辐照度 这同样受限于分辨率 另一种光栅化技术 屏幕空间反射 通过建立帧缓冲区像素的反射 可以解决其中一些问题 片段着色器使用法线 渐进式向外推进 并检查深度图来寻找附近的潜在物体 如果物体存在 直接从帧缓冲区取样色彩 将其着色在输出图像上 然而正如我们之前提到过的 它也受屏幕空间限制 在这个摩托车的例子中可以明显看出 对应帧缓冲器中的地面瓷砖 只有部分表面得到准确的反射 其余部分丢失了 更糟糕的是 黄色标记的挡泥板下半部分 完全丢失 我们无法知道 背对镜头那一面 是什么样的 光线行进也会使计算变得昂贵 然而利用光线追踪反射 能解决这两个问题 因为我们可以依靠 加速结构中的真实场景信息 我们先来看 一面完美的镜子是如何工作的 首先从镜头位置取入射光线 到某一点 然后经法线 反射该点 可以对这个方向 进行光线追踪 找出任何反射物体 为此 我们为光线追踪内核 提供了G-Buffer的法线深度 这个光线追踪内核计算出 从镜头到每一个点的视图矢量 反射这个矢量 再从该点出发 沿该方向追踪一条光线 最终 为得到准确的反射 可以对光线追踪内核找出的交点 进行着色 再来看看着色器的编码 我们再次从某点深度开始 重建位置 这次 我们希望这个位置 是在世界坐标系 因此在计算位置函数时 需要乘以视图矩阵的逆数 然后在法线上计算反射入射矢量 在这个方向上创建一条光线 接下来 创建扇区间并跟踪反射光线 如果撞到物体 对该点进行着色来产生反射 如果没有任何物体 只对天空盒进行色彩采样 来模拟天空中的反射 可以发现 在这项技术中 着色直接在计算内核中进行 比较一下反射探头和光线追踪反射 右边的图像使用了混合渲染 可以更加清晰的看到 地砖的细节 对于存在的建筑物 甚至可以从摩托车前挡板看出反射 光线追踪能与反射自然契合 很好地处理了镜面反射 和粗糙反射 这些可以通过沿锥体追踪多条光线 并对结果进行过滤来实现 因为它们依靠的是 来自加速结构的完整信息 光线追踪产生的反射 没有屏幕空间的假象 并能同时处理场景中的 静态和动态几何图形 一个重要的细节是 我们提到过 为了实现反射 需要在计算内核中 直接对该点进行着色 这类技术或全局光照 需要直接在计算内核中 访问顶点数据和Metal资源 在这种情况下 需要确保 GPU可以访问应用着色方程 所需要的数据 这是通过一个无绑定绑定模型实现的 在Metal中表示为参数缓冲区 请回顾今年的 《Metal无绑定渲染》 来获得更多信息 我们刚刚了解了 混合渲染 是如何通过几种不同技术实现的 这能使算法更自然 产生更准确的结果 在某些情况下 与传统的光栅化技术进行对比 可以删除渲染通道 来节省内存和带宽 由于渲染时加入光线追踪 甚至可以在芯片上处理全部工作 使用光线追踪技术是一项艰巨的任务 而我们有了优秀的新型工具 协助您的引擎 掌握这项技术 今年 我们介绍的新工具 可以帮您捕捉光线追踪 检查加速结构 并检查可见的交叉功能 下面由大卫为大家介绍 谢谢 阿里 我叫大卫·努涅斯·鲁比奥 是一名GPU软件工程师 去年 我们介绍过 使用Metal进行光线追踪 然而 开发复杂应用是一个挑战 幸好Metal Debugger 可以帮助您 今年我们介绍了 Metal Debugger 如何支持光线追踪 由于使用了混合渲染 我们的演示效果会比以往每次都要好 光线追踪柔和阴影 反射 环境遮蔽 结果都很惊人 在演示的开发过程中 我们遇到了一些问题 而工具能够帮助您解决这些问题 在演示的早期版本中 已经实现了光线追踪阴影 但如果仔细观察 会发现树叶在地面上有部分缺失 把它与参考版本做比较 更明显 这是参考版本 和光线追踪后版本 让我们进入Xcode 看看工具如何解决这个问题 按下Metal键 点击Capture 由于这是一个静态问题 我们只需要一个单一框架 在调试器中 API调用 被安排在调试导航器的左侧 展开屏幕外的命令缓冲区 找到阴影编码 我已经给我的计算命令编码器 标记为“光线追踪阴影” 标记您的Metal对象是个好方法 在Metal Debugger中 可以轻松找到它们 缩略图也给出提示 的确 这就是我们要找的编码器 现在可以点击调度 线程组的API调用 来显示带状资源 这张列表列出了 与当前内核调度有关的全部对象 而在这里 可以看到结构加速 我们也给它做了标记 我们的内核使用一个加速结构 来投射光线 通常利用边界体积层次结构来实现 也就是BVH 一种树状数据结构 来实现光线相交的3D世界 现在双击打开加速结构视图
这是内置在Metal调试器中的 一项伟大的新功能 我来大概说明一下它是如何工作的 在右侧 是3D视图 可以将3D场景的光线追踪可视化 包括任何自定义的几何图形 或相交函数 对于自定义形状 例如毛发 或alpha测试 都运行良好 可以使用常用控制器 来移动镜头查看四周 这里有个小技巧 滚动时按下Option键 可以放大和缩小 我们建立了一些优秀的可视化工具 来更好地了解场景 点击突出显示的菜单 可以查看可用的不同模式 例如 可以将边界体可视化 这是一张热力图 显示单独一条光线需要穿过多少结点 才能到达表面 较深的颜色意味着需要穿过更多结点 和较慢的交点测试 还可以对场景进行色彩编码 根据加速结构 几何图形 实物 或相交函数
现在我们对这个工具有了更多的了解 那么回到我们最初的问题 有了3D视图 我们可以确认几何图形的位置 那么一定还有其他的功能 在左边 有一个导航区 可以看到 顶部和底部的加速结构 我们可以展开任何加速结构 查看正在搭建的几何图形列表 可以再次展开查看属性 例如不透明度和原始数值 还可以看到这个加速结构的 实例列表 点击树叶在导航器上显示实例 查看它的属性 矩阵看起来是正确的 而且没有设置任何标志 但掩码这一栏好像有问题 在这个演示中使用的是交叉掩码 我们使用掩码最低位 来标记投射阴影的物体 使用逐位操作 来测试这个掩码 如果测试不通过 则拒绝该交叉点 我们可以在3D视图中 直接将这个过程可视化 点开交叉点提示菜单 在这里 可以配置 可视化光线穿透选项 可以改变删减操作 禁用自定义交叉点 或更改交叉点掩码 在默认情况下 它将与所有东西相交 把它改成用于投射阴影的值 当使用阴影掩码时 将展示场景的精确可视化 现在我们可以看到 树叶都消失了 一旦确定问题存在 就需要回到源头 查看是否输入了正确的掩码值 这是之前的阴影 这是修改掩码值后的阴影 这个例子展示的工作流程 可以帮您调试您的光线跟踪应用程序 如果您想了解关于工具的更多内容 可以回顾今年 WWDC的课程 《Metal调试 分析 和资源创建工具》 在本次课程中 我们回顾了 光线追踪技术如何提升视觉效果 混合渲染结合了 光栅化和光线追踪 可以取代光线近似技术 并且更精确 更简单 我们还介绍了一些新型工具 可以帮您在引擎中应用光线追踪 关于结合光栅化和光线追踪 将为您带来的新的可能性 我们只揭露了冰山一角 我们迫不及待地希望看到 您将这些技术用于实践 在未来 发展出创新的图形技术 谢谢大家 请继续观看其它WWDC课程 ♪
-
-
6:32 - Hybrid rendering in Metal 1
// Create render pass MTLRenderPassDescriptor* gbufferPass = [MTLRenderPassDescriptor new]; gbufferPass.depthAttachment.texture = gbuffer.depthTexture; gbufferPass.depthAttachment.storeAction = MTLStoreActionStore; gbufferPass.colorAttachments[0].texture = gbuffer.normalTexture; gbufferPass.colorAttachments[0].storeAction = MTLStoreActionStore;
-
6:50 - Hybrid rendering in Metal 2
// Create render pass id< MTLRenderCommandEncoder > renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:gbufferPass]; encodeRenderScene( scene, renderEncoder ); [renderEncoder endEncoding];
-
7:06 - Hybrid rendering in Metal 3
// Dispatch ray tracing via compute id< MTLComputeCommandEncoder > compEncoder = [commandBuffer computeCommandEncoder]; [compEncoder setTexture:gbuffer.depthTexture atIndex:0]; [compEncoder setTexture:gbuffer.normalTexture atIndex:1]; [compEncoder setTexture:outReflectionMap atIndex:2]; [compEncoder setComputePipelineState:raytraceReflectionKernel]; encode2dDispatch( width, height, compEncoder ); [compEncoder endEncoding];
-
11:54 - Ray-traced shadow kernel
// Calculate shadow ray from G-Buffer float3 p = calculatePosition( depth_texture, thread_id ); ray shadowRay( p, lightDirection, 0.01f, 1.0f ); // Trace for any intersections intersector< triangle_data, instancing > shadowIntersector; shadowIntersector.accept_any_intersection( true ); auto shadowIntersection = shadowIntersector.intersect( shadowRay, accel_structure ); // Point is in light if no intersections are found if ( intersection.type == intersection_type::none ) { // Point is illuminated by this light }
-
15:07 - Ray-traced ambient occlusion kernel
// Generate ray in hemisphere ray ray = cosineWeightedRay( thread_id ); ray.max_distance = 0.5f; // Trace nearby intersections intersector< triangle_data, instancing > i; auto intersection = i.intersect( ray, accel_structure ); if ( intersection.type != intersection_type::none ) { // Point is obscured by nearby geometry }
-
19:34 - Ray-traced reflection kernel
// Calculate shadow ray from G-Buffer float3 p = calculatePosition( depth_texture, thread_id ); float3 reflectedDir = reflect( p - cameraPosition, normal ); ray reflectedRay( p, reflectedDir, 0.01f, FLT_MAX ); // Trace for any intersections intersector< triangle_data, instancing > refIntersector; auto intersection = refIntersector.intersect( reflectedRay, accel_structure ); // Shade depending on intersection if ( intersection.type != intersection_type::none ) { // Reflected ray hit an object: perform shading } else { // No intersection: draw skybox }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。