Skip to content

⬅️ Back to Table of Contents

📄 XREstimatedLight.js

📊 Analysis Summary

Metric Count
🔧 Functions 3
🧱 Classes 2
📦 Imports 4
📊 Variables & Constants 4

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/webxr/XREstimatedLight.js

📦 Imports

Name Source
DirectionalLight three
Group three
LightProbe three
WebGLCubeRenderTarget three

Variables & Constants

Name Type Kind Value Exported
cubeRenderTarget any let/var new WebGLCubeRenderTarget( 16 )
session any let/var xrFrame.session
sessionLightProbe any let/var null
estimationStarted boolean let/var false

Functions

SessionLightProbe.updateReflection(): void

Returns: void

Calls:

  • this.renderer.properties.get
  • this.xrWebGLBinding.getReflectionCubeMap
Code
updateReflection() {

        const textureProperties = this.renderer.properties.get( this.xrLight.environment );

        if ( textureProperties ) {

            const cubeMap = this.xrWebGLBinding.getReflectionCubeMap( this.lightProbe );

            if ( cubeMap ) {

                textureProperties.__webglTexture = cubeMap;

                this.xrLight.environment.needsPMREMUpdate = true;

            }

        }

    }

SessionLightProbe.onXRFrame(time: any, xrFrame: any): void

Parameters:

  • time any
  • xrFrame any

Returns: void

Calls:

  • session.requestAnimationFrame
  • xrFrame.getLightEstimate
  • this.xrLight.lightProbe.sh.fromArray
  • Math.max
  • this.xrLight.directionalLight.color.setRGB
  • this.xrLight.directionalLight.position.copy
  • this.estimationStartCallback

Internal Comments:

// If either this object or the XREstimatedLight has been destroyed, stop
// running the frame loop.
// We can copy the estimate's spherical harmonics array directly into the light probe. (x7)
// For the directional light we have to normalize the color and set the scalar as the (x2)
// intensity, since WebXR can return color values that exceed 1.0. (x2)

Code
onXRFrame( time, xrFrame ) {

        // If either this object or the XREstimatedLight has been destroyed, stop
        // running the frame loop.
        if ( ! this.xrLight ) {

            return;

        }

        const session = xrFrame.session;
        session.requestAnimationFrame( this.frameCallback );

        const lightEstimate = xrFrame.getLightEstimate( this.lightProbe );
        if ( lightEstimate ) {

            // We can copy the estimate's spherical harmonics array directly into the light probe.
            this.xrLight.lightProbe.sh.fromArray( lightEstimate.sphericalHarmonicsCoefficients );
            this.xrLight.lightProbe.intensity = 1.0;

            // For the directional light we have to normalize the color and set the scalar as the
            // intensity, since WebXR can return color values that exceed 1.0.
            const intensityScalar = Math.max( 1.0,
                Math.max( lightEstimate.primaryLightIntensity.x,
                    Math.max( lightEstimate.primaryLightIntensity.y,
                        lightEstimate.primaryLightIntensity.z ) ) );

            this.xrLight.directionalLight.color.setRGB(
                lightEstimate.primaryLightIntensity.x / intensityScalar,
                lightEstimate.primaryLightIntensity.y / intensityScalar,
                lightEstimate.primaryLightIntensity.z / intensityScalar );
            this.xrLight.directionalLight.intensity = intensityScalar;
            this.xrLight.directionalLight.position.copy( lightEstimate.primaryLightDirection );

            if ( this.estimationStartCallback ) {

                this.estimationStartCallback();
                this.estimationStartCallback = null;

            }

        }

    }

SessionLightProbe.dispose(): void

Returns: void

Code
dispose() {

        this.xrLight = null;
        this.renderer = null;
        this.lightProbe = null;
        this.xrWebGLBinding = null;

    }

Classes

SessionLightProbe

Class Code
class SessionLightProbe {

    constructor( xrLight, renderer, lightProbe, environmentEstimation, estimationStartCallback ) {

        this.xrLight = xrLight;
        this.renderer = renderer;
        this.lightProbe = lightProbe;
        this.xrWebGLBinding = null;
        this.estimationStartCallback = estimationStartCallback;
        this.frameCallback = this.onXRFrame.bind( this );

        const session = renderer.xr.getSession();

        // If the XRWebGLBinding class is available then we can also query an
        // estimated reflection cube map.
        if ( environmentEstimation && 'XRWebGLBinding' in window ) {

            // This is the simplest way I know of to initialize a WebGL cubemap in Three.
            const cubeRenderTarget = new WebGLCubeRenderTarget( 16 );
            xrLight.environment = cubeRenderTarget.texture;

            const gl = renderer.getContext();

            // Ensure that we have any extensions needed to use the preferred cube map format.
            switch ( session.preferredReflectionFormat ) {

                case 'srgba8':
                    gl.getExtension( 'EXT_sRGB' );
                    break;

                case 'rgba16f':
                    gl.getExtension( 'OES_texture_half_float' );
                    break;

            }

            this.xrWebGLBinding = new XRWebGLBinding( session, gl );

            this.lightProbe.addEventListener( 'reflectionchange', () => {

                this.updateReflection();

            } );

        }

        // Start monitoring the XR animation frame loop to look for lighting
        // estimation changes.
        session.requestAnimationFrame( this.frameCallback );

    }

    updateReflection() {

        const textureProperties = this.renderer.properties.get( this.xrLight.environment );

        if ( textureProperties ) {

            const cubeMap = this.xrWebGLBinding.getReflectionCubeMap( this.lightProbe );

            if ( cubeMap ) {

                textureProperties.__webglTexture = cubeMap;

                this.xrLight.environment.needsPMREMUpdate = true;

            }

        }

    }

    onXRFrame( time, xrFrame ) {

        // If either this object or the XREstimatedLight has been destroyed, stop
        // running the frame loop.
        if ( ! this.xrLight ) {

            return;

        }

        const session = xrFrame.session;
        session.requestAnimationFrame( this.frameCallback );

        const lightEstimate = xrFrame.getLightEstimate( this.lightProbe );
        if ( lightEstimate ) {

            // We can copy the estimate's spherical harmonics array directly into the light probe.
            this.xrLight.lightProbe.sh.fromArray( lightEstimate.sphericalHarmonicsCoefficients );
            this.xrLight.lightProbe.intensity = 1.0;

            // For the directional light we have to normalize the color and set the scalar as the
            // intensity, since WebXR can return color values that exceed 1.0.
            const intensityScalar = Math.max( 1.0,
                Math.max( lightEstimate.primaryLightIntensity.x,
                    Math.max( lightEstimate.primaryLightIntensity.y,
                        lightEstimate.primaryLightIntensity.z ) ) );

            this.xrLight.directionalLight.color.setRGB(
                lightEstimate.primaryLightIntensity.x / intensityScalar,
                lightEstimate.primaryLightIntensity.y / intensityScalar,
                lightEstimate.primaryLightIntensity.z / intensityScalar );
            this.xrLight.directionalLight.intensity = intensityScalar;
            this.xrLight.directionalLight.position.copy( lightEstimate.primaryLightDirection );

            if ( this.estimationStartCallback ) {

                this.estimationStartCallback();
                this.estimationStartCallback = null;

            }

        }

    }

    dispose() {

        this.xrLight = null;
        this.renderer = null;
        this.lightProbe = null;
        this.xrWebGLBinding = null;

    }

}

Methods

updateReflection(): void
Code
updateReflection() {

        const textureProperties = this.renderer.properties.get( this.xrLight.environment );

        if ( textureProperties ) {

            const cubeMap = this.xrWebGLBinding.getReflectionCubeMap( this.lightProbe );

            if ( cubeMap ) {

                textureProperties.__webglTexture = cubeMap;

                this.xrLight.environment.needsPMREMUpdate = true;

            }

        }

    }
onXRFrame(time: any, xrFrame: any): void
Code
onXRFrame( time, xrFrame ) {

        // If either this object or the XREstimatedLight has been destroyed, stop
        // running the frame loop.
        if ( ! this.xrLight ) {

            return;

        }

        const session = xrFrame.session;
        session.requestAnimationFrame( this.frameCallback );

        const lightEstimate = xrFrame.getLightEstimate( this.lightProbe );
        if ( lightEstimate ) {

            // We can copy the estimate's spherical harmonics array directly into the light probe.
            this.xrLight.lightProbe.sh.fromArray( lightEstimate.sphericalHarmonicsCoefficients );
            this.xrLight.lightProbe.intensity = 1.0;

            // For the directional light we have to normalize the color and set the scalar as the
            // intensity, since WebXR can return color values that exceed 1.0.
            const intensityScalar = Math.max( 1.0,
                Math.max( lightEstimate.primaryLightIntensity.x,
                    Math.max( lightEstimate.primaryLightIntensity.y,
                        lightEstimate.primaryLightIntensity.z ) ) );

            this.xrLight.directionalLight.color.setRGB(
                lightEstimate.primaryLightIntensity.x / intensityScalar,
                lightEstimate.primaryLightIntensity.y / intensityScalar,
                lightEstimate.primaryLightIntensity.z / intensityScalar );
            this.xrLight.directionalLight.intensity = intensityScalar;
            this.xrLight.directionalLight.position.copy( lightEstimate.primaryLightDirection );

            if ( this.estimationStartCallback ) {

                this.estimationStartCallback();
                this.estimationStartCallback = null;

            }

        }

    }
dispose(): void
Code
dispose() {

        this.xrLight = null;
        this.renderer = null;
        this.lightProbe = null;
        this.xrWebGLBinding = null;

    }

XREstimatedLight

Class Code
export class XREstimatedLight extends Group {

    /**
     * Constructs a new light.
     *
     * @param {WebGLRenderer} renderer - The renderer.
     * @param {boolean} [environmentEstimation=true] - Whether to use environment estimation or not.
     */
    constructor( renderer, environmentEstimation = true ) {

        super();

        /**
         * The light probe that represents the estimated light.
         *
         * @type {LightProbe}
         */
        this.lightProbe = new LightProbe();
        this.lightProbe.intensity = 0;
        this.add( this.lightProbe );

        /**
         * Represents the primary light from the XR environment.
         *
         * @type {DirectionalLight}
         */
        this.directionalLight = new DirectionalLight();
        this.directionalLight.intensity = 0;
        this.add( this.directionalLight );

        /**
         * Will be set to a cube map in the SessionLightProbe if environment estimation is
         * available and requested.
         *
         * @type {?Texture}
         * @default null
         */
        this.environment = null;

        let sessionLightProbe = null;
        let estimationStarted = false;
        renderer.xr.addEventListener( 'sessionstart', () => {

            const session = renderer.xr.getSession();

            if ( 'requestLightProbe' in session ) {

                session.requestLightProbe( {

                    reflectionFormat: session.preferredReflectionFormat

                } ).then( ( probe ) => {

                    sessionLightProbe = new SessionLightProbe( this, renderer, probe, environmentEstimation, () => {

                        estimationStarted = true;

                        // Fired to indicate that the estimated lighting values are now being updated.
                        this.dispatchEvent( { type: 'estimationstart' } );

                    } );

                } );

            }

        } );

        renderer.xr.addEventListener( 'sessionend', () => {

            if ( sessionLightProbe ) {

                sessionLightProbe.dispose();
                sessionLightProbe = null;

            }

            if ( estimationStarted ) {

                // Fired to indicate that the estimated lighting values are no longer being updated.
                this.dispatchEvent( { type: 'estimationend' } );

            }

        } );

        /**
         * Frees the GPU-related resources allocated by this instance. Call this
         * method whenever this instance is no longer used in your app.
         */
        this.dispose = () => {

            if ( sessionLightProbe ) {

                sessionLightProbe.dispose();
                sessionLightProbe = null;

            }

            this.remove( this.lightProbe );
            this.lightProbe = null;

            this.remove( this.directionalLight );
            this.directionalLight = null;

            this.environment = null;

        };

    }

}