webgpu_generator_city.html 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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.inspector = new Inspector();
  58. document.body.appendChild( renderer.domElement );
  59. await renderer.init();
  60. pmremGenerator = new THREE.PMREMGenerator( renderer );
  61. scene = new THREE.Scene();
  62. scene.environmentIntensity = 0.2; // sky as a soft fill; the sun is the key
  63. // a physical sky drives both the backdrop and the image-based
  64. // lighting; the time-of-day slider walks the sun along its arc
  65. sky = new SkyMesh();
  66. sky.scale.setScalar( 10000 );
  67. sky.turbidity.value = 8;
  68. sky.rayleigh.value = 3;
  69. sky.mieCoefficient.value = 0.008;
  70. sky.mieDirectionalG.value = 0.88;
  71. sun = new THREE.Vector3();
  72. envScene = new THREE.Scene();
  73. camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 1, 20000 );
  74. camera.position.set( - 35, 55, - 100 );
  75. controls = new FirstPersonControls( camera, renderer.domElement );
  76. controls.movementSpeed = 15;
  77. controls.lookSpeed = 0.25;
  78. controls.lookAt( 35, 55, 0 );
  79. timer = new THREE.Timer();
  80. city = new CityGenerator( { seed: parameters.seed } );
  81. // road: wet asphalt with sidewalks, lane lines and crosswalks, all
  82. // aligned to the city grid via TSL. sized to the city footprint plus
  83. // one street ( a crosswalk's width ) of margin all round
  84. const floorW = city.layout.cityW + 2 * city.layout.street;
  85. const floorD = city.layout.cityD + 2 * city.layout.street;
  86. const ground = new THREE.Mesh( new THREE.PlaneGeometry( floorW, floorD ).rotateX( - Math.PI / 2 ), createRoadMaterial( city.layout ) );
  87. ground.receiveShadow = true;
  88. scene.add( ground );
  89. // a single directional key aligned with the sky's sun, for the crisp
  90. // relief shadows the IBL alone can't give. its colour, intensity and
  91. // position — and the baked environment — are set by updateSun()
  92. sunLight = new THREE.DirectionalLight();
  93. sunLight.castShadow = true;
  94. sunLight.shadow.camera.left = - 280;
  95. sunLight.shadow.camera.right = 280;
  96. sunLight.shadow.camera.top = 360;
  97. sunLight.shadow.camera.bottom = - 40;
  98. sunLight.shadow.camera.far = 2400;
  99. sunLight.shadow.mapSize.set( 4096, 4096 );
  100. sunLight.shadow.bias = - 0.0004;
  101. scene.add( sunLight );
  102. updateSun();
  103. materials = { building: createBuildingMaterial( city.layout, parameters.seed ) };
  104. generateCity();
  105. // bloom: a soft glow on the brightest areas, run on the linear HDR scene
  106. // colour before tone mapping folds it back to display range
  107. renderPipeline = new THREE.RenderPipeline( renderer );
  108. const scenePassColor = pass( scene, camera ).getTextureNode( 'output' );
  109. const bloomPass = bloom( scenePassColor, 0.05, 0.0, 0.0 );
  110. renderPipeline.outputNode = scenePassColor.add( bloomPass );
  111. // parameters
  112. const gui = renderer.inspector.createParameters( 'City' );
  113. gui.add( parameters, 'seed', 0, 100, 1 ).onChange( generateCity );
  114. gui.add( parameters, 'timeOfDay', 6, 18, 0.1 ).name( 'time of day' ).onChange( updateSun );
  115. gui.add( renderer, 'toneMappingExposure', 0.01, 1 ).name( 'exposure' );
  116. gui.add( bloomPass.strength, 'value', 0, 2 ).name( 'bloom' );
  117. window.addEventListener( 'resize', onWindowResize );
  118. }
  119. function updateSun() {
  120. const t = parameters.timeOfDay;
  121. const u = ( t - 12 ) / 6; // -1 at sunrise, 0 at noon, +1 at sunset
  122. const elevation = Math.max( 0, 1 - u * u ) * 72; // degrees above the horizon, peaks at noon
  123. const azimuth = 90 - u * 55; // the sun swings east → west across the day
  124. sun.setFromSphericalCoords(
  125. 1,
  126. THREE.MathUtils.degToRad( 90 - elevation ),
  127. THREE.MathUtils.degToRad( azimuth )
  128. );
  129. sky.sunPosition.value.copy( sun );
  130. // physically-motivated key light: real sunlight is far brighter than the
  131. // sky, so the sun stays the dominant source and cast shadows read clearly
  132. // against the soft sky fill. the longer air path near the horizon dims and
  133. // warms it ( the fill dims with it, as the environment is re-baked below ).
  134. const sinElevation = Math.sin( THREE.MathUtils.degToRad( elevation ) );
  135. const transmittance = Math.sqrt( Math.max( sinElevation, 0 ) ); // 0 at the horizon → 1 at the zenith
  136. sunLight.color.set( 0xffb072 ).lerp( new THREE.Color( 0xfff4e8 ), transmittance );
  137. sunLight.intensity = 6 * transmittance; // illuminance perpendicular to the rays; the renderer applies N·L per surface
  138. sunLight.position.copy( sun ).multiplyScalar( 600 );
  139. // re-bake the sky ( without the sun disc ) into the environment map for IBL
  140. sky.showSunDisc.value = false;
  141. envScene.add( sky );
  142. const env = pmremGenerator.fromScene( envScene ).texture;
  143. if ( scene.environment ) scene.environment.dispose();
  144. scene.environment = env;
  145. sky.showSunDisc.value = true;
  146. scene.add( sky );
  147. }
  148. function generateCity() {
  149. if ( cityGroup ) scene.remove( cityGroup );
  150. city.parameters.seed = parameters.seed;
  151. cityGroup = city.build( materials );
  152. scene.add( cityGroup );
  153. }
  154. function onWindowResize() {
  155. camera.aspect = window.innerWidth / window.innerHeight;
  156. camera.updateProjectionMatrix();
  157. renderer.setSize( window.innerWidth, window.innerHeight );
  158. }
  159. function animate() {
  160. timer.update();
  161. controls.update( timer.getDelta() );
  162. renderPipeline.render();
  163. }
  164. </script>
  165. </body>
  166. </html>
粤ICP备19079148号