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.
Download the Xcode project with this and many more labs from Step Into Vision.

Follow Step Into Vision