webgl_loader_3dtiles.html 13 KB

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