Collaborate on structured 3D models in visionOS
visionOS & Spatial Computing 高级 25m

在 visionOS 中协作处理结构化 3D 模型

Collaborate on structured 3D models in visionOS

2026年6月10日

在 Apple 官方观看视频

一句话判断

这场 Session 最值得关注的一件事是:RealityKit 新增的 ClippingComponent(剖切组件)和 ManipulationComponent(操控组件)的层级传递模式,直接把工业级 3D 模型的“拆解、剖切、爆炸图”交互从“手搓数学题”降维成了“搭积木”。

这场 Session 讲了什么

以前在 visionOS 里做复杂 3D 模型(比如发动机、建筑 BIM)的交互简直是受罪。你想让用户拆开外壳看里面的零件,得自己写射线检测、算碰撞体、处理手势冲突;想切个剖面,得自己写 Shader 或者用 CSG(构造实体几何)去减模型。

现在 Apple 把这套工业软件标配的交互直接做进了 RealityKit。通过把 ManipulationComponent 在 Entity(实体)树里上下移动,就能无缝切换“整体拖拽”和“零件拆解”;全新的 ClippingComponent 让你加个包围盒就能切掉模型的一半;甚至还给了基于体积加权方差的自动爆炸图算法。

这对做 CAD 审查、医疗设备展示、建筑设计的开发者来说是巨大的利好。你终于不用把时间花在算四元数和矩阵逆上了,可以专心搞业务逻辑。

值得深挖的点

ClippingComponent 的三状态机与坐标系地狱

Apple 这次给的剖切功能不是简单的“一刀切”,而是一个完整的三状态机(.off, .on, .editing)。在 .editing 状态下,包围盒的六个面会变成可拖拽的平面。

这里最大的坑,也是 Apple 花大篇幅讲的,是坐标系转换。ClippingComponent 的裁剪是在 Model(模型)局部坐标系下进行的,但用户的拖拽手势(Drag Gesture)是在 Clipping Plane(剖切面)坐标系下触发的。你要把拖拽的 delta 向量从平面坐标系转到世界坐标系,再转到模型坐标系,然后约束到特定的法线方向,最后更新包围盒的 bounds(边界)。

Apple 没有把这步数学运算完全黑盒化,而是给出了清晰的转换链路。这其实是个 Trade-off:如果完全黑盒,开发者就没法自定义剖切面的视觉反馈和交互阻尼;暴露出来虽然代码看着头晕,但灵活性拉满了。只要你搞懂了 convert(_:from:to:) 这几个 API,这套剖切逻辑就能复用到任何奇怪的模型上。

组件位置决定行为:优雅的拆解模式

ManipulationComponent 的设计哲学非常 Apple:不增加新 API,而是改变组件在树中的位置。

当你想整体移动一个 AirPods 模型时,把 ManipulationComponentInputTargetComponent 挂在根 Entity 上。当用户点击“拆解”时,把这两个组件从根节点删掉,遍历子节点给每个零件挂上。这时候每个零件就独立了,而且因为设置了 releaseBehavior = .stay,松手后零件会停在半空。

想组装回去?反向操作,把子节点的组件删掉,重新挂回根节点。没有复杂的动画状态机,没有额外的标志位,Entity-Component-System (ECS) 架构的优势在这里体现得淋漓尽致。这种“改结构即改行为”的思路,比写一堆 if isExploded 的判断条件优雅太多了。

代码片段

1. 模型的拆解与组装

场景:通过转移 ManipulationComponent 实现整体与零件交互的切换。

// 拆解模型:让每个子零件都可以被单独抓取和移动
func openAssembly() {
    // 移除根节点的交互和操控组件
    components[ManipulationComponent.self] = nil
    components[InputTargetComponent.self] = nil

    for child in assemblyChildren {
        // 给子节点添加输入目标(前提是你已经加好了 CollisionComponent)
        child.components.set(InputTargetComponent())

        var manipulation = ManipulationComponent()
        // 松手后零件保持在当前位置,不会弹回原位
        manipulation.releaseBehavior = .stay
        child.manipulationComponent = manipulation
    }
}

// 组装模型:恢复整体操控
func closeAssembly() {
    for child in assemblyChildren {
        child.manipulationComponent = nil
        child.components[InputTargetComponent.self] = nil
    }

    components.set(InputTargetComponent())
    var manipulation = ManipulationComponent()
    manipulation.releaseBehavior = .stay
    manipulationComponent = manipulation
}

坑:千万别忘了给子节点加 CollisionComponent(碰撞组件),没有碰撞体 InputTargetComponent 根本接收不到手势事件,这是新手最容易卡住的地方。

2. 配置剖切组件

场景:为模型添加基础的 AABB(轴对齐包围盒)剖切能力。

// 开启剖切模式,切掉模型 bounding box 之外的部分
func enableClipping(for entity: Entity, bounds: BoundingBox) {
    var clipping = ClippingComponent()
    // 设置剖切的边界框,必须是实体局部坐标系下的 AABB
    clipping.bounds = bounds 
    // 确保子节点也被剖切,否则外壳切了里面零件还在
    clipping.shouldClipChildren = true 
    // 裁剪实体自身的几何体
    clipping.shouldClipSelf = true 
    
    entity.components.set(clipping)
}

坑:bounds 必须是局部坐标系(Local Space)下的值。如果你的模型在导出时带了奇怪的 Transform,算出来的包围盒会完全错位,导致切不到东西或者切掉太多。

最佳实践

对于已有项目,如果你之前是用 Shader 修改透明度来模拟剖切,或者用物理引擎做零件拆解,建议直接重构替换为 ClippingComponentManipulationComponent。性能提升是肉眼可见的,因为裁剪是在渲染管线底层做的,而不是在 CPU 算碰撞。

新项目在准备 USDZ 资产时,必须逼着美术或建模师保留深层嵌套的层级结构(Deep, nested hierarchy)。不要接受所有网格都平铺在根节点下的“死”模型。没有合理的 Entity 树,代码里根本没法通过名字或层级去隔离特定的活塞或主板。

实战中最容易踩的坑是自动展开(Autoexpansion)时的轴选择。Session 里提到了基于“体积加权方差”(Volume-weighted variance)来计算模型最适合展开的轴向。不要自己拍脑袋硬编码沿 Z 轴展开,遇到扁平的模型(比如键盘)会直接穿模。老老实实算方差,或者在 USDZ 里预埋展开锚点。

还有什么值得关注

  • 自动展开算法:Apple 给出了计算爆炸图展开轴向的数学公式(体积加权方差),这比盲目沿中心点向外辐射的效果好得多,能避免零件互相穿插。
  • SharePlay 深度整合:这种多人协同看 3D 模型的场景,强依赖 WWDC25 提到的 SharePlay 空间同步机制,一个人剖切,所有人实时看到横截面。
  • 三状态机的缓存设计:在 .off 状态下使用自定义的 ClippingBoundsCache 记住上次编辑的剖切位置,避免每次重新开启剖切时包围盒重置,这种细节非常提升体验。
RealityKit ClippingComponent ManipulationComponent visionOS USDZ