Lab 105 – Vapor

I’m using this lab to build a basic scene that will be used for some other ideas later.

I decided to reproduce one of the scenes from a PlayCanvas project from a few years ago. I plan on making a few scenes and linking them together in interesting ways. I used this lab as as a place to run and preview this scene. Most of the work was done in Reality Composer Pro. My assets are sort of a mess. They are a mix of various 3D file formats converted to USD at different times with different technology.

Sky: two half domes with no ground between them. One is flipped over.

Ground: I used a simple checkboard texture and a shader graph material. I can adjust the UV Scale and an input Color.

Palm Trees: These use PRB materials with a mix of opacity and emission. (credit. Palm tree by Poly by Google [CC-BY], via Poly Pizza)

Columns: These use PRB materials with a mix of opacity and emission. (credit: Column_Round3 by Quaternius [CCO])

Display: This is the most complex entity as it is actually made of several discrete meshes behind the scenes. I’dd add more to this later. For now, notice the case, buttons, and screen have separate materials. (credit: crt_monitor by The Base Mesh [CC0])

Retro Computer (credit: retro_computer by The Base Mesh [CC0])

A screenshot of this scene in Reality Composer Pro.

Updates

2026.05.10 – Updated to include a portal on the surface of the scene. This loads a second scene, scales it down, and frames the content.

struct Lab105: View {
    var body: some View {
        RealityView { content in

            guard let rootEntity = try? await Entity(named: "Vapor", in: realityKitContentBundle) else { return }
            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. We need something to render the portal on
            if let screenSurface = rootEntity.findEntity(named: "ScreenSurface") {
                var portalMaterial = PortalMaterial()
                portalMaterial.faceCulling = .none

                if var modelComponent = screenSurface.components[ModelComponent.self] {
                    modelComponent.materials = modelComponent.materials.map { _ in portalMaterial }
                    screenSurface.components.set(modelComponent)
                    screenSurface.components.set(PortalComponent(
                        target: portalContentRoot,
                        clippingMode: .plane(.negativeY),
                        crossingMode: .disabled
                    ))
                }
            }

            // 4. We'll load some content to add to the portalContentRoot
            guard let scene = try? await Entity(named: "Mist", in: realityKitContentBundle) else { return }
            scene.scale = .init(repeating: 0.5)
            scene.position = [0, 4, -9]
            scene.orientation = simd_quatf(angle: -6, axis: [1, 0, 0])
            portalContentRoot.addChild(scene)

        }
    }
}

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.

Questions or feedback?