Highlight
RealityKit 今年新增软阴影、投影纹理、物理空间光照、导航网格、布料模拟、LOD 切换、3D Gaussian Splat 渲染和自定义混响网格,让 visionOS 上的 3D 游戏和沉浸式应用从「能跑」走向「真实」。
核心内容
光照与阴影:让虚拟世界融入真实环境
以前在 visionOS 里做室内场景,角落总是黑漆漆的。间接光照需要开发者自己想办法,动态光源的阴影边缘像刀切一样生硬。
Apple 今年从三个方向解决了这个问题。
Lightmap(光照贴图)让静态场景的间接光照变得简单。在 Reality Composer Pro 3 里烘焙好间接光照、环境光遮蔽和 beauty 贴图,直接贴到场景上,角落立刻亮了起来。(02:24)
但 Lightmap 只解决静态场景。动态光源怎么办?RealityKit 新增了软阴影(Soft Shadows)。真实世界里的光源都有面积,阴影边缘会出现半影区(penumbra)。通过设置 lightSize 和 quality,聚光灯的阴影可以从硬边变成自然的渐变。(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 里可以绕着高保真扫描件自由观看。每个展品旁边放一个虚拟解说牌,用自定义混响网格让解说声音在展厅里真实反射。入口是 GaussianSplatComponent 和 ReverbMeshResource。
4. 做一个有物理反馈的魔法 RPG
角色披风用 ClothBodyComponent 模拟,战斗时披风随风飘动。施法时的光效用 SpotLightComponent.ProjectiveTexture 投射符文图案到地面和墙壁上。热状态监听确保长时间游戏不卡顿。
5. 做一个虚拟天文馆
把星空投影到用户真实的卧室天花板上,用 SurroundingsLight 让虚拟投影仪照亮真实环境。用户转动头部,星空图案跟随视角变化。配合 SpotLightComponent 的 innerAngleInDegrees 和 outerAngleInDegrees 调整投影范围。
关联 Session
- Iterate your spatial scenes faster with Reality Composer Pro 3 — Reality Composer Pro 3 的新场景编辑工具和粒子/角色行为图接口
- Supercharge your spatial workflows with Reality Composer Pro 3 — 在 Reality Composer Pro 3 中构建导航网格和光照烘焙
- Explore immersive environments in visionOS — Immersive Space 的设计模式和最佳实践,配合物理空间光照使用
- Render 3D models with RealityKit — 3D 模型渲染基础,与 Gaussian Splat 互补
- Build spatial experiences with Object Tracking — 对象追踪与 RealityKit 场景结合,让虚拟内容锚定到真实物体
评论
GitHub Issues · utterances