大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 RealityKit 2 探索高级渲染
借助 RealityKit 中的顶尖渲染技术,为您的增强现实体验创造令人惊叹的视觉效果。您将了解如何编写自定义着色器以及绘制实时动态网格,并探索极具创意的后处理效果,帮助自己设计出风格独特的增强现实场景。
资源
- Building an Immersive Experience with RealityKit
- Creating a Fog Effect Using Scene Depth
- Displaying a Point Cloud Using Scene Depth
- Explore the RealityKit Developer Forums
- RealityKit
相关视频
WWDC22
WWDC21
WWDC20
-
下载
♪低音音乐播放♪ ♪ 柯特兰艾德斯特罗姆:哈啰 我是柯特兰艾德斯特罗姆 我是RealityKit团队的工程师 在这个视频中 我会告诉大家 如何在RealityKit 2中 使用新的渲染功能 RealityKit是用来 让建立增强现实app 更简单和直觉式的框架 渲染是RealityKit的关键 围绕着高度逼真 物理基础的渲染 从我们2019年首次推出后 我们就一直努力处理大家的反馈 而且我们要发布RealityKit的 重大更新 在《深入Reality Kit 2》的课程中 我们介绍了RealityKit的发展 提供许多改良 从实体组件系统的更新 更精进的材质和动画功能 以及在运行时动态生成音频和纹理资源 要展现这些改良 我们建立了一个app 把你们的客厅 变成一个海底水族馆 在这段介绍中 我们会示范一些 这个app中新的渲染性能 RealityKit 2让你能够灵活地控制 如何渲染对象 并且建立更棒的增强现实体验 今年我们提升了材质系统 让你能通过编写自定义Metal着色器 来添加自己的材质 你可以用自己的自定义后处理效果 来加强RealityKit的后处理效果 新的网格API让你能在执行时期内 建立、检查和修改网格 让我们从RealityKit 2中 最迫切的功能开始 支持自定义着色器 RealityKit的渲染是围绕在 物理基础的渲染模型上 它的内建着色器让它可以轻易地 在各种光线条件下 建立和真实对象看起来差不多的模型 今年 我们以这些 物理基础着色器为基础 发布让你可以用着色器 自定义模型几何和表面的能力 我们的第一个着色器API 是几何修改器 几何修改器是一个 以Metal着色器语言写入的程序 它让你有机会 在GPU上渲染时 每一帧改变物体的顶点属性 这包括移动它们和自定义它们的属性 例如颜色、法线或纹理坐标 它在RealityKit的顶点着色器内运作 而且很适合用于环境的动画 形变、粒子系统和公告板 我们的海草就是很棒的环境动画范例 海草因为周围的水的运动 所以动得很慢 让我们仔细看一下 这里大家可以看到 设计师建立的海草线框稿 这里显示顶点和包含网格的三角形 我们要写入一个着色器程序 在每个顶点上执行 建立它们的运动 我们会用正弦曲线 这个简单的周期性功能 来建立运动 我们在模拟水流 所以我们希望相邻的顶点 会有类似的表现 不论它们的模型大小或方向 基于这个理由 我们把顶点的世界坐标 做为正弦函数的输入值 我们也纳入时间值 所以它会随着时间移动 我们的第一个正弦曲线是在Y轴上 建立垂直移动 要控制运动的周期 我们要加上一个空间尺度 这样我们就能以振幅控制它的移动量 我们会将同样的函数 应用到X轴和Z轴上 所以它会在三轴上移动 现在 让我们就整体来看这个模型 有件事我们还没交代清楚 靠近茎的根部的顶点 只有很小的移动空间 而在上面的顶点 则可以非常自由地移动 要模拟这个运动 我们可以把顶点的y坐标 相对于对象原点的位置 当做全部三轴的比例系数 让我们得到最终的公式 现在我们有着色器的规划了 让我们看看哪里可以找到这些参数 几何参数被分成几个类别 第一个是统一常数 在一个帧里面 对象的每个顶点都具有相同的值 我们的海草需要时间 材质包含所有用于建立模型的材质 加上一个额外的自定义插槽 如果你在上面看到适合的就可以使用 材质常数有很多参数 例如着色和透明度 是建立在对象上面或是通过编码设定 几何包含一些只能读取的值 例如目前顶点的模型坐标或顶点ID 我们需要模型坐标世界坐标 来建立海草的运动 几何也有可读写的值 包括法线、纹理坐标 和模型坐标偏移量 一旦我们计算出偏移量 我们会把它存在这里 来移动我们的顶点 让我们深入讨论Metal着色器 我们会从包含RealityKit.h开始 现在我们宣告一个 有可见函数属性的函数 这会指示编译器 让它从其他函数中 独立出来使用 这个函数使用单一参数 也就是RealityKit的 geometry_parameters 我们会通过这个对象取回所有的值 利用params的几何成员 我们会同时要求世界坐标和模型坐标 接着我们根据在顶点的世界坐标 和时间来计算相位偏移 然后我们用我们的公式 来计算这个顶点的偏移量 我们把偏移量存在几何上 它会被加到顶点的模型坐标上 我们有几何修改器 但是它还没连接上我们的海草 让我们切换到以Swift写的 ARView子类别 我们先加载我们app的 预设Metal函数库 里面包含我们的着色器 接着我们用着色器的名字和函数库 建构一个geometryModifier 针对海草上的每一个材质 我们都建立一个新的自定义材质 我们传送出现有的材质 做为CustomMaterial的第一个参数 所以在加到我们的几何修改器时 它继承了来自基础材质的 纹理和材质属性 看起来很棒 因为我们在水底 所以我们让动画跑得很慢 通过调整振幅和相位 同样的效果可以延伸到草地 树 或其他叶子上 既然我们示范了如何调整几何 让我们来谈一下着色 这是我们海底场景中的章鱼 用内建的着色器看起来已经很棒了 尽管如此 这只章鱼会在不同外观之间变换 第二个外观带着这个微红的颜色 我们的设计师创作了两种底色纹理 两种外观各一个 除了颜色改变外 红色章鱼有比较高的粗糙度值 让它比较不反光 为了让我们的章鱼更特别 我们希望能把不同外观之间的过渡 做到很漂亮 这里大家可以看到过渡进行的样子 很吸引人 尽管每个外观都可以 用物理基础材质来描述 对于过渡本身 我们则需要写一个表面着色器 什么是表面着色器? 表面着色器能让你 定义对象的外貌 它在片段着色器内处理 对象的每一个可见像素 除了颜色以外 它还包括表面属性 例如法线、高光亮度和粗糙度 你可以写入能提升对象外表 或是完全取代对象 创造新效果的着色器 我们已经看过这只章鱼的 两种底色纹理 就过渡效果而言 我们的艺术家替我们编码了 特殊的纹理 这个纹理其实是 三个图层的结合 最上面是建立局部的过渡样式的 噪波图层 然后是过渡图层 它会命令整体的运动 从它的头开始 一路移动到触手 接着还有一个遮罩图层 那是我们不想改变颜色的区域 例如眼睛和触手的下面 这三个图层会结合成纹理的 红色、绿色和蓝色通道 我们将这些指定为自定义纹理插槽 有了纹理设定 让我们来看看如何从表面着色器 访问这些纹理 和几何修改器类似 表面着色器可以使用统一常数 纹理和材质常数 时间是我们章鱼过渡的输入 我们从在模型上使用的纹理取样 并且读取材质常数 让我们的设计师可以针对 整个模型做调整 几何 像是位置、法线或纹理坐标 会出现在几何结构中 这些是来自顶点着色器的内插输出 我们会以UV0为纹理坐标 表面着色器会写入表面结构 属性都是从预设值开始 只要有看到适合的属性 我们可以任意去计算这些值 我们会先计算底色和法线 接着 四个表面参数 粗糙度、金属度、环境光遮蔽 和高光亮度 现在我们知道这些值在哪里了 让我们开始写入我们的着色器 我们会通过三个步骤来做这件事 首先计算过渡值 其中0代表完全紫色的章鱼 1代表完全红色的章鱼 利用过渡值 我们会计算颜色和法线 接着通过指定材质属性来微调 让我们开始吧 第一步:过渡 我们要建立章鱼的表面函数 它会用到surface_parameters自变量 既然我们使用纹理 我们要宣告一个取样 在右边 大家可以看到 在空白表面着色器中 我们的章鱼长什么样子 它是灰色的而且有一点光泽 RealityKit让你可以完全控制 哪些东西能影响到你的模型外观 为了要计算颜色 我们必须先做几件事 我们会储存一些方便的变量 我们会拿我们的UV0 做为纹理坐标 Metal和USD有不同的纹理坐标系统 所以我们要把y坐标转化成 能符合从USD载入的纹理 现在我们要以我们的过渡纹理取样 我们设计师建立的三个图层的纹理 设计师建立了一个小函数 它会用遮罩值和时间 并且回传blend和colorBlend 在0到1的值 第二步:颜色和法线 有了前面计算得到的混合变量 现在我们可以计算章鱼的颜色 以及来看看这个过渡 要做到这个 我们从两个纹理取样 底色和储存在 emissive_color里面的第二底色 接着我们用先前 计算出来的colorBlend 来混合这两个颜色 我们会乘以base_color_tint 这个从材质来的值 然后在表面设定底色 接下来我们会使用法线贴图 这会加上表面偏差 最明显的是在头上和触手上 我们取样法线贴图纹理 开箱它的值 然后设在表面对象上 现在轮到材质属性 这是我们目前用颜色和法线 得到的章鱼 让我们来看看表面属性 如何影响它的外观 你可以在下半身看到的粗糙度 会让下半部变暗的环境光遮蔽 还有高光亮度 它会在眼睛加上漂亮的反光 以及在身体上加上额外的定义 让我们把这些加到我们的着色器上 我们在模型上以四个材质取样 每个属性都有一个 接着我们用材质设定 来调整这些值的比例 此外 在紫色过渡到红色的过程中 我们也增加粗糙度 然后我们把这四个值设到表面上 跟前面类似 我们需要把着色器 套用到我们的模型上 我们在ARView子类别中 将这个材质指定到模型上 首先 我们加载两个额外的纹理 然后加载我们的表面着色器 跟之前一样 我们是从对象的基本材质 来建构新的材质 不过这次 是用表面着色器 和两个额外的纹理 这样就完成了 让我们回顾一下 我们示范了 以几何修改器建立海草的动画 以及如何用表面着色器 建立章鱼的颜色过渡 我们把这两个独立出来示范 不过大家可以把它们结合在一起 做出更有趣的效果 让我们进入下一个高度要求的性能 支持加入自定义后处理效果 RealityKit有一整套 和相机相配的后处理效果 像是动态模糊、相机杂点和景深 这些效果都是为了让虚拟和真实对象 看起来是在同一个环境中 你可以在ARView上自定义这些效果 今年 我们也发布了能让你 建立你自己的全屏显示效果的功能 这让你可以通过RealityKit 来实现照片写实 并且在你app的结果上添加新的效果 什么是后处理? 后处理在对象渲染和打光后才执行的 一个或一系列的着色器 它也可以发生在任何RealityKit 后处理效果之后 它的输入有两种纹理 颜色和深度缓冲 这里深度缓冲是以灰阶呈现 它包含每个像素 相对于相机的距离值 后处理把它的结果 写入目标颜色纹理 最简单的后处理效果 会把来源颜色复制到目标颜色 我们有几个方法可以做到这些 Apple平台有一系列的技术 能将后处理效果整合得很好 例如Core Image Metal Performance Shader 和SpriteKit 你也可以用Metal着色器语言 来写你自己的 让我们从Core Image的一些效果开始 Core Image是Apple的图像处理框架 它有上百种颜色处理 风格化和形变效果 让你可以应用在图像和视频中 热感应是一个精巧的效果 你会想在海底世界的窗口上把它打开 让我们来看看要把它 和RealityKit整合有多么简单 我们所有的后处理效果 都遵循相同的模式 你先设定渲染回调 然后响应到prepareWithDevice 接着 每个帧都会叫出后处理 渲染回调是在RealityKit的ARView上 我们想要prepareWithDevice 和postProcess回调 prepareWithDevice会跟着MTLDevice 一起被调用一次 这是建立纹理 载入计算或渲染管线 以及检查装置性能的好机会 我们会在这里建立Core Image环境 每个帧都会调用postProcess回调 我们会参考我们的来源颜色纹理 建立一个CIImage 接着我们建立热感应滤镜 如果你使用不同的Core Image滤镜 这是你配置滤镜的其他参数的位置 之后我们建立渲染目的地 它以我们的输出颜色纹理为目标 并且使用环境的命令缓冲区 我们要求Core Image保存图像的方向 然后开始作业 就是这样! 有了Core Image 我们解锁了上百种现成的效果来使用 现在让我们来看看如何以 Metal Performance Shader 来建立新的效果 先来介绍全屏泛光 全屏泛光是一个屏幕空间技术 它可以在高亮度对象旁边建立光晕 模拟真实的镜头效果 Core Image包含全屏泛光效果 不过我们要建立我们自己的 这样我们才能控制过程的每一个步骤 我们用Metal Performance Shader 来建立这个效果 这是一个高度优化的 计算和图形着色器集合 要建立这个着色器 我们要以颜色为来源 建构滤镜的图形 首先我们想要分离出那些很亮的区域 我们用一个叫“阀值为零”运算指令 来做分离的动作 它会将颜色转成亮度 然后把每个亮度低于某个阀值的 像素设为0 接着用高斯模糊来模糊这个结果 把光线分散到旁边的区域 有效的模糊可能执行起来难度很高 而且通常需要好多个阶段 Metal Performance Shader 会帮我们处理这些 之后将这个模糊的纹理 加到原来的颜色上 在明亮的区域周围加上光晕 让我们实作这张图做为后处理效果 我们从建立中间bloomTexture开始 然后执行我们的 ThresholdToZero运算指令 从sourceColor读取 并且写入bloomTexture 之后我们在适当的位置 执行gaussianBlur 最后 我们把原本的颜色 和这个回调的颜色加在一起 就是这样! 现在我们看过几种 建立后处理效果的方式 让我们来看一下如何以SpriteKit 把效果放到我们的输出上 SpriteKit用于高性能省电2D游戏的 Apple框架 它很适合用在我们的3D图上 加上一些效果 我们会利用这个框架 把一些泡泡加到屏幕上 做为后处理效果 使用同样的prepareWithDevice 和postProcess回调 跟前面相同的两个步骤 在prepareWithDevice 我们会建立我们的SpriteKit渲染器 并且加载包含泡泡的场景 接着在我们的postProcess回调 我们会将来源颜色复制到目标颜色 更新我们的SpriteKit场景 在3D内容上做渲染 prepareWithDevice很简单 建立我们的渲染器 然后从档案中载入我们的场景 我们要把这个盖到增强现实的场景上 所以我们要把SpriteKit的背景 设成透明的 在postProcess中 我们先将来源颜色以位块传送到 targetColorTexture 这会是SpriteKit渲染时的背景 接着将我们的SpriteKit场景 推进到新的时间上 所以我们的泡泡能往上移动 设定一个RenderPassDescriptor 并且在那上面渲染 就是这样! 我们示范了如何使用现有的框架 来制作后处理效果 不过有时候你真的得自己从头开始 写一个新的框架 你也可以通过写入计算着色器 来创作全屏显示效果 在我们的海底世界的范例中 我们需要一个可以用在虚拟对象 和相机直通模式上的雾化效果 雾化会模拟光线通过介质的散射 它的强度和距离成比例 要建立这个效果 我们需要知道 每个像素和装置之间的距离 幸运地是 ARKit和RealityKit 都能存取深度数据 在能使用光学雷达的装置中 ARKit能够存取sceneDepth 包含从相机取得的距离 而且是以米计算 这些数值在低分辨率非常精准 比在全屏时更准确 我们可以直接使用这个深度 可是它不包含虚拟对象 所以它们没办法正确地雾化 在我们的postProcess中 RealityKit能存取虚拟内容的深度 并且在场景理解开启时 和真实对象大致上能相互融合 这个融合会在你移动的时候逐渐发生 所以上面会出现一些洞 那是我们还没扫描到的位置 这些洞会假设它们在无限远的位置 和显示雾化 我们会结合这两个深度纹理的数据 来解决这个差异 ARKit将深度值规定为纹理 每个像素都是采样点的距离 并且是以米计算 因为传感器是在你的iPhone或iPad上 位在固定的方向 我们会要求ARKit 从传感器的方向到目前屏幕的方向 来建构一个对话 然后再反转结果 要读取虚拟内容深度 我们需要知道一些关于 RealityKit如何打包深度的信息 你会注意到 跟ARKit的 sceneDepth不同 比较亮的值是比较靠近相机的 利用无线反向-Z投影 将值储存在介于0到1的范围之间 意思是0代表在无限远的位置 而1点是在接近相机的平面 你可以通过以接近平面深度 除以取样深度 轻易地反转这个转换 让我们写一个辅助函数来做这件事 我们有一个会使用样本深度 和投影矩阵的Metal函数 没有虚拟内容的像素为0 我们会在括号内放入epsilon 来避免除以0 要复原透视分割 我们会用最后一栏的z值 去除以我们的样本深度 太好了! 现在我们两个深度值都有了 我们可以用两个之中最小的 做为雾化函数的输入 我们的雾化有几个参数:最大距离 在那个距离下的最大强度 和功率曲线指数 切确的值是通过试验选择出来的 它们调整我们的深度值 来达到预期的雾化强度 现在我们准备好把这些组在一起了 我们有从ARKit来的深度值 RealityKit来的线性深度值 和雾化用的函数 让我们来写入计算着色器 针对每个像素 我们从采样两种直线深度值开始 接着我们使用雾化函数 通过微调参数 将直线深度转换成0到1的值 之后我们混合来源颜色和雾化颜色 根据fogBlend的值 把结果储存在outColor中 回顾一下 RealityKit 新的后处理API 可以做到很广泛的后处理效果 通过Core Image 我们解锁了上百种现成的效果 你可以用Metal Performance Shader 轻易地建立一个新的效果 用SpriteKit加上屏幕重叠 并且用Metal自己从头写一个新的 想更了解Core Image 或Metal Performance Shader 请观看列出的课程 我们已经介绍了渲染效果 让我们进行下一个主题:动态网格 在RealityKit中 网格资源储存着网格数据 过去 这个不透明数据类型 让你可以将网格指定到实体上 今年 我们提供了在执行时期 可以检查、建立和更新网格的功能 让我们来看看如何在潜水员身上 加入特效 在这个范例中 我们想示范螺旋效果 螺旋的轮廓会出现在潜水员周围 大家也可以看到螺旋是如何 跟着时间改变它的网格 来驱动它的运动 让我们来看看如何使用新的网格API 来建立这个效果 这个效果可以归纳成三个步骤 在网格检查中 通过查看它的顶点来测量模型 接着以测量值为依据来建立螺旋 最后 我们可以跟着时间来更新螺旋 从网格检查开始 要检视网格是怎么储存的 我们先来看一下我们的潜水员模型 在RealityKit中 潜水员的网格 是以网格资源表示 今年新释出的内容中 MeshResource现在可以包含一个叫做 目录的成员 这是所有经处理的网格几何 所在的位置 目录包含一系列的实例和模型 模型包含原始文件的顶点资料 而实例会参考这些数据并且加上变形 实例可以在不用复制数据的状况下 将同样的几何显示很多次 模型可以有好几个部分 一个部分代表一群同样材质的几何 最后 每个部分包含 我们有兴趣的顶点数据 例如位置、法线 纹理、坐标和索引 让我们先来看看如何在编码中 存取这个资料 我们要在MeshResource.Contents上 做延伸 这会在每个顶点的位置叫出闭包 我们从检查全部的实例开始 每一个实例都会对应到一个模型 针对每个实例 我们可以看到它相对于整体的变形 接着我们可以来看每个模型的部分 并且取得每个部分的属性 在这个函数中 我们只对位置有兴趣 我们可以将顶点转换成整体空间位置 和叫出我们的回调 现在我们可以来检视顶点 看看我们打算怎么使用这个数据 我们先把潜水员分割成水平截面 每一个截面都有模型的边界半径 我们把每个截面的半径值都找出来 要实作这个 我们先从以numSlices组件 建立全0数组开始 接着我们找出在y轴上网格的边界 来建立分割截面 利用我们刚刚建立的函数 我们找出模型的每个顶点 会落在哪一个截面中 然后我们用那个截面的最大半径值 来更新半径 最后 我们回传包含半径和边界的 截面对象 既然我们分析了网格 知道它有多大 让我们来看看如何建立螺旋网格 螺旋是一个动态产生的网格 要建立这个网格 我们需要向RealityKit 描述我们的数据 我们用网格描述器来做这件事 网格描述器包含位置、法线 纹理坐标、基元和材质索引 一旦有了网格描述器 你就可以生成网格资源 这会唤起RealityKit的网格处理器 这会优化你的网格 它会合并重复的顶点 三角测量你的四边形和多边形 并且将网格以最能有效渲染的格式 呈现 这个步骤的产物是网格资源 我们可以把它指定到实体上 注意法线、纹理坐标 和材质都是选择性的 我们的网格处理器会自动 生成正确的法线并且填充它们 作为优化程序的一部分 RealityKit会重新产生网格的拓扑 如果你需要特定的拓扑 你可以直接使用 MeshResource.Contents 知道怎么建立网格后 让我们来看看如何建立螺旋 要替螺旋建模 让我们仔细查看一个部分
螺旋又叫做螺旋体 我们会建立在等距的片段上 我们可以利用螺旋体的数学定义 和从我们分析的网格得到的半径 来计算每个点 在螺旋体上的每个片段套入这个函数 我们可以定义四个顶点 P0和P1就是p()回传的值 要计算P2和P3 我们可以用特定的厚度 垂直抵销P0和P1 我们在建立三角形 所以我们需要一条对角线 我们会用这些点做出两个三角形 让我们把这全部组在一起 我们的generateSpiral函数 必须储存位置和索引 索引会参考位置里面的值 在每一个片段上 我们会计算出四个位置 以及储存它们的索引 把i0加入数组时 它会是p0的索引 接着我们把这两个三角形的 四个位置和六个索引 加到它们的数组上 一旦你有了几何 建立网格就很简单 首先 建立一个新的MeshDescriptor 接着指定位置和基元 我们用的是三角形基元 不过我们也可以选四边形或多边形 填入这两个栏位后 我们就能建立MeshResource了 你也能提供其他顶点属性 像是法线 textureCoordinates或材质任务 我们介绍了如何建立网格 在这个螺旋范例中 最后要讲的是网格更新 我们利用网格更新 让螺旋围绕着潜水员移动 有两种方式可以更新网格 我们可以利用MeshDescriptors API 在每个帧建立新的MeshResource 不过这不是一个很有效率的途径 因为它会在每个帧上 执行网格优化器 比较有效率的途径是 更新在MeshResource中的目录 你可以建立新的MeshContents 并且用它来取代网格 不过有一个警告 如果我们以MeshDescriptor 建立原本的网格 RealityKit的网格处理器 就会去优化资料 拓扑也会被降为三角形 因此 在套用任何更新前 要知道它会怎么影响你的网格 让我们看一下要如何更新螺旋的编码 我们先从储存现有螺旋的目录开始 从现有的模型建立一个新的模型 接着 在每个部分上 我们用索引的子集去取代 triangleIndices 最后 我们可以用新目录 在现有的MeshResource调用取代 动态网格就讲到这里 总结一下动态网格的重点 我们在MeshResource 导入新的目录栏位 这个容器让你可以检查和修改 网格的原始文件资料 你可以用MeshDescriptor 建立新的网格 这个路径能让你灵活地 使用三角形、四边形 甚至多边形 而且RealityKit 会产生适合渲染的网格 最后 想要更新网格 我们提供 更新MeshResource的目录的功能 这很适合用在频繁的更新 总而言之 我们今天展示了 在RealityKit 2中 一些新的渲染功能 几何修改器让你可以移动和修改顶点 表面着色器能让你定义 你的模型表面外观 你可以用后处理效果在最后一帧上 套用效果 而动态网格 能让你在执行时期 建立和修改网格都更轻松 想要更认识今年的新性能 不要错过《深入RealityKit 2》 如果想了解RealityKit的更多内容 可以看《以RealityKit建立app》 今年更新的内容让我们很兴奋 也很期待看到你们用这些功能打造的体验 谢谢 ♪
-
-
4:52 - Seaweed Shader
#include <RealityKit/RealityKit.h> [[visible]] void seaweedGeometry(realitykit::geometry_parameters params) { float spatialScale = 8.0; float amplitude = 0.05; float3 worldPos = params.geometry().world_position(); float3 modelPos = params.geometry().model_position(); float phaseOffset = 3.0 * dot(worldPos, float3(1.0, 0.5, 0.7)); float time = 0.1 * params.uniforms().time() + phaseOffset; float3 maxOffset = float3(sin(spatialScale * 1.1 * (worldPos.x + time)), sin(spatialScale * 1.2 * (worldPos.y + time)), sin(spatialScale * 1.2 * (worldPos.z + time))); float3 offset = maxOffset * amplitude * max(0.0, modelPos.y); params.geometry().set_model_position_offset(offset); }
-
5:43 - Assign Seaweed Shader
// Assign seaweed shader to model. func assignSeaweedShader(to seaweed: ModelEntity) { let library = MTLCreateSystemDefaultDevice()!.makeDefaultLibrary()! let geometryModifier = CustomMaterial.GeometryModifier(named: "seaweedGeometry", in: library) seaweed.model!.materials = seaweed.model!.materials.map { baseMaterial in try! CustomMaterial(from: baseMaterial, geometryModifier: geometryModifier) } }
-
9:21 - Octopus Shader
#include <RealityKit/RealityKit.h> void transitionBlend(float time, half3 masks, thread half &blend, thread half &colorBlend) { half noise = masks.r; half gradient = masks.g; half mask = masks.b; half transition = (sin(time * 1.0) + 1) / 2; transition = saturate(transition); blend = 2 * transition - (noise + gradient) / 2; blend = 0.5 + 4.0 * (blend - 0.5); // more contrast blend = saturate(blend); blend = max(blend, mask); blend = 1 - blend; colorBlend = min(blend, mix(blend, 1 - transition, 0.8h)); } [[visible]] void octopusSurface(realitykit::surface_parameters params) { constexpr sampler bilinear(filter::linear); auto tex = params.textures(); auto surface = params.surface(); auto material = params.material_constants(); // USD textures have an inverse y orientation. float2 uv = params.geometry().uv0(); uv.y = 1.0 - uv.y; half3 mask = tex.custom().sample(bilinear, uv).rgb; half blend, colorBlend; transitionBlend(params.uniforms().time(), mask, blend, colorBlend); // Sample both color textures. half3 baseColor1, baseColor2; baseColor1 = tex.base_color().sample(bilinear, uv).rgb; baseColor2 = tex.emissive_color().sample(bilinear, uv).rgb; // Blend colors and multiply by the tint. half3 blendedColor = mix(baseColor1, baseColor2, colorBlend); blendedColor *= half3(material.base_color_tint()); // Set on the surface. surface.set_base_color(blendedColor); // Sample the normal and unpack. half3 texNormal = tex.normal().sample(bilinear, uv).rgb; half3 normal = realitykit::unpack_normal(texNormal); // Set on the surface. surface.set_normal(float3(normal)); // Sample material textures. half roughness = tex.roughness().sample(bilinear, uv).r; half metallic = tex.metallic().sample(bilinear, uv).r; half ao = tex.ambient_occlusion().sample(bilinear, uv).r; half specular = tex.roughness().sample(bilinear, uv).r; // Apply material scaling factors. roughness *= material.roughness_scale(); metallic *= material.metallic_scale(); specular *= material.specular_scale(); // Increase roughness for the red octopus. roughness *= (1 + blend); // Set material properties on the surface. surface.set_roughness(roughness); surface.set_metallic(metallic); surface.set_ambient_occlusion(ao); surface.set_specular(specular); }
-
11:41 - Assign Octopus Shader
// Apply the surface shader to the Octopus. func assignOctopusShader(to octopus: ModelEntity) { // Load additional textures. let color2 = try! TextureResource.load(named: "Octopus/Octopus_bc2") let mask = try! TextureResource.load(named: "Octopus/Octopus_mask") // Load the surface shader. let surfaceShader = CustomMaterial.SurfaceShader(named: "octopusSurface", in: library) // Construct a new material with the contents of an existing material. octopus.model!.materials = octopus.model!.materials.map { baseMaterial in let material = try! CustomMaterial(from: baseMaterial surfaceShader: surfaceShader) // Assign additional textures. material.emissiveColor.texture = .init(color2) material.custom.texture = .init(mask) return material } }
-
14:13 - CoreImage PostEffect
// Add RenderCallbacks to the ARView. var ciContext: CIContext? func initPostEffect(arView: ARView) { arView.renderCallbacks.prepareWithDevice = { [weak self] device in self?.prepareWithDevice(device) } arView.renderCallbacks.postProcess = { [weak self] context in self?.postProcess(context) } } func prepareWithDevice(_ device: MTLDevice) { self.ciContext = CIContext(mtlDevice: device) } // The CoreImage thermal filter. func postProcess(_ context: ARView.PostProcessContext) { // Create a CIImage for the input color. let sourceColor = CIImage(mtlTexture: context.sourceColorTexture)! // Create the thermal filter. let thermal = CIFilter.thermal() thermal.inputImage = sourceColor // Create the CIRenderDestination. let destination = CIRenderDestination(mtlTexture: context.targetColorTexture, commandBuffer: context.commandBuffer) // Preserve the image orientation. destination.isFlipped = false // Instruct CoreImage to start our render task. _ = try? self.ciContext?.startTask(toRender: thermal.outputImage!, to: destination) }
-
16:15 - Bloom Post Effect
var device: MTLDevice! var bloomTexture: MTLTexture! func initPostEffect(arView: ARView) { arView.renderCallbacks.prepareWithDevice = { [weak self] device in self?.prepareWithDevice(device) } arView.renderCallbacks.postProcess = { [weak self] context in self?.postProcess(context) } } func prepareWithDevice(_ device: MTLDevice) { self.device = device } func makeTexture(matching texture: MTLTexture) -> MTLTexture { let descriptor = MTLTextureDescriptor() descriptor.width = texture.width descriptor.height = texture.height descriptor.pixelFormat = texture.pixelFormat descriptor.usage = [.shaderRead, .shaderWrite] return device.makeTexture(descriptor: descriptor)! } func postProcess(_ context: ARView.PostProcessContext) { if self.bloomTexture == nil { self.bloomTexture = self.makeTexture(matching: context.sourceColorTexture) } // Reduce areas of 20% brightness or less to zero. let brightness = MPSImageThresholdToZero(device: context.device, thresholdValue: 0.2, linearGrayColorTransform: nil) brightness.encode(commandBuffer: context.commandBuffer, sourceTexture: context.sourceColorTexture, destinationTexture: bloomTexture!) // Blur the remaining areas. let gaussianBlur = MPSImageGaussianBlur(device: context.device, sigma: 9.0) gaussianBlur.encode(commandBuffer: context.commandBuffer, inPlaceTexture: &bloomTexture!) // Add color plus bloom, writing the result to targetColorTexture. let add = MPSImageAdd(device: context.device) add.encode(commandBuffer: context.commandBuffer, primaryTexture: context.sourceColorTexture, secondaryTexture: bloomTexture!, destinationTexture: context.targetColorTexture) }
-
17:15 - SpriteKit Post Effect
// Initialize the SpriteKit renderer. var skRenderer: SKRenderer! func initPostEffect(arView: ARView) { arView.renderCallbacks.prepareWithDevice = { [weak self] device in self?.prepareWithDevice(device) } arView.renderCallbacks.postProcess = { [weak self] context in self?.postProcess(context) } } func prepareWithDevice(_ device: MTLDevice) self.skRenderer = SKRenderer(device: device) self.skRenderer.scene = SKScene(fileNamed: "GameScene") self.skRenderer.scene!.scaleMode = .aspectFill // Make the background transparent. self.skRenderer.scene!.backgroundColor = .clear } func postProcess(context: ARView.PostProcessContext) { // Blit (Copy) sourceColorTexture onto targetColorTexture. let blitEncoder = context.commandBuffer.makeBlitCommandEncoder() blitEncoder?.copy(from: context.sourceColorTexture, to: context.targetColorTexture) blitEncoder?.endEncoding() // Advance the scene to the new time. self.skRenderer.update(atTime: context.time) // Create a RenderPass writing to the targetColorTexture. let desc = MTLRenderPassDescriptor() desc.colorAttachments[0].loadAction = .load desc.colorAttachments[0].storeAction = .store desc.colorAttachments[0].texture = context.targetColorTexture // Render! self.skRenderer.render(withViewport: CGRect(x: 0, y: 0, width: context.targetColorTexture.width, height: context.targetColorTexture.height), commandBuffer: context.commandBuffer, renderPassDescriptor: desc) }
-
19:08 - ARKit AR Depth
let width = context.sourceColorTexture.width let height = context.sourceColorTexture.height let transform = arView.session.currentFrame!.displayTransform( for: self.orientation, viewportSize: CGSize(width: width, height: height) ).inverted()
-
20:01 - Depth Fog Shader
typedef struct { simd_float4x4 viewMatrixInverse; simd_float4x4 viewMatrix; simd_float2x2 arTransform; simd_float2 arOffset; float fogMaxDistance; float fogMaxIntensity; float fogExponent; } DepthFogParams; float linearizeDepth(float sampleDepth, float4x4 viewMatrix) { constexpr float kDepthEpsilon = 1e-5f; float d = max(kDepthEpsilon, sampleDepth); // linearize (we have reverse infinite projection); d = abs(-viewMatrix[3].z / d); return d; } constexpr sampler textureSampler(address::clamp_to_edge, filter::linear); float getDepth(uint2 gid, constant DepthFogParams &args, texture2d<float, access::sample> inDepth, depth2d<float, access::sample> arDepth) { // normalized coordinates float2 coords = float2(gid) / float2(inDepth.get_width(), inDepth.get_height()); float2 arDepthCoords = args.arTransform * coords + args.arOffset; float realDepth = arDepth.sample(textureSampler, arDepthCoords); float virtualDepth = linearizeDepth(inDepth.sample(textureSampler, coords)[0], args.viewMatrix); return min(virtualDepth, realDepth); } [[kernel]] void depthFog(uint2 gid [[thread_position_in_grid]], constant DepthFogParams& args [[buffer(0)]], texture2d<half, access::sample> inColor [[texture(0)]], texture2d<float, access::sample> inDepth [[texture(1)]], texture2d<half, access::write> outColor [[texture(2)]], depth2d<float, access::sample> arDepth [[texture(3)]] ) { const half4 fogColor = half4(0.5, 0.5, 0.5, 1.0); float depth = getDepth(gid, args, inDepth, arDepth); // Ignore depth values greater than the maximum fog distance. float fogAmount = saturate(depth / args.fogMaxDistance); float fogBlend = pow(fogAmount, args.fogExponent) * args.fogMaxIntensity; half4 nearColor = inColor.read(gid); half4 color = mix(nearColor, fogColor, fogBlend); outColor.write(color, gid); }
-
23:32 - MeshResource.Contents extension
// Examine each vertex in a mesh. extension MeshResource.Contents { func forEachVertex(_ callback: (SIMD3<Float>) -> Void) { for instance in self.instances { guard let model = self.models[instance.model] else { continue } let instanceToModel = instance.transform for part in model.parts { for position in part.positions { let vertex = instanceToModel * SIMD4<Float>(position, 1.0) callback([vertex.x, vertex.y, vertex.z]) } } } } }
-
24:20 - Mesh Radii
struct Slices { var radii : [Float] = [] var range : ClosedRange<Float> = 0...0 var sliceHeight: Float { return (range.upperBound - range.lowerBound) / Float(sliceCount) } var sliceCount: Int { return radii.count } func heightAt(index: Int) -> Float { return range.lowerBound + Float(index) * self.sliceHeight + self.sliceHeight * 0.5 } func radiusAt(y: Float) -> Float { let relativeY = y - heightAt(index: 0) if relativeY < 0 { return radii.first! } let slice = relativeY / sliceHeight let sliceIndex = Int(slice) if sliceIndex+1 >= sliceCount { return radii.last! } // 0 to 1 let t = (slice - floor(slice)) // linearly interpolate between two closest values let prev = radii[sliceIndex] let next = radii[sliceIndex+1] return mix(prev, next, t) } func radiusAtIndex(i: Float) -> Float { let asFloat = i * Float(radii.count) var prevIndex = Int(asFloat.rounded(.down)) var nextIndex = Int(asFloat.rounded(.up)) if prevIndex < 0 { prevIndex = 0 } if nextIndex >= radii.count { nextIndex = radii.count - 1 } let prev = radii[prevIndex] let next = radii[nextIndex] let remainder = asFloat - Float(prevIndex) let lerped = mix(prev, next, remainder) return lerped + 0.5 } } func meshRadii(for mesh: MeshResource, numSlices: Int) -> Slices { var radiusForSlice: [Float] = .init(repeating: 0, count: numSlices) let (minY, maxY) = (mesh.bounds.min.y, mesh.bounds.max.y) mesh.contents.forEachVertex { modelPos in let normalizedY = (modelPos.y - minY) / (maxY - minY) let sliceY = min(Int(normalizedY * Float(numSlices)), numSlices - 1) let radius = length(SIMD2<Float>(modelPos.x, modelPos.z)) radiusForSlice[sliceY] = max(radiusForSlice[sliceY], radius) } return Slices(radii: radiusForSlice, range: minY...maxY) }
-
25:58 - Spiral Point
// The angle between two consecutive segments. let theta = (2 * .pi) / Float(segmentsPerRevolution) // How far to step in the y direction per segment. let yStep = height / Float(totalSegments) func p(_ i: Int, radius: Float = 1.0) -> SIMD3<Float> { let y = yStep * Float(i) let x = radius * cos(Float(i) * theta) let z = radius * sin(Float(i) * theta) return SIMD3<Float>(x, y, z) }
-
26:37 - Generate Spiral
extension MeshResource { static func generateSpiral( radiusAt: (Float)->Float, radiusAtIndex: (Float)->Float, thickness: Float, height: Float, revolutions: Int, segmentsPerRevolution: Int) -> MeshResource { let totalSegments = revolutions * segmentsPerRevolution let totalVertices = (totalSegments + 1) * 2 var positions: [SIMD3<Float>] = [] var normals: [SIMD3<Float>] = [] var indices: [UInt32] = [] var uvs: [SIMD2<Float>] = [] positions.reserveCapacity(totalVertices) normals.reserveCapacity(totalVertices) uvs.reserveCapacity(totalVertices) indices.reserveCapacity(totalSegments * 4) for i in 0..<totalSegments { let theta = Float(i) / Float(segmentsPerRevolution) * 2 * .pi let t = Float(i) / Float(totalSegments) let segmentY = t * height if i > 0 { let base = UInt32(positions.count - 2) let prevInner = base let prevOuter = base + 1 let newInner = base + 2 let newOuter = base + 3 indices.append(contentsOf: [ prevInner, newOuter, prevOuter, // first triangle prevInner, newInner, newOuter // second triangle ]) } let radialDirection = SIMD3<Float>(cos(theta), 0, sin(theta)) let radius = radiusAtIndex(t) var position = radialDirection * radius position.y = segmentY positions.append(position) positions.append(position + [0, thickness, 0]) normals.append(-radialDirection) normals.append(-radialDirection) // U = in/out // V = distance along spiral uvs.append(.init(0.0, t)) uvs.append(.init(1.0, t)) } var mesh = MeshDescriptor() mesh.positions = .init(positions) mesh.normals = .init(normals) mesh.primitives = .triangles(indices) mesh.textureCoordinates = .init(uvs) return try! MeshResource.generate(from: [mesh]) } }
-
28:17 - Update Spiral
if var contents = spiralEntity?.model?.mesh.contents { contents.models = .init(contents.models.map { model in var newModel = model newModel.parts = .init(model.parts.map { part in let start = min(self.allIndices.count, max(0, numIndices - stripeSize)) let end = max(0, min(self.allIndices.count, numIndices)) var newPart = part newPart.triangleIndices = .init(self.allIndices[start..<end]) return newPart }) return newModel }) try? spiralEntity?.model?.mesh.replace(with: contents) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。