Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions example/webgpu/fadingTiles.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta charset="utf-8"/>

<title>Dither Fade Tiles</title>
<link rel="stylesheet" href="../styles.css">
</head>
<body class="light">
<div id="info">
Demonstration of tiles using a dither fade to change, smoothing out the transition.
</div>
<script src="./fadingTiles.js" type="module"></script>
</body>
</html>
243 changes: 243 additions & 0 deletions example/webgpu/fadingTiles.js
Original file line number Diff line number Diff line change
@@ -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';

}
97 changes: 96 additions & 1 deletion src/three/plugins/fade/wrapFadeMaterial.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Adjusts the provided material to support fading in and out using a bayer pattern. Providing a "previous"

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' );
export function wrapFadeMaterial( material, previousOnBeforeCompile ) {
Expand All @@ -18,6 +21,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,
Expand Down Expand Up @@ -138,6 +157,82 @@ 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' } ]
} );

// 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 );

const outputNode = Fn( () => {

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 ) {

material.params = params;

let FEATURE_FADE = 0;

material.defines = {

get FEATURE_FADE() {

return FEATURE_FADE;

},

set FEATURE_FADE( value ) {

if ( value != FEATURE_FADE ) {

FEATURE_FADE = value;
material.outputNode = value ? outputNode : null;

}

}

};

}
Loading