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
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"webgl_animation_multiple",
"webgl_animation_walk",
"webgl_batch_lod_bvh",
"webgl_batch_lod_merged",
"webgl_camera",
"webgl_camera_array",
"webgl_camera_logarithmicdepthbuffer",
Expand Down
Binary file added examples/models/gltf/level-optimized.glb
Binary file not shown.
Binary file added examples/screenshots/webgl_batch_lod_merged.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"physics_rapier_character_controller": [ "external" ],
"physics_rapier_vehicle_controller": [ "external" ],
"webgl_batch_lod_bvh": [ "external", "accelerate", "performance", "extension", "plugin", "library", "three.ez" ],
"webgl_batch_lod_merged":[ "external", "accelerate", "performance", "extension", "plugin", "library", "three.ez" ],
"webgl_clipping": [ "solid" ],
"webgl_clipping_advanced": [ "solid" ],
"webgl_clipping_intersection": [ "solid" ],
Expand Down
344 changes: 344 additions & 0 deletions examples/webgl_batch_lod_merged.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
<!DOCTYPE html>
<html lang="en">

<head>
<title>three.js raycaster - batch - lod - from - instance - meshes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
a {
text-decoration: underline;
}
</style>
</head>

<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> batch lod - <a
href="https://github.com/agargaro/batched-mesh-extensions" target="_blank"
rel="noopener">@three.ez/batched-mesh-extensions</a><br />
Combining Instance Meshes that share a Material into a BatchedMesh + LOD<br>
Assets:
<a href="https://sketchfab.com/3d-models/set-of-rocks-in-a-desert-8754d3a4f1d14f698228723f54c69b6c"
target="_blank">Jefferson Frenay</a>
<a href="https://sketchfab.com/3d-models/rock-17-free-rock-pack-vol3-112533f65f3d4848ae14d18ed59f0db2"
target="_blank">Kless Gyzen</a>
<a href="https://sketchfab.com/3d-models/graveyard-angel-stone-sculpture-statue-bf55c6686ae7454181fa33662e7eca29"
target="_blank">Christians</a>
<a href="https://sketchfab.com/3d-models/medieval-torch-free-065861234a824cb982764f04627331c9"
target="_blank">Typhen</a>
<a href="https://sketchfab.com/3d-models/basalt-column-a510aaba8ec64e62873b936d1a87a491"
target="_blank">irs1182</a>
Environment assembly: <a href="https://bandinopla.github.io/" target="_blank">bandinopla</a>
. <br>
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/",

"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/[email protected]/build/index.module.js",
"@three.ez/batched-mesh-extensions": "https://cdn.jsdelivr.net/npm/@three.ez/[email protected]/build/webgl.js",
"bvh.js": "https://cdn.jsdelivr.net/npm/[email protected]/build/index.js",
"@three.ez/simplify-geometry": "https://cdn.jsdelivr.net/npm/@three.ez/[email protected]/build/index.js",
"meshoptimizer": "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import GUI.
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { computeBatchedBoundsTree } from 'three-mesh-bvh';
import { createRadixSort, extendBatchedMeshPrototype, getBatchedMeshLODCount } from '@three.ez/batched-mesh-extensions';
import { performanceRangeLOD, simplifyGeometriesByErrorLOD } from '@three.ez/simplify-geometry';

// add and override BatchedMesh methods ( @three.ez/batched-mesh-extensions )
extendBatchedMeshPrototype();
THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;

let stats;
let camera, scene, renderer;
let animatedCam, dirlight, onEnterFrame;
const clock = new THREE.Clock();

init();

async function init() {

// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
document.body.appendChild( renderer.domElement );

//

scene = new THREE.Scene();

const pmremGenerator = new THREE.PMREMGenerator( renderer );
scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;
scene.environmentIntensity = 0.1;

//

camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( 0, 20, 55 );
//

stats = new Stats();
document.body.appendChild( stats.dom );

const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );

loader.setDRACOLoader( dracoLoader );

const gltf = await loader.loadAsync( 'models/gltf/level-optimized.glb' );

setupScene( gltf );

window.addEventListener( 'resize', onWindowResize );
onWindowResize();

renderer.setAnimationLoop( animate );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

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

}

/**
* You can copy/paste this function. It will combine InstanceMesh objects into a single BatchMesh object if they share the same material.
* @param {THREE.InstancedMesh[]} imeshes the array of instance meshes to analyze
* @param {THREE.InstancedMesh[]} remove Here the combined instance meshes will be pushed so you can discard/remove them.
* @return {THREE.BatchedMesh[]} the combined instance meshes that shared the same material
*/
async function mergeInstanceMeshes( imeshes, remove ) {

//
// group instance meshes by material (assuming 1 material with a name)
//
const material2batch = {};
const out = [];

imeshes.forEach( imesh => {

if ( ! imesh.material || ! imesh.material.name ) return;

if ( ! material2batch[ imesh.material.name ] ) {

material2batch[ imesh.material.name ] = [ imesh ];

} else {

material2batch[ imesh.material.name ].push( imesh );

}

} );

//
// for each "material" one batch will be created
//
for ( const mname in material2batch ) {

const imeshes = material2batch[ mname ];

//
// if it has just 1, ignore it...
//
if ( imeshes.length < 2 ) continue;

let count = 0;
const geometries = [];

//
// collect geometries...
//
for ( let i = 0; i < imeshes.length; i ++ ) {

const imesh = imeshes[ i ];
count += imesh.count;
geometries.push( imesh.geometry );
remove.push( imesh );

}

const material = imeshes[ 1 ].material;
const geometriesLODArray = await simplifyGeometriesByErrorLOD( geometries, 4, performanceRangeLOD );
const { vertexCount, indexCount, LODIndexCount } = getBatchedMeshLODCount( geometriesLODArray );
const batchedMesh = new THREE.BatchedMesh( count, vertexCount, indexCount, material );
batchedMesh.customSort = createRadixSort( batchedMesh );
batchedMesh.castShadow = true;
batchedMesh.receiveShadow = true;

//
// add geometries to the batch + their LODs
//
for ( let i = 0; i < geometriesLODArray.length; i ++ ) {

const geometryLOD = geometriesLODArray[ i ];
const geometryId = batchedMesh.addGeometry( geometryLOD[ 0 ], - 1, LODIndexCount[ i ] );

batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 1 ], .05 );
batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 2 ], .01 );
batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 3 ], .003 );
batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 4 ], .001 );

}

const matrix = new THREE.Matrix4();

//
// create the instances in the BatchMesh by copying the matrices of the InstancedMesh instances...
//
for ( let i = 0; i < imeshes.length; i ++ ) {

const imesh = imeshes[ i ];
for ( let j = 0; j < imesh.count; j ++ ) {

const id = batchedMesh.addInstance( i );

imesh.getMatrixAt( j, matrix );
matrix.premultiply( imesh.matrixWorld ); //<-- in case the InstancedMesh was transformed...

batchedMesh.setMatrixAt( id, matrix );

}

}

out.push( batchedMesh );

//
// these lines are required for the automatic LOD switching to work....
//
batchedMesh.computeBoundsTree();
batchedMesh.computeBVH( THREE.WebGLCoordinateSystem );

}

return out;

}


async function setupScene( gltf ) {

const remove = [];
const imeshes = [];


gltf.scene.traverse( o => {

if ( o instanceof THREE.InstancedMesh ) {

imeshes.push( o );

} else if ( o instanceof THREE.DirectionalLight ) {

o.intensity = o.intensity / 120;
o.castShadow = true;
o.shadow.mapSize.set( 1024, 1024 );

const size = 60;

o.shadow.camera.top = - size;
o.shadow.camera.bottom = size;
o.shadow.camera.left = - size;
o.shadow.camera.right = size;
o.shadow.bias = - 0.0003;
dirlight = o;

} else if ( o instanceof THREE.Mesh ) {

o.castShadow = true;
o.receiveShadow = true;

} else if ( o instanceof THREE.PerspectiveCamera ) {

animatedCam = o;
camera.fov = animatedCam.fov;
camera.updateProjectionMatrix();

}

} );

scene.add( gltf.scene );

//******************************************************************************************
const batchMeshes = await mergeInstanceMeshes( imeshes, remove );

remove.forEach( o => o.removeFromParent() );
batchMeshes.forEach( o => scene.add( o ) );
//******************************************************************************************

scene.add( new THREE.AmbientLight( 0xffee99, .6 ) );
const skyColor = 0xfefefe;
scene.fog = new THREE.Fog( 0xfefefe, 3, 60 );
scene.background = new THREE.Color( skyColor );


renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;


const offset = new THREE.Vector3();
dirlight.getWorldPosition( offset ).sub( animatedCam.position );
offset.y = 0;

const mix = new THREE.AnimationMixer( animatedCam );
mix.clipAction( gltf.animations[ 0 ] ).play().timeScale = 2;

onEnterFrame = delta => {

mix.update( delta );

dirlight.position.x = animatedCam.position.x + offset.x;
dirlight.position.z = animatedCam.position.z + offset.z;

camera.position.copy( animatedCam.position );
camera.quaternion.copy( animatedCam.quaternion );

};

}

function animate() {

stats.begin();

if ( onEnterFrame )
onEnterFrame( clock.getDelta() );


renderer.render( scene, camera );

stats.end();

}

</script>

</body>

</html>
Loading