webgl_loader_3dtiles.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js loader - 3d tiles + clouds</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. <meta property="og:title" content="three.js loader - 3d tiles + clouds">
  8. <meta property="og:type" content="website">
  9. <meta property="og:url" content="https://threejs.org/examples/webgl_loader_3dtiles.html">
  10. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgl_loader_3dtiles.jpg">
  11. <link type="text/css" rel="stylesheet" href="main.css">
  12. <style>
  13. body {
  14. background-color: #000;
  15. color: #eee;
  16. }
  17. a {
  18. color: #b3e5fc;
  19. text-decoration: underline;
  20. pointer-events: all;
  21. }
  22. #credits {
  23. pointer-events: none;
  24. position: absolute;
  25. left: 10px;
  26. bottom: 5px;
  27. }
  28. img {
  29. height: 25px;
  30. margin-right: 0px;
  31. }
  32. </style>
  33. </head>
  34. <body>
  35. <div id="info">
  36. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> -
  37. <a href="https://github.com/NASA-AMMOS/3DTilesRendererJS" target="_blank" rel="noopener">3d-tiles-renderer</a> +
  38. <a href="https://github.com/takram-design-engineering/three-geospatial" target="_blank" rel="noopener">three-clouds</a><br/>
  39. <a href="https://developers.google.com/maps/documentation/tile/3d-tiles" target="_blank" rel="noopener">Google Photorealistic Tiles</a> token courtesy of <a href="https://ion.cesium.com/" target="_blank" rel="noopener">Cesium Ion</a>.<br/>
  40. <label>time of day: <input id="hour" type="range" min="0" max="24" value="0" step="0.01" style="pointer-events: all; vertical-align: middle;"></label>
  41. </div>
  42. <div id="credits">
  43. <img src="./textures/google_on_non_white_hdpi.png" />
  44. <a href="https://ion.cesium.com/" target="_blank" rel="noopener"><img src="./textures/cesiumion.png" /></a>
  45. </div>
  46. <!-- "three/examples/" import map entry required by 3d-tiles-renderer -->
  47. <script type="importmap">
  48. {
  49. "imports": {
  50. "three": "../build/three.module.js",
  51. "three/addons/": "./jsm/",
  52. "three/examples/": "./",
  53. "3d-tiles-renderer": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.24/build/index.js",
  54. "3d-tiles-renderer/core/plugins": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.24/build/index.core-plugins.js",
  55. "3d-tiles-renderer/three/plugins": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.24/build/index.three-plugins.js",
  56. "postprocessing": "https://cdn.jsdelivr.net/npm/postprocessing@6.39.0/build/index.js",
  57. "@takram/three-clouds": "https://cdn.jsdelivr.net/npm/@takram/three-clouds@0.7.3/build/index.js",
  58. "@takram/three-atmosphere": "https://cdn.jsdelivr.net/npm/@takram/three-atmosphere@0.17.1/build/index.js",
  59. "@takram/three-geospatial": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial@0.7.1/build/index.js",
  60. "@takram/three-geospatial/shaders": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial@0.7.1/build/shaders.js",
  61. "@takram/three-geospatial-effects": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial-effects@0.6.1/build/index.js",
  62. "@takram/three-atmosphere/shaders/bruneton": "https://cdn.jsdelivr.net/npm/@takram/three-atmosphere@0.17.1/build/shaders/bruneton.js"
  63. }
  64. }
  65. </script>
  66. <script type="module">
  67. import * as THREE from 'three';
  68. import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
  69. import { toCreasedNormals } from 'three/addons/utils/BufferGeometryUtils.js';
  70. import { TilesRenderer, GlobeControls, CAMERA_FRAME } from '3d-tiles-renderer';
  71. import { CesiumIonAuthPlugin } from '3d-tiles-renderer/core/plugins';
  72. import { GLTFExtensionsPlugin, TilesFadePlugin, UpdateOnChangePlugin } from '3d-tiles-renderer/three/plugins';
  73. import { EffectMaterial, EffectPass, NormalPass, SMAAEffect } from 'postprocessing';
  74. import {
  75. CloudsEffect,
  76. CLOUD_SHAPE_TEXTURE_SIZE,
  77. CLOUD_SHAPE_DETAIL_TEXTURE_SIZE,
  78. DEFAULT_LOCAL_WEATHER_URL,
  79. DEFAULT_SHAPE_URL,
  80. DEFAULT_SHAPE_DETAIL_URL,
  81. DEFAULT_TURBULENCE_URL
  82. } from '@takram/three-clouds';
  83. import { AerialPerspectiveEffect, PrecomputedTexturesGenerator, getSunDirectionECEF } from '@takram/three-atmosphere';
  84. import { STBNLoader, DEFAULT_STBN_URL } from '@takram/three-geospatial';
  85. import { DitheringEffect, LensFlareEffect } from '@takram/three-geospatial-effects';
  86. // Ion key provided by Cesium for use on threejs.org
  87. // A personal Cesium Ion key can be used for development.
  88. const ION_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiMTFiZTRmZS1mMWIxLTQ5YzYtYjA4Zi0xYTE0MjFmYzQ5OGYiLCJpZCI6MjY3NzgzLCJpYXQiOjE3MzY0NzQxMDh9.ppGPgpse1lq7QeNyljX7THUyK5w1x_4HksSHSlhe5sY';
  89. let camera, scene, renderer;
  90. let tiles, controls;
  91. let clouds, aerialPerspective;
  92. let _prevTime = 0, _deltaTime = 0;
  93. init();
  94. async function init() {
  95. // camera
  96. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 10, 1e6 );
  97. // scene
  98. scene = new THREE.Scene();
  99. // renderer
  100. renderer = new THREE.WebGLRenderer( { outputBufferType: THREE.HalfFloatType } );
  101. renderer.setPixelRatio( window.devicePixelRatio );
  102. renderer.setSize( window.innerWidth, window.innerHeight );
  103. renderer.toneMapping = THREE.AgXToneMapping;
  104. renderer.toneMappingExposure = 10;
  105. document.body.appendChild( renderer.domElement );
  106. // loader
  107. const dracoLoader = new DRACOLoader();
  108. dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
  109. dracoLoader.setDecoderConfig( { type: 'js' } );
  110. const DEG2RAD = Math.PI / 180;
  111. // tiles
  112. class TileCreasedNormalsPlugin {
  113. processTileModel( scene ) {
  114. scene.traverse( ( mesh ) => {
  115. if ( mesh.geometry ) {
  116. mesh.geometry = toCreasedNormals( mesh.geometry, 30 * DEG2RAD );
  117. }
  118. } );
  119. }
  120. }
  121. tiles = new TilesRenderer();
  122. tiles.registerPlugin( new CesiumIonAuthPlugin( { apiToken: ION_KEY, assetId: '2275207', autoRefreshToken: true } ) );
  123. tiles.registerPlugin( new GLTFExtensionsPlugin( { dracoLoader } ) );
  124. tiles.registerPlugin( new TileCreasedNormalsPlugin() );
  125. tiles.registerPlugin( new TilesFadePlugin() );
  126. tiles.registerPlugin( new UpdateOnChangePlugin() );
  127. tiles.setCamera( camera );
  128. tiles.setResolutionFromRenderer( camera, renderer );
  129. scene.add( tiles.group );
  130. // position camera above Tokyo
  131. tiles.ellipsoid.getObjectFrame(
  132. 35.6812 * DEG2RAD, 139.80 * DEG2RAD, 500,
  133. - 90 * DEG2RAD, - 10 * DEG2RAD, 0,
  134. camera.matrix, CAMERA_FRAME
  135. );
  136. camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );
  137. // controls
  138. controls = new GlobeControls( scene, camera, renderer.domElement );
  139. controls.setEllipsoid( tiles.ellipsoid, tiles.group );
  140. controls.enableDamping = true;
  141. // Workaround: adjustHeight causes camera drift as tiles load.
  142. // Disable until first user interaction.
  143. controls.adjustHeight = false;
  144. function enableAdjustHeight() {
  145. controls.adjustHeight = true;
  146. renderer.domElement.removeEventListener( 'pointerdown', enableAdjustHeight );
  147. renderer.domElement.removeEventListener( 'wheel', enableAdjustHeight );
  148. }
  149. renderer.domElement.addEventListener( 'pointerdown', enableAdjustHeight );
  150. renderer.domElement.addEventListener( 'wheel', enableAdjustHeight );
  151. // sun direction
  152. const sunDirection = new THREE.Vector3();
  153. const params = { hourUTC: 0 }; // 0:00 UTC = 9:00 AM Tokyo
  154. function updateSunDirection() {
  155. const ms = params.hourUTC * 3600000;
  156. const date = new Date( Date.UTC( 2024, 2, 1 ) + ms );
  157. getSunDirectionECEF( date, sunDirection );
  158. aerialPerspective.sunDirection.copy( sunDirection );
  159. clouds.sunDirection.copy( sunDirection );
  160. }
  161. // aerial perspective (sky + atmosphere + deferred lighting)
  162. aerialPerspective = new AerialPerspectiveEffect( camera );
  163. aerialPerspective.sky = true;
  164. aerialPerspective.sunLight = true;
  165. aerialPerspective.skyLight = true;
  166. const normalPass = new NormalPass( scene, camera );
  167. aerialPerspective.normalBuffer = normalPass.texture;
  168. // clouds
  169. clouds = new CloudsEffect( camera );
  170. clouds.coverage = 0.3;
  171. clouds.localWeatherVelocity.set( 0.001, 0 );
  172. clouds.shadow.farScale = 0.25;
  173. clouds.shadow.maxFar = 1e5;
  174. clouds.shadow.cascadeCount = 2;
  175. clouds.shadow.mapSize.set( 512, 512 );
  176. clouds.shadow.splitMode = 'practical';
  177. clouds.shadow.splitLambda = 0.71;
  178. // sync cloud shadows to atmosphere
  179. clouds.events.addEventListener( 'change', ( event ) => {
  180. if ( event.property === 'atmosphereOverlay' ) aerialPerspective.overlay = clouds.atmosphereOverlay;
  181. if ( event.property === 'atmosphereShadow' ) aerialPerspective.shadow = clouds.atmosphereShadow;
  182. if ( event.property === 'atmosphereShadowLength' ) aerialPerspective.shadowLength = clouds.atmosphereShadowLength;
  183. } );
  184. // adapter: bridges pmndrs postprocessing passes to renderer.setEffects()
  185. class EffectPassAdapter {
  186. constructor( pass ) {
  187. this.pass = pass;
  188. this.needsSwap = pass.needsSwap !== false;
  189. this.enabled = true;
  190. this._initialized = false;
  191. }
  192. render( renderer, writeBuffer, readBuffer ) {
  193. if ( ! this._initialized ) {
  194. this.pass.initialize( renderer, false, THREE.HalfFloatType );
  195. this.pass.setSize( readBuffer.width, readBuffer.height );
  196. if ( readBuffer.depthTexture && this.pass.setDepthTexture ) {
  197. this.pass.setDepthTexture( readBuffer.depthTexture );
  198. }
  199. this._initialized = true;
  200. }
  201. if ( this.pass.fullscreenMaterial instanceof EffectMaterial ) {
  202. this.pass.fullscreenMaterial.adoptCameraSettings( camera );
  203. }
  204. this.pass.render( renderer, readBuffer, writeBuffer, _deltaTime );
  205. }
  206. setSize( width, height ) {
  207. if ( this._initialized ) this.pass.setSize( width, height );
  208. }
  209. }
  210. // postprocessing
  211. renderer.setEffects( [
  212. new EffectPassAdapter( normalPass ),
  213. new EffectPassAdapter( new EffectPass( camera, clouds, aerialPerspective ) ),
  214. new EffectPassAdapter( new EffectPass( camera, new LensFlareEffect() ) ),
  215. new EffectPassAdapter( new EffectPass( camera, new SMAAEffect() ) ),
  216. new EffectPassAdapter( new EffectPass( camera, new DitheringEffect() ) ),
  217. ] );
  218. // generate precomputed atmosphere textures on GPU
  219. const texturesGenerator = new PrecomputedTexturesGenerator( renderer );
  220. const textures = await texturesGenerator.update();
  221. Object.assign( aerialPerspective, textures );
  222. Object.assign( clouds, textures );
  223. // load cloud textures
  224. const textureLoader = new THREE.TextureLoader();
  225. function loadCloudTexture( url, property ) {
  226. textureLoader.load( url, ( texture ) => {
  227. texture.minFilter = THREE.LinearMipMapLinearFilter;
  228. texture.magFilter = THREE.LinearFilter;
  229. texture.wrapS = THREE.RepeatWrapping;
  230. texture.wrapT = THREE.RepeatWrapping;
  231. texture.colorSpace = THREE.NoColorSpace;
  232. texture.needsUpdate = true;
  233. clouds[ property ] = texture;
  234. } );
  235. }
  236. loadCloudTexture( DEFAULT_LOCAL_WEATHER_URL, 'localWeatherTexture' );
  237. loadCloudTexture( DEFAULT_TURBULENCE_URL, 'turbulenceTexture' );
  238. function loadData3DTexture( url, size, property ) {
  239. fetch( url ).then( res => res.arrayBuffer() ).then( buffer => {
  240. const data = new Uint8Array( buffer );
  241. const texture = new THREE.Data3DTexture( data, size, size, size );
  242. texture.format = THREE.RedFormat;
  243. texture.minFilter = THREE.LinearFilter;
  244. texture.magFilter = THREE.LinearFilter;
  245. texture.wrapS = THREE.RepeatWrapping;
  246. texture.wrapT = THREE.RepeatWrapping;
  247. texture.wrapR = THREE.RepeatWrapping;
  248. texture.colorSpace = THREE.NoColorSpace;
  249. texture.needsUpdate = true;
  250. clouds[ property ] = texture;
  251. } );
  252. }
  253. loadData3DTexture( DEFAULT_SHAPE_URL, CLOUD_SHAPE_TEXTURE_SIZE, 'shapeTexture' );
  254. loadData3DTexture( DEFAULT_SHAPE_DETAIL_URL, CLOUD_SHAPE_DETAIL_TEXTURE_SIZE, 'shapeDetailTexture' );
  255. new STBNLoader().load( DEFAULT_STBN_URL, ( texture ) => {
  256. clouds.stbnTexture = texture;
  257. aerialPerspective.stbnTexture = texture;
  258. } );
  259. // initial sun position
  260. updateSunDirection();
  261. document.getElementById( 'hour' ).addEventListener( 'input', ( e ) => {
  262. params.hourUTC = parseFloat( e.target.value );
  263. updateSunDirection();
  264. } );
  265. // start rendering
  266. renderer.setAnimationLoop( animate );
  267. window.addEventListener( 'resize', onWindowResize );
  268. onWindowResize();
  269. }
  270. function onWindowResize() {
  271. camera.aspect = window.innerWidth / window.innerHeight;
  272. camera.updateProjectionMatrix();
  273. renderer.setSize( window.innerWidth, window.innerHeight );
  274. tiles.setResolutionFromRenderer( camera, renderer );
  275. }
  276. function animate( time ) {
  277. _deltaTime = ( time - _prevTime ) / 1000;
  278. _prevTime = time;
  279. controls.update();
  280. tiles.update();
  281. renderer.render( scene, camera );
  282. }
  283. </script>
  284. </body>
  285. </html>
粤ICP备19079148号