Skip to content

Commit ffef510

Browse files
cmhhelgesonMugen87
andauthored
Nodes: Add TransitionNode. (#28847)
* TransitionNode sketch * cleanup * fix non-functional controller * texture loading fixed * lint fix * fix screenshot * remove texture import * Update TransitionNode.js * Update webgpu_postprocessing_transition.html --------- Co-authored-by: Michael Herzog <[email protected]>
1 parent d82e3e6 commit ffef510

File tree

5 files changed

+343
-0
lines changed

5 files changed

+343
-0
lines changed

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@
379379
"webgpu_postprocessing_pixel",
380380
"webgpu_postprocessing_fxaa",
381381
"webgpu_postprocessing_sobel",
382+
"webgpu_postprocessing_transition",
382383
"webgpu_postprocessing",
383384
"webgpu_procedural_texture",
384385
"webgpu_reflection",
53.1 KB
Loading
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgpu - scenes transition</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+
<body>
10+
11+
<div id="info">
12+
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu scene transitions.<br/>
13+
Original implementation by <a href="https://twitter.com/fernandojsg">fernandojsg</a> - <a href="https://github.com/kile/three.js-demos">github</a>
14+
</div>
15+
16+
<script type="importmap">
17+
{
18+
"imports": {
19+
"three": "../build/three.webgpu.js",
20+
"three/tsl": "../build/three.webgpu.js",
21+
"three/addons/": "./jsm/"
22+
}
23+
}
24+
</script>
25+
26+
<script type="module">
27+
28+
import * as THREE from 'three';
29+
30+
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
31+
import TWEEN from 'three/addons/libs/tween.module.js';
32+
import { uniform, transition, pass } from 'three/tsl';
33+
34+
let renderer, postProcessing, transitionController, transitionPass;
35+
36+
const textures = [];
37+
const clock = new THREE.Clock();
38+
39+
const effectController = {
40+
animateScene: true,
41+
animateTransition: true,
42+
transition: 0,
43+
_transition: uniform( 0 ),
44+
useTexture: true,
45+
_useTexture: uniform( 1 ),
46+
texture: 5,
47+
cycle: true,
48+
threshold: uniform( 0.1 ),
49+
};
50+
51+
function generateInstancedMesh( geometry, material, count ) {
52+
53+
const mesh = new THREE.InstancedMesh( geometry, material, count );
54+
55+
const dummy = new THREE.Object3D();
56+
const color = new THREE.Color();
57+
58+
for ( let i = 0; i < count; i ++ ) {
59+
60+
dummy.position.x = Math.random() * 100 - 50;
61+
dummy.position.y = Math.random() * 60 - 30;
62+
dummy.position.z = Math.random() * 80 - 40;
63+
64+
dummy.rotation.x = Math.random() * 2 * Math.PI;
65+
dummy.rotation.y = Math.random() * 2 * Math.PI;
66+
dummy.rotation.z = Math.random() * 2 * Math.PI;
67+
68+
dummy.scale.x = Math.random() * 2 + 1;
69+
70+
if ( geometry.type === 'BoxGeometry' ) {
71+
72+
dummy.scale.y = Math.random() * 2 + 1;
73+
dummy.scale.z = Math.random() * 2 + 1;
74+
75+
} else {
76+
77+
dummy.scale.y = dummy.scale.x;
78+
dummy.scale.z = dummy.scale.x;
79+
80+
}
81+
82+
dummy.updateMatrix();
83+
84+
mesh.setMatrixAt( i, dummy.matrix );
85+
mesh.setColorAt( i, color.setScalar( 0.1 + 0.9 * Math.random() ) );
86+
87+
}
88+
89+
return mesh;
90+
91+
}
92+
93+
function FXScene( geometry, rotationSpeed, backgroundColor ) {
94+
95+
const camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 100 );
96+
camera.position.z = 20;
97+
98+
// Setup scene
99+
const scene = new THREE.Scene();
100+
scene.background = new THREE.Color( backgroundColor );
101+
scene.add( new THREE.AmbientLight( 0xaaaaaa, 3 ) );
102+
103+
const light = new THREE.DirectionalLight( 0xffffff, 3 );
104+
light.position.set( 0, 1, 4 );
105+
scene.add( light );
106+
107+
this.rotationSpeed = rotationSpeed;
108+
109+
const color = geometry.type === 'BoxGeometry' ? 0x0000ff : 0xff0000;
110+
const material = new THREE.MeshPhongNodeMaterial( { color: color, flatShading: true } );
111+
const mesh = generateInstancedMesh( geometry, material, 500 );
112+
scene.add( mesh );
113+
114+
this.scene = scene;
115+
this.camera = camera;
116+
this.mesh = mesh;
117+
118+
this.update = function ( delta ) {
119+
120+
if ( effectController.animateScene ) {
121+
122+
mesh.rotation.x += this.rotationSpeed.x * delta;
123+
mesh.rotation.y += this.rotationSpeed.y * delta;
124+
mesh.rotation.z += this.rotationSpeed.z * delta;
125+
126+
}
127+
128+
};
129+
130+
this.resize = function () {
131+
132+
camera.aspect = window.innerWidth / window.innerHeight;
133+
camera.updateProjectionMatrix();
134+
135+
};
136+
137+
}
138+
139+
const fxSceneA = new FXScene( new THREE.BoxGeometry( 2, 2, 2 ), new THREE.Vector3( 0, - 0.4, 0 ), 0xffffff );
140+
const fxSceneB = new FXScene( new THREE.IcosahedronGeometry( 1, 1 ), new THREE.Vector3( 0, 0.2, 0.1 ), 0x000000 );
141+
142+
function init() {
143+
144+
// Initialize textures
145+
146+
const loader = new THREE.TextureLoader();
147+
148+
for ( let i = 0; i < 6; i ++ ) {
149+
150+
textures[ i ] = loader.load( 'textures/transition/transition' + ( i + 1 ) + '.png' );
151+
152+
}
153+
154+
renderer = new THREE.WebGPURenderer( { antialias: true } );
155+
renderer.setPixelRatio( window.devicePixelRatio );
156+
renderer.setSize( window.innerWidth, window.innerHeight );
157+
renderer.setAnimationLoop( animate );
158+
document.body.appendChild( renderer.domElement );
159+
160+
postProcessing = new THREE.PostProcessing( renderer );
161+
162+
const scenePassA = pass( fxSceneA.scene, fxSceneA.camera );
163+
const scenePassB = pass( fxSceneB.scene, fxSceneB.camera );
164+
165+
transitionPass = transition( scenePassA, scenePassB, new THREE.TextureNode( textures[ effectController.texture ] ), effectController._transition, effectController.threshold, effectController._useTexture );
166+
167+
postProcessing.outputNode = transitionPass;
168+
169+
const gui = new GUI();
170+
171+
gui.add( effectController, 'animateScene' ).name( 'Animate Scene' );
172+
gui.add( effectController, 'animateTransition' ).name( 'Animate Transition' );
173+
transitionController = gui.add( effectController, 'transition', 0, 1, 0.01 ).name( 'transition' ).onChange( () => {
174+
175+
effectController._transition.value = effectController.transition;
176+
177+
} );
178+
gui.add( effectController, 'useTexture' ).onChange( () => {
179+
180+
const value = effectController.useTexture ? 1 : 0;
181+
effectController._useTexture.value = value;
182+
183+
} );
184+
gui.add( effectController, 'texture', { Perlin: 0, Squares: 1, Cells: 2, Distort: 3, Gradient: 4, Radial: 5 } );
185+
gui.add( effectController, 'cycle' );
186+
gui.add( effectController.threshold, 'value', 0, 1, 0.01 ).name( 'threshold' );
187+
188+
}
189+
190+
window.addEventListener( 'resize', onWindowResize );
191+
192+
function onWindowResize() {
193+
194+
fxSceneA.resize();
195+
fxSceneB.resize();
196+
renderer.setSize( window.innerWidth, window.innerHeight );
197+
198+
}
199+
200+
new TWEEN.Tween( effectController )
201+
.to( { transition: 1 }, 1500 )
202+
.onUpdate( function ( ) {
203+
204+
transitionController.setValue( effectController.transition );
205+
206+
// Change the current alpha texture after each transition
207+
if ( effectController.cycle ) {
208+
209+
if ( effectController.transition == 0 || effectController.transition == 1 ) {
210+
211+
effectController.texture = ( effectController.texture + 1 ) % textures.length;
212+
213+
}
214+
215+
}
216+
217+
} )
218+
.repeat( Infinity )
219+
.delay( 2000 )
220+
.yoyo( true )
221+
.start();
222+
223+
function animate() {
224+
225+
if ( effectController.animateTransition ) TWEEN.update();
226+
227+
if ( textures[ effectController.texture ] ) {
228+
229+
const mixTexture = textures[ effectController.texture ];
230+
transitionPass.mixTextureNode.value = mixTexture;
231+
232+
}
233+
234+
const delta = clock.getDelta();
235+
fxSceneA.update( delta );
236+
fxSceneB.update( delta );
237+
238+
render();
239+
240+
}
241+
242+
function render() {
243+
244+
// Prevent render both scenes when it's not necessary
245+
if ( effectController.transition === 0 ) {
246+
247+
renderer.render( fxSceneB.scene, fxSceneB.camera );
248+
249+
} else if ( effectController.transition === 1 ) {
250+
251+
renderer.render( fxSceneA.scene, fxSceneA.camera );
252+
253+
} else {
254+
255+
postProcessing.render();
256+
257+
}
258+
259+
}
260+
261+
init();
262+
263+
</script>
264+
</body>
265+
</html>

src/nodes/Nodes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export { default as GTAONode, ao } from './display/GTAONode.js';
138138
export { default as DenoiseNode, denoise } from './display/DenoiseNode.js';
139139
export { default as FXAANode, fxaa } from './display/FXAANode.js';
140140
export { default as BloomNode, bloom } from './display/BloomNode.js';
141+
export { default as TransitionNode, transition } from './display/TransitionNode.js';
141142
export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js';
142143
export { default as PixelationPassNode, pixelationPass } from './display/PixelationPassNode.js';
143144

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import TempNode from '../core/TempNode.js';
2+
import { uv } from '../accessors/UVNode.js';
3+
import { addNodeElement, tslFn, nodeObject, float, int, vec4, If } from '../shadernode/ShaderNode.js';
4+
import { clamp, mix } from '../math/MathNode.js';
5+
import { sub } from '../math/OperatorNode.js';
6+
7+
class TransitionNode extends TempNode {
8+
9+
constructor( textureNodeA, textureNodeB, mixTextureNode, mixRatioNode, thresholdNode, useTextureNode ) {
10+
11+
super();
12+
13+
// Input textures
14+
15+
this.textureNodeA = textureNodeA;
16+
this.textureNodeB = textureNodeB;
17+
this.mixTextureNode = mixTextureNode;
18+
19+
// Uniforms
20+
21+
this.mixRatioNode = mixRatioNode;
22+
this.thresholdNode = thresholdNode;
23+
this.useTextureNode = useTextureNode;
24+
25+
}
26+
27+
setup() {
28+
29+
const { textureNodeA, textureNodeB, mixTextureNode, mixRatioNode, thresholdNode, useTextureNode } = this;
30+
31+
const sampleTexture = ( textureNode ) => {
32+
33+
const uvNodeTexture = textureNode.uvNode || uv();
34+
return textureNode.uv( uvNodeTexture );
35+
36+
};
37+
38+
const transition = tslFn( () => {
39+
40+
const texelOne = sampleTexture( textureNodeA );
41+
const texelTwo = sampleTexture( textureNodeB );
42+
43+
const color = vec4().toVar();
44+
45+
If( useTextureNode.equal( int( 1 ) ), () => {
46+
47+
const transitionTexel = sampleTexture( mixTextureNode );
48+
const r = mixRatioNode.mul( thresholdNode.mul( 2.0 ).add( 1.0 ) ).sub( thresholdNode );
49+
const mixf = clamp( sub( transitionTexel.r, r ).mul( float( 1.0 ).div( thresholdNode ) ), 0.0, 1.0 );
50+
51+
color.assign( mix( texelOne, texelTwo, mixf ) );
52+
53+
} ).else( () => {
54+
55+
color.assign( mix( texelTwo, texelOne, mixRatioNode ) );
56+
57+
} );
58+
59+
return color;
60+
61+
} );
62+
63+
const outputNode = transition();
64+
65+
return outputNode;
66+
67+
}
68+
69+
}
70+
71+
export const transition = ( nodeA, nodeB, mixTexture, mixRatio = 0.0, threshold = 0.1, useTexture = 0 ) => nodeObject( new TransitionNode( nodeObject( nodeA ).toTexture(), nodeObject( nodeB ).toTexture(), nodeObject( mixTexture ).toTexture(), nodeObject( mixRatio ), nodeObject( threshold ), nodeObject( useTexture ) ) );
72+
73+
addNodeElement( 'transition', transition );
74+
75+
export default TransitionNode;
76+

0 commit comments

Comments
 (0)