瀏覽代碼

webgpu_generator_city: Add ambient occlusion, grimier glass.

GTAO contact shadows from a single MRT scene pass, plus dirt-driven glass roughness in SkyscraperGenerator so panes scatter the sun rather than mirror it. Bumps exposure to 0.5 and regenerates the screenshots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mr.doob 3 周之前
父節點
當前提交
32ffe081bc

+ 5 - 1
examples/jsm/generators/city/SkyscraperGenerator.js

@@ -1349,6 +1349,10 @@ function createSkyscraperMaterial( buildingBase = color( 0xc6c0b2 ) ) {
 	const dirtyGlass = mix( color( 0x13161a ), color( 0x232b31 ), valueFractal( positionWorld.mul( 0.3 ), 2 ).mul( 0.5 ).add( 0.5 ) );
 	const dirtyGlass = mix( color( 0x13161a ), color( 0x232b31 ), valueFractal( positionWorld.mul( 0.3 ), 2 ).mul( 0.5 ).add( 0.5 ) );
 	const glassColor = mix( room.xyz.mul( color( 0xb6c6bf ) ), dirtyGlass, grime ); // faint green-grey ( soda-lime ) room tint, dirtied toward grimy glass
 	const glassColor = mix( room.xyz.mul( color( 0xb6c6bf ) ), dirtyGlass, grime ); // faint green-grey ( soda-lime ) room tint, dirtied toward grimy glass
 
 
+	// dirt scatters the reflection too: rougher where grime pools at the sills, so the
+	// panes don't all mirror the sun in unison
+	const glassRough = float( 0.16 ).add( pooled.mul( 0.45 ) ).add( dustStreak.mul( 0.2 ) );
+
 	// window frames are smooth dressed stone, not brick
 	// window frames are smooth dressed stone, not brick
 	const frameColor = buildingBase.mul( 0.55 );
 	const frameColor = buildingBase.mul( 0.55 );
 
 
@@ -1385,7 +1389,7 @@ function createSkyscraperMaterial( buildingBase = color( 0xc6c0b2 ) ) {
 
 
 	const material = new MeshStandardNodeMaterial();
 	const material = new MeshStandardNodeMaterial();
 	material.colorNode = select( isGlass, glassColor, select( isFrame, frameColor, select( isOrnament, ornamentColor, select( isAC, acColor, stoneColor ) ) ) );
 	material.colorNode = select( isGlass, glassColor, select( isFrame, frameColor, select( isOrnament, ornamentColor, select( isAC, acColor, stoneColor ) ) ) );
-	material.roughnessNode = select( isGlass, float( 0.18 ), select( isOrnament, float( 0.8 ), select( isAC, acRough, rough ) ) ); // glass kept smooth for a sky reflection, but soft enough not to alias over the interior
+	material.roughnessNode = select( isGlass, glassRough, select( isOrnament, float( 0.8 ), select( isAC, acRough, rough ) ) ); // glass roughness rides on its grime, so dirty panes scatter the reflection
 	material.metalnessNode = float( 0 ); // all dielectric — stone, glass and the plastic AC shells
 	material.metalnessNode = float( 0 ); // all dielectric — stone, glass and the plastic AC shells
 	material.emissiveNode = select( isGlass, room.xyz.mul( room.w ).mul( 4 ).mul( grime.mul( 0.6 ).oneMinus() ), color( 0x000000 ) ); // room.w = emissive weight ( 0 unlit, < 1 behind curtains ), muted further by grime
 	material.emissiveNode = select( isGlass, room.xyz.mul( room.w ).mul( 4 ).mul( grime.mul( 0.6 ).oneMinus() ), color( 0x000000 ) ); // room.w = emissive weight ( 0 unlit, < 1 behind curtains ), muted further by grime
 	material.normalNode = bumpNormal( select( isGlass.or( isFrame ).or( isOrnament ), float( 0 ), select( isAC, acRelief, reliefHeight ) ) ); // glass / frames / ornament stay flat; AC has its own louvers
 	material.normalNode = bumpNormal( select( isGlass.or( isFrame ).or( isOrnament ), float( 0 ), select( isAC, acRelief, reliefHeight ) ) ); // glass / frames / ornament stay flat; AC has its own louvers

二進制
examples/screenshots/webgpu_generator_building.jpg


二進制
examples/screenshots/webgpu_generator_city.jpg


+ 27 - 9
examples/webgpu_generator_city.html

@@ -20,7 +20,7 @@
 			</div>
 			</div>
 
 
 			<small>
 			<small>
-				A few procedurally generated city blocks of Neo-Gothic terracotta skyscrapers at sunset.
+				A few procedurally generated city blocks of Neo-Gothic terracotta skyscrapers.
 			</small>
 			</small>
 		</div>
 		</div>
 
 
@@ -38,12 +38,13 @@
 		<script type="module">
 		<script type="module">
 
 
 			import * as THREE from 'three/webgpu';
 			import * as THREE from 'three/webgpu';
-			import { pass } from 'three/tsl';
+			import { pass, mrt, output, normalView, vec3, vec4 } from 'three/tsl';
 
 
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 			import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
 			import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
 			import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
 			import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
+			import { ao } from 'three/addons/tsl/display/GTAONode.js';
 			import { CityGenerator, createBuildingMaterial, createRoadMaterial } from 'three/addons/generators/CityGenerator.js';
 			import { CityGenerator, createBuildingMaterial, createRoadMaterial } from 'three/addons/generators/CityGenerator.js';
 
 
 			let camera, scene, renderer, controls, timer;
 			let camera, scene, renderer, controls, timer;
@@ -64,7 +65,7 @@
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
 				renderer.setAnimationLoop( animate );
 				renderer.toneMapping = THREE.ACESFilmicToneMapping;
 				renderer.toneMapping = THREE.ACESFilmicToneMapping;
-				renderer.toneMappingExposure = 0.45;
+				renderer.toneMappingExposure = 0.5;
 				renderer.shadowMap.enabled = true;
 				renderer.shadowMap.enabled = true;
 				renderer.shadowMap.type = THREE.PCFSoftShadowMap;
 				renderer.shadowMap.type = THREE.PCFSoftShadowMap;
 				renderer.inspector = new Inspector();
 				renderer.inspector = new Inspector();
@@ -132,15 +133,33 @@
 				materials = { building: createBuildingMaterial( city.layout, parameters.seed ) };
 				materials = { building: createBuildingMaterial( city.layout, parameters.seed ) };
 				generateCity();
 				generateCity();
 
 
-				// bloom: a soft glow on the brightest areas, run on the linear HDR scene
-				// colour before tone mapping folds it back to display range
+				// ground-truth ambient occlusion for the facade's contact shadows; MRT
+				// writes view normals so the AO needs no second geometry pass
 
 
 				renderPipeline = new THREE.RenderPipeline( renderer );
 				renderPipeline = new THREE.RenderPipeline( renderer );
 
 
-				const scenePassColor = pass( scene, camera ).getTextureNode( 'output' );
-				const bloomPass = bloom( scenePassColor, 0.05, 0.0, 0.0 );
+				const scenePass = pass( scene, camera );
+				scenePass.setMRT( mrt( { output, normal: normalView } ) );
 
 
-				renderPipeline.outputNode = scenePassColor.add( bloomPass );
+				const scenePassColor = scenePass.getTextureNode( 'output' );
+				const scenePassNormal = scenePass.getTextureNode( 'normal' );
+				const scenePassDepth = scenePass.getTextureNode( 'depth' );
+
+				const aoPass = ao( scenePassDepth, scenePassNormal, camera );
+				aoPass.resolutionScale = 0.5; // half-res is plenty for soft contact shading
+				aoPass.radius.value = 4;
+				aoPass.thickness.value = 4;
+				aoPass.scale.value = 2;
+
+				const aoColor = scenePassColor.mul( vec4( vec3( aoPass.getTextureNode().r ), 1 ) );
+
+				// bloom: a soft glow on the brightest areas, at quarter-res since the
+				// result is heavily blurred anyway
+
+				const bloomPass = bloom( aoColor, 0.05, 0.0, 0.0 );
+				bloomPass.setResolutionScale( 0.25 );
+
+				renderPipeline.outputNode = aoColor.add( bloomPass );
 
 
 				// parameters
 				// parameters
 
 
@@ -148,7 +167,6 @@
 				gui.add( parameters, 'seed', 0, 100, 1 ).onChange( generateCity );
 				gui.add( parameters, 'seed', 0, 100, 1 ).onChange( generateCity );
 				gui.add( parameters, 'timeOfDay', 6, 18, 0.1 ).name( 'time of day' ).onChange( updateSun );
 				gui.add( parameters, 'timeOfDay', 6, 18, 0.1 ).name( 'time of day' ).onChange( updateSun );
 				gui.add( renderer, 'toneMappingExposure', 0.01, 1 ).name( 'exposure' );
 				gui.add( renderer, 'toneMappingExposure', 0.01, 1 ).name( 'exposure' );
-				gui.add( bloomPass.strength, 'value', 0, 2 ).name( 'bloom' );
 
 
 				window.addEventListener( 'resize', onWindowResize );
 				window.addEventListener( 'resize', onWindowResize );
 
 

粤ICP备19079148号