Highlight
Object Capture 团队的 Hao Tang 和 Risa 介绍了 Object Capture 技术的最新进展和最佳实践。Object Capture 是 Apple 的摄影测量 API,可以将真实物体的照片转换为详细的 3D 模型。
核心内容
很多 AR 体验的难点不在 AR 本身,而在素材。一个真实木制棋子要进入游戏,过去通常要 3D 建模师和材质专家参与。形状、纹理、比例都要手工处理,成本很高。
Object Capture 把这个流程压缩成两步:先从多个角度拍摄真实物体,再把照片交给 macOS 上的 Photogrammetry API 生成 USDZ 模型。RealityKit 会输出几何网格和材质贴图,模型可以直接进入 AR 或游戏项目。
这场 session 用一个 oversized wooden chess pieces 的例子串起完整流程。先拍摄棋子,处理成 USDZ;再把模型导入 Reality Converter 调整纹理;最后放进 Xcode 的 RealityKit 项目,做成可以点击、移动、发光、被吃掉的 AR 棋盘。
Apple 在 WWDC 2022 还补上了一个关键环节:ARKit 可以在 ARSession 运行中拍摄原生相机分辨率的照片。开发者可以继续显示 3D 引导 UI,同时拿到更适合 Object Capture 的高分辨率图像。
详细内容
在 ARSession 中拍摄高分辨率照片
(05:15)Object Capture 的输入质量会直接影响 3D 模型质量。Hao 明确说,图片分辨率越高,Object Capture 能生成的模型质量越好。
(06:20)ARKit 6 提供了 high-resolution background photos API。它允许 App 在 ARSession 继续运行时,按原生相机分辨率拍摄照片。iPhone 13 的 Wide camera 对应 1200 万像素。
if let hiResCaptureVideoFormat = ARWorldTrackingConfiguration.recommendedVideoFormatForHighResolutionFrameCapturing {
// Assign the video format that supports hi-res capturing.
config.videoFormat = hiResCaptureVideoFormat
}
// Run the session.
session.run(config)
session.captureHighResolutionFrame { frame, error in
if let frame = frame {
// save frame.capturedImage
// …
}
}
关键点:
recommendedVideoFormatForHighResolutionFrameCapturing查询当前设备是否有支持高分辨率照片捕捉的视频格式。config.videoFormat = hiResCaptureVideoFormat把 ARWorldTrackingConfiguration 切换到该格式。session.run(config)用新的配置启动 ARSession。captureHighResolutionFrame异步返回高分辨率 ARFrame。frame.capturedImage是后续保存并送入 Object Capture 流程的图像数据。
这个 API 的价值在于拍摄流程不中断。用户仍然看到 AR 覆盖层,App 可以用 3D 引导 UI 提醒用户哪些角度还没拍到。
选择适合 Object Capture 的拍摄对象
(07:44)Object Capture 适合表面有足够纹理的物体。透明、反光、缺少纹理的区域,重建细节会变差。
拍摄环境也很重要。Hao 建议使用均匀、漫射的光,保持背景稳定,并给物体周围留出足够空间。如果房间较暗,可以使用照明良好的转台。
(09:24)示例中的 pirate ship 放在干净桌面中央。拍摄者缓慢绕物体移动,从不同高度拍摄,让物体始终占据画面中心,并保持相邻照片之间有高重叠度。
(10:15)这次示例先拍约 80 张照片,再把船翻到侧面拍约 20 张,用来重建底部。Hao 也提醒,只有物体翻转后仍保持刚性,才适合这样处理。
用 PhotogrammetrySession 生成 USDZ 模型
(11:00)拍摄完成后,照片会被复制到 Mac,并由 Object Capture API 处理。输出细节级别有 reduced、medium、full、raw 四种。
reduced 和 medium 面向 Web、移动端和 AR Quick Look,三角面和材质通道更少,内存占用更低。full 和 raw 面向游戏、后期制作等高端交互场景,几何细节更高,也需要更多内存。
(14:17)在 AR 棋盘示例中,Risa 使用 2021 年引入的 PhotogrammetrySession 处理 rook 的照片,并选择 reduced detail level。原因很直接:AR 游戏需要稳定运行,模型不能过重。
这个 session 没有给出 PhotogrammetrySession 的代码片段。官方资源中给出了配套入口:Creating a photogrammetry command-line app 和 Using object capture assets in RealityKit,适合作为实现起点。
把 Object Capture 模型变成 RealityKit 游戏对象
(15:26)棋子模型导入 Xcode 后,Risa 用 RealityKit 创建棋盘。棋盘格来自缩放后的 primitive cubes,颜色在黑白之间交替。
(17:00)启动动画分两步:先把棋盘格沿 y 轴上移 10 厘米,再用 move(to:relativeTo:duration:) 回到原来的 transform。边框 USDZ 自带动画,直接调用 playAnimation。
// Board Animation
class Chessboard: Entity {
func playAnimation() {
checkers
.forEach { entity in
let currentTransform = entity.transform
// Move checker square 10cm up
entity.transform.translation += SIMD3<Float>(0, 0.1, 0)
entity.move(to: currentTransform,
relativeTo: entity.parent,
duration: BoardGame.startupAnimationDuration)
}
// Play built-in animation for board border
border.availableAnimations.forEach {
border.playAnimation($0)
}
}
}
关键点:
checkers.forEach遍历每一个棋盘格实体。currentTransform保存最终落点。translation += SIMD3<Float>(0, 0.1, 0)先把格子抬高 10 厘米。move(to:relativeTo:duration:)让实体在指定时间内回到保存的 transform。border.availableAnimations读取 USDZ 中已有的动画。border.playAnimation($0)播放边框模型内置动画。
这个片段没有改变模型资产本身,只在 RealityKit 场景中操作 Entity 的 transform。
用 raycast 选择真实棋子模型
(18:00)棋盘要能互动,第一步是选中棋子。Risa 已经把 UITapGestureRecognizer 加到 ARView。用户点击屏幕后,App 从相机原点向点击位置发出一条 ray,再对 3D 场景做 raycast。
// Select chess piece
class ChessViewport: ARView {
@objc
func handleTap(sender: UITapGestureRecognizer) {
guard let ray = ray(through: sender.location(in: self)) else { return }
// No piece is selected yet, we want to select one
guard let raycastResult = scene.raycast(origin: ray.origin,
direction: ray.direction,
length: 5,
query: .nearest,
mask: .piece).first,
let piece = raycastResult.entity.parentChessPiece else {
return
}
boardGame.select(piece)
gameManager.selectedPiece = piece
}
}
关键点:
sender.location(in: self)取得用户在 ARView 中点击的 2D 坐标。ray(through:)把 2D 点击点转换成从相机出发的 3D ray。scene.raycast在 3D 场景中查找命中的实体。length: 5限制 raycast 的距离。query: .nearest只取最近的命中结果。mask: .piece只检测棋子的 collision group。parentChessPiece从命中的子实体回到棋子对象。boardGame.select(piece)和gameManager.selectedPiece更新游戏状态。
Risa 特别提醒,raycast 会忽略没有 CollisionComponent 的实体。导入 Object Capture 模型后,如果想点击它,碰撞组件不能漏掉。
用 CustomMaterial 在 CPU 和 GPU 之间传递动画进度
(20:38)吃子动画使用 RealityKit custom materials 的 geometry modifiers。它们在 RealityKit 的 vertex shader 中逐顶点调用,可以改变位置、法线、纹理坐标等顶点数据。
(21:16)capturedProgress 表示棋子被吃掉的动画进度,范围是 0 到 1。CPU 侧把这个值写进 CustomMaterial.custom.value,Metal 侧再通过 uniforms 的 custom parameter 读取。
// Capture Geometry Modifier
class ChessPiece: Entity, HasChessPiece {
var capturedProgress: Float {
get {
(pieceEntity?.model?.materials.first as? CustomMaterial)?.custom.value[0] ?? 0
}
set {
pieceEntity?.modifyMaterials { material in
guard var customMaterial = material as? CustomMaterial else {
return material
}
customMaterial.custom.value = SIMD4<Float>(newValue, 0, 0, 0)
return customMaterial
}
}
}
}
关键点:
capturedProgress是棋子实体上的状态属性。- getter 从第一个材质读取
CustomMaterial.custom.value[0]。 - 读取失败时返回
0,表示动画还没开始。 modifyMaterials遍历并替换实体材质。guard var customMaterial = material as? CustomMaterial只处理自定义材质。SIMD4<Float>(newValue, 0, 0, 0)把动画进度放进 custom value 的第一个通道。- 返回修改后的
customMaterial,RealityKit 会把新值送给着色器。
(21:50)Metal 侧根据这个进度缩放 y 方向,并在 x 方向加入波浪偏移。Risa 提到这些修改是 transient 的,不会改变实体原始顶点数据。
用 surface shader 和 bloom 标出可移动位置
(22:30)为了帮助新手看懂棋子可以移动到哪里,示例给潜在落点的棋盘格加了脉冲效果。每个棋盘格都是独立 Entity,都有自己的 Model Component,因此可以单独套用 surface shader。
(23:00)CPU 侧传入布尔值,Metal surface shader 读取 custom parameter。如果当前格子是可能落点,就修改 emissive color。
// Checker animation to show potential moves
void checkerSurface(realitykit::surface_parameters params,
float amplitude,
bool isBlack = false)
{
// ...
bool isPossibleMove = params.uniforms().custom_parameter()[0];
if (isPossibleMove) {
const float a = amplitude * sin(params.uniforms().time() * M_PI_F) + amplitude;
params.surface().set_emissive_color(half3(a));
if (isBlack) {
params.surface().set_base_color(half3(a));
}
}
}
关键点:
checkerSurface是 RealityKit surface shader 使用的 Metal 函数。params.uniforms().custom_parameter()[0]读取 CPU 侧传来的自定义值。isPossibleMove决定当前棋盘格是否需要高亮。sin(params.uniforms().time() * M_PI_F)用时间生成脉冲变化。set_emissive_color(half3(a))改变自发光颜色。- 黑色棋盘格额外调用
set_base_color,让高亮更明显。
(23:20)为了让发光效果更明显,示例又在 ARView 上加了 bloom 后处理。实现入口是 renderCallbacks.postProcess,具体图像处理使用 Metal Performance Shaders。
import MetalPerformanceShaders
class ChessViewport: ARView {
init(gameManager: GameManager) {
/// ...
renderCallbacks.postProcess = postEffectBloom
}
func postEffectBloom(context: ARView.PostProcessContext) {
let brightness = MPSImageThresholdToZero(device: context.device,
thresholdValue: 0.85,
linearGrayColorTransform: nil)
brightness.encode(commandBuffer: context.commandBuffer,
sourceTexture: context.sourceColorTexture,
destinationTexture: bloomTexture!)
/// ...
}
}
关键点:
import MetalPerformanceShaders引入 Metal Performance Shaders。renderCallbacks.postProcess = postEffectBloom把自定义后处理接到 ARView 渲染流程。ARView.PostProcessContext提供后处理所需的 device、commandBuffer 和纹理。MPSImageThresholdToZero用阈值筛出较亮区域。thresholdValue: 0.85表示低于阈值的颜色会被压掉。brightness.encode把处理命令编码到当前 command buffer。sourceColorTexture是 ARView 渲染出的颜色纹理。bloomTexture接收亮度筛选后的中间结果。
核心启发
-
做一个商品 AR 预览工具:用 Object Capture 生成商品 USDZ,再用 RealityKit 放进用户空间。适合鞋、植物、摆件等有纹理、低反光的物体。起点是拍摄规范、PhotogrammetrySession 和 RealityKit 模型加载。
-
做一个带拍摄引导的 3D 扫描 App:在 ARSession 中显示覆盖层,提示用户从不同高度和角度拍摄。使用
captureHighResolutionFrame保存原生分辨率照片,再把照片传到 Mac 端处理。 -
做一个真实玩具变 AR 角色的游戏原型:先用 Object Capture 扫描玩具,再在 RealityKit 中给模型添加碰撞组件、点击选择和移动动画。示例中的
scene.raycast和move(to:relativeTo:duration:)可以直接作为交互入口。 -
做一套 RealityKit 自定义材质效果库:把选中发光、吃子压扁、潜在位置脉冲这类效果抽成可复用组件。CPU 侧通过
CustomMaterial.custom.value传递状态,GPU 侧用 surface shader 或 geometry modifier 表现动画。 -
做一个 AR 教学棋盘:根据规则计算可移动格子,把结果写入棋盘格材质的 custom parameter,再用 emissive color 和 bloom 高亮。这个方向适合象棋、国际象棋、桌游规则教学。
关联 Session
- Discover ARKit 6 — 本 session 的高分辨率照片捕捉能力来自 ARKit 6,适合继续了解相机和 ARSession 更新。
- Create parametric 3D room scans with RoomPlan — 同样围绕 AR 扫描,只是目标从单个物体扩展到房间结构。
- Boost performance with MetalFX Upscaling — 如果 AR 游戏加入更多后处理和高质量渲染,需要关注 MetalFX 的性能收益。
- Discover Metal 3 — 本 session 的 custom material、shader 和后处理都和 Metal 图形能力相邻。
评论
GitHub Issues · utterances