Explorar o código

WebGPURenderer: Optimize WebXR render path. (#31134)

* optimize WebXR render path

* address review comments
Rik Cabanier hai 7 meses
pai
achega
76b2a34828

+ 1 - 0
examples/files.json

@@ -462,6 +462,7 @@
 		"webgpu_volume_lighting_rectarea",
 		"webgpu_volume_perlin",
 		"webgpu_water",
+		"webgpu_xr_rollercoaster",
 		"webgpu_xr_cubes",
 		"webgpu_xr_native_layers"
 	],

BIN=BIN
examples/screenshots/webgpu_xr_rollercoaster.jpg


+ 3 - 3
examples/webgpu_xr_native_layers.html

@@ -305,7 +305,7 @@
 				window.addEventListener( 'resize', onWindowResize );
 
 				// set up rollercoaster
-				rollercoasterLayer = renderer.xr.createCylinderLayer( 1, Math.PI / 2, 2, new THREE.Vector3( 0, 1.5, - 0.5 ), new THREE.Quaternion(), 1024, 1024, renderRollercoaster );
+				rollercoasterLayer = renderer.xr.createCylinderLayer( 1, Math.PI / 2, 2, new THREE.Vector3( 0, 1.5, - 0.5 ), new THREE.Quaternion(), 1500, 1000, renderRollercoaster );
 				scene.add( rollercoasterLayer );
 
 				rcscene = new THREE.Scene();
@@ -410,7 +410,7 @@
 				funfairs.push( rcmesh );
 
 				// set up horse animation
-				horseLayer = renderer.xr.createQuadLayer( 1, 1, new THREE.Vector3( - 1.5, 1.5, - 1.5 ), new THREE.Quaternion(), 1024, 1024, renderQuad );
+				horseLayer = renderer.xr.createQuadLayer( 1, 1, new THREE.Vector3( - 1.5, 1.5, - 1.5 ), new THREE.Quaternion(), 800, 800, renderQuad );
 				scene.add( horseLayer );
 
 				horseLayer.geometry = new THREE.CircleGeometry( .5, 64 );
@@ -473,7 +473,7 @@
 
 				const bbox = new THREE.Box3().setFromObject( guiScene );
 
-				guiLayer = renderer.xr.createQuadLayer( 1.2, .8, new THREE.Vector3( 1.5, 1.5, - 1.5 ), new THREE.Quaternion(), 1024, 1024, renderGui );
+				guiLayer = renderer.xr.createQuadLayer( 1.2, .8, new THREE.Vector3( 1.5, 1.5, - 1.5 ), new THREE.Quaternion(), 1280, 800, renderGui );
 				scene.add( guiLayer );
 
 				guiCamera.left = bbox.min.x;

+ 251 - 0
examples/webgpu_xr_rollercoaster.html

@@ -0,0 +1,251 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js vr - roller coaster</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/webgpu": "../build/three.webgpu.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import {
+				RollerCoasterGeometry,
+				RollerCoasterShadowGeometry,
+				RollerCoasterLiftersGeometry,
+				TreesGeometry,
+				SkyGeometry
+			} from 'three/addons/misc/RollerCoaster.js';
+			import { VRButton } from 'three/addons/webxr/VRButton.js';
+
+			let mesh, material, geometry;
+
+			const renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true, colorBufferType: THREE.UnsignedByteType, multiview: false } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.setAnimationLoop( animate );
+			renderer.xr.enabled = true;
+			renderer.xr.setReferenceSpaceType( 'local' );
+			document.body.appendChild( renderer.domElement );
+
+			document.body.appendChild( VRButton.createButton( renderer ) );
+
+			//
+
+			const scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0xf0f0ff );
+
+			const light = new THREE.HemisphereLight( 0xfff0f0, 0x60606, 3 );
+			light.position.set( 1, 1, 1 );
+			scene.add( light );
+
+			const train = new THREE.Object3D();
+			scene.add( train );
+
+			const camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 500 );
+			train.add( camera );
+
+			// environment
+
+			geometry = new THREE.PlaneGeometry( 500, 500, 15, 15 );
+			geometry.rotateX( - Math.PI / 2 );
+
+			const positions = geometry.attributes.position.array;
+			const vertex = new THREE.Vector3();
+
+			for ( let i = 0; i < positions.length; i += 3 ) {
+
+				vertex.fromArray( positions, i );
+
+				vertex.x += Math.random() * 10 - 5;
+				vertex.z += Math.random() * 10 - 5;
+
+				const distance = ( vertex.distanceTo( scene.position ) / 5 ) - 25;
+				vertex.y = Math.random() * Math.max( 0, distance );
+
+				vertex.toArray( positions, i );
+
+			}
+
+			geometry.computeVertexNormals();
+
+			material = new THREE.MeshLambertMaterial( {
+				color: 0x407000
+			} );
+
+			mesh = new THREE.Mesh( geometry, material );
+			scene.add( mesh );
+
+			geometry = new TreesGeometry( mesh );
+			material = new THREE.MeshBasicMaterial( {
+				side: THREE.DoubleSide, vertexColors: true
+			} );
+			mesh = new THREE.Mesh( geometry, material );
+			scene.add( mesh );
+
+			geometry = new SkyGeometry();
+			material = new THREE.MeshBasicMaterial( { color: 0xffffff } );
+			mesh = new THREE.Mesh( geometry, material );
+			scene.add( mesh );
+
+			//
+
+			const PI2 = Math.PI * 2;
+
+			const curve = ( function () {
+
+				const vector = new THREE.Vector3();
+				const vector2 = new THREE.Vector3();
+
+				return {
+
+					getPointAt: function ( t ) {
+
+						t = t * PI2;
+
+						const x = Math.sin( t * 3 ) * Math.cos( t * 4 ) * 50;
+						const y = Math.sin( t * 10 ) * 2 + Math.cos( t * 17 ) * 2 + 5;
+						const z = Math.sin( t ) * Math.sin( t * 4 ) * 50;
+
+						return vector.set( x, y, z ).multiplyScalar( 2 );
+
+					},
+
+					getTangentAt: function ( t ) {
+
+						const delta = 0.0001;
+						const t1 = Math.max( 0, t - delta );
+						const t2 = Math.min( 1, t + delta );
+
+						return vector2.copy( this.getPointAt( t2 ) )
+							.sub( this.getPointAt( t1 ) ).normalize();
+
+					}
+
+				};
+
+			} )();
+
+			geometry = new RollerCoasterGeometry( curve, 1500 );
+			material = new THREE.MeshPhongMaterial( {
+				vertexColors: true
+			} );
+			mesh = new THREE.Mesh( geometry, material );
+			scene.add( mesh );
+
+			geometry = new RollerCoasterLiftersGeometry( curve, 100 );
+			material = new THREE.MeshPhongMaterial();
+			mesh = new THREE.Mesh( geometry, material );
+			mesh.position.y = 0.1;
+			scene.add( mesh );
+
+			geometry = new RollerCoasterShadowGeometry( curve, 500 );
+			material = new THREE.MeshBasicMaterial( {
+				color: 0x305000, depthWrite: false, transparent: true
+			} );
+			mesh = new THREE.Mesh( geometry, material );
+			mesh.position.y = 0.1;
+			scene.add( mesh );
+
+			const funfairs = [];
+
+			//
+
+			geometry = new THREE.CylinderGeometry( 10, 10, 5, 15 );
+			material = new THREE.MeshLambertMaterial( {
+				color: 0xff8080
+			} );
+			mesh = new THREE.Mesh( geometry, material );
+			mesh.position.set( - 80, 10, - 70 );
+			mesh.rotation.x = Math.PI / 2;
+			scene.add( mesh );
+
+			funfairs.push( mesh );
+
+			geometry = new THREE.CylinderGeometry( 5, 6, 4, 10 );
+			material = new THREE.MeshLambertMaterial( {
+				color: 0x8080ff
+			} );
+			mesh = new THREE.Mesh( geometry, material );
+			mesh.position.set( 50, 2, 30 );
+			scene.add( mesh );
+
+			funfairs.push( mesh );
+
+			//
+
+			window.addEventListener( 'resize', onWindowResize );
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			const position = new THREE.Vector3();
+			const tangent = new THREE.Vector3();
+
+			const lookAt = new THREE.Vector3();
+
+			let velocity = 0;
+			let progress = 0;
+
+			let prevTime = performance.now();
+
+			function animate() {
+
+				const time = performance.now();
+				const delta = time - prevTime;
+
+				for ( let i = 0; i < funfairs.length; i ++ ) {
+
+					funfairs[ i ].rotation.y = time * 0.0004;
+
+				}
+
+				//
+
+				progress += velocity;
+				progress = progress % 1;
+
+				position.copy( curve.getPointAt( progress ) );
+				position.y += 0.3;
+
+				train.position.copy( position );
+
+				tangent.copy( curve.getTangentAt( progress ) );
+
+				velocity -= tangent.y * 0.0000001 * delta;
+				velocity = Math.max( 0.00004, Math.min( 0.0002, velocity ) );
+
+				train.lookAt( lookAt.copy( position ).sub( tangent ) );
+
+				//
+
+				renderer.render( scene, camera );
+
+				prevTime = time;
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 18 - 1
src/renderers/common/Renderer.js

@@ -314,6 +314,8 @@ class Renderer {
 		 */
 		this._scissor = new Vector4( 0, 0, this._width, this._height );
 
+		this._forceViewport = false;
+
 		/**
 		 * Whether the scissor test should be enabled or not.
 		 *
@@ -1250,6 +1252,7 @@ class Renderer {
 		frameBufferTarget.scissor.multiplyScalar( this._pixelRatio );
 		frameBufferTarget.scissorTest = this._scissorTest;
 		frameBufferTarget.multiview = outputRenderTarget !== null ? outputRenderTarget.multiview : false;
+		frameBufferTarget.resolveDepthBuffer = outputRenderTarget !== null ? outputRenderTarget.resolveDepthBuffer : true;
 
 		return frameBufferTarget;
 
@@ -1380,7 +1383,7 @@ class Renderer {
 		renderContext.viewportValue.height >>= activeMipmapLevel;
 		renderContext.viewportValue.minDepth = minDepth;
 		renderContext.viewportValue.maxDepth = maxDepth;
-		renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false;
+		renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false || this._forceViewport;
 
 		renderContext.scissorValue.copy( scissor ).multiplyScalar( pixelRatio ).floor();
 		renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals( _screen ) === false;
@@ -1505,6 +1508,17 @@ class Renderer {
 
 	}
 
+	_setXRLayerSize( width, height ) {
+
+		this._width = width;
+		this._height = height;
+
+		this._forceViewport = true;
+
+		this.setViewport( 0, 0, width, height );
+
+	}
+
 	/**
 	 * The output pass performs tone mapping and color space conversion.
 	 *
@@ -1688,6 +1702,7 @@ class Renderer {
 
 		this.domElement.width = Math.floor( width * pixelRatio );
 		this.domElement.height = Math.floor( height * pixelRatio );
+		this._forceViewport = false;
 
 		this.setViewport( 0, 0, width, height );
 
@@ -1713,6 +1728,8 @@ class Renderer {
 		this.domElement.width = Math.floor( width * this._pixelRatio );
 		this.domElement.height = Math.floor( height * this._pixelRatio );
 
+		this._forceViewport = false;
+
 		if ( updateStyle === true ) {
 
 			this.domElement.style.width = width + 'px';

+ 48 - 15
src/renderers/common/XRManager.js

@@ -11,6 +11,8 @@ import { AddEquation, BackSide, CustomBlending, DepthFormat, DepthStencilFormat,
 import { DepthTexture } from '../../textures/DepthTexture.js';
 import { XRRenderTarget } from './XRRenderTarget.js';
 import { CylinderGeometry } from '../../geometries/CylinderGeometry.js';
+import QuadMesh from './QuadMesh.js';
+import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
 import { PlaneGeometry } from '../../geometries/PlaneGeometry.js';
 import { MeshBasicMaterial } from '../../materials/MeshBasicMaterial.js';
 import { Mesh } from '../../objects/Mesh.js';
@@ -169,6 +171,8 @@ class XRManager extends EventDispatcher {
 		 */
 		this._supportsLayers = false;
 
+		this._frameBufferTargets = null;
+
 		/**
 		 * Helper function to create native WebXR Layer.
 		 *
@@ -788,12 +792,17 @@ class XRManager extends EventDispatcher {
 
 		const translationObject = new Vector3();
 		const quaternionObject = new Quaternion();
+		const renderer = this._renderer;
 
 		const wasPresenting = this.isPresenting;
-		const rendererOutputTarget = this._renderer.getOutputRenderTarget();
-		const rendererFramebufferTarget = this._renderer._frameBufferTarget;
+		const rendererOutputTarget = renderer.getOutputRenderTarget();
+		const rendererFramebufferTarget = renderer._frameBufferTarget;
 		this.isPresenting = false;
 
+		const rendererSize = new Vector2();
+		renderer.getSize( rendererSize );
+		const rendererQuad = renderer._quad;
+
 		for ( const layer of this._layers ) {
 
 			layer.renderTarget.isXRRenderTarget = this._session !== null;
@@ -804,28 +813,49 @@ class XRManager extends EventDispatcher {
 				layer.xrlayer.transform = new XRRigidTransform( layer.plane.getWorldPosition( translationObject ), layer.plane.getWorldQuaternion( quaternionObject ) );
 
 				const glSubImage = this._glBinding.getSubImage( layer.xrlayer, this._xrFrame );
-				this._renderer.backend.setXRRenderTargetTextures(
+				renderer.backend.setXRRenderTargetTextures(
 					layer.renderTarget,
 					glSubImage.colorTexture,
 					undefined );
 
-				this._renderer.setOutputRenderTarget( layer.renderTarget );
-				this._renderer.setRenderTarget( null );
+				renderer._setXRLayerSize( layer.renderTarget.width, layer.renderTarget.height );
+				renderer.setOutputRenderTarget( layer.renderTarget );
+				renderer.setRenderTarget( null );
+				renderer._frameBufferTarget = null;
+
+				this._frameBufferTargets || ( this._frameBufferTargets = new WeakMap() );
+				const { frameBufferTarget, quad } = this._frameBufferTargets.get( layer.renderTarget ) || { frameBufferTarget: null, quad: null };
+				if ( ! frameBufferTarget ) {
+
+					renderer._quad = new QuadMesh( new NodeMaterial() );
+					this._frameBufferTargets.set( layer.renderTarget, { frameBufferTarget: renderer._getFrameBufferTarget(), quad: renderer._quad } );
+
+				} else {
+
+					renderer._frameBufferTarget = frameBufferTarget;
+					renderer._quad = quad;
+
+				}
+
+				layer.rendercall();
+
+				renderer._frameBufferTarget = null;
 
 			} else {
 
-				this._renderer.setRenderTarget( layer.renderTarget );
+				renderer.setRenderTarget( layer.renderTarget );
+				layer.rendercall();
 
 			}
 
-			layer.rendercall();
-
 		}
 
+		renderer.setRenderTarget( null );
+		renderer.setOutputRenderTarget( rendererOutputTarget );
+		renderer._frameBufferTarget = rendererFramebufferTarget;
+		renderer._setXRLayerSize( rendererSize.x, rendererSize.y );
+		renderer._quad = rendererQuad;
 		this.isPresenting = wasPresenting;
-		this._renderer.setRenderTarget( null );
-		this._renderer.setOutputRenderTarget( rendererOutputTarget );
-		this._renderer._frameBufferTarget = rendererFramebufferTarget;
 
 	}
 
@@ -904,7 +934,8 @@ class XRManager extends EventDispatcher {
 				const projectionlayerInit = {
 					colorFormat: gl.RGBA8,
 					depthFormat: glDepthFormat,
-					scaleFactor: this._framebufferScaleFactor
+					scaleFactor: this._framebufferScaleFactor,
+					clearOnAccess: false
 				};
 
 				if ( this._useMultiviewIfPossible && renderer.hasFeature( 'OVR_multiview2' ) ) {
@@ -922,7 +953,7 @@ class XRManager extends EventDispatcher {
 				this._glProjLayer = glProjLayer;
 
 				renderer.setPixelRatio( 1 );
-				renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false );
+				renderer._setXRLayerSize( glProjLayer.textureWidth, glProjLayer.textureHeight );
 
 				const depth = this._useMultiview ? 2 : 1;
 				const depthTexture = new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat, depth );
@@ -1453,7 +1484,8 @@ function createXRLayer( layer ) {
 			height: layer.height / 2,
 			space: this._referenceSpace,
 			viewPixelWidth: layer.pixelwidth,
-			viewPixelHeight: layer.pixelheight
+			viewPixelHeight: layer.pixelheight,
+			clearOnAccess: false
 		} );
 
 	} else {
@@ -1465,7 +1497,8 @@ function createXRLayer( layer ) {
 			aspectRatio: layer.aspectRatio,
 			space: this._referenceSpace,
 			viewPixelWidth: layer.pixelwidth,
-			viewPixelHeight: layer.pixelheight
+			viewPixelHeight: layer.pixelheight,
+			clearOnAccess: false
 		} );
 
 	}

+ 14 - 4
src/renderers/webgl-fallback/WebGLBackend.js

@@ -472,10 +472,8 @@ class WebGLBackend extends Backend {
 		this._currentContext = renderContext;
 
 		this._setFramebuffer( renderContext );
-
 		this.clear( renderContext.clearColor, renderContext.clearDepth, renderContext.clearStencil, renderContext, false );
 
-
 		const occlusionQueryCount = renderContext.occlusionQueryCount;
 
 		if ( occlusionQueryCount > 0 ) {
@@ -546,7 +544,7 @@ class WebGLBackend extends Backend {
 
 			const renderTargetContextData = this.get( renderContext.renderTarget );
 
-			const { samples } = renderContext.renderTarget;
+			const { resolveDepthBuffer, samples } = renderContext.renderTarget;
 
 			if ( samples > 0 && this._useMultisampledExtension( renderContext.renderTarget ) === false ) {
 
@@ -593,8 +591,13 @@ class WebGLBackend extends Backend {
 
 				}
 
-			}
+			} else if ( resolveDepthBuffer === false ) {
+
+				const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ];
+				state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
+				gl.invalidateFramebuffer( gl.DRAW_FRAMEBUFFER, renderTargetContextData.depthInvalidationArray );
 
+			}
 
 		}
 
@@ -2030,6 +2033,7 @@ class WebGLBackend extends Backend {
 				state.bindFramebuffer( gl.FRAMEBUFFER, fb );
 
 				const textures = descriptor.textures;
+				const depthInvalidationArray = [];
 
 				if ( isCube ) {
 
@@ -2089,11 +2093,14 @@ class WebGLBackend extends Backend {
 					const renderbuffer = gl.createRenderbuffer();
 					this.textureUtils.setupRenderBufferStorage( renderbuffer, descriptor, 0, useMultisampledRTT );
 					renderTargetContextData.xrDepthRenderbuffer = renderbuffer;
+					depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT );
 
 				} else {
 
 					if ( descriptor.depthTexture !== null ) {
 
+						depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT );
+
 						const textureData = this.get( descriptor.depthTexture );
 						const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
 						textureData.renderTarget = descriptor.renderTarget;
@@ -2127,6 +2134,9 @@ class WebGLBackend extends Backend {
 
 				}
 
+				renderTargetContextData.depthInvalidationArray = depthInvalidationArray;
+
+
 			} else {
 
 				const isRenderCameraDepthArray = this._isRenderCameraDepthArray( descriptor );

粤ICP备19079148号