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.

Follow Step Into Vision