Lab 046 – Portals can do what?
I’m not sure what to do with this information.
I don’t know why it occurred to me, but I started to wonder if entities with portal component could have opacity adjusted.
Yes.
Yes they can, and it gets weird. I made a quick lab to test this idea. This is based on Lab 037 with a portal in a window. I can adjust the opacity using of entities by using OpacityComponent or when using the .opacity bind target with AnimationResource.makeActionAnimation. (Interestingly, this second method does not require an entity to have an opacity component).
portalEntity.components.set(OpacityComponent(opacity: opacity))Or
Task {
let action = FromToByAction<Float>(to: 0.0,
timing: .linear,
isAdditive: false
)
let animation = try! AnimationResource.makeActionAnimation(for: action,
duration: 5,
bindTarget: .opacity,
)
portalEntity.playAnimation(animation)
}Things get weird when the numbers get small. Between 0.0 and 0.2 or so, we can still see the portal contents, but we also start to see whatever is behind the window. This could be the passthrough feed, or the content from a system environment, or even the content of an immersive space. When the number reaches 0.0 it is fully transparent, as expected. Watch the video to see what I mean.
This is really neat, but I’m not sure what to do with this information. I thought it might be cool to use opacity to open and close portals but with this oddness I’m not sure that is a good idea. I’m also not sure if I should report a bug. Has anyone else even tried to use opacity on a portal? I doubt the developers anticipated that…
Video Demo
visionOS simulator demo of adjusting opacity of an entity showing a portal material
Lab Code
struct Lab046: View {
@State var opacity: Float = 1.0
var body: some View {
RealityView { content in
// Portal example from Lab 037
// 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)
// 3. An entity that will render the portal
// We need to use PortalMaterial
let portalEntity = ModelEntity(
mesh: .generatePlane(width: 0.4, height: 0.2, cornerRadius: 0.03),
materials: [PortalMaterial()]
)
portalEntity.name = "Portal"
portalEntity.position.z = -0.175
// We also need to add a PortalComponent that targets the portalContentRoot
portalEntity.components.set(PortalComponent(target: portalContentRoot))
rootEntity.addChild(portalEntity)
// 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
} update: { content in
// Note: this is not an ideal way to adjust opacity, but it works as a quick hack
if let portalEntity = content.entities.first?.findEntity(named: "Portal") {
portalEntity.components.set(OpacityComponent(opacity: opacity))
}
}
.toolbar {
ToolbarItem(placement: .bottomOrnament, content: {
Slider(value: $opacity,
in: 0.0...1.0,
step: 0.1,
minimumValueLabel: Image(systemName: "circle.dotted"),
maximumValueLabel: Image(systemName: "circle.fill"),
label: {
Text("Opacity")
})
.frame(width: 300)
})
}
}
}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