Skip to content

Commit

Permalink
Nodes: Add StereoPassNode. (#29173)
Browse files Browse the repository at this point in the history
* Nodes: Add `StereoPassNode`.

* Exampels: Improve title.
  • Loading branch information
Mugen87 authored Aug 19, 2024
1 parent eb2d9a6 commit cd8f55c
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file added examples/screenshots/webgpu_display_stereo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 162 additions & 0 deletions examples/webgpu_display_stereo.html
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions src/nodes/Nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
77 changes: 77 additions & 0 deletions src/nodes/display/StereoPassNode.js
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit cd8f55c

Please sign in to comment.