Skip to content

Commit cd8f55c

Browse files
authored
Nodes: Add StereoPassNode. (#29173)
* Nodes: Add `StereoPassNode`. * Exampels: Improve title.
1 parent eb2d9a6 commit cd8f55c

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
"webgpu_custom_fog",
325325
"webgpu_custom_fog_background",
326326
"webgpu_depth_texture",
327+
"webgpu_display_stereo",
327328
"webgpu_equirectangular",
328329
"webgpu_instance_mesh",
329330
"webgpu_instance_points",
51.1 KB
Loading

examples/webgpu_display_stereo.html

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgpu - stereo</title>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7+
<link type="text/css" rel="stylesheet" href="main.css">
8+
</head>
9+
10+
<body>
11+
<div id="info">
12+
<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>
13+
</div>
14+
15+
<script type="importmap">
16+
{
17+
"imports": {
18+
"three": "../build/three.webgpu.js",
19+
"three/tsl": "../build/three.webgpu.js",
20+
"three/addons/": "./jsm/"
21+
}
22+
}
23+
</script>
24+
25+
<script type="module">
26+
27+
import * as THREE from 'three';
28+
29+
import { stereoPass } from 'three/tsl';
30+
31+
let camera, scene, renderer, postProcessing;
32+
33+
let mesh, dummy;
34+
35+
let mouseX = 0, mouseY = 0;
36+
37+
let windowHalfX = window.innerWidth / 2;
38+
let windowHalfY = window.innerHeight / 2;
39+
40+
const position = new THREE.Vector3();
41+
42+
init();
43+
44+
function init() {
45+
46+
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
47+
camera.position.z = 3;
48+
49+
scene = new THREE.Scene();
50+
scene.background = new THREE.CubeTextureLoader()
51+
.setPath( 'textures/cube/Park3Med/' )
52+
.load( [ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg' ] );
53+
54+
const geometry = new THREE.SphereGeometry( 0.1, 32, 16 );
55+
56+
const textureCube = new THREE.CubeTextureLoader()
57+
.setPath( 'textures/cube/Park3Med/' )
58+
.load( [ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg' ] );
59+
textureCube.mapping = THREE.CubeRefractionMapping;
60+
61+
const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube, refractionRatio: 0.95 } );
62+
63+
mesh = new THREE.InstancedMesh( geometry, material, 500 );
64+
mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
65+
66+
dummy = new THREE.Mesh();
67+
68+
for ( let i = 0; i < 500; i ++ ) {
69+
70+
dummy.position.x = Math.random() * 10 - 5;
71+
dummy.position.y = Math.random() * 10 - 5;
72+
dummy.position.z = Math.random() * 10 - 5;
73+
dummy.scale.x = dummy.scale.y = dummy.scale.z = Math.random() * 3 + 1;
74+
75+
dummy.updateMatrix();
76+
77+
mesh.setMatrixAt( i, dummy.matrix );
78+
79+
}
80+
81+
scene.add( mesh );
82+
83+
//
84+
85+
renderer = new THREE.WebGPURenderer();
86+
renderer.setPixelRatio( window.devicePixelRatio );
87+
renderer.setSize( window.innerWidth, window.innerHeight );
88+
renderer.setAnimationLoop( animate );
89+
document.body.appendChild( renderer.domElement );
90+
91+
postProcessing = new THREE.PostProcessing( renderer );
92+
const pass = stereoPass( scene, camera );
93+
postProcessing.outputNode = pass;
94+
95+
//
96+
97+
window.addEventListener( 'resize', onWindowResize );
98+
99+
document.addEventListener( 'mousemove', onDocumentMouseMove );
100+
101+
}
102+
103+
function onWindowResize() {
104+
105+
windowHalfX = window.innerWidth / 2;
106+
windowHalfY = window.innerHeight / 2;
107+
108+
camera.aspect = window.innerWidth / window.innerHeight;
109+
camera.updateProjectionMatrix();
110+
111+
}
112+
113+
function onDocumentMouseMove( event ) {
114+
115+
mouseX = ( event.clientX - windowHalfX ) * 0.01;
116+
mouseY = ( event.clientY - windowHalfY ) * 0.01;
117+
118+
}
119+
120+
function extractPosition( matrix, position ) {
121+
122+
position.x = matrix.elements[ 12 ];
123+
position.y = matrix.elements[ 13 ];
124+
position.z = matrix.elements[ 14 ];
125+
126+
}
127+
128+
//
129+
130+
function animate() {
131+
132+
const timer = 0.0001 * Date.now();
133+
134+
camera.position.x += ( mouseX - camera.position.x ) * .05;
135+
camera.position.y += ( - mouseY - camera.position.y ) * .05;
136+
camera.lookAt( scene.position );
137+
138+
for ( let i = 0; i < mesh.count; i ++ ) {
139+
140+
mesh.getMatrixAt( i, dummy.matrix );
141+
142+
extractPosition( dummy.matrix, position );
143+
144+
position.x = 5 * Math.cos( timer + i );
145+
position.y = 5 * Math.sin( timer + i * 1.1 );
146+
147+
dummy.matrix.setPosition( position );
148+
149+
mesh.setMatrixAt( i, dummy.matrix );
150+
151+
mesh.instanceMatrix.needsUpdate = true;
152+
153+
}
154+
155+
postProcessing.render();
156+
157+
}
158+
159+
</script>
160+
161+
</body>
162+
</html>

src/nodes/Nodes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export { default as TransitionNode, transition } from './display/TransitionNode.
146146
export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js';
147147
export { default as PixelationPassNode, pixelationPass } from './display/PixelationPassNode.js';
148148
export { default as SSAAPassNode, ssaaPass } from './display/SSAAPassNode.js';
149+
export { default as StereoPassNode, stereoPass } from './display/StereoPassNode.js';
149150
export { bleach } from './display/BleachBypassNode.js';
150151
export { sepia } from './display/SepiaNode.js';
151152

src/nodes/display/StereoPassNode.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { nodeObject } from '../shadernode/ShaderNode.js';
2+
import PassNode from './PassNode.js';
3+
import { Vector2 } from '../../math/Vector2.js';
4+
import { StereoCamera } from '../../cameras/StereoCamera.js';
5+
6+
const _size = /*@__PURE__*/ new Vector2();
7+
8+
class StereoPassNode extends PassNode {
9+
10+
constructor( scene, camera ) {
11+
12+
super( PassNode.COLOR, scene, camera );
13+
14+
this.isStereoPassNode = true;
15+
16+
this.stereo = new StereoCamera();
17+
this.stereo.aspect = 0.5;
18+
19+
}
20+
21+
updateBefore( frame ) {
22+
23+
const { renderer } = frame;
24+
const { scene, camera, stereo, renderTarget } = this;
25+
26+
this._pixelRatio = renderer.getPixelRatio();
27+
28+
stereo.cameraL.coordinateSystem = renderer.coordinateSystem;
29+
stereo.cameraR.coordinateSystem = renderer.coordinateSystem;
30+
stereo.update( camera );
31+
32+
const size = renderer.getSize( _size );
33+
this.setSize( size.width, size.height );
34+
35+
const currentAutoClear = renderer.autoClear;
36+
renderer.autoClear = false;
37+
38+
const currentRenderTarget = renderer.getRenderTarget();
39+
const currentMRT = renderer.getMRT();
40+
41+
this._cameraNear.value = camera.near;
42+
this._cameraFar.value = camera.far;
43+
44+
for ( const name in this._previousTextures ) {
45+
46+
this.toggleTexture( name );
47+
48+
}
49+
50+
renderer.setRenderTarget( renderTarget );
51+
renderer.setMRT( this._mrt );
52+
renderer.clear();
53+
54+
renderTarget.scissorTest = true;
55+
56+
renderTarget.scissor.set( 0, 0, renderTarget.width / 2, renderTarget.height );
57+
renderTarget.viewport.set( 0, 0, renderTarget.width / 2, renderTarget.height );
58+
renderer.render( scene, stereo.cameraL );
59+
60+
renderTarget.scissor.set( renderTarget.width / 2, 0, renderTarget.width / 2, renderTarget.height );
61+
renderTarget.viewport.set( renderTarget.width / 2, 0, renderTarget.width / 2, renderTarget.height );
62+
renderer.render( scene, stereo.cameraR );
63+
64+
renderTarget.scissorTest = false;
65+
66+
renderer.setRenderTarget( currentRenderTarget );
67+
renderer.setMRT( currentMRT );
68+
69+
renderer.autoClear = currentAutoClear;
70+
71+
}
72+
73+
}
74+
75+
export const stereoPass = ( scene, camera ) => nodeObject( new StereoPassNode( scene, camera ) );
76+
77+
export default StereoPassNode;

0 commit comments

Comments
 (0)