Sfoglia il codice sorgente

Nodes: Add `StereoPassNode`. (#29173)

* Nodes: Add `StereoPassNode`.

* Exampels: Improve title.
Michael Herzog 1 anno fa
parent
commit
cd8f55ceb8

+ 1 - 0
examples/files.json

@@ -324,6 +324,7 @@
 		"webgpu_custom_fog",
 		"webgpu_custom_fog_background",
 		"webgpu_depth_texture",
+		"webgpu_display_stereo",
 		"webgpu_equirectangular",
 		"webgpu_instance_mesh",
 		"webgpu_instance_points",

BIN
examples/screenshots/webgpu_display_stereo.jpg


+ 162 - 0
examples/webgpu_display_stereo.html

@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - stereo</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - stereo. skybox by <a href="http://www.zfight.com/" target="_blank" rel="noopener">Jochum Skoglund</a>
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/tsl": "../build/three.webgpu.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { stereoPass } from 'three/tsl';
+
+			let camera, scene, renderer, postProcessing;
+
+			let mesh, dummy;
+
+			let mouseX = 0, mouseY = 0;
+
+			let windowHalfX = window.innerWidth / 2;
+			let windowHalfY = window.innerHeight / 2;
+
+			const position = new THREE.Vector3();
+
+			init();
+
+			function init() {
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.z = 3;
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.CubeTextureLoader()
+					.setPath( 'textures/cube/Park3Med/' )
+					.load( [ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg' ] );
+
+				const geometry = new THREE.SphereGeometry( 0.1, 32, 16 );
+
+				const textureCube = new THREE.CubeTextureLoader()
+					.setPath( 'textures/cube/Park3Med/' )
+					.load( [ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg' ] );
+				textureCube.mapping = THREE.CubeRefractionMapping;
+
+				const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube, refractionRatio: 0.95 } );
+
+				mesh = new THREE.InstancedMesh( geometry, material, 500 );
+				mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
+			
+				dummy = new THREE.Mesh();
+
+				for ( let i = 0; i < 500; i ++ ) {
+
+					dummy.position.x = Math.random() * 10 - 5;
+					dummy.position.y = Math.random() * 10 - 5;
+					dummy.position.z = Math.random() * 10 - 5;
+					dummy.scale.x = dummy.scale.y = dummy.scale.z = Math.random() * 3 + 1;
+			
+					dummy.updateMatrix();
+
+					mesh.setMatrixAt( i, dummy.matrix );
+
+				}
+
+				scene.add( mesh );
+
+				//
+
+				renderer = new THREE.WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				postProcessing = new THREE.PostProcessing( renderer );
+				const pass = stereoPass( scene, camera );
+				postProcessing.outputNode = pass;
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				document.addEventListener( 'mousemove', onDocumentMouseMove );
+
+			}
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = ( event.clientX - windowHalfX ) * 0.01;
+				mouseY = ( event.clientY - windowHalfY ) * 0.01;
+
+			}
+
+			function extractPosition( matrix, position ) {
+
+				position.x = matrix.elements[ 12 ];
+				position.y = matrix.elements[ 13 ];
+				position.z = matrix.elements[ 14 ];
+
+			}
+
+			//
+
+			function animate() {
+
+				const timer = 0.0001 * Date.now();
+
+				camera.position.x += ( mouseX - camera.position.x ) * .05;
+				camera.position.y += ( - mouseY - camera.position.y ) * .05;
+				camera.lookAt( scene.position );
+
+				for ( let i = 0; i < mesh.count; i ++ ) {
+
+					mesh.getMatrixAt( i, dummy.matrix );
+
+					extractPosition( dummy.matrix, position );
+
+					position.x = 5 * Math.cos( timer + i );
+					position.y = 5 * Math.sin( timer + i * 1.1 );
+
+					dummy.matrix.setPosition( position );
+
+					mesh.setMatrixAt( i, dummy.matrix );
+
+					mesh.instanceMatrix.needsUpdate = true;
+
+				}
+
+				postProcessing.render();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
src/nodes/Nodes.js

@@ -146,6 +146,7 @@ export { default as TransitionNode, transition } from './display/TransitionNode.
 export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js';
 export { default as PixelationPassNode, pixelationPass } from './display/PixelationPassNode.js';
 export { default as SSAAPassNode, ssaaPass } from './display/SSAAPassNode.js';
+export { default as StereoPassNode, stereoPass } from './display/StereoPassNode.js';
 export { bleach } from './display/BleachBypassNode.js';
 export { sepia } from './display/SepiaNode.js';
 

+ 77 - 0
src/nodes/display/StereoPassNode.js

@@ -0,0 +1,77 @@
+import { nodeObject } from '../shadernode/ShaderNode.js';
+import PassNode from './PassNode.js';
+import { Vector2 } from '../../math/Vector2.js';
+import { StereoCamera } from '../../cameras/StereoCamera.js';
+
+const _size = /*@__PURE__*/ new Vector2();
+
+class StereoPassNode extends PassNode {
+
+	constructor( scene, camera ) {
+
+		super( PassNode.COLOR, scene, camera );
+
+		this.isStereoPassNode = true;
+
+		this.stereo = new StereoCamera();
+		this.stereo.aspect = 0.5;
+
+	}
+
+	updateBefore( frame ) {
+
+		const { renderer } = frame;
+		const { scene, camera, stereo, renderTarget } = this;
+
+		this._pixelRatio = renderer.getPixelRatio();
+
+		stereo.cameraL.coordinateSystem = renderer.coordinateSystem;
+		stereo.cameraR.coordinateSystem = renderer.coordinateSystem;
+		stereo.update( camera );
+
+		const size = renderer.getSize( _size );
+		this.setSize( size.width, size.height );
+
+		const currentAutoClear = renderer.autoClear;
+		renderer.autoClear = false;
+
+		const currentRenderTarget = renderer.getRenderTarget();
+		const currentMRT = renderer.getMRT();
+
+		this._cameraNear.value = camera.near;
+		this._cameraFar.value = camera.far;
+
+		for ( const name in this._previousTextures ) {
+
+			this.toggleTexture( name );
+
+		}
+
+		renderer.setRenderTarget( renderTarget );
+		renderer.setMRT( this._mrt );
+		renderer.clear();
+
+		renderTarget.scissorTest = true;
+
+		renderTarget.scissor.set( 0, 0, renderTarget.width / 2, renderTarget.height );
+		renderTarget.viewport.set( 0, 0, renderTarget.width / 2, renderTarget.height );
+		renderer.render( scene, stereo.cameraL );
+
+		renderTarget.scissor.set( renderTarget.width / 2, 0, renderTarget.width / 2, renderTarget.height );
+		renderTarget.viewport.set( renderTarget.width / 2, 0, renderTarget.width / 2, renderTarget.height );
+		renderer.render( scene, stereo.cameraR );
+
+		renderTarget.scissorTest = false;
+
+		renderer.setRenderTarget( currentRenderTarget );
+		renderer.setMRT( currentMRT );
+
+		renderer.autoClear = currentAutoClear;
+
+	}
+
+}
+
+export const stereoPass = ( scene, camera ) => nodeObject( new StereoPassNode( scene, camera ) );
+
+export default StereoPassNode;

粤ICP备19079148号