I'm trying to develop an immersive visionOS app, which you can move an Entity having a PerspectiveCamera as its child in immersive space, and render the camera view on 2D window.
According to this thread, this seems to can be achieved using RealityRenderer. But when I added the scene entity loaded from realityKitContentBundle
to realityRenderer.entities,
I needed to clone all entities of the scene, otherwise all entities in the immersive space will disappear.
@Observable
@MainActor
final class OffscreenRenderModel {
private let renderer: RealityRenderer
private let colorTexture: MTLTexture
init(scene: Entity) throws {
renderer = try RealityRenderer()
// If not clone entities in the scene, all entities in the immersive space will disappear
renderer.entities.append(scene.clone(recursive: true))
let camera = PerspectiveCamera()
renderer.activeCamera = camera
renderer.entities.append(camera)
...
}
}
Is this the expected behavior? Or is there any other way to do this (move camera in immersive space and render its output on 2D window)?
Here is my sample code: https://github.com/TAATHub/RealityKitPerspectiveCamera
Hi @TAAT
I really like your use case! Super creative.
The behavior you're observing is expected. An entity can only have a single parent. Anytime you add an entity to another entity, it is removed from its parent. In this case, when you add scene
to renderer
it removes scene from the immersive space's content. Here's a focused code snippet to demonstrate this. Tapping the sphere adds it to reality renderer and a timer adds it back to the reality view's content a few seconds later.
import SwiftUI
import RealityKit
struct ImmersiveView: View {
@State var sphere = ModelEntity()
var body: some View {
RealityView { content in
sphere.components.set(
ModelComponent(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: false)])
)
sphere.generateCollisionShapes(recursive: false)
sphere.components.set(InputTargetComponent())
sphere.position = [0, 1.4, -1]
content.add(sphere)
}
.gesture(TapGesture().targetedToAnyEntity().onEnded { _ in
// Keep a reference to the reality view's content.
let oldParent = sphere.parent
guard let renderer = try? RealityRenderer() else { return }
// Appending sphere to renderer.entities removes it from
// the reality view's content.
renderer.entities.append(sphere)
Task { // Wait a few seconds then add the sphere back to the reality view's content.
try? await Task.sleep(nanoseconds: 3_000_000_000)
oldParent?.addChild(sphere)
}
})
}
}
Cloning the entity is a reasonable solution. Alternatively you can load 2 copies of your RealityKitContent
; one for the immersive view and another for the reality renderer.