关于 axiom-realitykit
axiom-realitykit 是使用 RealityKit 构建 3D、AR 和空间计算应用的开发指南,讲解实体-组件-系统模型,并涵盖 SwiftUI、交互、物理、渲染和多人同步。
主要功能
- 01讲解实体-组件-系统架构
- 02涵盖实体、组件和系统
- 03将 3D 内容集成到 SwiftUI
- 04介绍 AR 锚点与交互
- 05涵盖材质、渲染和物理
- 06包含多人同步
适用场景
- 01构建 RealityKit 3D 游戏或体验
- 02在 SwiftUI 应用中嵌入 3D 模型
- 03添加 AR 放置和手势交互
- 04实现物理或共享 AR 体验
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-realitykit安装后在 Claude Code 中输入/axiom-realitykit确认已加载。
详细介绍
RealityKit 开发指南
目的:使用 RealityKit 的实体-组件-系统架构,构建 3D 内容、AR 体验和空间计算应用 iOS 版本:iOS 13+(基础版本)、iOS 18+(iOS 上的 RealityView)、visionOS 1.0+ Xcode:Xcode 15+
何时使用此技能
在以下情况使用此技能:
- 构建任何 3D 体验(AR、游戏、可视化、空间计算)
- 创建包含 3D 内容的 SwiftUI 应用(RealityView、Model3D)
- 使用锚点实现 AR(世界、图像、人脸、身体跟踪)
- 使用实体-组件-系统(ECS)架构
- 设置物理、碰撞或空间交互
- 构建多人或共享 AR 体验
- 从 SceneKit 迁移到 RealityKit
- 面向 visionOS
请勿在以下情况使用此技能:
- 维护 SceneKit(使用
axiom-scenekit) - 开发 2D 游戏(使用
axiom-spritekit) - 编写 Metal 着色器(使用
axiom-metal-migration-ref) - 纯 GPU 计算(直接使用 Metal)
1. 思维模型:ECS 与场景图
场景图(SceneKit)
在 SceneKit 中,节点拥有自己的属性。节点本身就是可渲染、可碰撞、可动画的对象。
实体-组件-系统(RealityKit)
在 RealityKit 中,实体是空容器。组件添加数据,系统处理这些数据。
Entity (identity + hierarchy)
├── TransformComponent (position, rotation, scale)
├── ModelComponent (mesh + materials)
├── CollisionComponent (collision shapes)
├── PhysicsBodyComponent (mass, mode)
└── [YourCustomComponent] (game-specific data)
System (processes entities with specific components each frame)
ECS 的重要性:
- 组合优于继承:在任意实体上组合任意组件
- 面向数据:系统高效地处理组件数组
- 逻辑解耦:系统之间互不知晓
- 易于测试:组件是纯数据,系统是纯逻辑
ECS 思维转变
| 场景图思维 | ECS 思维 |
|---|---|
| “玩家节点会移动” | “移动系统处理具有 MovementComponent 的实体” |
| “向节点子类添加方法” | “添加组件,创建系统” |
“在节点中重写 update(_:)” | “注册一个查询组件的 System” |
| “节点知道自己的生命值” | “HealthComponent 保存数据,DamageSystem 处理数据” |
2. 实体层级结构
创建实体
// Empty entity
let entity = Entity()
entity.name = "player"
// Entity with components
let entity = Entity()
entity.components[ModelComponent.self] = ModelComponent(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .blue, isMetallic: false)]
)
// ModelEntity convenience (has ModelComponent built in)
let box = ModelEntity(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .red, isMetallic: true)]
)
层级管理
// Parent-child
parent.addChild(child)
child.removeFromParent()
// Find entities
let found = root.findEntity(named: "player")
// Enumerate
for child in entity.children {
// Process children
}
// Clone
let clone = entity.clone(recursive: true)
变换
// Local transform (relative to parent)
entity.position = SIMD3<Float>(0, 1, 0)
entity.orientation = simd_quatf(angle: .pi / 4, axis: SIMD3(0, 1, 0))
entity.scale = SIMD3<Float>(repeating: 2.0)
// World-space queries
let worldPos = entity.position(relativeTo: nil)
let worldTransform = entity.transform(relativeTo: nil)
// Set world-space transform
entity.setPosition(SIMD3(1, 0, 0), relativeTo: nil)
// Look at a point
entity.look(at: targetPosition, from: entity.position, relativeTo: nil)
3. 组件
内置组件
| 组件 | 用途 |
|---|---|
Transform | 位置、旋转、缩放 |
ModelComponent | 网格几何体 + 材质 |
CollisionComponent | 用于物理和交互的碰撞形状 |
PhysicsBodyComponent | 质量、物理模式(动态/静态/运动学) |
PhysicsMotionComponent | 线速度和角速度 |
AnchoringComponent | AR 锚点附着 |
SynchronizationComponent | 多人同步 |
PerspectiveCameraComponent | 摄像头设置 |
DirectionalLightComponent | 定向光 |
PointLightComponent | 点光源 |
SpotLightComponent | 聚光灯 |
CharacterControllerComponent | 角色物理控制器 |
AudioMixGroupsComponent | 音频混合 |
SpatialAudioComponent | 3D 空间定位音频 |
AmbientAudioComponent | 非空间定位音频 |
ChannelAudioComponent | 多声道音频 |
OpacityComponent | 实体透明度 |
GroundingShadowComponent | 接触阴影 |
InputTargetComponent | 手势输入(visionOS) |
HoverEffectComponent | 悬停高亮(visionOS) |
AccessibilityComponent | VoiceOver 支持 |
自定义组件
struct HealthComponent: Component {
var current: Int
var maximum: Int
var percentage: Float {
Float(current) / Float(maximum)
}
}
// Register before use (typically in app init)
HealthComponent.registerComponent()
// Attach to entity
entity.components[HealthComponent.self] = HealthComponent(current: 100, maximum: 100)
// Read
if let health = entity.components[HealthComponent.self] {
print(health.current)
}
// Modify
entity.components[HealthComponent.self]?.current -= 10
组件生命周期
组件是值类型(结构体)。读取组件、修改它并写回时,实际上是在替换整个组件:
// Read-modify-write pattern
var health = entity.components[HealthComponent.self]!
health.current -= damage
entity.components[HealthComponent.self] = health
反面模式:持有组件的引用并期待修改会自动传播。读取组件时会复制它。
4. 系统
System 协议
struct DamageSystem: System {
// Define which components this system needs
static let query = EntityQuery(where: .has(HealthComponent.self))
init(scene: RealityKit.Scene) {
// One-time setup
}
func update(context: SceneUpdateContext) {
for entity in context.entities(matching: Self.query,
updatingSystemWhen: .rendering) {
var health = entity.components[HealthComponent.self]!
if health.current <= 0 {
entity.removeFromParent()
}
}
}
}
// Register system
DamageSystem.registerSystem()
系统最佳实践
- 每个系统只负责一项职责:MovementSystem、DamageSystem、RenderingSystem,而不是 GameLogicSystem
- 查询过滤:使用精确查询,避免处理无关实体
- 顺序很重要:系统按注册顺序运行。请先注册依赖项。
- 避免存储实体引用:每帧重新查询。实体引用可能会过期。
事件处理
// Subscribe to collision events
scene.subscribe(to: CollisionEvents.Began.self) { event in
let entityA = event.entityA
let entityB = event.entityB
// Handle collision
}
// Subscribe to scene update
scene.subscribe(to: SceneEvents.Update.self) { event in
let deltaTime = event.deltaTime
// Per-frame logic
}
5. SwiftUI 集成
RealityView(iOS 18+、visionOS 1.0+)
struct ContentView: View {
var body: some View {
RealityView { content in
// make closure — called once
let box = ModelEntity(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .blue, isMetallic: false)]
)
content.add(box)
} update: { content in
// update closure — called when SwiftUI state changes
}
}
}
带摄像头的 RealityView(iOS)
在 iOS 上,RealityView 提供了用于配置 AR 或虚拟摄像头的摄像头内容参数:
RealityView { content, attachments in
// Load 3D content
if let model = try? await ModelEntity(named: "scene") {
content.add(model)
}
}
异步加载内容
RealityView { content in
// Load from bundle
if let entity = try? await Entity(named: "MyScene", in: .main) {
content.add(entity)
}
// Load from URL
if let entity = try? await Entity(contentsOf: modelURL) {
content.add(entity)
}
}
Model3D(简单显示)
// Simple 3D model display (no interaction)
Model3D(named: "toy_robot") { model in
model
.resizable()
.scaledToFit()
} placeholder: {
ProgressView()
}
SwiftUI 附加视图(visionOS)
RealityView { content, attachments in
let entity = ModelEntity(mesh: .generateSphere(radius: 0.1))
content.add(entity)
if let label = attachments.entity(for: "priceTag") {
label.position = SIMD3(0, 0.15, 0)
entity.addChild(label)
}
} attachments: {
Attachment(id: "priceTag") {
Text("$9.99")
.padding()
.glassBackgroundEffect()
}
}
状态绑定模式
struct GameView: View {
@State private var score = 0
var body: some View {
VStack {
Text("Score: \(score)")
RealityView { content in
let scene = try! await Entity(named: "GameScene")
content.add(scene)
} update: { content in
// React to state changes
// Note: update is called when SwiftUI state changes,
// not every frame. Use Systems for per-frame logic.
}
}
}
}
6. iOS 上的 AR
AnchorEntity
// Horizontal plane
let anchor = AnchorEntity(.plane(.horizontal, classification: .table,
minimumBounds: SIMD2(0.2, 0.2)))
// Vertical plane
let anchor = AnchorEntity(.plane(.vertical, classification: .wall,
minimumBounds: SIMD2(0.5, 0.5)))
// World position
let anchor = AnchorEntity(world: SIMD3<Float>(0, 0, -1))
// Image anchor
let anchor = AnchorEntity(.image(group: "AR Resources", name: "poster"))
// Face anchor (front camera)
let anchor = AnchorEntity(.face)
// Body anchor
let anchor = AnchorEntity(.body)
SpatialTrackingSession(iOS 18+)
let session = SpatialTrackingSession()
let configuration = SpatialTrackingSession.Configuration(tracking: [.plane, .object])
let result = await session.run(configuration)
if let notSupported = result {
// Handle unsupported tracking on this device
for denied in notSupported.deniedTrackingModes {
print("Not supported: \(denied)")
}
}
AR 最佳实践
- 将锚定实体放置在检测到的表面上,而非世界坐标位置,以提高稳定性
- 使用平面分类(
.table、.floor、.wall)将内容放置在合适的位置 - 从水平平面检测开始——这是最可靠的方式
- 在真机上测试;模拟器的 AR 功能有限
- 在表面检测期间提供视觉反馈(引导叠加层)
7. 交互
ManipulationComponent(iOS、visionOS)
// Enable drag, rotate, scale gestures
entity.components[ManipulationComponent.self] = ManipulationComponent(
allowedModes: .all // .translate, .rotate, .scale
)
// Also requires CollisionComponent for hit testing
entity.generateCollisionShapes(recursive: true)
InputTargetComponent(visionOS)
// Required for visionOS gesture input
entity.components[InputTargetComponent.self] = InputTargetComponent()
entity.components[CollisionComponent.self] = CollisionComponent(
shapes: [.generateBox(size: SIMD3(0.1, 0.1, 0.1))]
)
与 SwiftUI 集成手势
RealityView { content in
let entity = ModelEntity(mesh: .generateBox(size: 0.1))
entity.generateCollisionShapes(recursive: true)
entity.components.set(InputTargetComponent())
content.add(entity)
}
.gesture(
TapGesture()
.targetedToAnyEntity()
.onEnded { value in
let tappedEntity = value.entity
// Handle tap
}
)
.gesture(
DragGesture()
.targetedToAnyEntity()
.onChanged { value in
value.entity.position = value.convert(value.location3D,
from: .local, to: .scene)
}
)
命中测试
// Ray-cast from screen point
if let result = arView.raycast(from: screenPoint,
allowing: .estimatedPlane,
alignment: .horizontal).first {
let worldPosition = result.worldTransform.columns.3
// Place entity at worldPosition
}
8. 材质与渲染
材质类型
| 材质 | 用途 | 自定义 |
|---|---|---|
SimpleMaterial | 纯色或纹理 | 颜色、金属度、粗糙度 |
PhysicallyBasedMaterial | 完整的 PBR | 所有 PBR 贴图(基础颜色、法线、金属度、粗糙度、AO、自发光) |
UnlitMaterial | 不受光照影响 | 颜色或纹理,始终完全受光 |
OcclusionMaterial | 不可见但可遮挡 | 遮挡被真实物体挡住的 AR 内容 |
VideoMaterial | 在表面播放视频 | 由 AVPlayer 驱动 |
ShaderGraphMaterial | 自定义着色器图 | Reality Composer Pro |
CustomMaterial | Metal 着色器函数 | 完整的 Metal 控制 |
PhysicallyBasedMaterial
var material = PhysicallyBasedMaterial()
material.baseColor = .init(tint: .white,
texture: .init(try! .load(named: "albedo")))
material.metallic = .init(floatLiteral: 0.0)
material.roughness = .init(floatLiteral: 0.5)
material.normal = .init(texture: .init(try! .load(named: "normal")))
material.ambientOcclusion = .init(texture: .init(try! .load(named: "ao")))
material.emissiveColor = .init(color: .blue)
material.emissiveIntensity = 2.0
let entity = ModelEntity(
mesh: .generateSphere(radius: 0.1),
materials: [material]
)
OcclusionMaterial(AR)
// Invisible plane that hides 3D content behind it
let occluder = ModelEntity(
mesh: .generatePlane(width: 1, depth: 1),
materials: [OcclusionMaterial()]
)
occluder.position = SIMD3(0, 0, 0)
anchor.addChild(occluder)
环境光照
// Image-based lighting
if let resource = try? await EnvironmentResource(named: "studio_lighting") {
// Apply via RealityView content
}
9. 物理与碰撞
碰撞形状
// Generate from mesh (accurate but expensive)
entity.generateCollisionShapes(recursive: true)
// Manual shapes (prefer for performance)
entity.components[CollisionComponent.self] = CollisionComponent(
shapes: [
.generateBox(size: SIMD3(0.1, 0.2, 0.1)), // Box
.generateSphere(radius: 0.1), // Sphere
.generateCapsule(height: 0.3, radius: 0.05) // Capsule
]
)
物理实体
// Dynamic — physics simulation controls movement
entity.components[PhysicsBodyComponent.self] = PhysicsBodyComponent(
massProperties: .init(mass: 1.0),
material: .generate(staticFriction: 0.5,
dynamicFriction: 0.3,
restitution: 0.4),
mode: .dynamic
)
// Static — immovable collision surface
ground.components[PhysicsBodyComponent.self] = PhysicsBodyComponent(
mode: .static
)
// Kinematic — code-controlled, participates in collisions
platform.components[PhysicsBodyComponent.self] = PhysicsBodyComponent(
mode: .kinematic
)
碰撞组与筛选器
// Define groups
let playerGroup = CollisionGroup(rawValue: 1 << 0)
let enemyGroup = CollisionGroup(rawValue: 1 << 1)
let bulletGroup = CollisionGroup(rawValue: 1 << 2)
// Filter: player collides with enemies and bullets
entity.components[CollisionComponent.self] = CollisionComponent(
shapes: [.generateSphere(radius: 0.1)],
filter: CollisionFilter(
group: playerGroup,
mask: enemyGroup | bulletGroup
)
)
碰撞事件
// Subscribe in RealityView make closure or System
scene.subscribe(to: CollisionEvents.Began.self, on: playerEntity) { event in
let otherEntity = event.entityA == playerEntity ? event.entityB : event.entityA
handleCollision(with: otherEntity)
}
施加力
if var motion = entity.components[PhysicsMotionComponent.self] {
motion.linearVelocity = SIMD3(0, 5, 0) // Impulse up
entity.components[PhysicsMotionComponent.self] = motion
}
10. 动画
变换动画
// Animate to position over duration
entity.move(
to: Transform(
scale: SIMD3(repeating: 1.5),
rotation: simd_quatf(angle: .pi, axis: SIMD3(0, 1, 0)),
translation: SIMD3(0, 2, 0)
),
relativeTo: entity.parent,
duration: 2.0,
timingFunction: .easeInOut
)
播放 USD 动画
if let entity = try? await Entity(named: "character") {
// Play all available animations
for animation in entity.availableAnimations {
entity.playAnimation(animation.repeat())
}
}
动画播放控制
let controller = entity.playAnimation(animation)
controller.pause()
controller.resume()
controller.speed = 2.0 // 2x playback speed
controller.blendFactor = 0.5 // Blend with current state
11. 音频
空间音频
// Load audio resource
let resource = try! AudioFileResource.load(named: "engine.wav",
configuration: .init(shouldLoop: true))
// Create entity with spatial audio
let audioEntity = Entity()
audioEntity.components[SpatialAudioComponent.self] = SpatialAudioComponent()
let controller = audioEntity.playAudio(resource)
// Position the audio source in 3D space
audioEntity.position = SIMD3(2, 0, -1)
环境音频
entity.components[AmbientAudioComponent.self] = AmbientAudioComponent()
entity.playAudio(backgroundMusic)
12. 性能
实体数量
- 少于 100 个实体:无需担忧
- 100–1000 个实体:使用 RealityKit 调试器监控
- 1000 个以上实体:使用实例化和 LOD 策略
实例化
// Share mesh and material across many entities
let sharedMesh = MeshResource.generateSphere(radius: 0.01)
let sharedMaterial = SimpleMaterial(color: .white, isMetallic: false)
for i in 0..<1000 {
let entity = ModelEntity(mesh: sharedMesh, materials: [sharedMaterial])
entity.position = randomPosition()
parent.addChild(entity)
}
RealityKit 会自动批处理网格和材质资源相同的实体。
组件变动
反模式:每帧都创建并替换组件。
// BAD — component allocation every frame
func update(context: SceneUpdateContext) {
for entity in context.entities(matching: query, updatingSystemWhen: .rendering) {
entity.components[ModelComponent.self] = ModelComponent(
mesh: .generateBox(size: 0.1),
materials: [newMaterial] // New allocation every frame
)
}
}
// GOOD — modify existing component
func update(context: SceneUpdateContext) {
for entity in context.entities(matching: query, updatingSystemWhen: .rendering) {
// Only update when actually needed
if needsUpdate {
var model = entity.components[ModelComponent.self]!
model.materials = [cachedMaterial]
entity.components[ModelComponent.self] = model
}
}
}
碰撞形状优化
- 使用简单形状(盒体、球体、胶囊体),而非基于网格的碰撞形状
generateCollisionShapes(recursive: true)使用方便,但开销较大- 对于静态几何体,在设置期间只生成一次形状
性能分析
使用 Xcode 的 RealityKit 调试器:
- 实体检查器:查看实体层级结构和组件
- 统计信息叠加层:实体数量、绘制调用次数、三角形数量
- 物理可视化:显示碰撞形状
13. 多人游戏
同步基础
// Components sync automatically if they conform to Codable
struct ScoreComponent: Component, Codable {
var points: Int
}
// SynchronizationComponent controls what syncs
entity.components[SynchronizationComponent.self] = SynchronizationComponent()
MultipeerConnectivityService
let service = try MultipeerConnectivityService(session: mcSession)
// Entities with SynchronizationComponent auto-sync across peers
所有权
- 只有实体的所有者才能修改该实体
- 修改共享实体前,请先请求所有权
- 不符合 Codable 的组件数据不会同步
14. 反模式
反模式 1:用 UIKit 的思维方式看待 ECS
时间成本:与架构对抗,令人沮丧数小时
// BAD — subclassing Entity for behavior
class PlayerEntity: Entity {
func takeDamage(_ amount: Int) { /* logic in entity */ }
}
// GOOD — component holds data, system has logic
struct HealthComponent: Component { var hp: Int }
struct DamageSystem: System {
static let query = EntityQuery(where: .has(HealthComponent.self))
func update(context: SceneUpdateContext) {
// Process damage here
}
}
反模式 2:实体过于庞大
时间成本:架构无法测试,也不够灵活
不要把所有游戏逻辑都放在一种实体类型中。应拆分为可自由组合的组件。
反模式 3:没有系统的逐帧更新
时间成本:错过帧更新,行为不一致
// BAD — timer-based updates
Timer.scheduledTimer(withTimeInterval: 1/60, repeats: true) { _ in
entity.position.x += 0.01
}
// GOOD — System update
struct MovementSystem: System {
static let query = EntityQuery(where: .has(VelocityComponent.self))
func update(context: SceneUpdateContext) {
for entity in context.entities(matching: Self.query,
updatingSystemWhen: .rendering) {
let velocity = entity.components[VelocityComponent.self]!
entity.position += velocity.value * Float(context.deltaTime)
}
}
}
反模式 4:不为可交互实体生成碰撞形状
时间成本:花 15-30 分钟排查“为什么点击不起作用”
手势需要 CollisionComponent。如果实体有 InputTargetComponent(visionOS)或 ManipulationComponent,但没有 CollisionComponent,手势就永远不会触发。
反模式 5:在系统中存储实体引用
时间成本:因引用过期而崩溃
// BAD — entity might be removed between frames
struct BadSystem: System {
var playerEntity: Entity? // Stale reference risk
func update(context: SceneUpdateContext) {
playerEntity?.position.x += 0.1 // May crash
}
}
// GOOD — query each frame
struct GoodSystem: System {
static let query = EntityQuery(where: .has(PlayerComponent.self))
func update(context: SceneUpdateContext) {
for entity in context.entities(matching: Self.query,
updatingSystemWhen: .rendering) {
entity.position.x += Float(context.deltaTime)
}
}
}
15. 代码审查清单
- 使用自定义组件前,通过
registerComponent()注册 - 场景加载前,通过
registerSystem()注册系统 - 组件使用值类型(结构体),而非类
- 更新组件时使用读取-修改-写入模式
- 可交互实体具有
CollisionComponent - visionOS 上的可交互实体具有
InputTargetComponent - 尽可能使用简单的碰撞形状(盒体/球体/胶囊体)
- 系统中不跨帧存储实体引用
- 相同实体之间共享网格和材质资源
- 仅在值实际发生变化时更新组件
- 3D 资源使用 USD/USDZ 格式(而非 .scn)
- 所有模型/场景加载都使用异步加载
- 如果基于闭包的订阅会保留视图/控制器,则在闭包中使用
[weak self]
16. 压力场景
场景 1:“对于我们的简单应用,ECS 是大材小用”
压力:团队想避免学习 ECS,只需要显示一个 3D 模型
错误做法:跳过 ECS,把所有逻辑都塞进 RealityView 闭包。
正确做法:即使是简单的应用也能从 ECS 中受益。RealityView 中的单个 ModelEntity 已经在使用 ECS——只是还没有添加自定义组件。先从简单开始,随着复杂度增加再添加组件。
回应模板:“我们已经在使用 ECS——Entity 和 ModelComponent。这个模式可以扩展。需要行为时,添加自定义组件只需定义一个结构体,并不涉及架构变更。”
场景 2:“直接用 SceneKit,我们熟悉它”
压力:团队有 SceneKit 使用经验,对 RealityKit 不熟悉
错误做法:用 SceneKit 开发新功能。
正确做法:SceneKit 已基本弃用,不会再添加新功能。现在投入学习 RealityKit——如果有需要,ECS 概念也能迁移到其他游戏引擎(Unity、Unreal、Bevy)。
回应模板:“SceneKit 已进入维护模式——不会添加新功能,只提供安全补丁。我们写下的每一行 SceneKit 代码都会增加迁移负担。RealityKit 的概念(Entity、Component、System)是业界标准的 ECS。”
场景 3:“不使用碰撞形状也能实现”
压力:截止日期临近,设置碰撞形状似乎很复杂
错误做法:跳过碰撞形状,改用基于位置的邻近检测。
正确做法:entity.generateCollisionShapes(recursive: true) 只需一行。没有它,手势就无法生效,物理实体也不会发生碰撞。这个“捷径”带来的调试时间比省下的时间更多。
回应模板:“手势和物理模拟都需要碰撞形状。只需一行:entity.generateCollisionShapes(recursive: true)。跳过这一步会导致手势悄无声息地失效——这类问题更难诊断。”
资源
WWDC:2019-603、2019-605、2021-10074、2022-10074、2023-10080、2023-10081、2024-10103、2024-10153
文档:/realitykit、/realitykit/entity、/realitykit/realityview、/realitykit/modelentity、/realitykit/anchorentity、/realitykit/component
技能:axiom-realitykit-ref、axiom-realitykit-diag、axiom-scenekit、axiom-scenekit-ref