Просмотр исходного кода

SSRNode: Add distance-aware blur for roughness. (#32834)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
mrdoob 2 недель назад
Родитель
Сommit
0ac50924a1

+ 33 - 15
examples/jsm/tsl/display/SSRNode.js

@@ -262,6 +262,7 @@ class SSRNode extends TempNode {
 		this._copyMaterial = new NodeMaterial();
 		this._copyMaterial = new NodeMaterial();
 		this._copyMaterial.name = 'SSRNode.Copy';
 		this._copyMaterial.name = 'SSRNode.Copy';
 
 
+
 		/**
 		/**
 		 * The result of the effect is represented as a separate texture node.
 		 * The result of the effect is represented as a separate texture node.
 		 *
 		 *
@@ -270,24 +271,13 @@ class SSRNode extends TempNode {
 		 */
 		 */
 		this._textureNode = passTexture( this, this._ssrRenderTarget.texture );
 		this._textureNode = passTexture( this, this._ssrRenderTarget.texture );
 
 
-		let blurredTextureNode = null;
-
-		if ( this.roughnessNode !== null ) {
-
-			const mips = this._blurRenderTarget.texture.mipmaps.length - 1;
-			const lod = float( this.roughnessNode ).mul( mips ).clamp( 0, mips );
-
-			blurredTextureNode = passTexture( this, this._blurRenderTarget.texture ).level( lod );
-
-		}
-
 		/**
 		/**
 		 * Holds the blurred SSR reflections.
 		 * Holds the blurred SSR reflections.
 		 *
 		 *
 		 * @private
 		 * @private
-		 * @type {?PassTextureNode}
+		 * @type {?Node}
 		 */
 		 */
-		this._blurredTextureNode = blurredTextureNode;
+		this._blurredTextureNode = null;
 
 
 	}
 	}
 
 
@@ -585,9 +575,10 @@ class SSRNode extends TempNode {
 						const fresnelCoe = div( dot( viewIncidentDir, viewReflectDir ).add( 1 ), 2 );
 						const fresnelCoe = div( dot( viewIncidentDir, viewReflectDir ).add( 1 ), 2 );
 						op.mulAssign( fresnelCoe );
 						op.mulAssign( fresnelCoe );
 
 
-						// output
+						// output: RGB = color * opacity (premultiplied), A = normalized distance
 						const reflectColor = this.colorNode.sample( uvNode );
 						const reflectColor = this.colorNode.sample( uvNode );
-						output.assign( vec4( reflectColor.rgb, op ) );
+						const normalizedDistance = distance.div( this.maxDistance ).clamp( 0, 1 );
+						output.assign( vec4( reflectColor.rgb.mul( op ), normalizedDistance ) );
 						Break();
 						Break();
 
 
 					} );
 					} );
@@ -616,6 +607,33 @@ class SSRNode extends TempNode {
 		this._copyMaterial.fragmentNode = reflectionBuffer;
 		this._copyMaterial.fragmentNode = reflectionBuffer;
 		this._copyMaterial.needsUpdate = true;
 		this._copyMaterial.needsUpdate = true;
 
 
+		// distance-aware blur sampling
+
+		if ( this.roughnessNode !== null ) {
+
+			const blurBuffer = texture( this._blurRenderTarget.texture );
+			const mips = this._blurRenderTarget.texture.mipmaps.length - 1;
+
+			// sample distance from unblurred SSR alpha and use roughness² * distance for LOD
+			// roughness² matches GGX microfacet distribution behavior
+			const distanceAwareSample = Fn( () => {
+
+				// get distance from the unblurred SSR texture's alpha channel
+				const reflectionDistance = reflectionBuffer.sample( uvNode ).a;
+				const r = float( this.roughnessNode );
+				// squared roughness for more physically accurate falloff (GGX-like)
+				const lod = r.mul( r ).mul( reflectionDistance ).mul( mips ).clamp( 0, mips );
+				const blurred = blurBuffer.sample( uvNode ).level( lod );
+
+				// output: RGB is premultiplied color, keep alpha as distance for potential further use
+				return blurred;
+
+			} );
+
+			this._blurredTextureNode = distanceAwareSample();
+
+		}
+
 		//
 		//
 
 
 		return this.getTextureNode();
 		return this.getTextureNode();

BIN
examples/screenshots/webgpu_postprocessing_ssr.jpg


+ 19 - 6
examples/webgpu_postprocessing_ssr.html

@@ -40,7 +40,7 @@
 	<script type="module">
 	<script type="module">
 
 
 		import * as THREE from 'three/webgpu';
 		import * as THREE from 'three/webgpu';
-		import { pass, mrt, output, normalView, metalness, roughness, blendColor, screenUV, color, sample, directionToColor, colorToDirection, vec2, colorSpaceToWorking } from 'three/tsl';
+		import { pass, mrt, output, normalView, metalness, roughness, screenUV, color, sample, directionToColor, colorToDirection, vec2, colorSpaceToWorking } from 'three/tsl';
 		import { ssr } from 'three/addons/tsl/display/SSRNode.js';
 		import { ssr } from 'three/addons/tsl/display/SSRNode.js';
 		import { smaa } from 'three/addons/tsl/display/SMAANode.js';
 		import { smaa } from 'three/addons/tsl/display/SMAANode.js';
 
 
@@ -52,10 +52,10 @@
 
 
 		const params = {
 		const params = {
 			quality: 0.5,
 			quality: 0.5,
-			blurQuality: 2,
-			maxDistance: 0.5,
+			blurQuality: 1,
+			maxDistance: 1,
 			opacity: 1,
 			opacity: 1,
-			thickness: 0.015,
+			thickness: 0.03,
 			roughness: 1,
 			roughness: 1,
 			enabled: true
 			enabled: true
 		};
 		};
@@ -105,6 +105,19 @@
 
 
 			} );
 			} );
 
 
+			// Add a reflective plane under the camera
+
+			const floorGeometry = new THREE.CircleGeometry( 2, 64 );
+			const floorMaterial = new THREE.MeshStandardMaterial( {
+				color: 0xffffff,
+				metalness: 1,
+				roughness: 0.5
+			} );
+			const floor = new THREE.Mesh( floorGeometry, floorMaterial );
+			floor.rotation.x = - Math.PI / 2;
+			floor.position.y = - 0.8;
+			scene.add( floor );
+
 			//
 			//
 
 
 			renderer = new THREE.WebGPURenderer();
 			renderer = new THREE.WebGPURenderer();
@@ -166,9 +179,9 @@
 
 
 			ssrPass = ssr( scenePassColor, scenePassDepth, sceneNormal, scenePassMetalRough.r, scenePassMetalRough.g ).toInspector( 'SSR' );
 			ssrPass = ssr( scenePassColor, scenePassDepth, sceneNormal, scenePassMetalRough.r, scenePassMetalRough.g ).toInspector( 'SSR' );
 
 
-			// blend SSR over beauty
+			// blend SSR over beauty (SSR outputs premultiplied color, so use additive blending)
 
 
-			const outputNode = smaa( blendColor( scenePassColor, ssrPass ) );
+			const outputNode = smaa( scenePassColor.add( ssrPass.rgb ) );
 
 
 			renderPipeline.outputNode = outputNode;
 			renderPipeline.outputNode = outputNode;
 
 

粤ICP备19079148号