Skip to content

Commit

Permalink
Add support for fading BatchedMesh (#894)
Browse files Browse the repository at this point in the history
* Remove mixing of fade state and materials

* Change fade handle to tile

* Separate material fade with fade tracking

* Wrap the material with batched mesh uniforms

* Add a FadeBatchedMesh class

* Add instance data texture classe

* Add fade helpers

* Get basic implementation working

* fix

* some fixes

* Working

* updats

* fix assignment

* 32 -> 8 bit fade texture

* Fix texture type

* comments, clarification
  • Loading branch information
gkjohnson authored Dec 25, 2024
1 parent 177ed9c commit 2baf3ef
Show file tree
Hide file tree
Showing 8 changed files with 650 additions and 207 deletions.
5 changes: 1 addition & 4 deletions example/googleMapsExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ function reinstantiateTiles() {
tiles.registerPlugin( new TileCompressionPlugin() );
tiles.registerPlugin( new UpdateOnChangePlugin() );
tiles.registerPlugin( new UnloadTilesPlugin() );
tiles.registerPlugin( new TilesFadePlugin() );
tiles.registerPlugin( new GLTFExtensionsPlugin( {
// Note the DRACO compression files need to be supplied via an explicit source.
// We use unpkg here but in practice should be provided by the application.
Expand All @@ -80,10 +81,6 @@ function reinstantiateTiles() {
instanceCount: 250,
} ) );

} else {

tiles.registerPlugin( new TilesFadePlugin() );

}

tiles.group.rotation.x = - Math.PI / 2;
Expand Down
2 changes: 1 addition & 1 deletion src/base/TilesRendererBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ export class TilesRendererBase {
if ( parentTile === null ) {

tile.__depth = 0;
tile.__depthFromRenderedParent = 0;
tile.__depthFromRenderedParent = ( tile.__hasRenderableContent ? 1 : 0 );
tile.refine = tile.refine || 'REPLACE';

} else {
Expand Down
107 changes: 107 additions & 0 deletions src/plugins/three/fade/FadeBatchedMesh.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { PassThroughBatchedMesh } from './PassThroughBatchedMesh.js';
import { RGFormat, UnsignedByteType, DataTexture } from 'three';
import { wrapFadeMaterial } from './wrapFadeMaterial.js';

export class FadeBatchedMesh extends PassThroughBatchedMesh {

constructor( ...args ) {

super( ...args );

// construct a version of the material that supports fading
const material = this.material;
const params = wrapFadeMaterial( material, material.onBeforeCompile );
material.defines.FEATURE_FADE = 1;
material.defines.USE_BATCHING_FRAG = 1;
material.needsUpdate = true;

// fade parameters
this.fadeTexture = null;
this._fadeParams = params;

}

// Set the fade state
setFadeAt( index, fadeIn, fadeOut ) {

this._initFadeTexture();
this.fadeTexture.setValueAt( index, fadeIn * 255, fadeOut * 255 );

}

// initialize the texture and resize it if needed
_initFadeTexture() {

// calculate the new size
let size = Math.sqrt( this._maxInstanceCount );
size = Math.ceil( size );

const length = size * size * 2;
const oldFadeTexture = this.fadeTexture;
if ( ! oldFadeTexture || oldFadeTexture.image.data.length !== length ) {

// 2 bytes per RG pixel
const fadeArray = new Uint8Array( length );
const fadeTexture = new InstanceDataTexture( fadeArray, size, size, RGFormat, UnsignedByteType );

// copy the data from the old fade texture if it exists
if ( oldFadeTexture ) {

oldFadeTexture.dispose();

const src = oldFadeTexture.image.data;
const dst = this.fadeTexture.image.data;
const len = Math.min( src.length, dst.length );
dst.set( new src.constructor( src.buffer, 0, len ) );

}

// assign the new fade texture to the uniform, member variable
this.fadeTexture = fadeTexture;
this._fadeParams.fadeTexture.value = fadeTexture;
fadeTexture.needsUpdate = true;

}

}

// dispose the fade texture. Super cannot be used here due to proxy
dispose() {

this.fadeTexture.dispose();

}

}

// Version of data texture that can assign pixel values
class InstanceDataTexture extends DataTexture {

setValueAt( instance, ...values ) {

const { data, width, height } = this.image;
const itemSize = Math.floor( data.length / ( width * height ) );
let needsUpdate = false;
for ( let i = 0; i < itemSize; i ++ ) {

const index = instance * itemSize + i;
const prevValue = data[ index ];
const newValue = values[ i ] || 0;
if ( prevValue !== newValue ) {

data[ index ] = newValue;
needsUpdate = true;

}

}

if ( needsUpdate ) {

this.needsUpdate = true;

}

}

}
173 changes: 10 additions & 163 deletions src/plugins/three/fade/FadeManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,13 @@ export class FadeManager {
this.fadeCount = 0;
this._lastTick = - 1;
this._fadeState = new Map();
this._fadeParams = new WeakMap();
this.onFadeComplete = null;
this.onFadeStart = null;
this.onFadeSetComplete = null;
this.onFadeSetStart = null;

}

// initialize materials in the object
prepareObject( object ) {

object.traverse( child => {

if ( child.material ) {

this.prepareMaterial( child.material );

}

} );

}

// delete the object from the fade, reset the material data
deleteObject( object ) {

Expand All @@ -43,103 +27,6 @@ export class FadeManager {

this.completeFade( object );

const fadeParams = this._fadeParams;
object.traverse( child => {

const material = child.material;
if ( material ) {

fadeParams.delete( material );
material.onBeforeCompile = () => {};
material.needsUpdate = true;

}

} );

}

// initialize the material
prepareMaterial( material ) {

const fadeParams = this._fadeParams;
if ( fadeParams.has( material ) ) {

return;

}

const params = {
fadeIn: { value: 0 },
fadeOut: { value: 0 },
};

material.defines = {
FEATURE_FADE: 0,
};

material.onBeforeCompile = shader => {

shader.uniforms = {
...shader.uniforms,
...params,
};

shader.fragmentShader = shader.fragmentShader
.replace( /void main\(/, value => /* glsl */`
#if FEATURE_FADE
// adapted from https://www.shadertoy.com/view/Mlt3z8
float bayerDither2x2( vec2 v ) {
return mod( 3.0 * v.y + 2.0 * v.x, 4.0 );
}
float bayerDither4x4( vec2 v ) {
vec2 P1 = mod( v, 2.0 );
vec2 P2 = floor( 0.5 * mod( v, 4.0 ) );
return 4.0 * bayerDither2x2( P1 ) + bayerDither2x2( P2 );
}
uniform float fadeIn;
uniform float fadeOut;
#endif
${ value }
` )
.replace( /#include <dithering_fragment>/, value => /* glsl */`
${ value }
#if FEATURE_FADE
float bayerValue = bayerDither4x4( floor( mod( gl_FragCoord.xy, 4.0 ) ) );
float bayerBins = 16.0;
float dither = ( 0.5 + bayerValue ) / bayerBins;
if ( dither >= fadeIn ) {
discard;
}
if ( dither < fadeOut ) {
discard;
}
#endif
` );


};

fadeParams.set( material, params );

}

// Ensure we're storing a fade timer for the provided object
Expand All @@ -162,20 +49,6 @@ export class FadeManager {

fadeState.set( object, state );

const fadeParams = this._fadeParams;
object.traverse( child => {

const material = child.material;
if ( material && fadeParams.has( material ) ) {

const params = fadeParams.get( material );
params.fadeIn.value = 0;
params.fadeOut.value = 0;

}

} );

return true;

}
Expand All @@ -184,22 +57,15 @@ export class FadeManager {
completeFade( object ) {

const fadeState = this._fadeState;
if ( ! fadeState.has( object ) ) return;
if ( ! fadeState.has( object ) ) {

const visible = fadeState.get( object ).fadeOutTarget === 0;

fadeState.delete( object );
object.traverse( child => {

const material = child.material;
if ( material && material.defines.FEATURE_FADE !== 0 ) {
return;

material.defines.FEATURE_FADE = 0;
material.needsUpdate = true;
}

}
const visible = fadeState.get( object ).fadeOutTarget === 0;

} );
fadeState.delete( object );

// fire events
this.fadeCount --;
Expand Down Expand Up @@ -230,7 +96,11 @@ export class FadeManager {

forEachObject( cb ) {

this._fadeState.forEach( ( info, scene ) => cb( scene ) );
this._fadeState.forEach( ( info, object ) => {

cb( object, info );

} );

}

Expand Down Expand Up @@ -321,7 +191,6 @@ export class FadeManager {
this._lastTick = time;

const fadeState = this._fadeState;
const fadeParams = this._fadeParams;
fadeState.forEach( ( state, object ) => {

// tick the fade values
Expand All @@ -344,28 +213,6 @@ export class FadeManager {
state.fadeIn = fadeIn;
state.fadeOut = fadeOut;

// update the material fields
const defineValue = Number( fadeOut !== fadeOutTarget || fadeIn !== fadeInTarget );
object.traverse( child => {

const material = child.material;
if ( material && fadeParams.has( material ) ) {

const uniforms = fadeParams.get( material );
uniforms.fadeIn.value = fadeIn;
uniforms.fadeOut.value = fadeOut;

if ( defineValue !== material.defines.FEATURE_FADE ) {

material.defines.FEATURE_FADE = defineValue;
material.needsUpdate = true;

}

}

} );

// Check if the fade in and fade out animations are complete
const fadeOutComplete = fadeOut === 1 || fadeOut === 0;
const fadeInComplete = fadeIn === 1 || fadeIn === 0;
Expand Down
Loading

0 comments on commit 2baf3ef

Please sign in to comment.