You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
earthquake_3d_viewer_front/three/examples/webgl_batch_lod_bvh.html

281 lines
8.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js raycaster - batch - lod - bvh</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 bvh - <a href="https://github.com/agargaro/batched-mesh-extensions" target="_blank" rel="noopener">@three.ez/batched-mesh-extensions</a><br/>
BatchedMesh with 10 geometries and 500k instances.<br>
Each geometry has 5 LODs (4 generated with meshoptimizer). <br>
Frustum culling and raycasting are accelerated by using BVHs (TLAS & BLAS). <br>
</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/",
"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.0/build/index.module.js",
"@three.ez/batched-mesh-extensions": "https://cdn.jsdelivr.net/npm/@three.ez/batched-mesh-extensions@0.0.8/build/webgl.js",
"bvh.js": "https://cdn.jsdelivr.net/npm/bvh.js@0.0.13/build/index.js",
"@three.ez/simplify-geometry": "https://cdn.jsdelivr.net/npm/@three.ez/simplify-geometry@0.0.1/build/index.js",
"meshoptimizer": "https://cdn.jsdelivr.net/npm/meshoptimizer@0.23.0/+esm"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { MapControls } from 'three/addons/controls/MapControls.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { acceleratedRaycast, 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();
// add the extension functions ( three-mesh-bvh )
THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
let stats;
let camera, scene, renderer;
const instancesCount = 500000;
let batchedMesh;
let lastHoveredInstance = null;
const lastHoveredColor = new THREE.Color();
const highlight = new THREE.Color( 'red' );
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2( 1, 1 );
const position = new THREE.Vector3();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3( 1, 1, 1 );
const matrix = new THREE.Matrix4();
const color = new THREE.Color();
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 = 0.8;
document.body.appendChild( renderer.domElement );
//
scene = new THREE.Scene();
const pmremGenerator = new THREE.PMREMGenerator( renderer );
scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;
//
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( 0, 20, 55 );
//
raycaster.firstHitOnly = true;
stats = new Stats();
document.body.appendChild( stats.dom );
const controls = new MapControls( camera, renderer.domElement );
controls.maxPolarAngle = Math.PI / 2;
const geometries = [
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 1 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 2 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 3 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 4 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 5 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 2, 1 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 2, 3 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 3, 1 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 4, 1 ),
new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 5, 3 )
];
// generate 4 LODs (levels of detail) for each geometry
const geometriesLODArray = await simplifyGeometriesByErrorLOD( geometries, 4, performanceRangeLOD );
// create BatchedMesh
const { vertexCount, indexCount, LODIndexCount } = getBatchedMeshLODCount( geometriesLODArray );
batchedMesh = new THREE.BatchedMesh( instancesCount, vertexCount, indexCount, new THREE.MeshStandardMaterial( { metalness: 1, roughness: 0.8 } ) );
// enable radix sort for better performance
batchedMesh.customSort = createRadixSort( batchedMesh );
// add geometries and their LODs to the batched mesh ( all LODs share the same position array )
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 ], 50 );
batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 2 ], 100 );
batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 3 ], 125 );
batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 4 ], 200 );
}
// place instances in a 2D grid with randomized rotation and color
const sqrtCount = Math.ceil( Math.sqrt( instancesCount ) );
const size = 5.5;
const start = ( sqrtCount / - 2 * size ) + ( size / 2 );
for ( let i = 0; i < instancesCount; i ++ ) {
const r = Math.floor( i / sqrtCount );
const c = i % sqrtCount;
const id = batchedMesh.addInstance( Math.floor( Math.random() * geometriesLODArray.length ) );
position.set( c * size + start, 0, r * size + start );
quaternion.random();
batchedMesh.setMatrixAt( id, matrix.compose( position, quaternion, scale ) );
batchedMesh.setColorAt( id, color.setHSL( Math.random(), 0.6, 0.5 ) );
}
// compute blas (bottom-level acceleration structure) bvh ( three-mesh-bvh )
batchedMesh.computeBoundsTree();
// compute tlas (top-level acceleration structure) bvh ( @three.ez/batched-mesh-extensions )
batchedMesh.computeBVH( THREE.WebGLCoordinateSystem );
scene.add( batchedMesh );
// set up gui
const config = {
freeze: false,
useBVH: true,
useLOD: true
};
const bvh = batchedMesh.bvh;
const lods = batchedMesh._geometryInfo.map( x => x.LOD );
const onBeforeRender = batchedMesh.onBeforeRender;
const gui = new GUI();
gui.add( batchedMesh, 'instanceCount' ).disable();
gui.add( config, 'freeze' ).onChange( v => {
batchedMesh.onBeforeRender = v ? () => {} : onBeforeRender;
} );
const frustumCullingFolder = gui.addFolder( 'Frustum culling & raycasting' );
frustumCullingFolder.add( config, 'useBVH' ).onChange( v => {
batchedMesh.bvh = v ? bvh : null;
} );
const geometriesFolder = gui.addFolder( 'Geometries' );
geometriesFolder.add( config, 'useLOD' ).onChange( v => {
const geometryInfo = batchedMesh._geometryInfo;
for ( let i = 0; i < geometryInfo.length; i ++ ) {
geometryInfo[ i ].LOD = v ? lods[ i ] : null;
}
} );
document.addEventListener( 'pointermove', onPointerMove );
window.addEventListener( 'resize', onWindowResize );
onWindowResize();
renderer.setAnimationLoop( animate );
}
function onPointerMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycast();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function raycast() {
raycaster.setFromCamera( mouse, camera );
const intersection = raycaster.intersectObject( batchedMesh );
const batchId = intersection.length > 0 ? intersection[ 0 ].batchId : null;
if ( lastHoveredInstance === batchId ) return;
if ( lastHoveredInstance ) {
batchedMesh.setColorAt( lastHoveredInstance, lastHoveredColor );
}
if ( batchId ) {
batchedMesh.getColorAt( batchId, lastHoveredColor );
batchedMesh.setColorAt( batchId, highlight );
}
lastHoveredInstance = batchId;
}
function animate() {
stats.begin();
renderer.render( scene, camera );
stats.end();
}
</script>
</body>
</html>