Lab 038 – Portal Sphere in a Volume

We can render Portal Material on more than just a plane.

Building on what we learned in Lab 037, I was curious if portals would render on other shapes. This lab uses a sphere to show the portal contents. I added an overlay orb around it that will toggle between a mirror surface and transparent. We can use a long press gesture to trigger this change. We set up the portal in the same way we did in Lab 037.

Video demo of a portal component attached to a sphere in a virtual crystal ball.

Full Lab Code

struct Lab038: View {
    @State var orbIsActive: Bool = false
    @State var orbOverlay = Entity()
    var body: some View {
        RealityView { content in

            // 1. The root for our scene *outside* of the portal
            let rootEntity = Entity()
            content.add(rootEntity)

            // 2. The root for the content that will appear *inside* the portal
            // We need a WorldComponent here
            let portalContentRoot = Entity()
            portalContentRoot.components.set(WorldComponent())
            rootEntity.addChild(portalContentRoot)

            guard let portalSphereScene = try? await Entity(named: "PortalBall", in: realityKitContentBundle) else { return }
            rootEntity.addChild(portalSphereScene)
            portalSphereScene.position.y = -0.28

            // 3. An entity that will render the portal
            if let portalSphere = portalSphereScene.findEntity(named: "PortalSphere") {

                // Replace the material with PortalMaterial
                portalSphere.components[ModelComponent.self]?.materials[0] = PortalMaterial()

                // We also need to add a PortalComponent that targets the portalContentRoot
                portalSphere.components.set(PortalComponent(target: portalContentRoot))
            }

            // Handle the overlay sphere
            if let overlay = portalSphereScene.findEntity(named: "Overlay") {
                // Stash this in state so we can target a gesture to it
                orbOverlay = overlay
            }

            // 4. We'll load some content to add to the portalContentRoot
            guard let scene = try? await Entity(named: "TeleportLabs", in: realityKitContentBundle) else { return }

            portalContentRoot.addChild(scene)
            scene.position.y = -1.4

        }
        .gesture(longPressGesture)
    }

    var longPressGesture: some Gesture {
        LongPressGesture()
            .targetedToEntity(orbOverlay)
            .onEnded { value in
                let subject = value.entity
                orbIsActive.toggle()
                
                Task {
                    let startOpacity: Float = orbIsActive ? 1.0 : 0.2
                    let endOpacity: Float = orbIsActive ? 0.2 : 1.0
                    
                    for step in 0...20 {
                        let progress = Float(step) / 10.0
                        let currentOpacity = startOpacity + (endOpacity - startOpacity) * progress
                        
                        if var mat = subject.components[ModelComponent.self]?.materials.first as? PhysicallyBasedMaterial {
                            mat.blending = 
                                .transparent(opacity: PhysicallyBasedMaterial.Opacity(floatLiteral: currentOpacity))
                            subject.components[ModelComponent.self]?.materials[0] = mat
                        }
                        try? await Task.sleep(for: .milliseconds(25))
                    }
                }
            }
    }
}

Support our work so we can continue to bring you new examples and articles.

Questions or feedback?