Spaces:
Running
Running
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 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; | |
} | |
} | |
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; | |
}; | |
} | |
} | |