Develop
Develop
Select your platform

Using the Scene class

Updated: Oct 6, 2025

Overview

This page covers the Scene class API for controlling your scene. For conceptual understanding of immersive scenes, see Understand scenes.
Spatial SDK applications use the metric system for all 3D measurements. Features like physics work only with the metric system. Spatial SDK uses a left-handed coordinate system: X+ is right, Y+ is up, and Z+ is forward.

Understanding the Scene class

The Scene class provides an API for controlling how immersive scenes appear and behave.

Scene and ECS relationship

The scene represents your individual view of the 3D world. The Entity-Component-System (ECS) pattern uses systems to modify the scene every tick. This continuously updates your representation of the world.
This creates an important relationship to understand: ECS systems operate on the scene and scene objects each frame. Certain changes you make directly to the scene and scene objects may not persist. ECS systems operating on them can override these changes. Conversely, some changes to scene objects may not be immediately reflected in the ECS. The relevant systems must process them first.
The MeshCreationSystem creates SceneMaterials, SceneMeshes, and SceneObjects from the data inside Mesh and Material components. This demonstrates how ECS systems transform component data into the visual scene representation.

Set up a basic immersive scene

This example from our StarterSample demonstrates how multiple Scene class features work together to create a fully immersive scene:
override fun onSceneReady() {
    super.onSceneReady()

    // 1. Set reference space to enable recentering
    scene.setReferenceSpace(ReferenceSpace.LOCAL_FLOOR)

    // 2. Configure scene lighting with ambient, sun color, and direction
    scene.setLightingEnvironment(
        ambientColor = Vector3(0f),
        sunColor = Vector3(7.0f, 7.0f, 7.0f),
        sunDirection = -Vector3(1.0f, 3.0f, -2.0f),
        environmentIntensity = 0.3f,
    )

    // 3. Update environment with Image Based Lighting
    scene.updateIBLEnvironment("environment.env")

    // 4. Set initial viewer position and orientation
    scene.setViewOrigin(0.0f, 0.0f, 2.0f, 180.0f)

    // 5. Create a skybox with custom texture
    Entity.create(
        listOf(
            Mesh(Uri.parse("mesh://skybox"), hittable = MeshCollision.NoCollision),
            Material().apply {
                baseTextureAndroidResourceId = R.drawable.skydome
                unlit = true // Prevent scene lighting from affecting the skybox
            },
            Transform(Pose(Vector3(x = 0f, y = 0f, z = 0f))),
        )
    )

    // 6. Load GLXF scene composition
    // wait for GLXF to load before accessing nodes inside it
    loadGLXF { composition ->
      // get the environment mesh from Spatial Editor and set it to use an unlit shader.
      val environmentEntity: Entity? = composition.getNodeByName("Environment").entity
      val environmentMesh = environmentEntity?.getComponent<Mesh>()
      environmentMesh?.defaultShaderOverride = SceneMaterial.UNLIT_SHADER
      environmentEntity?.setComponent(environmentMesh!!)
    }
  }

// Load GLXF scene composition from assets
    private fun loadGLXF(onLoaded: ((GLXFInfo) -> Unit) = {}): Job {
        gltfxEntity = Entity.create()
        return activityScope.launch {
            glXFManager.inflateGLXF(
                Uri.parse("apk:///scenes/Composition.glxf"),
                rootEntity = gltfxEntity!!,
                onLoaded = onLoaded,
            )
        }
    }
This example shows the typical workflow for setting up an immersive scene: establish the reference space, configure realistic lighting, position the viewer, add environmental elements like a skybox, and load pre-designed GLXF scene compositions. These Scene class methods work together to create a cohesive, immersive experience.
The following sections detail the key elements of the API.

Set the OpenXR reference space

The setReferenceSpace method specifies the OpenXR reference space in your app. This reference space affects how you spawn in your app and how recentering behaves.
LOCAL_FLOOR is the recommended space for most virtual reality apps. Re-centering works as expected out of the box. To set your app’s reference space to LOCAL_FLOOR, add this line to your app’s onSceneReady():
By default, Spatial SDK uses the STAGE reference space. This is a world-relative reference space. It suits mixed reality apps using Mixed Reality Utility Kit. However, re-centering doesn’t work in this space. The world around you is not physically moving. Override VRActivity.onRecenter to attach custom logic when the user presses the recenter button.
// Set reference space to enable recentering
scene.setReferenceSpace(ReferenceSpace.LOCAL_FLOOR)

Control your app’s lighting

The setLightingEnvironment(ambientColor, sunColor, sunDirection, environmentIntensity) method controls the lighting in your app.
  • ambientColor: The base color to add to everything (even when there is no light).
  • environmentIntensity: Controls how intense the current Image Based Lighting (IBL) is. The default is 1.0f.
  • sunColor: The color tint the virtual sun should have.
  • sunDirection: The direction the light should point.
// Set up scene lighting with ambient light color, sun color, and direction
scene.setLightingEnvironment(
    ambientColor = Vector3(0f),
    sunColor = Vector3(7.0f, 7.0f, 7.0f),
    sunDirection = -Vector3(1.0f, 3.0f, -2.0f),
    environmentIntensity = 0.3f,
)

Set the lighting environment

The updateIBLEnvironment(envFilename) method sets the lighting environment to the .env file path. The path is relative to the assets folder. See the Environment page’s information on image based lighting for more details.
// Update the Image Based Lighting environment
scene.updateIBLEnvironment("environment.env")

Set the user’s position and rotation

The setViewOrigin attribute sets the user’s position and rotation in the virtual world. It can be useful for implementing a locomotion system.
When positioning users, consider user comfort principles such as maintaining appropriate scale and avoiding abrupt transitions that can cause disorientation.
// Set the viewer's position and rotation in the virtual world
scene.setViewOrigin(0.0f, 0.0f, 2.0f, 180.0f)

Enable Passthrough

The enablePassthrough method enables Passthrough. For understanding different types of scenes including Passthrough scenes for mixed reality, see the conceptual overview. For implementation details, see the Passthrough page.
Limitation: Passthrough will not be work if a skybox entity exists.
// Enable or disable passthrough
scene.enablePassthrough(true)

Draw debug lines

The drawDebugLine(from, to, color, displayCount) method displays a small line in the scene from the from parameter to the to parameter. The line displays for the number of frames specified by displayCount. This method helps debug values or create pointers.

Sound APIs :

The Audio API supports spatial audio, enhancing realism in your VR app. For more information, see the Audio page.
// Play a spatial sound at a specific position
scene.playSound(createSound, position, 1f)

// Create and play looping spatial audio
val ambientSoundPlayer = SceneAudioPlayer(scene, ambientSound)
ambientSoundPlayer.play(speaker.getComponent<Transform>().transform.t, 0.2f, true)
Sample taken from Focus Showcase

Dolby Atmos integration

For advanced spatial audio using Dolby Atmos, enable the SpatialAudioFeature and use the AudioSessionId component with ExoPlayer. For detailed Dolby Atmos setup, see the Spatial audio page.

Traverse a line segment

The lineSegmentIntersect(from, to) method traverses a line segment from the from parameter to the to parameter. It returns the HitInfo? value of the first mesh it intersects in the scene. This method helps capture what a user may be pointing at.

Interacting with Scene objects

Typically, working with components such as Mesh, Material, and Transform will suffice. However, you may need more control over your objects. In these cases, you will interact with Scene objects:
This connects to the Entity-Component-System architecture where ECS systems create and manage these Scene objects from component data.
  • SceneObject: An instance of a 3D mesh in a scene, which points to a SceneMesh.
  • SceneMesh: The mesh data, such as vertices. It points to a SceneMaterial.
  • SceneMaterial: Describes the material properties for a mesh, such as roughness and metallicness, and points to a SceneTexture.
  • SceneTexture: The image data used in a material.
Consider the Mesh Component. The MeshCreationSystem creates SceneMaterials, SceneMeshes, and SceneObjects from the data inside the Mesh and Material components. Working with these scene objects enables you to create robust systems like the MeshCreationSystem.

Working with SceneObjects

SceneObjects allow you to attach input listeners and set object visibility.
To get a SceneObject from an Entity, see this code example:
// getSceneObject returns a CompletableFuture<SceneObject>
systemManager.findSystem<SceneObjectSystem>().getSceneObject(myEntity)?.thenAccept {
    sceneObject -> // do something with sceneObject here
}
This code executes on myEntity once the MeshCreationSystem has loaded it. Models may load asynchronously.
Sample taken from Focus Showcase
To attach a listener to an object that hides it when the user presses the “A” button while pointing at the object, use this code:
// Example within a SystemBase class - listen for changes to Mesh components
private fun findNewObjects() {
    val q = Query.where { changed(Mesh.id) and has(Transform.id) }
    for (entity in q.eval()) {
        systemManager.findSystem<SceneObjectSystem>().getSceneObject(entity)?.thenAccept { sceneObject ->
            sceneObject.addInputListener(
                object : InputListener {
                    override fun onInput(
                        receiver: SceneObject,
                        hitInfo: HitInfo,
                        sourceOfInput: Entity,
                        changed: Int,
                        clicked: Int,
                        downTime: Long
                    ): Boolean {
                        if ((clicked and ButtonBits.ButtonA) != 0) {
                            receiver.setIsVisible(false)
                        }
                        return false
                    }
                }
            )
        }
    }
}
You must wait for your entity’s Mesh component to be picked up by the MeshCreationSystem. Otherwise, you will get null if you try to get the SceneObject before it is set up. This approach works best in a system where you can listen for changes on a Mesh component.
For more information about handling button presses, see the Inputs and controllers page.

Design guidelines

Did you find this page helpful?
Thumbs up icon
Thumbs down icon