224 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			224 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
									DirectionalLight,
							 | 
						||
| 
								 | 
							
									Group,
							 | 
						||
| 
								 | 
							
									LightProbe,
							 | 
						||
| 
								 | 
							
									WebGLCubeRenderTarget
							 | 
						||
| 
								 | 
							
								} from 'three';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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 obejct 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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export class XREstimatedLight extends Group {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									constructor( renderer, environmentEstimation = true ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										super();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.lightProbe = new LightProbe();
							 | 
						||
| 
								 | 
							
										this.lightProbe.intensity = 0;
							 | 
						||
| 
								 | 
							
										this.add( this.lightProbe );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										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.
							 | 
						||
| 
								 | 
							
										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' } );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Done inline to provide access to sessionLightProbe.
							 | 
						||
| 
								 | 
							
										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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 |