webgpu_generator_city.html 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgpu - city generator</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 webgpu - city generator">
  8. <meta property="og:type" content="website">
  9. <meta property="og:url" content="https://threejs.org/examples/webgpu_generator_city.html">
  10. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_generator_city.jpg">
  11. <link type="text/css" rel="stylesheet" href="example.css">
  12. </head>
  13. <body>
  14. <div id="info">
  15. <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
  16. <div class="title-wrapper">
  17. <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>City Generator</span>
  18. </div>
  19. <small>
  20. A few procedurally generated city blocks of Neo-Gothic terracotta skyscrapers at sunset.
  21. </small>
  22. </div>
  23. <script type="importmap">
  24. {
  25. "imports": {
  26. "three": "../build/three.webgpu.js",
  27. "three/webgpu": "../build/three.webgpu.js",
  28. "three/tsl": "../build/three.tsl.js",
  29. "three/addons/": "./jsm/"
  30. }
  31. }
  32. </script>
  33. <script type="module">
  34. import * as THREE from 'three/webgpu';
  35. import { pass } from 'three/tsl';
  36. import { Inspector } from 'three/addons/inspector/Inspector.js';
  37. import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
  38. import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
  39. import { bloom } from 'three/addons/tsl/display/BloomNode.js';
  40. import { CityGenerator, createBuildingMaterial, createRoadMaterial } from 'three/addons/generators/CityGenerator.js';
  41. let camera, scene, renderer, controls, timer;
  42. let cityGroup, materials, city, renderPipeline;
  43. let sky, sun, sunLight, pmremGenerator, envScene;
  44. const parameters = {
  45. seed: 94,
  46. timeOfDay: 6.4 // hours: 6 sunrise, 12 noon, 18 sunset
  47. };
  48. init();
  49. async function init() {
  50. renderer = new THREE.WebGPURenderer( { antialias: true } );
  51. renderer.setPixelRatio( window.devicePixelRatio );
  52. renderer.setSize( window.innerWidth, window.innerHeight );
  53. renderer.setAnimationLoop( animate );
  54. renderer.toneMapping = THREE.ACESFilmicToneMapping;
  55. renderer.toneMappingExposure = 0.45;
  56. renderer.shadowMap.enabled = true;
  57. renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  58. renderer.inspector = new Inspector();
  59. document.body.appendChild( renderer.domElement );
  60. await renderer.init();
  61. pmremGenerator = new THREE.PMREMGenerator( renderer );
  62. scene = new THREE.Scene();
  63. scene.environmentIntensity = 0.2; // sky as a soft fill; the sun is the key
  64. // a physical sky drives both the backdrop and the image-based
  65. // lighting; the time-of-day slider walks the sun along its arc
  66. sky = new SkyMesh();
  67. sky.scale.setScalar( 10000 );
  68. sky.turbidity.value = 8;
  69. sky.rayleigh.value = 3;
  70. sky.mieCoefficient.value = 0.008;
  71. sky.mieDirectionalG.value = 0.88;
  72. sun = new THREE.Vector3();
  73. envScene = new THREE.Scene();
  74. camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 1, 20000 );
  75. camera.position.set( - 35, 55, - 100 );
  76. controls = new FirstPersonControls( camera, renderer.domElement );
  77. controls.movementSpeed = 15;
  78. controls.lookSpeed = 0.25;
  79. controls.lookAt( 35, 55, 0 );
  80. timer = new THREE.Timer();
  81. city = new CityGenerator( { seed: parameters.seed } );
  82. // road: wet asphalt with sidewalks, lane lines and crosswalks, all
  83. // aligned to the city grid via TSL. sized to the city footprint plus
  84. // one street ( a crosswalk's width ) of margin all round
  85. const floorW = city.layout.cityW + 2 * city.layout.street;
  86. const floorD = city.layout.cityD + 2 * city.layout.street;
  87. const ground = new THREE.Mesh( new THREE.PlaneGeometry( floorW, floorD ).rotateX( - Math.PI / 2 ), createRoadMaterial( city.layout ) );
  88. ground.receiveShadow = true;
  89. scene.add( ground );
  90. // a single directional key aligned with the sky's sun, for the crisp
  91. // relief shadows the IBL alone can't give. its colour, intensity and
  92. // position — and the baked environment — are set by updateSun()
  93. sunLight = new THREE.DirectionalLight();
  94. sunLight.castShadow = true;
  95. sunLight.shadow.camera.left = - 280;
  96. sunLight.shadow.camera.right = 280;
  97. sunLight.shadow.camera.top = 360;
  98. sunLight.shadow.camera.bottom = - 40;
  99. sunLight.shadow.camera.far = 2400;
  100. sunLight.shadow.mapSize.set( 4096, 4096 );
  101. sunLight.shadow.bias = - 0.0004;
  102. scene.add( sunLight );
  103. updateSun();
  104. materials = { building: createBuildingMaterial( city.layout, parameters.seed ) };
  105. generateCity();
  106. // bloom: a soft glow on the brightest areas, run on the linear HDR scene
  107. // colour before tone mapping folds it back to display range
  108. renderPipeline = new THREE.RenderPipeline( renderer );
  109. const scenePassColor = pass( scene, camera ).getTextureNode( 'output' );
  110. const bloomPass = bloom( scenePassColor, 0.05, 0.0, 0.0 );
  111. renderPipeline.outputNode = scenePassColor.add( bloomPass );
  112. // parameters
  113. const gui = renderer.inspector.createParameters( 'City' );
  114. gui.add( parameters, 'seed', 0, 100, 1 ).onChange( generateCity );
  115. gui.add( parameters, 'timeOfDay', 6, 18, 0.1 ).name( 'time of day' ).onChange( updateSun );
  116. gui.add( renderer, 'toneMappingExposure', 0.01, 1 ).name( 'exposure' );
  117. gui.add( bloomPass.strength, 'value', 0, 2 ).name( 'bloom' );
  118. window.addEventListener( 'resize', onWindowResize );
  119. }
  120. function updateSun() {
  121. const t = parameters.timeOfDay;
  122. const u = ( t - 12 ) / 6; // -1 at sunrise, 0 at noon, +1 at sunset
  123. const elevation = Math.max( 0, 1 - u * u ) * 72; // degrees above the horizon, peaks at noon
  124. const azimuth = 90 - u * 55; // the sun swings east → west across the day
  125. sun.setFromSphericalCoords(
  126. 1,
  127. THREE.MathUtils.degToRad( 90 - elevation ),
  128. THREE.MathUtils.degToRad( azimuth )
  129. );
  130. sky.sunPosition.value.copy( sun );
  131. // physically-motivated key light: real sunlight is far brighter than the
  132. // sky, so the sun stays the dominant source and cast shadows read clearly
  133. // against the soft sky fill. the longer air path near the horizon dims and
  134. // warms it ( the fill dims with it, as the environment is re-baked below ).
  135. const sinElevation = Math.sin( THREE.MathUtils.degToRad( elevation ) );
  136. const transmittance = Math.sqrt( Math.max( sinElevation, 0 ) ); // 0 at the horizon → 1 at the zenith
  137. sunLight.color.set( 0xffb072 ).lerp( new THREE.Color( 0xfff4e8 ), transmittance );
  138. sunLight.intensity = 6 * transmittance; // illuminance perpendicular to the rays; the renderer applies N·L per surface
  139. sunLight.position.copy( sun ).multiplyScalar( 600 );
  140. // re-bake the sky ( without the sun disc ) into the environment map for IBL
  141. sky.showSunDisc.value = false;
  142. envScene.add( sky );
  143. const env = pmremGenerator.fromScene( envScene ).texture;
  144. if ( scene.environment ) scene.environment.dispose();
  145. scene.environment = env;
  146. sky.showSunDisc.value = true;
  147. scene.add( sky );
  148. }
  149. function generateCity() {
  150. if ( cityGroup ) scene.remove( cityGroup );
  151. city.parameters.seed = parameters.seed;
  152. cityGroup = city.build( materials );
  153. scene.add( cityGroup );
  154. }
  155. function onWindowResize() {
  156. camera.aspect = window.innerWidth / window.innerHeight;
  157. camera.updateProjectionMatrix();
  158. renderer.setSize( window.innerWidth, window.innerHeight );
  159. }
  160. function animate() {
  161. timer.update();
  162. controls.update( timer.getDelta() );
  163. renderPipeline.render();
  164. }
  165. </script>
  166. </body>
  167. </html>
粤ICP备19079148号