Michael Herzog 1 год назад
Родитель
Сommit
35a466df78

+ 0 - 224
examples/jsm/webxr/XRButtonGPU.js

@@ -1,224 +0,0 @@
-// temporary version of XRButton until WebGPURenderer fully support layers
-
-class XRButton {
-
-	static createButton( renderer, sessionInit = {} ) {
-
-		const button = document.createElement( 'button' );
-
-		function showStartXR( mode ) {
-
-			let currentSession = null;
-
-			async function onSessionStarted( session ) {
-
-				session.addEventListener( 'end', onSessionEnded );
-
-				await renderer.xr.setSession( session );
-
-				button.textContent = 'STOP XR';
-
-				currentSession = session;
-
-			}
-
-			function onSessionEnded( /*event*/ ) {
-
-				currentSession.removeEventListener( 'end', onSessionEnded );
-
-				button.textContent = 'START XR';
-
-				currentSession = null;
-
-			}
-
-			//
-
-			button.style.display = '';
-
-			button.style.cursor = 'pointer';
-			button.style.left = 'calc(50% - 50px)';
-			button.style.width = '100px';
-
-			button.textContent = 'START XR';
-
-			const sessionOptions = {
-				...sessionInit,
-				optionalFeatures: [
-					'local-floor',
-					'bounded-floor',
-					...( sessionInit.optionalFeatures || [] )
-				],
-			};
-
-			button.onmouseenter = function () {
-
-				button.style.opacity = '1.0';
-
-			};
-
-			button.onmouseleave = function () {
-
-				button.style.opacity = '0.5';
-
-			};
-
-			button.onclick = function () {
-
-				if ( currentSession === null ) {
-
-					navigator.xr.requestSession( mode, sessionOptions )
-						.then( onSessionStarted );
-
-				} else {
-
-					currentSession.end();
-
-					if ( navigator.xr.offerSession !== undefined ) {
-
-						navigator.xr.offerSession( mode, sessionOptions )
-							.then( onSessionStarted )
-							.catch( ( err ) => {
-
-								console.warn( err );
-
-							} );
-
-					}
-
-				}
-
-			};
-
-			if ( navigator.xr.offerSession !== undefined ) {
-
-				navigator.xr.offerSession( mode, sessionOptions )
-					.then( onSessionStarted )
-					.catch( ( err ) => {
-
-						console.warn( err );
-
-					} );
-
-			}
-
-		}
-
-		function disableButton() {
-
-			button.style.display = '';
-
-			button.style.cursor = 'auto';
-			button.style.left = 'calc(50% - 75px)';
-			button.style.width = '150px';
-
-			button.onmouseenter = null;
-			button.onmouseleave = null;
-
-			button.onclick = null;
-
-		}
-
-		function showXRNotSupported() {
-
-			disableButton();
-
-			button.textContent = 'XR NOT SUPPORTED';
-
-		}
-
-		function showXRNotAllowed( exception ) {
-
-			disableButton();
-
-			console.warn( 'Exception when trying to call xr.isSessionSupported', exception );
-
-			button.textContent = 'XR NOT ALLOWED';
-
-		}
-
-		function stylizeElement( element ) {
-
-			element.style.position = 'absolute';
-			element.style.bottom = '20px';
-			element.style.padding = '12px 6px';
-			element.style.border = '1px solid #fff';
-			element.style.borderRadius = '4px';
-			element.style.background = 'rgba(0,0,0,0.1)';
-			element.style.color = '#fff';
-			element.style.font = 'normal 13px sans-serif';
-			element.style.textAlign = 'center';
-			element.style.opacity = '0.5';
-			element.style.outline = 'none';
-			element.style.zIndex = '999';
-
-		}
-
-		if ( 'xr' in navigator ) {
-
-			button.id = 'XRButton';
-			button.style.display = 'none';
-
-			stylizeElement( button );
-
-			navigator.xr.isSessionSupported( 'immersive-ar' )
-				.then( function ( supported ) {
-
-					if ( supported ) {
-
-						showStartXR( 'immersive-ar' );
-
-					} else {
-
-						navigator.xr.isSessionSupported( 'immersive-vr' )
-							.then( function ( supported ) {
-
-								if ( supported ) {
-
-									showStartXR( 'immersive-vr' );
-
-								} else {
-
-									showXRNotSupported();
-
-								}
-
-							} ).catch( showXRNotAllowed );
-
-					}
-
-				} ).catch( showXRNotAllowed );
-
-			return button;
-
-		} else {
-
-			const message = document.createElement( 'a' );
-
-			if ( window.isSecureContext === false ) {
-
-				message.href = document.location.href.replace( /^http:/, 'https:' );
-				message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message
-
-			} else {
-
-				message.href = 'https://immersiveweb.dev/';
-				message.innerHTML = 'WEBXR NOT AVAILABLE';
-
-			}
-
-			message.style.left = 'calc(50% - 90px)';
-			message.style.width = '180px';
-			message.style.textDecoration = 'none';
-
-			stylizeElement( message );
-
-			return message;
-
-		}
-
-	}
-
-}
-
-export { XRButton };

+ 1 - 1
examples/webgpu_xr_cubes.html

@@ -28,7 +28,7 @@
 			import * as THREE from 'three';
 
 			import { BoxLineGeometry } from 'three/addons/geometries/BoxLineGeometry.js';
-			import { XRButton } from 'three/addons/webxr/XRButtonGPU.js';
+			import { XRButton } from 'three/addons/webxr/XRButton.js';
 			import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
 
 			const clock = new THREE.Clock();

+ 0 - 23
src/renderers/common/Renderer.js

@@ -2033,29 +2033,6 @@ class Renderer {
 
 	}
 
-	/**
-	 * Ensures the renderer is XR compatible.
-	 *
-	 * @async
-	 * @return {Promise} A Promise that resolve when the renderer is XR compatible.
-	 */
-	async makeXRCompatible() {
-
-		await this.backend.makeXRCompatible();
-
-	}
-
-	/**
-	 * Sets the XR rendering destination.
-	 *
-	 * @param {WebGLFramebuffer} xrTarget - The XR target.
-	 */
-	setXRTarget( xrTarget ) {
-
-		this.backend.setXRTarget( xrTarget );
-
-	}
-
 	/**
 	 * Sets the given render target. Calling this method means the renderer does not
 	 * target the default framebuffer (meaning the canvas) anymore but a custom framebuffer.

+ 10 - 5
src/renderers/common/Textures.js

@@ -124,19 +124,24 @@ class Textures extends DataMap {
 
 		//
 
+
 		const options = { sampleCount };
 
-		for ( let i = 0; i < textures.length; i ++ ) {
+		if ( ( renderTarget.isXRRenderTarget === true && renderTarget.hasExternalTextures === true ) === false ) {
+
+			for ( let i = 0; i < textures.length; i ++ ) {
 
-			const texture = textures[ i ];
+				const texture = textures[ i ];
 
-			if ( textureNeedsUpdate ) texture.needsUpdate = true;
+				if ( textureNeedsUpdate ) texture.needsUpdate = true;
 
-			this.updateTexture( texture, options );
+				this.updateTexture( texture, options );
+
+			}
 
 		}
 
-		if ( depthTexture ) {
+		if ( depthTexture && renderTarget.autoAllocateDepthBuffer !== false ) {
 
 			this.updateTexture( depthTexture, options );
 

+ 154 - 34
src/renderers/common/XRManager.js

@@ -7,7 +7,8 @@ import { Vector2 } from '../../math/Vector2.js';
 import { Vector3 } from '../../math/Vector3.js';
 import { Vector4 } from '../../math/Vector4.js';
 import { WebXRController } from '../webxr/WebXRController.js';
-import { RGBAFormat, UnsignedByteType } from '../../constants.js';
+import { DepthFormat, DepthStencilFormat, RGBAFormat, UnsignedByteType, UnsignedInt248Type, UnsignedIntType } from '../../constants.js';
+import { DepthTexture } from '../../textures/DepthTexture.js';
 
 const _cameraLPos = /*@__PURE__*/ new Vector3();
 const _cameraRPos = /*@__PURE__*/ new Vector3();
@@ -287,6 +288,24 @@ class XRManager extends EventDispatcher {
 		 */
 		this._glBaseLayer = null;
 
+		/**
+		 * A reference to the current XR binding.
+		 *
+		 * @private
+		 * @type {XRWebGLBinding?}
+		 * @default null
+		 */
+		this._glBinding = null;
+
+		/**
+		 * A reference to the current XR projection layer.
+		 *
+		 * @private
+		 * @type {XRProjectionLayer?}
+		 * @default null
+		 */
+		this._glProjLayer = null;
+
 		/**
 		 * A reference to the current XR frame.
 		 *
@@ -296,6 +315,15 @@ class XRManager extends EventDispatcher {
 		 */
 		this._xrFrame = null;
 
+		/**
+		 * Whether to use projection layers or not.
+		 *
+		 * @private
+		 * @type {Boolean}
+		 * @readonly
+		 */
+		this._useLayers = ( typeof XRWebGLBinding !== 'undefined' && 'createProjectionLayer' in XRWebGLBinding.prototype ); // eslint-disable-line compat/compat
+
 	}
 
 	/**
@@ -349,11 +377,11 @@ class XRManager extends EventDispatcher {
 	/**
 	 * Returns the foveation value.
 	 *
-	 * @return {Number|undefined} The foveation value. Returns `undefined` if no base layer is defined.
+	 * @return {Number|undefined} The foveation value. Returns `undefined` if no base or projection layer is defined.
 	 */
 	getFoveation() {
 
-		if ( this._glBaseLayer === null ) {
+		if ( this._glProjLayer === null && this._glBaseLayer === null ) {
 
 			return undefined;
 
@@ -373,6 +401,12 @@ class XRManager extends EventDispatcher {
 
 		this._foveation = foveation;
 
+		if ( this._glProjLayer !== null ) {
+
+			this._glProjLayer.fixedFoveation = foveation;
+
+		}
+
 		if ( this._glBaseLayer !== null && this._glBaseLayer.fixedFoveation !== undefined ) {
 
 			this._glBaseLayer.fixedFoveation = foveation;
@@ -523,13 +557,15 @@ class XRManager extends EventDispatcher {
 	async setSession( session ) {
 
 		const renderer = this._renderer;
+		const backend = renderer.backend;
+
 		const gl = renderer.getContext();
 
 		this._session = session;
 
 		if ( session !== null ) {
 
-			if ( renderer.backend.isWebGPUBackend === true ) throw new Error( 'THREE.XRManager: XR is currently not supported with a WebGPU backend. Use WebGL by passing "{ forceWebGL: true }" to the constructor of the renderer.' );
+			if ( backend.isWebGPUBackend === true ) throw new Error( 'THREE.XRManager: XR is currently not supported with a WebGPU backend. Use WebGL by passing "{ forceWebGL: true }" to the constructor of the renderer.' );
 
 			this._currentRenderTarget = renderer.getRenderTarget();
 
@@ -542,7 +578,7 @@ class XRManager extends EventDispatcher {
 			session.addEventListener( 'end', this._onSessionEnd );
 			session.addEventListener( 'inputsourceschange', this._onInputSourcesChange );
 
-			await renderer.makeXRCompatible();
+			await backend.makeXRCompatible();
 
 			this._currentPixelRatio = renderer.getPixelRatio();
 			renderer.getSize( this._currentSize );
@@ -551,37 +587,93 @@ class XRManager extends EventDispatcher {
 			this._currentAnimationLoop = renderer._animation.getAnimationLoop();
 			renderer._animation.stop();
 
+			//
+
 			const attributes = gl.getContextAttributes();
 
-			const layerInit = {
-				antialias: attributes.antialias,
-				alpha: true,
-				depth: attributes.depth,
-				stencil: attributes.stencil,
-				framebufferScaleFactor: this.getFramebufferScaleFactor()
-			};
-
-			const glBaseLayer = new XRWebGLLayer( session, gl, layerInit );
-			this._glBaseLayer = glBaseLayer;
-
-			session.updateRenderState( { baseLayer: glBaseLayer } );
-
-			renderer.setPixelRatio( 1 );
-			renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false );
-
-			this._xrRenderTarget = new RenderTarget(
-				glBaseLayer.framebufferWidth,
-				glBaseLayer.framebufferHeight,
-				{
-					format: RGBAFormat,
-					type: UnsignedByteType,
-					colorSpace: renderer.outputColorSpace,
-					stencilBuffer: attributes.stencil
+			if ( this._useLayers === true ) {
+
+				// default path using XRWebGLBinding/XRProjectionLayer
+
+				let depthFormat = null;
+				let depthType = null;
+				let glDepthFormat = null;
+
+				if ( attributes.depth ) {
+
+					glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24;
+					depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat;
+					depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType;
+
 				}
-			);
+
+				const projectionlayerInit = {
+					colorFormat: gl.RGBA8,
+					depthFormat: glDepthFormat,
+					scaleFactor: this._framebufferScaleFactor
+				};
+
+				const glBinding = new XRWebGLBinding( session, gl );
+				const glProjLayer = glBinding.createProjectionLayer( projectionlayerInit );
+
+				this._glBinding = glBinding;
+				this._glProjLayer = glProjLayer;
+
+				session.updateRenderState( { layers: [ glProjLayer ] } );
+
+				renderer.setPixelRatio( 1 );
+				renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false );
+
+				this._xrRenderTarget = new RenderTarget(
+					glProjLayer.textureWidth,
+					glProjLayer.textureHeight,
+					{
+						format: RGBAFormat,
+						type: UnsignedByteType,
+						colorSpace: renderer.outputColorSpace,
+						depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ),
+						stencilBuffer: attributes.stencil
+					} );
+
+				this._xrRenderTarget.hasExternalTextures = true;
+
+			} else {
+
+				// fallback to XRWebGLLayer
+
+				const layerInit = {
+					antialias: attributes.antialias,
+					alpha: true,
+					depth: attributes.depth,
+					stencil: attributes.stencil,
+					framebufferScaleFactor: this.getFramebufferScaleFactor()
+				};
+
+				const glBaseLayer = new XRWebGLLayer( session, gl, layerInit );
+				this._glBaseLayer = glBaseLayer;
+
+				session.updateRenderState( { baseLayer: glBaseLayer } );
+
+				renderer.setPixelRatio( 1 );
+				renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false );
+
+				this._xrRenderTarget = new RenderTarget(
+					glBaseLayer.framebufferWidth,
+					glBaseLayer.framebufferHeight,
+					{
+						format: RGBAFormat,
+						type: UnsignedByteType,
+						colorSpace: renderer.outputColorSpace,
+						stencilBuffer: attributes.stencil
+					}
+				);
+
+			}
 
 			this._xrRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278
 
+			//
+
 			this.setFoveation( this.getFoveation() );
 
 			this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() );
@@ -882,7 +974,7 @@ function onSessionEnd() {
 
 	// restore framebuffer/rendering state
 
-	renderer.setXRTarget( null );
+	renderer.backend.setXRTarget( null );
 	renderer.setRenderTarget( this._currentRenderTarget );
 
 	this._session = null;
@@ -980,6 +1072,7 @@ function onAnimationFrame( time, frame ) {
 
 	const cameraXR = this._cameraXR;
 	const renderer = this._renderer;
+	const backend = renderer.backend;
 
 	const glBaseLayer = this._glBaseLayer;
 
@@ -992,8 +1085,11 @@ function onAnimationFrame( time, frame ) {
 
 		const views = pose.views;
 
-		renderer.setXRTarget( glBaseLayer.framebuffer );
-		renderer.setRenderTarget( this._xrRenderTarget );
+		if ( this._glBaseLayer !== null ) {
+
+			backend.setXRTarget( glBaseLayer.framebuffer );
+
+		}
 
 		let cameraXRNeedsUpdate = false;
 
@@ -1010,7 +1106,29 @@ function onAnimationFrame( time, frame ) {
 
 			const view = views[ i ];
 
-			const viewport = glBaseLayer.getViewport( view );
+			let viewport;
+
+			if ( this._useLayers === true ) {
+
+				const glSubImage = this._glBinding.getViewSubImage( this._glProjLayer, view );
+				viewport = glSubImage.viewport;
+
+				// For side-by-side projection, we only produce a single texture for both eyes.
+				if ( i === 0 ) {
+
+					backend.setRenderTargetTextures(
+						this._xrRenderTarget,
+						glSubImage.colorTexture,
+						this._glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture
+					);
+
+				}
+
+			} else {
+
+				viewport = glBaseLayer.getViewport( view );
+
+			}
 
 			let camera = this._cameras[ i ];
 
@@ -1044,6 +1162,8 @@ function onAnimationFrame( time, frame ) {
 
 		}
 
+		renderer.setRenderTarget( this._xrRenderTarget );
+
 	}
 
 	//

+ 49 - 1
src/renderers/webgl-fallback/WebGLBackend.js

@@ -322,6 +322,27 @@ class WebGLBackend extends Backend {
 
 	}
 
+	/**
+	 * Configures the render target with external textures.
+	 *
+	 * @param {RenderTarget} renderTarget - The render target.
+	 * @param {WebGLTexture} colorTexture - A native color texture.
+	 * @param {WebGLTexture?} [depthTexture=null] - A native depth texture.
+	 */
+	setRenderTargetTextures( renderTarget, colorTexture, depthTexture = null ) {
+
+		this.set( renderTarget.texture, { textureGPU: colorTexture } );
+
+		if ( depthTexture !== null ) {
+
+			this.set( renderTarget.depthTexture, { textureGPU: depthTexture } );
+
+			renderTarget.autoAllocateDepthBuffer = false;
+
+		}
+
+	}
+
 	/**
 	 * Inits a time stamp query for the given render context.
 	 *
@@ -1908,6 +1929,7 @@ class WebGLBackend extends Backend {
 			const isRenderTarget3D = renderTarget.isRenderTarget3D === true;
 			const isRenderTargetArray = renderTarget.isRenderTargetArray === true;
 			const isXRRenderTarget = renderTarget.isXRRenderTarget === true;
+			const hasExternalTextures = renderTarget.hasExternalTextures === true;
 
 			let msaaFb = renderTargetContextData.msaaFrameBuffer;
 			let depthRenderbuffer = renderTargetContextData.depthRenderbuffer;
@@ -1922,7 +1944,7 @@ class WebGLBackend extends Backend {
 
 				fb = renderTargetContextData.cubeFramebuffers[ cacheKey ];
 
-			} else if ( isXRRenderTarget ) {
+			} else if ( isXRRenderTarget && hasExternalTextures === false ) {
 
 				fb = this._xrFamebuffer;
 
@@ -1996,6 +2018,32 @@ class WebGLBackend extends Backend {
 
 				}
 
+			} else {
+
+				// rebind external XR textures
+
+				if ( isXRRenderTarget && hasExternalTextures ) {
+
+					state.bindFramebuffer( gl.FRAMEBUFFER, fb );
+
+					// rebind color
+
+					const textureData = this.get( descriptor.textures[ 0 ] );
+					gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0 );
+
+					// rebind depth
+
+					if ( descriptor.depthTexture !== null ) {
+
+						const textureData = this.get( descriptor.depthTexture );
+						const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
+
+						gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 );
+
+					}
+
+				}
+
 			}
 
 			if ( samples > 0 ) {

粤ICP备19079148号