Transition effect between multiple scenes

I came up with this example WebGL Image Transitions | Demo 3 | Codrops and looks really cool with transittion between images. I have already a scene with multiple objects based on GitHub - Sean-Bradley/Three.js-TypeScript-Boilerplate at annotations and i was wondering if would be possible to switch scenes using those transitions. Let’s say i have some sprites that would load something on click (instead of the annotations.json file)
sceneA and sceneB should switch between a nice transition.
Any ideas??

renderer.domElement.addEventListener('dblclick', onDoubleClick, false)
function gotoAnnotation(a: any): void {
if(a.sub == "A" ) {
 //switch to sceneB with transition effect
}

There are many ways to tackle the problem, search for “transitioning threejs scenes” in your favourite search engine.

Here is a quick example that uses CSS to transition iframes containing threejs scenes.

Problems you will need to consider depend greatly on what is in your scene and if it uses many resources.

You can see in my demo that the animation loop freezes momentarily between transitions. You may have to come up with some kind of work around for that.

1 Like

Thanks for the reply, i do not see any freezes between iframes maybe because i am on a high end macbook. I already have crossfade transition with jQuery and it will also create some “gap” between the transition but i was thinking to do something more complex like the example on the url and use vanilla three.js method

For some ideas: Why the scene don't update? - #3 by prisoner849

2 Likes

In one of my projects I needed some inspiration and I thought that I might accidentally stumble upon the file index.36aab0d1.js
And actually I found there… various inspirations…
https://shaders-slider.uiinitiative.com/

available for reference
https://threejs.org/examples/?q=transition#webgl_postprocessing_transition

Make two scene changes using shaders

looks like your localhost is not live at the moment but thanks for all the info points,.I am gonna read/practice more and i hopefully i will manage to make the transition between my iframe pages onclick ( i should also build those html pages in pure three.js)

beautifull. i am gonna spent more time practicing those examples, thanks!

I don’t mean to send it wrong, In the official example:
https://threejs.org/examples/?q=transition#webgl_postprocessing_transition

ok it seems i am very close but there are some things that i do not understand, . First of all i have a very complex scene, i will add the part of the code that should work the composer and the transition between scenes. There are many unnecessary parts in the code that i am trying to test, for example how i will add those transition1.png etc between the two scenes. The main issue also is that in my final animate function if i add composer.render() it will result in white screen, but i can hear the music or if i debug i see that all my objects render to the screen. The only way to render correct is to add renderer.render(currentScene, camera); but this will not make any transition effect cause no composer render?

  const renderTargetParameters = {
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter,
     
     
//      	side: THREE.BackSide,
				
			// 		transparent: true,
// 					depthWrite: true,
// 					depthTest: true,
      format: THREE.RGBAFormat,
      
      stencilBuffer: false
    };
    const renderTarget1 = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetParameters);
    const renderTarget2 = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetParameters);
    
    console.log('RenderTarget1:', renderTarget1);
    
    const tLoader = new THREE.TextureLoader()
let textures = [
	tLoader.load("img/textures/transition/transition1.png"),
  tLoader.load("img/textures/transition/transition2.png")
];
 const texture2 = new THREE.TextureLoader().load('img/textures/transition/transition2.png');
    const material22 = new THREE.MeshBasicMaterial({ map: texture2 });
    const geometry22 = new THREE.SphereGeometry();
    const sphere = new THREE.Mesh(geometry22, material22);
    


  
    scene2.add(sphere);

const composer = new EffectComposer(renderer);
    const renderPass1 = new RenderPass(scene, camera);
    const renderPass2 = new RenderPass(scene2, camera);
    renderPass2.enabled = false;
    composer.addPass(renderPass1);
    composer.addPass(renderPass2);

    // Custom transition shader (cross-fade)
const customTransitionShader = {
//     uniforms: {
//         tDiffuse1: { value: new THREE.TextureLoader().load('img/textures/transition/transition2.png') },
//         tDiffuse2: { value: new THREE.TextureLoader().load('img/textures/transition/transition6.png') },
//         mixRatio: { value: 0.0 }
//     },
    
    
         uniforms: {
        tDiffuse1: { value: null },
        tDiffuse2: { value: null },
        mixRatio: { value: 0.0 }
    },
    
    
//      uniforms: {
//         tDiffuse1: { value: renderTarget1.texture },
//         tDiffuse2: { value: renderTarget2.texture },
//         mixRatio: { value: 0.0 }
//     },
    vertexShader: `
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: `
        uniform sampler2D tDiffuse1;
        uniform sampler2D tDiffuse2;
        uniform float mixRatio;
        varying vec2 vUv;
        void main() {
            vec4 texel1 = texture2D(tDiffuse1, vUv);
            vec4 texel2 = texture2D(tDiffuse2, vUv);
            gl_FragColor = mix(texel1, texel2, mixRatio);
        }
    `
};
    // Create shader pass with custom transition shader
    const customTransitionPass = new ShaderPass(customTransitionShader);
    customTransitionPass.renderToScreen = true;
    composer.addPass(customTransitionPass);

    // Start transition function
    function startTransition(targetScene:THREE.Scene) {
      if (targetScene === scene) {
        renderPass1.enabled = true;
        renderPass2.enabled = false;
      } else {
        renderPass1.enabled = false;
        renderPass2.enabled = true;
      }

      const transitionDuration = 1; // seconds
      const transitionStartTime = performance.now();

      function animateTransition() {
        const elapsed = (performance.now() - transitionStartTime) / 1000;
        const mixValue = Math.min(1, elapsed / transitionDuration); // Clamp to 1 at max
        customTransitionPass.uniforms.mixRatio.value = mixValue;

        if (elapsed < transitionDuration) {
          requestAnimationFrame(animateTransition);
        } else {
          customTransitionPass.uniforms.mixRatio.value = 0.0; // Reset mix ratio
          currentScene = targetScene;
          
       //       // Update render passes
//             renderPass1.enabled = (currentScene === scene);
//             renderPass2.enabled = (currentScene === scene2);
        }
      }

      requestAnimationFrame(animateTransition);
    }

window.addEventListener( 'click', onPointerMove );

function animate() {


    requestAnimationFrame(animate)
const t = clock.getElapsedTime( );
flagwaving( t );

    controls.update()
camera.updateProjectionMatrix () 
    TWEEN.update()

    render()

//     renderer.render(currentScene, camera);
    
//     renderer.clear();
  renderer.setRenderTarget(renderTarget1);
    renderer.render(scene, camera);

    renderer.setRenderTarget(renderTarget2);
    renderer.render(scene2, camera);

    renderer.setRenderTarget(null); // Reset to default framebuffer
//  renderer.clear();
    // Use composer to render the scene with effects
    
    composer.render();
    
    }

animate()

can you please check my last reply

const renderTargetParameters = {
				minFilter: THREE.LinearFilter,
				magFilter: THREE.LinearFilter,

				//      	side: THREE.BackSide,

				// 		transparent: true,
				// 					depthWrite: true,
				// 					depthTest: true,
				format: THREE.RGBAFormat,

				stencilBuffer: false,
			};
			const renderTarget1 = new THREE.WebGLRenderTarget(
				window.innerWidth,
				window.innerHeight,
				renderTargetParameters
			);
			const renderTarget2 = new THREE.WebGLRenderTarget(
				window.innerWidth,
				window.innerHeight,
				renderTargetParameters
			);

			console.log("RenderTarget1:", renderTarget1);

			const tLoader = new THREE.TextureLoader();
			let textures = [
				tLoader.load("./transition1.png"),
				tLoader.load("./transition2.png"),
			];
			console.log(`textures:`,textures)
			const texture2 = new THREE.TextureLoader().load(
				"img/textures/transition/transition2.png"
			);
			const material22 = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
			const geometry22 = new THREE.SphereGeometry();
			const sphere = new THREE.Mesh(geometry22, material22);

			const scene2 = new THREE.Scene();

			scene2.add(sphere);

			const composer = new EffectComposer(renderer);
			const renderPass1 = new RenderPass(scene, camera);
			const renderPass2 = new RenderPass(scene2, camera);
			
			// renderPass2.enabled = false;
			// composer.addPass(renderPass1);
			// composer.addPass(renderPass2);

			const outputPass = new OutputPass();
			composer.addPass( outputPass );

			composer.setSize( window.innerWidth, window.innerHeight );

			// Custom transition shader (cross-fade)
			const customTransitionShader = {
				//     uniforms: {
				//         tDiffuse1: { value: new THREE.TextureLoader().load('img/textures/transition/transition2.png') },
				//         tDiffuse2: { value: new THREE.TextureLoader().load('img/textures/transition/transition6.png') },
				//         mixRatio: { value: 0.0 }
				//     },

				uniforms: {
					tDiffuse1: { value: textures[0] },
					tDiffuse2: { value: textures[1] },
					mixRatio: { value: 0.0 },
				},

				    //  uniforms: {
				    //     tDiffuse1: { value: renderTarget1.texture },
				    //     tDiffuse2: { value: renderTarget2.texture },
				    //     mixRatio: { value: 0.0 }
				    // },
				vertexShader: `
				  varying vec2 vUv;
				  void main() {
					  vUv = uv;
					  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
				  }
			  `,
				fragmentShader: `
				  uniform sampler2D tDiffuse1;
				  uniform sampler2D tDiffuse2;
				  uniform float mixRatio;
				  varying vec2 vUv;
				  void main() {
					  vec4 texel1 = texture2D(tDiffuse1, vUv);
					  vec4 texel2 = texture2D(tDiffuse2, vUv);

					  gl_FragColor = mix(texel1, texel2, mixRatio);
				  }
			  `,
			};
			// Create shader pass with custom transition shader
			const customTransitionPass = new ShaderPass(customTransitionShader);
			// customTransitionPass.renderToScreen = true;
			composer.addPass(customTransitionPass);
			const params={
				transition:0
			}
			new TWEEN.Tween( params )
				.to( { transition: 1 }, 2000 )
				.onUpdate( function () {
					customTransitionPass.uniforms.mixRatio.value = params.transition;
				} )
				.repeat( Infinity )
				.delay( 2000 )
				.yoyo( true )
				.start();
			function animate() {
				requestAnimationFrame(animate);
				TWEEN.update()
				if(params.transition===0){
					// renderer.render(scene2, camera2)
				}else if(params.transition===1){
					// renderer.render(scene1, camera1)
				}else{
				}
				composer.render();
			}

			animate();

Thanks so much. However if i add

uniforms: {
					tDiffuse1: { value: textures[0] },
					tDiffuse2: { value: textures[1] },
					mixRatio: { value: 0.0 },
				},

the screen will show only the transition1.png switching to transition2.png i mean the whole screen always and i do not see my other objects. i know that my objects are rendered in the background but i cannot make the main screen to switch to scene2 using this transition to “blend” the scenes. Let me know if you need any other information. I have only one function animate in my code if i add any image to tDiffuse1,2 then this image will cover the whole screen.

You can take the shader in RenderTransitionPass and try it out, or change it with my code

你可以把RenderTransitionPass three/addons/postprocessing/RenderTransitionPass.js 里的着色器拿出来用试试看,或者用我的代码修改一下( Chinese)

fragmentShader: /* glsl */`
				uniform float mixRatio;

				uniform sampler2D tDiffuse1;
				uniform sampler2D tDiffuse2;
				uniform sampler2D tMixTexture;

				uniform int useTexture;
				uniform float threshold;

				varying vec2 vUv;

				void main() {

					vec4 texel1 = texture2D( tDiffuse1, vUv );
					vec4 texel2 = texture2D( tDiffuse2, vUv );

					if (useTexture == 1) {

						vec4 transitionTexel = texture2D( tMixTexture, vUv );
						float r = mixRatio * ( 1.0 + threshold * 2.0 ) - threshold;
						float mixf = clamp( ( transitionTexel.r - r ) * ( 1.0 / threshold ), 0.0, 1.0 );

						gl_FragColor = mix( texel1, texel2, mixf );

					} else {

						gl_FragColor = mix( texel2, texel1, mixRatio );

					}

					#include <tonemapping_fragment>
					#include <colorspace_fragment>

				}
			`
import * as THREE from 'three';
import { createElement, useRef, useEffect } from 'react';
import { World } from '@/utils/three.utils';

import TWEEN from 'three/addons/libs/tween.module.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderTransitionPass } from 'three/addons/postprocessing/RenderTransitionPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';

/**
 * RenderTransitionPass
 * 使用3个纹理,实现过渡效果
 * 一个纹理为过渡效果,另外两个为场景
 */
export default function(){
	const canvasRef = useRef(null);  
	useEffect(() => {
		const canvas=canvasRef.current
	  	if (canvas) {  
		
			const world=new World(canvas)
			world.init()
			// world.update()
			world.updateSize()
			world.addOrbitControls()

			const {scene,camera,renderer}=world

			let transition=0
			let scene1,camera1,
			scene2,camera2
			{
				scene1=new THREE.Scene()
				camera1=new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
				camera1.position.set(5,5,1)
				world.addOrbitControls(camera1,renderer.domElement)
				const light = new THREE.AmbientLight( 0x404040 ,10); // 柔和的白光
				scene1.add(light)

				camera1.lookAt(0,0,0)
				
				const box=new THREE.BoxGeometry(1,1,1)
				const material=new THREE.MeshPhongMaterial({color:0xfffffff})
				const instanced=new THREE.InstancedMesh(box,material,1000)
				console.log(`instanced:`,instanced)
				const object=new THREE.Object3D()
				const color=new THREE.Color()
				function getRandomSphericalPosition(radius) {
					var u = Math.random();  
					var v = Math.random();  
					var theta = 2 * Math.PI * u; // 极角  
					var phi = Math.acos(2 * v - 1); // 方位角(转化为弧度)  
					var r = radius * Math.sqrt(Math.random()); // 随机半径在[0, radius]之间  
				
					var x = r * Math.sin(phi) * Math.cos(theta);  
					var y = r * Math.sin(phi) * Math.sin(theta);  
					var z = r * Math.cos(phi);  
				
					return new THREE.Vector3(x, y, z);  
				}  
				

				for (let index = 0; index < instanced.count; index++) {
					object.position.copy(getRandomSphericalPosition(10))
					object.scale.set(Math.random(),Math.random(),Math.random())
					object.updateMatrix();
					instanced.setMatrixAt(index,object.matrix)
					instanced.setColorAt( index, color.setScalar( 0.1 + 0.9 * Math.random()))
					color.setRGB(Math.random(),Math.random(),Math.random())
					instanced.setColorAt( index, color)

					
				}
				scene1.add(instanced)
			}
			{
				scene2=new THREE.Scene()
				camera2=new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
				camera2.position.set(5,5,1)
				
				world.addOrbitControls(camera2,renderer.domElement)
				const light = new THREE.AmbientLight( 0x404040 ,10); // 柔和的白光
				scene2.add(light)

				camera2.lookAt(0,0,0)
				
				const box=new THREE.BoxGeometry(1,1,1)
				const material=new THREE.MeshPhongMaterial({color:0xfffffff})
				const instanced=new THREE.InstancedMesh(box,material,1000)
				console.log(`instanced:`,instanced)
				const object=new THREE.Object3D()
				const color=new THREE.Color()
				function getRandomSphericalPosition(radius) {
					var u = Math.random();  
					var v = Math.random();  
					var theta = 2 * Math.PI * u; // 极角  
					var phi = Math.acos(2 * v - 1); // 方位角(转化为弧度)  
					var r = radius * Math.sqrt(Math.random()); // 随机半径在[0, radius]之间  
				
					var x = r * Math.sin(phi) * Math.cos(theta);  
					var y = r * Math.sin(phi) * Math.sin(theta);  
					var z = r * Math.cos(phi);  
					return new THREE.Vector3(x, y, z);  
				}  
				

				for (let index = 0; index < instanced.count; index++) {
					object.position.copy(getRandomSphericalPosition(10))
					object.scale.set(Math.random(),Math.random(),Math.random())
					object.updateMatrix();
					instanced.setMatrixAt(index,object.matrix)
					instanced.setColorAt( index, color.setScalar( 0.1 + 0.9 * Math.random()))
					color.setRGB(Math.random(),Math.random(),Math.random())
					instanced.setColorAt( index, color)

					
				}
				scene2.add(instanced)
			}
			const textures=[]
			const loader = new THREE.TextureLoader();
			textures[ 0 ] = loader.load( './transition1.png' );
			textures[ 1 ] = loader.load( './transition2.png' );
			const composer = new EffectComposer( renderer );
			const renderTransitionPass = new RenderTransitionPass( scene1, camera1, scene2, camera2 );
			renderTransitionPass.setTexture( textures[0] );
			composer.addPass( renderTransitionPass );

			const outputPass = new OutputPass();
			composer.addPass( outputPass );

			composer.setSize( window.innerWidth, window.innerHeight );
			const params={
				transition:0
			}
			new TWEEN.Tween( params )
				.to( { transition: 1 }, 2000 )
				.onUpdate( function () {

					renderTransitionPass.setTransition( params.transition );
					// renderTransitionPass.setTexture( textures[1] );
					// Change the current alpha texture after each transition
					// if ( params.cycle ) {

					// 	if ( params.transition == 0 || params.transition == 1 ) {

					// 		params.texture = ( params.texture + 1 ) % textures.length;
					// 		renderTransitionPass.setTexture( textures[ params.texture ] );

					// 	}

					// }

				} )
				.repeat( Infinity )
				.delay( 2000 )
				.yoyo( true )
				.start();
			world.addAction(()=>{
				TWEEN.update()
				if(params.transition===0){
					renderer.render(scene2, camera2)
				}else if(params.transition===1){
					renderer.render(scene1, camera1)
				}else{
					composer.render();
				}
				// renderer.render(scene1, camera1)
			})
			// world.createModel(instanced)
	  }
	}, []);

	return <>
	    <canvas style={{display:"block",width:"100vw",height:"100vh"}} ref={canvasRef} />  
	</>
}