WWDC Quick Look 💓 By SwiftGGTeam
Enhance your spatial computing app with RealityKit

Enhance your spatial computing app with RealityKit

观看原视频

Highlight

RealityKit 在 visionOS 上新增了 Attachments、VideoPlayerComponent、Portals、Particle Emitters 和 Anchors 五大特性,让开发者可以把 SwiftUI 视图嵌入 3D 场景、在空间中播放视频、创建通往异世界的传送门、添加粒子特效,以及把内容锚定到真实世界的墙面和地板。

核心内容

Attachments:把 SwiftUI 视图放进 3D 场景

以前想在 3D 模型旁边加文字标签,需要创建纹理贴图或复杂的 3D 文本。Attachments 让这个过程变得像写 SwiftUI 一样简单(02:08)。

在 RealityView 的 attachments view builder 中定义 SwiftUI 视图,在 make closure 中通过 entity(for:) 获取对应的实体,然后像普通实体一样添加到场景中。文字标签、说明卡片、交互按钮都可以这样做。

VideoPlayerComponent:在 3D 空间中播放视频

VideoPlayerComponent 是 RealityKit 的新组件,用于在 3D 场景中嵌入视频(06:27)。

它自动根据视频宽高比生成矩形 mesh,支持所有 AVFoundation 格式,包括 2D 视频和 3D MV-HEVC 视频。视频默认高度 1 米,通过缩放实体调整大小。还支持透视着色(passthrough tinting),让环境光线匹配视频色调。

Portals:通往另一个世界的窗户

Portal 在空间中打开一扇”窗户”,透过它可以看到另一个独立的世界(11:15)。

这个世界有自己的光照、内容和空间关系。Portal 用 PortalMaterial 作为表面材质,配合 PortalComponent 链接到目标世界。适合创建魔法窗户、门廊、展示空间等效果。

Particle Emitters:粒子特效

ParticleEmitterComponent 可以在场景中创建火花、雪花、冲击波等视觉效果(15:09)。

可以在 Reality Composer Pro 中设计粒子效果,然后在运行时通过 RealityKit 动态调整属性。配合自定义 System,可以让粒子效果响应场景变化。

Anchors:把内容固定在现实世界

AnchorEntity 可以把 3D 内容锚定到墙面、地板或相对于头部/手部的位置(17:14)。

支持两种追踪模式:.continuous 持续跟随锚点移动,.once 定位后不再移动。锚定状态变化时可以通过 AnchoredStateChanged 事件监听。

详细内容

Attachments 代码示例

02:30

import SwiftUI
import RealityKit

struct MoonOrbit: View {
    var body: some View {
        RealityView { content, attachments in
            guard let earth = try? await Entity(named: "Earth") else {
                return
            }
            content.add(earth)

            if let earthAttachment = attachments.entity(for: "earth_label") {
                earthAttachment.position = [0, -0.15, 0]
                earth.addChild(earthAttachment)
            }
        } attachments: {
            Attachment(id: "earth_label") {
                Text("Earth")
            }
        }
    }
}

关键点

  • RealityView 的 make closure 新增 attachments 参数
  • attachments view builder 中定义要嵌入的 SwiftUI 视图
  • 每个 Attachment 需要唯一的 id,可以是任意 Hashable 值
  • attachments.entity(for:) 通过 id 获取对应的实体
  • 获取到的实体可以设置位置、作为子实体添加到其他实体上
  • 可以用 glassBackgroundEffect() 为 Attachment 添加毛玻璃背景

VideoPlayerComponent 代码示例

08:03

public func makeVideoEntity() -> Entity {
    let entity = Entity()

    let asset = AVURLAsset(url: Bundle.main.url(forResource: "tides_video",
                                                withExtension: "mp4")!)
    let playerItem = AVPlayerItem(asset: asset)

    let player = AVPlayer()
    entity.components[VideoPlayerComponent.self] = .init(avPlayer: player)

    entity.scale *= 0.4

    player.replaceCurrentItem(with: playerItem)
    player.play()

    return entity
}

关键点

  • VideoPlayerComponent 需要关联一个 AVPlayer 实例
  • 组件自动根据视频宽高比生成矩形 mesh
  • 默认高度 1 米,这里缩放到 0.4 米(40 厘米)
  • 支持所有 AVFoundation 格式,包括 MV-HEVC 3D 视频
  • 自动显示 AVPlayer 提供的字幕

透视着色

10:05

var videoPlayerComponent = VideoPlayerComponent(avPlayer: player)
videoPlayerComponent.isPassthroughTintingEnabled = true

entity.components[VideoPlayerComponent.self] = videoPlayerComponent

关键点

  • isPassthroughTintingEnabled 让透视内容匹配视频色调
  • 类似 TV app 中看电影时的环境光效果
  • 增强沉浸感,让虚拟内容和真实环境更协调

Portal 代码示例

13:12

struct PortalView : View {
    var body: some View {
        RealityView { content in
            let world = makeWorld()
            let portal = makePortal(world: world)

            content.add(world)
            content.add(portal)
        }
    }
}

public func makeWorld() -> Entity {
    let world = Entity()
    world.components[WorldComponent.self] = .init()

    let environment = try! EnvironmentResource.load(named: "SolarSystem")
    world.components[ImageBasedLightComponent.self] = .init(source: .single(environment),
                                                            intensityExponent: 6)
    world.components[ImageBasedLightReceiverComponent.self] = .init(imageBasedLight: world)

    let earth = try! Entity.load(named: "Earth")
    let moon = try! Entity.load(named: "Moon")
    let sky = try! Entity.load(named: "OuterSpace")
    world.addChild(earth)
    world.addChild(moon)
    world.addChild(sky)

    return world
}

public func makePortal(world: Entity) -> Entity {
    let portal = Entity()

    portal.components[ModelComponent.self] = .init(
        mesh: .generatePlane(width: 1, height: 1, cornerRadius: 0.5),
        materials: [PortalMaterial()]
    )
    portal.components[PortalComponent.self] = .init(target: world)

    return portal
}

关键点

  • WorldComponent 标记实体树属于另一个独立世界
  • 世界中的内容只通过 Portal 表面可见
  • ImageBasedLightComponent 为世界设置基于图像的光照
  • PortalMaterial() 让 mesh 成为 Portal 表面
  • PortalComponent(target: world) 把 Portal 链接到目标世界
  • 世界和 Portal 都要添加到 RealityView 的 content 中

粒子效果 System

15:50

public class ParticleTransitionSystem: System {
    private static let query = EntityQuery(where: .has(ParticleEmitterComponent.self))

    public func update(context: SceneUpdateContext) {
        let entities = context.scene.performQuery(Self.query)
        for entity in entities {
            updateParticles(entity: entity)
        }
    }
}

public func updateParticles(entity: Entity) {
    guard var particle = entity.components[ParticleEmitterComponent.self] else {
        return
    }

    let scale = max(entity.scale(relativeTo: nil).x, 0.3)

    let vortexStrength: Float = 2.0
    let lifeSpan: Float = 1.0
    particle.mainEmitter.vortexStrength = scale * vortexStrength
    particle.mainEmitter.lifeSpan = Double(scale * lifeSpan)

    entity.components[ParticleEmitterComponent.self] = particle
}

关键点

  • System 查询所有带有 ParticleEmitterComponent 的实体
  • 根据实体缩放动态调整粒子属性
  • vortexStrength 控制粒子旋转强度
  • lifeSpan 控制粒子存活时间
  • 修改后必须把 Component 重新赋值回实体
  • 粒子资源可以在 Reality Composer Pro 中设计

锚点到墙面

18:19

import SwiftUI
import RealityKit

struct PortalApp: App {

    @State private var immersionStyle: ImmersionStyle = .mixed

    var body: some SwiftUI.Scene {
        ImmersiveSpace {
            RealityView { content in
                let anchor = AnchorEntity(.plane(.vertical, classification: .wall,
                                                 minimumBounds: [1, 1]))
                content.add(anchor)

                anchor.addChild(makePortal())
            }
        }
        .immersionStyle(selection: $immersionStyle, in: .mixed)
    }
}

关键点

  • 锚点需要在 ImmersiveSpace 中使用
  • .plane(.vertical, classification: .wall) 寻找垂直墙面
  • minimumBounds: [1, 1] 要求墙面至少 1x1 米
  • 找到匹配的锚点后,RealityKit 自动把内容附加到墙面
  • .mixed immersion style 保留透视内容,同时显示虚拟内容
  • 锚点变换对 App 不可见,保护用户隐私

核心启发

  • 做什么:做一个魔法窗户应用,透过墙上的 Portal 看到世界各地的风景。

  • 为什么值得做:Portal 让用户在真实房间里打开通往任意世界的窗户,配合空间音频营造身临其境的感觉。

  • 怎么开始:创建 WorldComponent 定义世界内容,用 AnchorEntity(.plane(.vertical)) 把 Portal 锚定到墙面。

  • 做什么:开发一个教育应用,在 3D 模型旁边用 Attachment 显示知识点标签。

  • 为什么值得做:Attachments 让 SwiftUI 视图和 3D 内容无缝结合,文字说明可以随模型一起移动。

  • 怎么开始:在 RealityView 的 attachments 中定义 Text 视图,用 entity(for:) 获取后作为子实体添加到模型上。

  • 做什么:做一个沉浸式影院应用,在空间中播放 3D 电影。

  • 为什么值得做:VideoPlayerComponent 支持 MV-HEVC 3D 视频,passthrough tinting 让环境光匹配电影色调。

  • 怎么开始:用 VideoPlayerComponent(avPlayer: player) 创建视频实体,设置 isPassthroughTintingEnabled = true

  • 做什么:开发一个节日装饰应用,让用户在房间里放置粒子特效(雪花、烟花)。

  • 为什么值得做:ParticleEmitterComponent 配合锚点可以把特效固定在真实空间位置。

  • 怎么开始:在 Reality Composer Pro 中设计粒子效果,用 AnchorEntity 锚定到天花板或墙面,通过 System 动态调整粒子属性。

关联 Session

评论

GitHub Issues · utterances