webgl_volume_mesh.html 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - VolumeMesh</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <link type="text/css" rel="stylesheet" href="main.css">
  8. </head>
  9. <body>
  10. <div id="info">
  11. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - VolumeMesh<br/>
  12. Generation time: <span id="output">-</span>
  13. </div>
  14. <script type="importmap">
  15. {
  16. "imports": {
  17. "three": "../build/three.module.js",
  18. "three/addons/": "./jsm/",
  19. "three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.7.8/build/index.module.js"
  20. }
  21. }
  22. </script>
  23. <script type="module">
  24. import * as THREE from 'three';
  25. import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
  26. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  27. import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
  28. import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
  29. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
  30. import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
  31. import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh';
  32. import { VolumeMesh } from 'three/addons/utils/VolumeMesh.js';
  33. import { RenderSDFLayerMaterial } from 'three/addons/utils/RenderSDFLayerMaterial.js';
  34. // Add BVH extension to THREE.BufferGeometry
  35. THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
  36. THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
  37. THREE.Mesh.prototype.raycast = acceleratedRaycast;
  38. const params = {
  39. resolution: 100,
  40. margin: 0.05,
  41. surface: 0.0,
  42. regenerate: () => regenerateVolume(),
  43. showMultiple: false,
  44. showLayers: true,
  45. layer: 0
  46. };
  47. let renderer, camera, scene, gui;
  48. let outputContainer;
  49. let sourceMesh, sourceMaterial;
  50. let volumeMeshes = [];
  51. let layerPass;
  52. init();
  53. async function init() {
  54. outputContainer = document.getElementById( 'output' );
  55. // renderer setup
  56. renderer = new THREE.WebGLRenderer( { antialias: true } );
  57. renderer.setPixelRatio( window.devicePixelRatio );
  58. renderer.setSize( window.innerWidth, window.innerHeight );
  59. renderer.setAnimationLoop( render );
  60. renderer.toneMapping = THREE.NeutralToneMapping;
  61. document.body.appendChild( renderer.domElement );
  62. // scene setup
  63. scene = new THREE.Scene();
  64. scene.background = new THREE.Color( 0x111111 );
  65. // Setup environment map
  66. const pmremGenerator = new THREE.PMREMGenerator( renderer );
  67. const environment = new RoomEnvironment();
  68. const envMapRT = pmremGenerator.fromScene( environment );
  69. scene.environment = envMapRT.texture;
  70. environment.dispose();
  71. pmremGenerator.dispose();
  72. // camera setup
  73. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
  74. camera.position.set( 1, 1, 2 );
  75. camera.far = 100;
  76. camera.updateProjectionMatrix();
  77. new OrbitControls( camera, renderer.domElement );
  78. // screen pass to render a single layer of the 3d texture
  79. layerPass = new FullScreenQuad( new RenderSDFLayerMaterial() );
  80. // Load model
  81. new GLTFLoader()
  82. .load( 'models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', async ( gltf ) => {
  83. const object = gltf.scene;
  84. object.updateMatrixWorld( true );
  85. // Get material from first mesh
  86. object.traverse( c => {
  87. if ( c.isMesh && c.material && ! sourceMaterial ) {
  88. sourceMaterial = c.material;
  89. }
  90. } );
  91. // Merge into single geometry
  92. const geometries = [];
  93. object.traverse( c => {
  94. if ( c.geometry ) {
  95. const cloned = c.geometry.clone();
  96. cloned.applyMatrix4( c.matrixWorld );
  97. geometries.push( cloned );
  98. }
  99. } );
  100. const mergedGeometry = BufferGeometryUtils.mergeGeometries( geometries );
  101. mergedGeometry.center();
  102. // Compute BVH (required for VolumeMesh)
  103. mergedGeometry.computeBoundsTree( { maxLeafTris: 1 } );
  104. sourceMesh = new THREE.Mesh( mergedGeometry, sourceMaterial );
  105. // Generate the first volume mesh
  106. await regenerateVolume();
  107. } );
  108. // GUI
  109. gui = new GUI();
  110. gui.add( params, 'resolution', 32, 200, 1 ).name( 'Resolution' );
  111. gui.add( params, 'margin', 0, 0.5 ).name( 'Margin' );
  112. gui.add( params, 'surface', - 0.2, 0.5 ).name( 'Surface' ).onChange( () => {
  113. volumeMeshes.forEach( v => v.surface = params.surface );
  114. } );
  115. gui.add( params, 'regenerate' ).name( 'Regenerate' );
  116. gui.add( params, 'showMultiple' ).name( 'Show Multiple' ).onChange( ( value ) => {
  117. if ( value ) {
  118. createMultipleVolumes();
  119. } else {
  120. removeExtraVolumes();
  121. }
  122. } );
  123. gui.add( params, 'showLayers' );
  124. gui.add( params, 'layer', 0, params.resolution - 1, 1 );
  125. window.addEventListener( 'resize', onResize );
  126. }
  127. async function regenerateVolume() {
  128. if ( ! sourceMesh ) return;
  129. const startTime = window.performance.now();
  130. // Remove existing volume meshes
  131. volumeMeshes.forEach( v => {
  132. scene.remove( v );
  133. v.dispose();
  134. } );
  135. volumeMeshes = [];
  136. // Create new VolumeMesh - this is all you need!
  137. const volume = new VolumeMesh( {
  138. resolution: params.resolution,
  139. margin: params.margin,
  140. surface: params.surface,
  141. roughness: 1.0,
  142. metalness: 1.0
  143. } );
  144. // Generate the SDF from the source mesh
  145. await volume.generate( sourceMesh );
  146. // Add to scene
  147. scene.add( volume );
  148. volumeMeshes.push( volume );
  149. const delta = window.performance.now() - startTime;
  150. outputContainer.innerText = `${ delta.toFixed( 2 ) }ms`;
  151. console.log( `VolumeMesh generated in ${delta.toFixed( 2 )}ms` );
  152. // If showing multiple, create them
  153. if ( params.showMultiple ) {
  154. createMultipleVolumes();
  155. }
  156. }
  157. async function createMultipleVolumes() {
  158. if ( volumeMeshes.length === 0 || ! sourceMesh ) return;
  159. // Position the first one
  160. volumeMeshes[ 0 ].position.x = - 1.5;
  161. // Create 2 more volumes
  162. for ( let i = 1; i < 3; i ++ ) {
  163. const volume = new VolumeMesh( {
  164. resolution: params.resolution,
  165. margin: params.margin,
  166. surface: params.surface,
  167. roughness: 1.0,
  168. metalness: 1.0
  169. } );
  170. // Reuse the SDF texture from the first volume
  171. volume.sdfTexture = volumeMeshes[ 0 ].sdfTexture;
  172. volume.inverseBoundsMatrix.copy( volumeMeshes[ 0 ].inverseBoundsMatrix );
  173. // Copy material properties
  174. if ( sourceMesh.material ) {
  175. const mat = sourceMesh.material;
  176. if ( mat.map ) volume.material.map = mat.map;
  177. if ( mat.normalMap ) volume.material.normalMap = mat.normalMap;
  178. if ( mat.metalnessMap ) volume.material.metalnessMap = mat.metalnessMap;
  179. if ( mat.roughnessMap ) volume.material.roughnessMap = mat.roughnessMap;
  180. if ( mat.aoMap ) volume.material.aoMap = mat.aoMap;
  181. volume.material.needsUpdate = true;
  182. }
  183. // Set scale and position
  184. const sdfBoundsMatrix = volume.inverseBoundsMatrix.clone().invert();
  185. const boundsCenter = new THREE.Vector3();
  186. const boundsQuat = new THREE.Quaternion();
  187. const boundsScale = new THREE.Vector3();
  188. sdfBoundsMatrix.decompose( boundsCenter, boundsQuat, boundsScale );
  189. volume.scale.copy( boundsScale );
  190. volume.position.copy( boundsCenter );
  191. volume.position.x = i * 1.5;
  192. volume.updateMatrixWorld();
  193. scene.add( volume );
  194. volumeMeshes.push( volume );
  195. }
  196. }
  197. function removeExtraVolumes() {
  198. // Keep only the first volume
  199. while ( volumeMeshes.length > 1 ) {
  200. const v = volumeMeshes.pop();
  201. scene.remove( v );
  202. // Don't dispose the shared texture, only dispose materials
  203. v.geometry.dispose();
  204. v.material.dispose();
  205. }
  206. // Reset position of the first one
  207. if ( volumeMeshes.length > 0 ) {
  208. const sdfBoundsMatrix = volumeMeshes[ 0 ].inverseBoundsMatrix.clone().invert();
  209. const boundsCenter = new THREE.Vector3();
  210. const boundsQuat = new THREE.Quaternion();
  211. const boundsScale = new THREE.Vector3();
  212. sdfBoundsMatrix.decompose( boundsCenter, boundsQuat, boundsScale );
  213. volumeMeshes[ 0 ].position.copy( boundsCenter );
  214. volumeMeshes[ 0 ].updateMatrixWorld();
  215. }
  216. }
  217. function onResize() {
  218. camera.aspect = window.innerWidth / window.innerHeight;
  219. camera.updateProjectionMatrix();
  220. renderer.setSize( window.innerWidth, window.innerHeight );
  221. }
  222. function render() {
  223. renderer.render( scene, camera );
  224. if ( params.showLayers && volumeMeshes.length > 0 ) {
  225. const layerSize = 256;
  226. renderer.setScissorTest( true );
  227. renderer.setScissor( 0, window.innerHeight - layerSize, layerSize, layerSize );
  228. renderer.setViewport( 0, window.innerHeight - layerSize, layerSize, layerSize );
  229. layerPass.material.uniforms.sdfTex.value = volumeMeshes[ 0 ].sdfTexture;
  230. layerPass.material.uniforms.layer.value = params.layer * ( 1 / params.resolution );
  231. layerPass.render( renderer );
  232. renderer.setScissorTest( false );
  233. renderer.setViewport( 0, 0, window.innerWidth, window.innerHeight );
  234. }
  235. }
  236. </script>
  237. </body>
  238. </html>
粤ICP备19079148号