Explorar o código

SSRNode: Add blurred mipmaps and honor roughness. (#31649)

* SSRNode: Add blurred mipmaps and honor roughness.

* E2E: Update screenshot.
Michael Herzog hai 4 meses
pai
achega
c0f77e8afe

+ 129 - 26
examples/jsm/tsl/display/SSRNode.js

@@ -1,5 +1,6 @@
-import { RenderTarget, Vector2, RendererUtils, QuadMesh, TempNode, NodeMaterial, NodeUpdateType, HalfFloatType } from 'three/webgpu';
-import { reference, viewZToPerspectiveDepth, logarithmicDepthToViewZ, getScreenPosition, getViewPosition, sqrt, mul, div, cross, float, Continue, Break, Loop, int, max, abs, sub, If, dot, reflect, normalize, screenCoordinate, nodeObject, Fn, passTexture, uv, uniform, perspectiveDepthToViewZ, orthographicDepthToViewZ, vec2, vec3, vec4 } from 'three/tsl';
+import { HalfFloatType, RenderTarget, Vector2, RendererUtils, QuadMesh, TempNode, NodeMaterial, NodeUpdateType, LinearFilter, LinearMipmapLinearFilter } from 'three/webgpu';
+import { texture, reference, viewZToPerspectiveDepth, logarithmicDepthToViewZ, getScreenPosition, getViewPosition, sqrt, mul, div, cross, float, Continue, Break, Loop, int, max, abs, sub, If, dot, reflect, normalize, screenCoordinate, nodeObject, Fn, passTexture, uv, uniform, perspectiveDepthToViewZ, orthographicDepthToViewZ, vec2, vec3, vec4 } from 'three/tsl';
+import { boxBlur } from './boxBlur.js';
 
 const _quadMesh = /*@__PURE__*/ new QuadMesh();
 const _size = /*@__PURE__*/ new Vector2();
@@ -27,10 +28,11 @@ class SSRNode extends TempNode {
 	 * @param {Node<vec4>} colorNode - The node that represents the beauty pass.
 	 * @param {Node<float>} depthNode - A node that represents the beauty pass's depth.
 	 * @param {Node<vec3>} normalNode - A node that represents the beauty pass's normals.
-	 * @param {Node<float>} metalnessNode - A node that represents the beauty pass's metalness.
+	 * @param {Node<float>} metalnessRoughnessNode - A node that represents the beauty pass's metalness and roughness.
 	 * @param {Camera} camera - The camera the scene is rendered with.
+	 * @param {boolean} [blurred=false] - Whether the SSR reflections should be blurred or not.
 	 */
-	constructor( colorNode, depthNode, normalNode, metalnessNode, camera ) {
+	constructor( colorNode, depthNode, normalNode, metalnessRoughnessNode, camera, blurred = false ) {
 
 		super( 'vec4' );
 
@@ -60,7 +62,7 @@ class SSRNode extends TempNode {
 		 *
 		 * @type {Node<float>}
 		 */
-		this.metalnessNode = metalnessNode;
+		this.metalnessRoughnessNode = metalnessRoughnessNode;
 
 		/**
 		 * The camera the scene is rendered with.
@@ -89,15 +91,6 @@ class SSRNode extends TempNode {
 		 */
 		this.updateBeforeType = NodeUpdateType.FRAME;
 
-		/**
-		 * The render target the SSR is rendered into.
-		 *
-		 * @private
-		 * @type {RenderTarget}
-		 */
-		this._ssrRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
-		this._ssrRenderTarget.texture.name = 'SSRNode.SSR';
-
 		/**
 		 * Controls how far a fragment can reflect. Increasing this value result in more
 		 * computational overhead but also increases the reflection distance.
@@ -133,6 +126,32 @@ class SSRNode extends TempNode {
 		 */
 		this.quality = uniform( 0.5 );
 
+		/**
+		 * The quality of the blur. Must be an integer in the range `[1,3]`.
+		 *
+		 * @type {UniformNode<int>}
+		 */
+		this.blurQuality = uniform( 2 );
+
+		/**
+		 * The spread of the blur. Automatically set when generating mips.
+		 *
+		 * @private
+		 * @type {UniformNode<int>}
+		 */
+		this._blurSpread = uniform( 1 );
+
+		/**
+		 * Whether the SSR reflections should be blurred or not. Blurring is a costly
+		 * operation so turn it off if you encounter performance issues on certain
+		 * devices.
+		 *
+		 * @private
+		 * @type {boolean}
+		 * @default false
+		 */
+		this._blurred = blurred;
+
 		/**
 		 * Represents the projection matrix of the scene's camera.
 		 *
@@ -181,14 +200,51 @@ class SSRNode extends TempNode {
 		 */
 		this._resolution = uniform( new Vector2() );
 
+		/**
+		 * The render target the SSR is rendered into.
+		 *
+		 * @private
+		 * @type {RenderTarget}
+		 */
+		this._ssrRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
+		this._ssrRenderTarget.texture.name = 'SSRNode.SSR';
+
+		/**
+		 * The render target for the blurred SSR reflections.
+		 *
+		 * @private
+		 * @type {RenderTarget}
+		 */
+		this._blurRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType, minFilter: LinearMipmapLinearFilter, magFilter: LinearFilter } );
+		this._blurRenderTarget.texture.name = 'SSRNode.Blur';
+		this._blurRenderTarget.texture.mipmaps.push( {}, {}, {}, {}, {} );
+
 		/**
 		 * The material that is used to render the effect.
 		 *
 		 * @private
 		 * @type {NodeMaterial}
 		 */
-		this._material = new NodeMaterial();
-		this._material.name = 'SSRNode.SSR';
+		this._ssrMaterial = new NodeMaterial();
+		this._ssrMaterial.name = 'SSRNode.SSR';
+
+		/**
+		 * The blur material.
+		 *
+		 * @private
+		 * @type {NodeMaterial}
+		 */
+		this._blurMaterial = new NodeMaterial();
+		this._blurMaterial.name = 'SSRNode.Blur';
+
+		/**
+		 * The copy material.
+		 *
+		 * @private
+		 * @type {NodeMaterial}
+		 */
+		this._copyMaterial = new NodeMaterial();
+		this._copyMaterial.name = 'SSRNode.Copy';
 
 		/**
 		 * The result of the effect is represented as a separate texture node.
@@ -198,6 +254,17 @@ class SSRNode extends TempNode {
 		 */
 		this._textureNode = passTexture( this, this._ssrRenderTarget.texture );
 
+		const mips = this._blurRenderTarget.texture.mipmaps.length - 1;
+		const lod = this.metalnessRoughnessNode.g.mul( mips ).clamp( 0, mips );
+
+		/**
+		 * Holds the blurred SSR reflections.
+		 *
+		 * @private
+		 * @type {PassTextureNode}
+		 */
+		this._blurredTextureNode = passTexture( this, this._blurRenderTarget.texture ).level( lod );
+
 	}
 
 	/**
@@ -207,7 +274,7 @@ class SSRNode extends TempNode {
 	 */
 	getTextureNode() {
 
-		return this._textureNode;
+		return this._blurred ? this._blurredTextureNode : this._textureNode;
 
 	}
 
@@ -224,6 +291,7 @@ class SSRNode extends TempNode {
 
 		this._resolution.value.set( width, height );
 		this._ssrRenderTarget.setSize( width, height );
+		this._blurRenderTarget.setSize( width, height );
 
 	}
 
@@ -238,9 +306,12 @@ class SSRNode extends TempNode {
 
 		_rendererState = RendererUtils.resetRendererState( renderer, _rendererState );
 
+		const ssrRenderTarget = this._ssrRenderTarget;
+		const blurRenderTarget = this._blurRenderTarget;
+
 		const size = renderer.getDrawingBufferSize( _size );
 
-		_quadMesh.material = this._material;
+		_quadMesh.material = this._ssrMaterial;
 
 		this.setSize( size.width, size.height );
 
@@ -251,9 +322,27 @@ class SSRNode extends TempNode {
 
 		// ssr
 
-		renderer.setRenderTarget( this._ssrRenderTarget );
+		renderer.setRenderTarget( ssrRenderTarget );
 		_quadMesh.render( renderer );
 
+		// blur (optional)
+
+		if ( this._blurred === true ) {
+
+			// blur mips but leave the base mip unblurred
+
+			for ( let i = 0; i < blurRenderTarget.texture.mipmaps.length; i ++ ) {
+
+				_quadMesh.material = ( i === 0 ) ? this._copyMaterial : this._blurMaterial;
+
+				this._blurSpread.value = i;
+				renderer.setRenderTarget( blurRenderTarget, 0, i );
+				_quadMesh.render( renderer );
+
+			}
+
+		}
+
 		// restore
 
 		RendererUtils.restoreRendererState( renderer, _rendererState );
@@ -328,7 +417,7 @@ class SSRNode extends TempNode {
 
 		const ssr = Fn( () => {
 
-			const metalness = this.metalnessNode.sample( uvNode ).r;
+			const metalness = this.metalnessRoughnessNode.sample( uvNode ).r;
 
 			// fragments with no metalness do not reflect their environment
 			metalness.equal( 0.0 ).discard();
@@ -487,12 +576,22 @@ class SSRNode extends TempNode {
 
 		} );
 
-		this._material.fragmentNode = ssr().context( builder.getSharedContext() );
-		this._material.needsUpdate = true;
+		this._ssrMaterial.fragmentNode = ssr().context( builder.getSharedContext() );
+		this._ssrMaterial.needsUpdate = true;
+
+		// below materials are used for blurring
+
+		const reflectionBuffer = texture( this._ssrRenderTarget.texture );
+
+		this._blurMaterial.fragmentNode = boxBlur( reflectionBuffer, { size: this.blurQuality, separation: this._blurSpread } );
+		this._blurMaterial.needsUpdate = true;
+
+		this._copyMaterial.fragmentNode = reflectionBuffer;
+		this._copyMaterial.needsUpdate = true;
 
 		//
 
-		return this._textureNode;
+		return this.getTextureNode();
 
 	}
 
@@ -503,8 +602,11 @@ class SSRNode extends TempNode {
 	dispose() {
 
 		this._ssrRenderTarget.dispose();
+		this._blurRenderTarget.dispose();
 
-		this._material.dispose();
+		this._ssrMaterial.dispose();
+		this._blurMaterial.dispose();
+		this._copyMaterial.dispose();
 
 	}
 
@@ -520,8 +622,9 @@ export default SSRNode;
  * @param {Node<vec4>} colorNode - The node that represents the beauty pass.
  * @param {Node<float>} depthNode - A node that represents the beauty pass's depth.
  * @param {Node<vec3>} normalNode - A node that represents the beauty pass's normals.
- * @param {Node<float>} metalnessNode - A node that represents the beauty pass's metalness.
+ * @param {Node<float>} metalnessRoughnessNode - A node that represents the beauty pass's metalness and roughness.
  * @param {Camera} camera - The camera the scene is rendered with.
+ * @param {boolean} [blurred=false] - Whether the SSR reflections should be blurred or not.
  * @returns {SSRNode}
  */
-export const ssr = ( colorNode, depthNode, normalNode, metalnessNode, camera ) => nodeObject( new SSRNode( nodeObject( colorNode ), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( metalnessNode ), camera ) );
+export const ssr = ( colorNode, depthNode, normalNode, metalnessRoughnessNode, camera, blurred ) => nodeObject( new SSRNode( nodeObject( colorNode ), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( metalnessRoughnessNode ), camera, blurred ) );

BIN=BIN
examples/screenshots/webgpu_postprocessing_ssr.jpg


+ 37 - 23
examples/webgpu_postprocessing_ssr.html

@@ -45,13 +45,15 @@
 
 		const params = {
 			quality: 0.5,
+			blurQuality: 2,
 			maxDistance: 0.5,
 			opacity: 1,
 			thickness: 0.015,
+			roughness: 1,
 			enabled: true
 		};
 
-		let camera, scene, renderer, postProcessing, ssrPass;
+		let camera, scene, model, renderer, postProcessing, ssrPass;
 		let gui, stats, controls;
 
 		init();
@@ -72,7 +74,9 @@
 			loader.setDRACOLoader( dracoLoader );
 			loader.load( 'models/gltf/steampunk_camera.glb', function ( gltf ) {
 
-				gltf.scene.traverse( function ( object ) {
+				model = gltf.scene;
+
+				model.traverse( function ( object ) {
 
 					if ( object.material ) {
 
@@ -84,13 +88,13 @@
 
 						// Avoid overdrawing
 						object.material.side = THREE.FrontSide;
-		
+
 					}
 		
 				} );
 		
-				gltf.scene.position.y = 0.1;
-				scene.add( gltf.scene );
+				model.position.y = 0.1;
+				scene.add( model );
 
 			} );
 
@@ -116,11 +120,11 @@
 
 			postProcessing = new THREE.PostProcessing( renderer );
 
-			const scenePass = pass( scene, camera, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter } );
+			const scenePass = pass( scene, camera );
 			scenePass.setMRT( mrt( {
 				output: output,
 				normal: directionToColor( normalView ),
-				metalrough: vec2( metalness, roughness )
+				metalrough: vec2( metalness, roughness ) // pack metalness and roughness into a single attachement
 			} ) );
 
 			const scenePassColor = scenePass.getTextureNode( 'output' );
@@ -128,7 +132,7 @@
 			const scenePassDepth = scenePass.getTextureNode( 'depth' );
 			const scenePassMetalRough = scenePass.getTextureNode( 'metalrough' );
 
-			// optimization bandwidth packing the normals and reducing the texture precision if possible
+			// optional: optimize bandwidth by reducing the texture precision for normals and metal/roughness
 
 			const normalTexture = scenePass.getTexture( 'normal' );
 			normalTexture.type = THREE.UnsignedByteType;
@@ -142,19 +146,13 @@
 
 			} );
 
-			const customMetalness = sample( ( uv ) => {
-
-				return scenePassMetalRough.sample( uv ).r;
-
-			} );
-
 			//
 
-			ssrPass = ssr( scenePassColor, scenePassDepth, customNormal, customMetalness, camera );
+			ssrPass = ssr( scenePassColor, scenePassDepth, customNormal, scenePassMetalRough, camera, true );
 
 			// blend SSR over beauty
 
-			const outputNode = smaa( blendColor( scenePassColor, ssrPass.mul( scenePassMetalRough.g.oneMinus() ) ) );
+			const outputNode = smaa( blendColor( scenePassColor, ssrPass ) );
 		
 			postProcessing.outputNode = outputNode;
 
@@ -174,11 +172,13 @@
 			// GUI
 
 			gui = new GUI();
-			gui.add( params, 'quality' ).min( 0 ).max( 1 ).onChange( updateParameters );
-			gui.add( params, 'maxDistance' ).min( 0 ).max( 1 ).onChange( updateParameters );
-			gui.add( params, 'opacity' ).min( 0 ).max( 1 ).onChange( updateParameters );
-			gui.add( params, 'thickness' ).min( 0 ).max( 0.05 ).onChange( updateParameters );
-			gui.add( params, 'enabled' ).onChange( () => {
+			const ssrFolder = gui.addFolder( 'SSR' );
+			ssrFolder.add( params, 'quality' ).min( 0 ).max( 1 ).onChange( updateParameters );
+			ssrFolder.add( params, 'blurQuality' ).min( 1 ).max( 3 ).step( 1 ).onChange( updateParameters );
+			ssrFolder.add( params, 'maxDistance' ).min( 0 ).max( 1 ).onChange( updateParameters );
+			ssrFolder.add( params, 'opacity' ).min( 0 ).max( 1 ).onChange( updateParameters );
+			ssrFolder.add( params, 'thickness' ).min( 0 ).max( 0.05 ).onChange( updateParameters );
+			ssrFolder.add( params, 'enabled' ).onChange( () => {
 
 				if ( params.enabled === true ) {
 
@@ -192,6 +192,20 @@
 
 				postProcessing.needsUpdate = true;
 
+			} );
+			const modelFolder = gui.addFolder( 'Model' );
+			modelFolder.add( params, 'roughness' ).min( 0 ).max( 1 ).onChange( ( value ) => {
+
+				model.traverse( function ( object ) {
+
+					if ( object.material ) {
+
+						object.material.roughness = value;
+		
+					}
+		
+				} );
+
 			} );
 		
 			updateParameters();
@@ -201,11 +215,11 @@
 		function updateParameters() {
 
 			ssrPass.quality.value = params.quality;
+			ssrPass.blurQuality.value = params.blurQuality;
 			ssrPass.maxDistance.value = params.maxDistance;
 			ssrPass.opacity.value = params.opacity;
 			ssrPass.thickness.value = params.thickness;
 
-
 		}
 
 		function onWindowResize() {
@@ -232,4 +246,4 @@
 	</script>
 </body>
 
-</html>
+</html>

粤ICP备19079148号