Ver Fonte

TSL: Improve `retroPass` and example (#32954)

sunag há 2 semanas atrás
pai
commit
08ee34e0c9

+ 113 - 16
examples/jsm/tsl/display/RetroPassNode.js

@@ -1,5 +1,5 @@
-import { MeshBasicNodeMaterial, PassNode, UnsignedByteType, NearestFilter } from 'three/webgpu';
-import { float, vec2, vec4, Fn, uv, varying, cameraProjectionMatrix, cameraViewMatrix, positionWorld, screenSize, materialColor, replaceDefaultUV } from 'three/tsl';
+import { MeshBasicNodeMaterial, PassNode, UnsignedByteType, NearestFilter, CubeMapNode, MeshPhongNodeMaterial } from 'three/webgpu';
+import { float, vec2, vec4, Fn, uv, varying, cameraProjectionMatrix, cameraViewMatrix, positionWorld, screenSize, materialColor, uint, texture, uniform, context, reflectVector } from 'three/tsl';
 
 
 const _affineUv = varying( vec2() );
 const _affineUv = varying( vec2() );
 const _w = varying( float() );
 const _w = varying( float() );
@@ -50,7 +50,8 @@ class RetroPassNode extends PassNode {
 		super( PassNode.COLOR, scene, camera );
 		super( PassNode.COLOR, scene, camera );
 
 
 		const {
 		const {
-			affineDistortion = null
+			affineDistortion = null,
+			filterTextures = false
 		} = options;
 		} = options;
 
 
 		this.setResolutionScale( .25 );
 		this.setResolutionScale( .25 );
@@ -61,6 +62,8 @@ class RetroPassNode extends PassNode {
 
 
 		this.affineDistortionNode = affineDistortion;
 		this.affineDistortionNode = affineDistortion;
 
 
+		this.filterTextures = filterTextures;
+
 		this._materialCache = new Map();
 		this._materialCache = new Map();
 
 
 	}
 	}
@@ -80,36 +83,125 @@ class RetroPassNode extends PassNode {
 
 
 		renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, ...params ) => {
 		renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, ...params ) => {
 
 
-			let retroMaterial = this._materialCache.get( material );
+			const retroMaterialData = this._materialCache.get( material );
+
+			let retroMaterial;
+
+			if ( retroMaterialData === undefined || retroMaterialData.version !== material.version ) {
+
+				if ( retroMaterialData !== undefined ) {
+
+					retroMaterialData.material.dispose();
+
+				}
+
+				if ( material.isMeshBasicMaterial || material.isMeshBasicNodeMaterial ) {
+
+					retroMaterial = new MeshBasicNodeMaterial();
 
 
-			if ( retroMaterial === undefined ) {
+				} else {
 
 
-				retroMaterial = new MeshBasicNodeMaterial();
+					retroMaterial = new MeshPhongNodeMaterial();
+
+				}
 
 
 				retroMaterial.colorNode = material.colorNode || null;
 				retroMaterial.colorNode = material.colorNode || null;
 				retroMaterial.opacityNode = material.opacityNode || null;
 				retroMaterial.opacityNode = material.opacityNode || null;
 				retroMaterial.positionNode = material.positionNode || null;
 				retroMaterial.positionNode = material.positionNode || null;
 				retroMaterial.vertexNode = material.vertexNode || _clipSpaceRetro;
 				retroMaterial.vertexNode = material.vertexNode || _clipSpaceRetro;
 
 
+				let colorNode = material.colorNode || materialColor;
+
+				if ( material.isMeshStandardNodeMaterial || material.isMeshStandardMaterial ) {
+
+					const envMap = material.envMap || scene.environment;
+
+					if ( envMap ) {
+
+						const reflection = new CubeMapNode( texture( envMap ) );
+
+						let metalness;
+
+						if ( material.metalnessNode ) {
+
+							metalness = material.metalnessNode;
+
+						} else {
+
+							metalness = uniform( material.metalness ).onRenderUpdate( ( { material } ) => material.metalness );
+
+							if ( material.metalnessMap ) {
+
+								const textureUniform = texture( material.metalnessMap ).onRenderUpdate( ( { material } ) => material.metalnessMap );
+
+								metalness = metalness.mul( textureUniform.b );
+
+							}
+
+						}
+
+						colorNode = metalness.mix( colorNode, reflection );
+
+					}
+
+				}
+
+				retroMaterial.colorNode = colorNode;
+
+				//
+
+				const contextData = {};
+
 				if ( this.affineDistortionNode ) {
 				if ( this.affineDistortionNode ) {
 
 
-					retroMaterial.colorNode = replaceDefaultUV( () => {
+					contextData.getUV = ( texture ) => {
+
+						let finalUV;
+
+						if ( texture.isCubeTextureNode ) {
 
 
-						return this.affineDistortionNode.mix( uv(), _affineUv.div( _w ) );
+							finalUV = reflectVector;
 
 
-					}, retroMaterial.colorNode || materialColor );
+						} else {
+
+							finalUV = this.affineDistortionNode.mix( uv(), _affineUv.div( _w ) );
+
+						}
+
+						return finalUV;
+
+					};
 
 
 				}
 				}
 
 
-				this._materialCache.set( material, retroMaterial );
+				if ( this.filterTextures !== true ) {
+
+					contextData.getTextureLevel = () => uint( 0 );
+
+				}
+
+				retroMaterial.contextNode = context( contextData );
+
+				//
+
+				this._materialCache.set( material, {
+					material: retroMaterial,
+					version: material.version
+				} );
+
+			} else {
+
+				retroMaterial = retroMaterialData.material;
 
 
 			}
 			}
 
 
-			retroMaterial.map = material.map;
-			retroMaterial.color = material.color;
-			retroMaterial.opacity = material.opacity;
-			retroMaterial.transparent = material.transparent;
-			retroMaterial.side = material.side;
+			for ( const property in material ) {
+
+				if ( retroMaterial[ property ] === undefined ) continue;
+
+				retroMaterial[ property ] = material[ property ];
+
+			}
 
 
 			renderer.renderObject( object, scene, camera, geometry, retroMaterial, ...params );
 			renderer.renderObject( object, scene, camera, geometry, retroMaterial, ...params );
 
 
@@ -131,7 +223,12 @@ class RetroPassNode extends PassNode {
 
 
 		super.dispose();
 		super.dispose();
 
 
-		this._materialCache.forEach( material => material.dispose() );
+		this._materialCache.forEach( ( data ) => {
+
+			data.material.dispose();
+
+		} );
+
 		this._materialCache.clear();
 		this._materialCache.clear();
 
 
 	}
 	}

BIN
examples/screenshots/webgpu_postprocessing_retro.jpg


+ 99 - 14
examples/webgpu_postprocessing_retro.html

@@ -35,7 +35,7 @@
 		<script type="module">
 		<script type="module">
 
 
 			import * as THREE from 'three/webgpu';
 			import * as THREE from 'three/webgpu';
-			import { pass, mix, mul, oneMinus, positionLocal, smoothstep, texture, time, rotateUV, Fn, uv, vec2, vec3, vec4, uniform, posterize, floor, float, sin, fract, dot, step, color, normalWorld, length, atan, replaceDefaultUV } from 'three/tsl';
+			import { pass, mix, mul, oneMinus, positionLocal, smoothstep, texture, time, rotateUV, Fn, uv, vec2, vec3, vec4, uniform, posterize, floor, float, sin, fract, dot, step, color, normalWorld, length, atan, replaceDefaultUV, screenSize } from 'three/tsl';
 			import { retroPass } from 'three/addons/tsl/display/RetroPassNode.js';
 			import { retroPass } from 'three/addons/tsl/display/RetroPassNode.js';
 			import { bayerDither } from 'three/addons/tsl/math/Bayer.js';
 			import { bayerDither } from 'three/addons/tsl/math/Bayer.js';
 			import { scanlines, vignette, colorBleeding, barrelUV } from 'three/addons/tsl/display/CRT.js';
 			import { scanlines, vignette, colorBleeding, barrelUV } from 'three/addons/tsl/display/CRT.js';
@@ -43,10 +43,13 @@
 
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
 
 
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 
 
 			let camera, scene, renderer, renderPipeline, controls;
 			let camera, scene, renderer, renderPipeline, controls;
+			let environment;
+			let currentModel;
 
 
 			init();
 			init();
 
 
@@ -136,17 +139,90 @@
 				const gltfLoader = new GLTFLoader();
 				const gltfLoader = new GLTFLoader();
 				const textureLoader = new THREE.TextureLoader();
 				const textureLoader = new THREE.TextureLoader();
 
 
-				// baked model
+				// Model
 
 
-				gltfLoader.load(
-					'./models/gltf/coffeeMug.glb',
-					( gltf ) => {
+				const models = {
+					'Coffee Mug': 'models/gltf/coffeeMug.glb',
+					'Damaged Helmet': 'models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf',
+				};
 
 
-        				gltf.scene.getObjectByName( 'baked' ).material.map.anisotropy = 8;
-						scene.add( gltf.scene );
+				function loadModel( name ) {
+
+					function loadEnvironment() {
+
+						if ( environment ) return;
+
+						environment = new HDRLoader()
+							.setPath( 'textures/equirectangular/' )
+							.load( 'venice_sunset_1k.hdr', ( texture ) => {
+
+								texture.mapping = THREE.EquirectangularReflectionMapping;
+								scene.environment = texture;
+
+								// re-invalidate retro pass textures
+
+								retro.dispose();
+
+							} );
 
 
 					}
 					}
-				);
+
+					if ( currentModel ) {
+
+						scene.remove( currentModel );
+						currentModel.traverse( ( child ) => {
+
+							if ( child.isMesh ) {
+
+								child.geometry.dispose();
+								child.material.dispose();
+
+							}
+
+						} );
+
+					}
+
+					gltfLoader.load( models[ name ], ( gltf ) => {
+
+						currentModel = gltf.scene;
+						currentModel.position.set( 0, 0, 0 );
+
+						smoke.visible = false;
+
+						if ( name === 'Damaged Helmet' ) {
+
+							loadEnvironment();
+
+							currentModel.scale.setScalar( 3 );
+							currentModel.position.y = 1;
+
+						} else if ( name === 'Coffee Mug' ) {
+
+							smoke.visible = true;
+
+						}
+
+						scene.add( currentModel );
+
+					} );
+
+				}
+
+				loadModel( 'Coffee Mug' );
+
+				// lighting
+
+				const ambientLight = new THREE.AmbientLight( 0x404040, 2 );
+				scene.add( ambientLight );
+
+				const directionalLight = new THREE.DirectionalLight( 0xffffff, 3 );
+				directionalLight.position.set( 5, 10, 5 );
+				scene.add( directionalLight );
+
+				const pointLight = new THREE.PointLight( 0xff6600, 5, 20 );
+				pointLight.position.set( - 3, 3, 2 );
+				scene.add( pointLight );
 
 
 				// geometry
 				// geometry
 
 
@@ -235,9 +311,9 @@
 				const colorDepthSteps = uniform( 32 );
 				const colorDepthSteps = uniform( 32 );
 
 
 				// CRT effect parameters (subtle for PS1 look)
 				// CRT effect parameters (subtle for PS1 look)
-				const scanlineIntensity = uniform( 0.08 ); // subtle scanlines
-				const scanlineCount = uniform( 60 ); // match vertical resolution
-				const scanlineSpeed = uniform( 0.05 ); // slow scroll
+				const scanlineIntensity = uniform( 0.3 ); // subtle scanlines
+				const scanlineDensity = uniform( 1 ); // 0.1-1: normalized scanline density (1 = full screen resolution)
+				const scanlineSpeed = uniform( 0.0 ); // no scanline movement
 				const vignetteIntensity = uniform( 0.3 ); // subtle vignette
 				const vignetteIntensity = uniform( 0.3 ); // subtle vignette
 				const bleeding = uniform( 0.001 ); // minimal bleeding
 				const bleeding = uniform( 0.001 ); // minimal bleeding
 				const curvature = uniform( 0.02 ); // subtle curve
 				const curvature = uniform( 0.02 ); // subtle curve
@@ -252,13 +328,15 @@
 				const distortedUV = barrelUV( curvature );
 				const distortedUV = barrelUV( curvature );
 				const distortedDelta = circle( curvature.add( .1 ).mul( 10 ), 1 ).mul( curvature ).mul( .05 );
 				const distortedDelta = circle( curvature.add( .1 ).mul( 10 ), 1 ).mul( curvature ).mul( .05 );
 
 
-				let retroPipeline = retroPass( scene, camera, { affineDistortion } );
+				const retro = retroPass( scene, camera, { affineDistortion } );
+
+				let retroPipeline = retro;
 				retroPipeline = replaceDefaultUV( distortedUV, retroPipeline );
 				retroPipeline = replaceDefaultUV( distortedUV, retroPipeline );
 				retroPipeline = colorBleeding( retroPipeline, bleeding.add( distortedDelta ) );
 				retroPipeline = colorBleeding( retroPipeline, bleeding.add( distortedDelta ) );
 				retroPipeline = bayerDither( retroPipeline, colorDepthSteps );
 				retroPipeline = bayerDither( retroPipeline, colorDepthSteps );
 				retroPipeline = posterize( retroPipeline, colorDepthSteps );
 				retroPipeline = posterize( retroPipeline, colorDepthSteps );
 				retroPipeline = vignette( retroPipeline, vignetteIntensity, 0.6 );
 				retroPipeline = vignette( retroPipeline, vignetteIntensity, 0.6 );
-				retroPipeline = scanlines( retroPipeline, scanlineIntensity, scanlineCount, scanlineSpeed );
+				retroPipeline = scanlines( retroPipeline, scanlineIntensity, screenSize.y.mul( scanlineDensity ), scanlineSpeed );
 
 
 				renderPipeline.outputNode = retroPipeline;
 				renderPipeline.outputNode = retroPipeline;
 
 
@@ -278,6 +356,8 @@
 
 
 				const gui = renderer.inspector.createParameters( 'Settings' );
 				const gui = renderer.inspector.createParameters( 'Settings' );
 
 
+				gui.add( { model: 'Coffee Mug' }, 'model', Object.keys( models ) ).name( 'Model' ).onChange( loadModel );
+
 				gui.add( { enabled: true }, 'enabled' ).onChange( v => {
 				gui.add( { enabled: true }, 'enabled' ).onChange( v => {
 
 
 					renderPipeline.outputNode = v ? retroPipeline : defaultPass;
 					renderPipeline.outputNode = v ? retroPipeline : defaultPass;
@@ -288,11 +368,16 @@
 				gui.add( curvature, 'value', 0, 0.2, 0.01 ).name( 'Curvature' );
 				gui.add( curvature, 'value', 0, 0.2, 0.01 ).name( 'Curvature' );
 				gui.add( colorDepthSteps, 'value', 4, 32, 1 ).name( 'Color Depth' );
 				gui.add( colorDepthSteps, 'value', 4, 32, 1 ).name( 'Color Depth' );
 				gui.add( scanlineIntensity, 'value', 0, 1, 0.01 ).name( 'Scanlines' );
 				gui.add( scanlineIntensity, 'value', 0, 1, 0.01 ).name( 'Scanlines' );
-				gui.add( scanlineCount, 'value', 20, 480, 1 ).name( 'Scanline Count' );
+				gui.add( scanlineDensity, 'value', 0.02, 1, 0.01 ).name( 'Scanline Density' );
 				gui.add( scanlineSpeed, 'value', 0, .1, 0.01 ).name( 'Scanline Speed' );
 				gui.add( scanlineSpeed, 'value', 0, .1, 0.01 ).name( 'Scanline Speed' );
 				gui.add( vignetteIntensity, 'value', 0, 1, 0.01 ).name( 'Vignette' );
 				gui.add( vignetteIntensity, 'value', 0, 1, 0.01 ).name( 'Vignette' );
 				gui.add( bleeding, 'value', 0, 0.005, 0.001 ).name( 'Color Bleeding' );
 				gui.add( bleeding, 'value', 0, 0.005, 0.001 ).name( 'Color Bleeding' );
 				gui.add( affineDistortion, 'value', 0, 1 ).name( 'Affine Distortion' );
 				gui.add( affineDistortion, 'value', 0, 1 ).name( 'Affine Distortion' );
+				gui.add( retro, 'filterTextures' ).name( 'Filter Textures' ).onChange( () => {
+
+					retro.dispose();
+
+				} );
 
 
 				window.addEventListener( 'resize', onWindowResize );
 				window.addEventListener( 'resize', onWindowResize );
 
 

粤ICP备19079148号