WWDC Quick Look 💓 By SwiftGGTeam
Explore advances in RealityKit

Explore advances in RealityKit

观看原视频

Highlight

RealityKit 今年新增软阴影、投影纹理、物理空间光照、导航网格、布料模拟、LOD 切换、3D Gaussian Splat 渲染和自定义混响网格,让 visionOS 上的 3D 游戏和沉浸式应用从「能跑」走向「真实」。

核心内容

光照与阴影:让虚拟世界融入真实环境

以前在 visionOS 里做室内场景,角落总是黑漆漆的。间接光照需要开发者自己想办法,动态光源的阴影边缘像刀切一样生硬。

Apple 今年从三个方向解决了这个问题。

Lightmap(光照贴图)让静态场景的间接光照变得简单。在 Reality Composer Pro 3 里烘焙好间接光照、环境光遮蔽和 beauty 贴图,直接贴到场景上,角落立刻亮了起来。(02:24

但 Lightmap 只解决静态场景。动态光源怎么办?RealityKit 新增了软阴影(Soft Shadows)。真实世界里的光源都有面积,阴影边缘会出现半影区(penumbra)。通过设置 lightSizequality,聚光灯的阴影可以从硬边变成自然的渐变。(04:02

最惊艳的是物理空间光照(Physical Space Lighting)。以前虚拟光源只能照亮虚拟物体,现在虚拟的聚光灯和点光源可以直接照亮你真实的房间。配合投影纹理(Projective Textures),你可以做一个虚拟天文馆投影仪,把星空和星云直接打在卧室的墙壁和家具上。(05:16

导航网格:NPC 终于知道该怎么走了

做 3D 游戏最烦的一件事就是寻路。以前 visionOS 开发者要自己手搓 A* 算法,还要处理障碍物、地形成本、 ladders 和跳跃点。

RealityKit 今年原生支持了导航网格(Navigation Mesh)。你在 Reality Composer Pro 3 里画好可行走区域,给森林设置高 traversal cost,给桥梁添加 off-mesh connection,然后在代码里用 NavigationController.computePath 异步计算路径。(07:44

异步是关键。visionOS 对帧率极其敏感,寻路计算扔给后台线程,主线程专心渲染。(09:46

布料模拟:窗帘、披风、床单都能动了

RealityKit 新增了完整的布料模拟系统。把网格的顶点当作粒子,边当作弹簧,就能实时模拟布料的褶皱和流动。(11:01

更实用的是「锚点」功能。你可以把布料的某些顶点设为 kinematic,让它们跟随实体 transform 移动,但不受布料模拟影响。这样窗帘就能挂在门框上,披风能固定在角色肩膀。(12:51

性能优化:LOD 和热状态管理

软阴影、布料模拟、高斯泼溅都很吃性能。RealityKit 提供了两个工具来应对。

LOD(多细节层次)让远处的物体自动切换低模。RealityKit 支持按相机距离和屏幕面积两种策略切换。按屏幕面积更适合 visionOS,因为视野边缘的物体即使距离近,占据的像素也少。(14:42

热状态监听让 App 能主动降级。当设备发热到 .serious.critical 时,你可以提高 LOD 切换阈值、降低阴影质量,避免系统强制降频导致卡顿。(16:26

3D Gaussian Splat:把真实世界搬进虚拟空间

3D Gaussian Splat 是一种用 3D 高斯分布表示场景的技术,比传统摄影测量更轻量、细节更丰富。RealityKit 今年原生支持渲染 Gaussian Splat,你只需要提供位置、缩放、旋转、透明度和球谐函数缓冲区,就能在 visionOS 里展示高保真的真实世界扫描数据。(17:13

空间音频:自定义混响让声音更真实

空间音频的沉浸感取决于反射和混响的准确度。RealityKit 新增了自定义混响网格(Custom Reverb Mesh),你可以用 ReverbMeshResource 定义房间几何形状,并为每个表面指定吸收和散射系数。虚拟乐队在博物馆里演奏时,声音会在墙壁间真实反射后才到达用户耳朵。(19:08

详细内容

软阴影配置

04:02

guard var shadow = hearthSpotlight.components[SpotLightComponent.Shadow.self] else {
    // handle error
}
shadow.lightSize = 0.7 // meters
shadow.quality = .medium // or .high
// shadow.quality = .low // will result in hard shadows
hearthSpotlight.components.set(shadow)

关键点:

  • lightSize 表示光源直径(米),数值越大阴影边缘越柔和
  • quality 控制采样数量,.medium.high 才能产生软阴影,.low 会退化成硬阴影
  • .high 效果更好但性能开销更大,中远景物体用 .medium 性价比最高

投影纹理与物理空间光照

06:13

let spotLightEntity = Entity()
spotLightEntity.components.set(SpotLightComponent(
    color: .white,
    intensity: intensity,
    innerAngleInDegrees: innerAngle,
    outerAngleInDegrees: outerAngle,
    attenuationRadius: attenuationRadius,
))

let projectiveTexture: TextureResource = generateStarsAndNebulaeTexture()
spotLightEntity.components.set(SpotLightComponent.ProjectiveTexture(
    texture: projectiveTexture
))

07:13

spotLightEntity.components.set(SpotLightComponent.SurroundingsLight())

关键点:

  • SpotLightComponent.ProjectiveTexture 把纹理当作幻灯片投射出去,适合模拟窗户光影、海底焦散、星空投影
  • SurroundingsLight 让虚拟光源照亮真实环境,目前只支持 SpotLight 和 PointLight
  • 颜色设为白色,避免给投影纹理叠加额外色调

导航网格路径查询

09:46

extension Entity {
    public func navigate(/* ... */) async {
        let navigator = try! NavigationController(entity: self)
        guard let result = await navigator.computePath(from: fromPosition, to: toPosition)
        else {
            return
        }
        if result.isEmpty {
            return
        }
        for node in result {
            switch node.category {
                case .meshPoint:
                    finalPath.append(node.position)
                case .offMeshConnection:
                    // handle ladders
            }
        }
    }
}

关键点:

  • NavigationController 需要挂载了 NavigationComponent 的实体来初始化
  • computePath 是异步方法,不阻塞主线程
  • 返回的 path nodes 分两类:.meshPoint 是网格上的普通路径点,.offMeshConnection 是 ladders 或跳跃点等特殊连接
  • 返回 nil 表示找不到有效路径,空数组表示已到达目标

布料锚点固定

12:51

for (pin, pinComponent) in pins {
    let position = pin.position(relativeTo: event.entity)
    let selectionSphere = ClothSphereShape(radius: pinComponent.radius)

    let vertices = clothMesh.vertices(in: .sphere(selectionSphere),
                                center: position)
    clothBody.motionTypes.set(vertexIndices: vertices, value: .kinematic)
}

关键点:

  • ClothSphereShape 定义了一个球形区域来选取要固定的顶点
  • clothMesh.vertices(in:center:) 返回球形区域内的所有顶点索引
  • .kinematic 表示这些顶点只跟随实体 transform 移动,不受布料模拟影响
  • 适合实现窗帘挂钩、披风肩带等效果

LOD 按相机距离切换

14:42

let lod0 = [ModelEntity(mesh: lodMesh0)]
let lod1 = [ModelEntity(mesh: lodMesh1)]
let lod2 = [ModelEntity(mesh: lodMesh2)]

let entity = Entity()

LevelOfDetailComponent.addByCameraDistance(to: entity, levels: [
    (entities: lod0, maxDistance: 1.0 /* meters */), // highest detail
    (entities: lod1, maxDistance: 5.0),              // medium detail
    (entities: lod2, maxDistance: .infinity),        // lowest detail
])

关键点:

  • lod0 是最高精度,在 1 米内使用
  • 超过 1 米切换到 lod1,超过 5 米切换到 lod2
  • 最后一个 LOD 的 maxDistance 设为 .infinity,确保远距离始终有模型可渲染

LOD 按屏幕面积切换

15:58

let lod0 = [ModelEntity(mesh: lodMesh0)]
let lod1 = [ModelEntity(mesh: lodMesh1)]
let lod2 = [ModelEntity(mesh: lodMesh2)]

let entity = Entity()

LevelOfDetailComponent.addByScreenArea(to: entity, levels: [
    (entities: lod0, minArea: 0.2 /* fraction of screen area */),  // highest detail
    (entities: lod1, minArea: 0.1),                                // medium detail
    (entities: lod2, minArea: 0.01),                               // lowest detail
])

关键点:

  • minArea 是占屏幕总面积的比例
  • 物体占据屏幕 20% 以上用最高精度,10%-20% 用中等精度,1%-10% 用低精度
  • 按屏幕面积比按距离更准确,尤其适合视野边缘的物体

热状态监听

16:26

NotificationCenter.default.addObserver(of: ProcessInfo.self,
                                       for: .thermalStateDidChange) { _ in
    switch ProcessInfo.processInfo.thermalState {
        case .nominal, .fair:
            // Stay the course
        case .serious, .critical:
            // Improve performance by:
            // More aggressive LOD switching
            // Lower shadow quality
    }
}

关键点:

  • 监听 .thermalStateDidChange 通知,在设备发热时主动降级
  • .nominal.fair 表示温度正常,可以保持当前画质
  • .serious.critical 需要提高 LOD 阈值、降低阴影质量或减少布料模拟精度

3D Gaussian Splat 渲染

18:44

let resource = try GaussianSplatResource.BufferResource(
    count: splatCount,
    position: positionBuffer,
    scale: scaleBuffer,
    rotation: rotationBuffer,
    opacity: opacityBuffer,
    sphericalHarmonics: (sphericalHarmonicsBuffer, degree)
)

let splatResource = GaussianSplatResource(resource)
let splatComponent = GaussianSplatComponent(splatResource)
splatEntity.components.set(splatComponent)

关键点:

  • BufferResource 接收位置、缩放、旋转、透明度和球谐函数五个缓冲区
  • degree 控制颜色随视角变化的程度,0 表示纯色,数值越高颜色变化越丰富
  • RealityKit 不绑定特定文件格式,开发者需要自己解析 .ply 或其他格式并填充缓冲区
  • 渲染优化由 RealityKit 内部处理,开发者不需要手动排序或混合

自定义混响网格

20:49

let mesh: ReverbMeshResource = .shoebox(size: [5, 4, 6])
let reverb: Reverb = .simulated(mesh: mesh, materials: [.dryWall])
entity.components.set(ReverbComponent(reverb: reverb))

21:33

let thickCarpet: Audio.Material = .carpet.scalingAbsorption { freq in 0.1 }

let bookshelfAbsorption = Audio.Absorption(
    [0.10, 0.15, 0.28, 0.20, 0.15, 0.10, 0.10, 0.07, 0.07, 0.05])

let bookshelfScattering = Audio.Scattering([500: 0.5, 1000: 0.6, 4000: 0.7])

let bookshelf = Audio.Material(
    absorption: bookshelfAbsorption,
    scattering: bookshelfScattering
)

关键点:

  • .shoebox(size:) 是最简单的入门方式,创建一个面朝内的盒子
  • 内置预设材质包括 .dryWall.carpet
  • 自定义材质需要定义吸收系数(10 个频段,从 31.5Hz 到 16kHz)和散射系数(500Hz、1kHz、4kHz)
  • 只支持 Immersive Space,Shared Space 会自动使用系统根据真实房间构建的混响

核心启发

1. 做一个「虚实融合」的室内设计师 App

把虚拟家具放进用户真实的房间,然后用 SurroundingsLight 让虚拟台灯照亮真实的墙壁和地板。用户转动台灯,光影在真实墙面上实时变化。入口 API 是 SpotLightComponent.SurroundingsLight()

2. 做一个 visionOS 上的密室逃脱游戏

NavigationMeshResource 在 Reality Composer Pro 3 里画好房间的可行走区域,给狭窄通道设置高 traversal cost,让玩家角色自动绕过障碍物。NPC 用 NavigationController.computePath 异步追踪玩家。配合投影纹理做密码线索的墙面投影。

3. 做一个沉浸式博物馆导览

用 3D Gaussian Splat 扫描博物馆的真实展品,用户在 visionOS 里可以绕着高保真扫描件自由观看。每个展品旁边放一个虚拟解说牌,用自定义混响网格让解说声音在展厅里真实反射。入口是 GaussianSplatComponentReverbMeshResource

4. 做一个有物理反馈的魔法 RPG

角色披风用 ClothBodyComponent 模拟,战斗时披风随风飘动。施法时的光效用 SpotLightComponent.ProjectiveTexture 投射符文图案到地面和墙壁上。热状态监听确保长时间游戏不卡顿。

5. 做一个虚拟天文馆

把星空投影到用户真实的卧室天花板上,用 SurroundingsLight 让虚拟投影仪照亮真实环境。用户转动头部,星空图案跟随视角变化。配合 SpotLightComponentinnerAngleInDegreesouterAngleInDegrees 调整投影范围。

关联 Session

评论

GitHub Issues · utterances