From dfba1cd4b6f18cb48f451ab72a86ca5975e8f392 Mon Sep 17 00:00:00 2001 From: Shota Matsuda Date: Sun, 17 Aug 2025 06:46:50 +0900 Subject: [PATCH 1/5] Add support for NodeMaterial --- src/three/plugins/fade/wrapFadeMaterial.js | 104 ++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/src/three/plugins/fade/wrapFadeMaterial.js b/src/three/plugins/fade/wrapFadeMaterial.js index 60cc8001e..88ab638c2 100644 --- a/src/three/plugins/fade/wrapFadeMaterial.js +++ b/src/three/plugins/fade/wrapFadeMaterial.js @@ -1,4 +1,13 @@ // Adjusts the provided material to support fading in and out using a bayer pattern. Providing a "previous" + +import { + Discard, Fn, If, + output, + reference, + screenCoordinate +} from 'three/tsl'; +import { Node } from 'three/webgpu'; + // before compile can be used to chain shader adjustments. Returns the added uniforms used for fading. const FADE_PARAMS = Symbol( 'FADE_PARAMS' ); export function wrapFadeMaterial( material, previousOnBeforeCompile ) { @@ -18,6 +27,22 @@ export function wrapFadeMaterial( material, previousOnBeforeCompile ) { material[ FADE_PARAMS ] = params; + if ( material.isNodeMaterial ) { + + modifyNodeMaterial( material, params ); + + } else { + + modifyMaterial( material, params, previousOnBeforeCompile ); + + } + + return params; + +} + +function modifyMaterial( material, params, previousOnBeforeCompile ) { + material.defines = { ...( material.defines || {} ), FEATURE_FADE: 0, @@ -138,6 +163,83 @@ export function wrapFadeMaterial( material, previousOnBeforeCompile ) { }; - return params; +} + +// adapted from https://www.shadertoy.com/view/Mlt3z8 +const bayerDither2x2 = Fn( ( [ v ] ) => { + + return v.y.mul( 3 ).add( v.x.mul( 2 ) ).mod( 4 ); + +} ).setLayout( { + name: 'bayerDither2x2', + type: 'float', + inputs: [ { name: 'v', type: 'vec2' } ] +} ); + +const bayerDither4x4 = Fn( ( [ v ] ) => { + + const P1 = v.mod( 2 ); + const P2 = v.mod( 4 ).mul( 0.5 ).floor(); + return bayerDither2x2( P1 ).mul( 4 ).add( bayerDither2x2( P2 ) ); + +} ).setLayout( { + name: 'bayerDither4x4', + type: 'float', + inputs: [ { name: 'v', type: 'vec2' } ] +} ); + +class FadeNode extends Node { + + constructor( params ) { + + super( 'vec4' ); + this.params = params; + + } + + setup() { + + const fadeIn = reference( 'value', 'float', this.params.fadeIn ); + const fadeOut = reference( 'value', 'float', this.params.fadeOut ); + const bayerValue = bayerDither4x4( screenCoordinate.xy.mod( 4 ).floor() ); + const bayerBins = 16; + const dither = bayerValue.add( 0.5 ).div( bayerBins ); + + If( dither.greaterThanEqual( fadeIn ), () => { + + Discard(); + + } ); + + If( dither.lessThan( fadeOut ), () => { + + Discard(); + + } ); + + return output; + + } + +} + +function modifyNodeMaterial( material, params ) { + + let FEATURE_FADE = false; + + material.defines = { + + set FEATURE_FADE( value ) { + + if ( value != FEATURE_FADE ) { + + FEATURE_FADE = value; + material.outputNode = value ? new FadeNode( params ) : null; + + } + + } + + }; } From dae7dd4dd4f1c97d6dc67d3b77436504e43f1269 Mon Sep 17 00:00:00 2001 From: Shota Matsuda Date: Sun, 17 Aug 2025 06:47:05 +0900 Subject: [PATCH 2/5] Add example for WebGPU --- example/webgpu/fadingTiles.html | 16 +++ example/webgpu/fadingTiles.js | 243 ++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 example/webgpu/fadingTiles.html create mode 100644 example/webgpu/fadingTiles.js diff --git a/example/webgpu/fadingTiles.html b/example/webgpu/fadingTiles.html new file mode 100644 index 000000000..d4fe52e56 --- /dev/null +++ b/example/webgpu/fadingTiles.html @@ -0,0 +1,16 @@ + + + + + + + Dither Fade Tiles + + + +
+ Demonstration of tiles using a dither fade to change, smoothing out the transition. +
+ + + diff --git a/example/webgpu/fadingTiles.js b/example/webgpu/fadingTiles.js new file mode 100644 index 000000000..af2ac9fc4 --- /dev/null +++ b/example/webgpu/fadingTiles.js @@ -0,0 +1,243 @@ +import { + Scene, + PerspectiveCamera, + OrthographicCamera, + Group, +} from 'three'; +import { MeshBasicNodeMaterial, WebGPURenderer } from 'three/webgpu'; +import { TilesFadePlugin } from '3d-tiles-renderer/plugins'; +import { EnvironmentControls, TilesRenderer, CameraTransitionManager } from '3d-tiles-renderer'; +import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; + +let controls, scene, renderer; +let groundTiles, skyTiles, tilesParent, transition; + +const params = { + + reinstantiateTiles, + fadeRootTiles: false, + useFade: true, + errorTarget: 6, + fadeDuration: 0.5, + renderScale: 1, + fadingGroundTiles: '0 tiles', + + orthographic: false, + transitionDuration: 0.25, + +}; + +init().then( () => { + + render(); + +} ); + +async function init() { + + // renderer + renderer = new WebGPURenderer( { antialias: true } ); + await renderer.init(); + renderer.setPixelRatio( window.devicePixelRatio ); + renderer.setSize( window.innerWidth, window.innerHeight ); + renderer.setClearColor( 0xd8cec0 ); + + document.body.appendChild( renderer.domElement ); + + // scene + scene = new Scene(); + + // set up cameras and ortho / perspective transition + transition = new CameraTransitionManager( + new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.25, 4000 ), + new OrthographicCamera( - 1, 1, 1, - 1, 0, 4000 ), + ); + transition.camera.position.set( 20, 10, 20 ); + transition.camera.lookAt( 0, 0, 0 ); + transition.autoSync = false; + + transition.addEventListener( 'camera-change', ( { camera, prevCamera } ) => { + + skyTiles.deleteCamera( prevCamera ); + groundTiles.deleteCamera( prevCamera ); + + skyTiles.setCamera( camera ); + groundTiles.setCamera( camera ); + + controls.setCamera( camera ); + + } ); + + // controls + controls = new EnvironmentControls( scene, transition.camera, renderer.domElement ); + controls.minZoomDistance = 2; + controls.cameraRadius = 1; + + // tiles parent group + tilesParent = new Group(); + tilesParent.rotation.set( Math.PI / 2, 0, 0 ); + scene.add( tilesParent ); + + // init tiles + reinstantiateTiles(); + + // events + onWindowResize(); + window.addEventListener( 'resize', onWindowResize, false ); + + // gui initialization + const gui = new GUI(); + const cameraFolder = gui.addFolder( 'camera' ); + cameraFolder.add( params, 'orthographic' ).onChange( v => { + + transition.fixedPoint.copy( controls.pivotPoint ); + + // adjust the camera before the transition begins + transition.syncCameras(); + controls.adjustCamera( transition.perspectiveCamera ); + controls.adjustCamera( transition.orthographicCamera ); + transition.toggle(); + + } ); + cameraFolder.add( params, 'transitionDuration', 0, 1.5 ); + + const fadeFolder = gui.addFolder( 'fade' ); + fadeFolder.add( params, 'useFade' ); + fadeFolder.add( params, 'fadeRootTiles' ); + fadeFolder.add( params, 'errorTarget', 0, 1000 ); + fadeFolder.add( params, 'fadeDuration', 0, 5 ); + fadeFolder.add( params, 'renderScale', 0.1, 1.0, 0.05 ).onChange( v => renderer.setPixelRatio( v * window.devicePixelRatio ) ); + + const textController = fadeFolder.add( params, 'fadingGroundTiles' ).listen().disable(); + textController.domElement.style.opacity = 1.0; + + gui.add( params, 'reinstantiateTiles' ); + + gui.open(); + +} + +function replaceMaterial( tiles ) { + + tiles.addEventListener( 'load-model', ( { scene } ) => { + + scene.traverse( c => { + + if ( c.material ) { + + const originalMaterial = c.material; + const material = new MeshBasicNodeMaterial(); + if ( originalMaterial.map ) { + + material.map = originalMaterial.map.clone(); + + } + c.originalMaterial = c.material; + c.material = material; + + } + + } ); + + } ); + + tiles.addEventListener( 'dispose-model', ( { scene } ) => { + + scene.traverse( c => { + + if ( c.material ) { + + c.material.dispose(); + + } + + } ); + + } ); + +} + +function reinstantiateTiles() { + + if ( groundTiles ) { + + groundTiles.dispose(); + groundTiles.group.removeFromParent(); + + skyTiles.dispose(); + skyTiles.group.removeFromParent(); + + } + + groundTiles = new TilesRenderer( 'https://raw.githubusercontent.com/NASA-AMMOS/3DTilesSampleData/master/msl-dingo-gap/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_colorize_tileset.json' ); + groundTiles.fetchOptions.mode = 'cors'; + groundTiles.lruCache.minSize = 900; + groundTiles.lruCache.maxSize = 1300; + groundTiles.errorTarget = 12; + replaceMaterial( groundTiles ); + groundTiles.registerPlugin( new TilesFadePlugin() ); + groundTiles.setCamera( transition.camera ); + + skyTiles = new TilesRenderer( 'https://raw.githubusercontent.com/NASA-AMMOS/3DTilesSampleData/master/msl-dingo-gap/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_sky/0528_0260184_to_s64o256_sky_tileset.json' ); + skyTiles.fetchOptions.mode = 'cors'; + skyTiles.lruCache = groundTiles.lruCache; + replaceMaterial( skyTiles ); + skyTiles.registerPlugin( new TilesFadePlugin() ); + skyTiles.setCamera( transition.camera ); + + + tilesParent.add( groundTiles.group, skyTiles.group ); + +} + +function onWindowResize() { + + const { perspectiveCamera, orthographicCamera } = transition; + const aspect = window.innerWidth / window.innerHeight; + + orthographicCamera.bottom = - 40; + orthographicCamera.top = 40; + orthographicCamera.left = - 40 * aspect; + orthographicCamera.right = 40 * aspect; + orthographicCamera.updateProjectionMatrix(); + + perspectiveCamera.aspect = aspect; + perspectiveCamera.updateProjectionMatrix(); + + renderer.setSize( window.innerWidth, window.innerHeight ); + +} + +function render() { + + requestAnimationFrame( render ); + + controls.enabled = ! transition.animating; + controls.update(); + + transition.duration = 1000 * params.transitionDuration; + transition.update(); + + const camera = transition.camera; + camera.updateMatrixWorld(); + + const groundPlugin = groundTiles.getPluginByName( 'FADE_TILES_PLUGIN' ); + groundPlugin.fadeRootTiles = params.fadeRootTiles; + groundPlugin.fadeDuration = params.useFade ? params.fadeDuration * 1000 : 0; + groundTiles.errorTarget = params.errorTarget; + groundTiles.setCamera( camera ); + groundTiles.setResolutionFromRenderer( camera, renderer ); + groundTiles.update(); + + const skyPlugin = skyTiles.getPluginByName( 'FADE_TILES_PLUGIN' ); + skyPlugin.fadeRootTiles = params.fadeRootTiles; + skyPlugin.fadeDuration = params.useFade ? params.fadeDuration * 1000 : 0; + skyTiles.setCamera( camera ); + skyTiles.setResolutionFromRenderer( camera, renderer ); + skyTiles.update(); + + renderer.render( scene, camera ); + + params.fadingGroundTiles = groundPlugin.fadingTiles + ' tiles'; + +} From 9bd566bb3be4eba042734fa4e44f6800b597aa71 Mon Sep 17 00:00:00 2001 From: Shota Matsuda Date: Wed, 20 Aug 2025 15:35:32 +0900 Subject: [PATCH 3/5] Cache outputNode to mitigate node transpilation time --- src/three/plugins/fade/wrapFadeMaterial.js | 51 ++++++++-------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/src/three/plugins/fade/wrapFadeMaterial.js b/src/three/plugins/fade/wrapFadeMaterial.js index 88ab638c2..0739ccfb4 100644 --- a/src/three/plugins/fade/wrapFadeMaterial.js +++ b/src/three/plugins/fade/wrapFadeMaterial.js @@ -1,12 +1,6 @@ // Adjusts the provided material to support fading in and out using a bayer pattern. Providing a "previous" -import { - Discard, Fn, If, - output, - reference, - screenCoordinate -} from 'three/tsl'; -import { Node } from 'three/webgpu'; +import { Discard, Fn, If, output, screenCoordinate, uniform } from 'three/tsl'; // before compile can be used to chain shader adjustments. Returns the added uniforms used for fading. const FADE_PARAMS = Symbol( 'FADE_PARAMS' ); @@ -188,43 +182,36 @@ const bayerDither4x4 = Fn( ( [ v ] ) => { inputs: [ { name: 'v', type: 'vec2' } ] } ); -class FadeNode extends Node { +// Define shared uniforms for fadeIn/fadeOut so that "outputNode" can be cached. +const fadeIn = uniform( 0 ).onObjectUpdate( ( { material } ) => material.params.fadeIn.value ); +const fadeOut = uniform( 0 ).onObjectUpdate( ( { material } ) => material.params.fadeOut.value ); - constructor( params ) { +const outputNode = Fn( () => { - super( 'vec4' ); - this.params = params; + const bayerValue = bayerDither4x4( screenCoordinate.xy.mod( 4 ).floor() ); + const bayerBins = 16; + const dither = bayerValue.add( 0.5 ).div( bayerBins ); - } - - setup() { - - const fadeIn = reference( 'value', 'float', this.params.fadeIn ); - const fadeOut = reference( 'value', 'float', this.params.fadeOut ); - const bayerValue = bayerDither4x4( screenCoordinate.xy.mod( 4 ).floor() ); - const bayerBins = 16; - const dither = bayerValue.add( 0.5 ).div( bayerBins ); - - If( dither.greaterThanEqual( fadeIn ), () => { + If( dither.greaterThanEqual( fadeIn ), () => { - Discard(); + Discard(); - } ); + } ); - If( dither.lessThan( fadeOut ), () => { + If( dither.lessThan( fadeOut ), () => { - Discard(); + Discard(); - } ); + } ); - return output; + return output; - } - -} +} )(); function modifyNodeMaterial( material, params ) { + material.params = params; + let FEATURE_FADE = false; material.defines = { @@ -234,7 +221,7 @@ function modifyNodeMaterial( material, params ) { if ( value != FEATURE_FADE ) { FEATURE_FADE = value; - material.outputNode = value ? new FadeNode( params ) : null; + material.outputNode = value ? outputNode : null; } From 39d48c8a029efeb9afdc70d94f4a62cd30d748a8 Mon Sep 17 00:00:00 2001 From: Shota Matsuda Date: Wed, 20 Aug 2025 15:37:20 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Add=20getter=20for=20FEATURE=5FFADE=20in=20?= =?UTF-8?q?case=20it=E2=80=99s=20read=20from=20another=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/three/plugins/fade/wrapFadeMaterial.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/three/plugins/fade/wrapFadeMaterial.js b/src/three/plugins/fade/wrapFadeMaterial.js index 0739ccfb4..732b5855f 100644 --- a/src/three/plugins/fade/wrapFadeMaterial.js +++ b/src/three/plugins/fade/wrapFadeMaterial.js @@ -212,10 +212,16 @@ function modifyNodeMaterial( material, params ) { material.params = params; - let FEATURE_FADE = false; + let FEATURE_FADE = 0; material.defines = { + get FEATURE_FADE() { + + return FEATURE_FADE; + + }, + set FEATURE_FADE( value ) { if ( value != FEATURE_FADE ) { From 90ed997fe0a17497b5218c34ea2a8c63ea162dcd Mon Sep 17 00:00:00 2001 From: Shota Matsuda Date: Wed, 20 Aug 2025 15:38:27 +0900 Subject: [PATCH 5/5] Fix path to style --- example/webgpu/fadingTiles.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/webgpu/fadingTiles.html b/example/webgpu/fadingTiles.html index d4fe52e56..27174a453 100644 --- a/example/webgpu/fadingTiles.html +++ b/example/webgpu/fadingTiles.html @@ -5,7 +5,7 @@ Dither Fade Tiles - +