Browse Source

Add `SSAONode` and `depthAwareBlur` (#33921)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
mrdoob 1 week ago
parent
commit
0d2986293c

+ 428 - 0
examples/jsm/tsl/display/SSAONode.js

@@ -0,0 +1,428 @@
+import { RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, RendererUtils, RedFormat } from 'three/webgpu';
+import { reference, logarithmicDepthToViewZ, viewZToPerspectiveDepth, getViewPosition, getScreenPositionFromClip, vogelDiskSample, interleavedGradientNoise, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec4, int, dot, max, clamp, length, screenCoordinate, PI2, texture, passTexture } from 'three/tsl';
+import { depthAwareBlur } from './depthAwareBlur.js';
+
+const _quadMesh = /*@__PURE__*/ new QuadMesh();
+const _size = /*@__PURE__*/ new Vector2();
+
+let _rendererState;
+
+/**
+ * Post processing node for a fast, screen-space ambient occlusion (SSAO).
+ *
+ * It point-samples a per-pixel rotated Vogel disk and estimates obscurance with a single depth tap
+ * per sample, trading the ground-truth accuracy of {@link GTAONode}'s horizon ray-marching for
+ * lower cost. A built-in separable, depth-aware blur denoises the result so it can be used without
+ * temporal accumulation.
+ * ```js
+ * const scenePass = pass( scene, camera );
+ * scenePass.setMRT( mrt( { output, normal: normalView } ) );
+ * const scenePassColor = scenePass.getTextureNode( 'output' );
+ * const scenePassDepth = scenePass.getTextureNode( 'depth' );
+ * const scenePassNormal = scenePass.getTextureNode( 'normal' );
+ *
+ * const aoPass = ssao( scenePassDepth, scenePassNormal, camera );
+ *
+ * renderPipeline.outputNode = scenePassColor.mul( aoPass.r );
+ * ```
+ *
+ * @augments TempNode
+ * @three_import import { ssao } from 'three/addons/tsl/display/SSAONode.js';
+ */
+class SSAONode extends TempNode {
+
+	static get type() {
+
+		return 'SSAONode';
+
+	}
+
+	/**
+	 * Constructs a new SSAO node.
+	 *
+	 * @param {Node<float>} depthNode - A node that represents the scene's depth.
+	 * @param {Node<vec3>} normalNode - A node that represents the scene's normals.
+	 * @param {Camera} camera - The camera the scene is rendered with.
+	 */
+	constructor( depthNode, normalNode, camera ) {
+
+		super( 'float' );
+
+		/**
+		 * A node that represents the scene's depth.
+		 *
+		 * @type {Node<float>}
+		 */
+		this.depthNode = depthNode;
+
+		/**
+		 * A node that represents the scene's normals.
+		 *
+		 * @type {Node<vec3>}
+		 */
+		this.normalNode = normalNode;
+
+		/**
+		 * The resolution scale. The effect renders at a fraction of the drawing buffer
+		 * for extra speed; `0.5` is a good default for a low-frequency signal like AO.
+		 *
+		 * @type {number}
+		 * @default 0.5
+		 */
+		this.resolutionScale = 0.5;
+
+		/**
+		 * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders
+		 * its effect once per frame in `updateBefore()`.
+		 *
+		 * @type {string}
+		 * @default 'frame'
+		 */
+		this.updateBeforeType = NodeUpdateType.FRAME;
+
+		/**
+		 * The world-space radius the occlusion is gathered within.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.radius = uniform( 0.5 );
+
+		/**
+		 * The strength of the occlusion.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.intensity = uniform( 1 );
+
+		/**
+		 * An angle bias that suppresses self-occlusion on near-flat surfaces.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.bias = uniform( 0.025 );
+
+		/**
+		 * How many samples are used to estimate the occlusion. A higher value
+		 * results in a smoother result at a higher runtime cost.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.samples = uniform( 16 );
+
+		/**
+		 * Whether the depth-aware blur that denoises the raw AO is applied or not.
+		 *
+		 * @type {boolean}
+		 * @default true
+		 */
+		this.blurEnabled = true;
+
+		/**
+		 * How strongly the blur rejects samples across depth discontinuities,
+		 * relative to the AO radius. A higher value keeps edges crisper.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.blurSharpness = uniform( 2 );
+
+		/**
+		 * The resolution of the effect. Set from the drawing buffer size and `resolutionScale`.
+		 *
+		 * @type {Vector2}
+		 */
+		this.resolution = new Vector2();
+
+		/**
+		 * The render target the raw ambient occlusion is rendered into. Also holds the
+		 * final result after the separable blur has ping-ponged back into it.
+		 *
+		 * @private
+		 * @type {RenderTarget}
+		 */
+		this._aoRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, format: RedFormat } );
+		this._aoRenderTarget.texture.name = 'SSAONode.AO';
+
+		/**
+		 * The render target the intermediate (horizontally blurred) result is rendered into.
+		 *
+		 * @private
+		 * @type {RenderTarget}
+		 */
+		this._blurRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, format: RedFormat } );
+		this._blurRenderTarget.texture.name = 'SSAONode.Blur';
+
+		/**
+		 * Represents the projection matrix of the scene's camera.
+		 *
+		 * @private
+		 * @type {UniformNode<mat4>}
+		 */
+		this._cameraProjectionMatrix = uniform( camera.projectionMatrix );
+
+		/**
+		 * Represents the inverse projection matrix of the scene's camera.
+		 *
+		 * @private
+		 * @type {UniformNode<mat4>}
+		 */
+		this._cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse );
+
+		/**
+		 * Represents the near value of the scene's camera.
+		 *
+		 * @private
+		 * @type {ReferenceNode<float>}
+		 */
+		this._cameraNear = reference( 'near', 'float', camera );
+
+		/**
+		 * Represents the far value of the scene's camera.
+		 *
+		 * @private
+		 * @type {ReferenceNode<float>}
+		 */
+		this._cameraFar = reference( 'far', 'float', camera );
+
+		/**
+		 * The camera the scene is rendered with.
+		 *
+		 * @private
+		 * @type {Camera}
+		 */
+		this._camera = camera;
+
+		/**
+		 * The input texture the blur material reads from. Swapped between the two render
+		 * targets to run the horizontal and vertical passes with a single material.
+		 *
+		 * @private
+		 * @type {TextureNode}
+		 */
+		this._blurInput = texture( this._aoRenderTarget.texture );
+
+		/**
+		 * The blur direction (one texel along x or y).
+		 *
+		 * @private
+		 * @type {UniformNode<vec2>}
+		 */
+		this._blurDirection = uniform( new Vector2() );
+
+		/**
+		 * The material that computes the raw ambient occlusion.
+		 *
+		 * @private
+		 * @type {NodeMaterial}
+		 */
+		this._aoMaterial = new NodeMaterial();
+		this._aoMaterial.name = 'SSAO.AO';
+
+		/**
+		 * The material that applies the separable, depth-aware blur.
+		 *
+		 * @private
+		 * @type {NodeMaterial}
+		 */
+		this._blurMaterial = new NodeMaterial();
+		this._blurMaterial.name = 'SSAO.Blur';
+
+		/**
+		 * The result of the effect is represented as a separate texture node.
+		 *
+		 * @private
+		 * @type {PassTextureNode}
+		 */
+		this._textureNode = passTexture( this, this._aoRenderTarget.texture );
+
+	}
+
+	/**
+	 * Returns the result of the effect as a texture node.
+	 *
+	 * @return {PassTextureNode} A texture node that represents the result of the effect.
+	 */
+	getTextureNode() {
+
+		return this._textureNode;
+
+	}
+
+	/**
+	 * Sets the size of the effect.
+	 *
+	 * @param {number} width - The width of the effect.
+	 * @param {number} height - The height of the effect.
+	 */
+	setSize( width, height ) {
+
+		width = Math.max( 1, Math.round( this.resolutionScale * width ) );
+		height = Math.max( 1, Math.round( this.resolutionScale * height ) );
+
+		this.resolution.set( width, height );
+		this._aoRenderTarget.setSize( width, height );
+		this._blurRenderTarget.setSize( width, height );
+
+	}
+
+	/**
+	 * This method is used to render the effect once per frame.
+	 *
+	 * @param {NodeFrame} frame - The current node frame.
+	 */
+	updateBefore( frame ) {
+
+		const { renderer } = frame;
+
+		_rendererState = RendererUtils.resetRendererState( renderer, _rendererState );
+
+		const size = renderer.getDrawingBufferSize( _size );
+		this.setSize( size.width, size.height );
+
+		renderer.setClearColor( 0xffffff, 1 );
+
+		// ambient occlusion
+
+		_quadMesh.material = this._aoMaterial;
+		_quadMesh.name = 'SSAO.AO';
+		renderer.setRenderTarget( this._aoRenderTarget );
+		_quadMesh.render( renderer );
+
+		// separable, depth-aware blur: horizontal ( AO -> blur ) then vertical ( blur -> AO )
+
+		if ( this.blurEnabled === true ) {
+
+			_quadMesh.material = this._blurMaterial;
+			_quadMesh.name = 'SSAO.Blur';
+
+			this._blurInput.value = this._aoRenderTarget.texture;
+			this._blurDirection.value.set( 1 / this.resolution.x, 0 );
+			renderer.setRenderTarget( this._blurRenderTarget );
+			_quadMesh.render( renderer );
+
+			this._blurInput.value = this._blurRenderTarget.texture;
+			this._blurDirection.value.set( 0, 1 / this.resolution.y );
+			renderer.setRenderTarget( this._aoRenderTarget );
+			_quadMesh.render( renderer );
+
+		}
+
+		RendererUtils.restoreRendererState( renderer, _rendererState );
+
+	}
+
+	/**
+	 * This method is used to setup the effect's TSL code.
+	 *
+	 * @param {NodeBuilder} builder - The current node builder.
+	 * @return {PassTextureNode}
+	 */
+	setup( builder ) {
+
+		const uvNode = uv();
+
+		const sampleDepth = ( uv ) => {
+
+			const depth = this.depthNode.sample( uv ).r;
+
+			if ( builder.renderer.logarithmicDepthBuffer === true ) {
+
+				const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar );
+
+				return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar );
+
+			}
+
+			return depth;
+
+		};
+
+		// ambient occlusion
+
+		const ao = Fn( () => {
+
+			const depth = sampleDepth( uvNode ).toVar();
+
+			depth.greaterThanEqual( 1.0 ).discard();
+
+			const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar();
+			const viewNormal = this.normalNode.sample( uvNode ).rgb.normalize().toVar();
+
+			// a low-discrepancy Vogel disk rotated per-pixel decorrelates the samples spatially,
+			// so a small blur is enough to hide the sampling pattern
+
+			const phi = interleavedGradientNoise( screenCoordinate ).mul( PI2 ).toVar();
+			const samples = this.samples;
+
+			// the fragment's clip position is loop-invariant; projection is linear, so each
+			// sample only has to project its (view-plane) offset and add it
+
+			const clipPosition = this._cameraProjectionMatrix.mul( vec4( viewPosition, 1.0 ) ).toVar();
+
+			const occlusion = float( 0 ).toVar();
+
+			Loop( { start: int( 0 ), end: samples, type: 'int', condition: '<' }, ( { i } ) => {
+
+				// disk offset in the view-aligned plane at the fragment's depth, then reprojected
+
+				const offset = vogelDiskSample( i, samples, phi ).mul( this.radius );
+				const clipOffset = this._cameraProjectionMatrix.mul( vec4( offset, 0.0, 0.0 ) );
+				const sampleUv = getScreenPositionFromClip( clipPosition.add( clipOffset ) );
+
+				const sampleViewPosition = getViewPosition( sampleUv, sampleDepth( sampleUv ), this._cameraProjectionMatrixInverse );
+
+				// normalized obscurance: only occluders above the tangent plane count, faded by distance
+
+				const v = sampleViewPosition.sub( viewPosition ).toVar();
+				const dist = length( v ).toVar();
+				const cosAngle = dot( v, viewNormal ).div( max( dist, float( 0.0001 ) ) );
+				const falloff = this.radius.div( this.radius.add( dist ) );
+
+				occlusion.addAssign( max( cosAngle.sub( this.bias ), 0.0 ).mul( falloff ) );
+
+			} );
+
+			return clamp( occlusion.div( samples ).mul( this.intensity ).oneMinus(), 0.0, 1.0 );
+
+		} );
+
+		this._aoMaterial.fragmentNode = ao().context( builder.getSharedContext() );
+		this._aoMaterial.needsUpdate = true;
+
+		// separable, depth-aware blur ( run horizontally then vertically via `_blurDirection` )
+
+		this._blurMaterial.fragmentNode = depthAwareBlur( this._blurInput, this.depthNode, this._blurDirection, this._camera, this.blurSharpness, this.radius ).context( builder.getSharedContext() );
+		this._blurMaterial.needsUpdate = true;
+
+		return this._textureNode;
+
+	}
+
+	/**
+	 * Frees internal resources. This method should be called
+	 * when the effect is no longer required.
+	 */
+	dispose() {
+
+		this._aoRenderTarget.dispose();
+		this._blurRenderTarget.dispose();
+
+		this._aoMaterial.dispose();
+		this._blurMaterial.dispose();
+
+	}
+
+}
+
+export default SSAONode;
+
+/**
+ * TSL function for creating a fast screen-space ambient occlusion (SSAO) effect.
+ *
+ * @tsl
+ * @function
+ * @param {Node<float>} depthNode - A node that represents the scene's depth.
+ * @param {Node<vec3>} normalNode - A node that represents the scene's normals.
+ * @param {Camera} camera - The camera the scene is rendered with.
+ * @returns {SSAONode}
+ */
+export const ssao = ( depthNode, normalNode, camera ) => new SSAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera );

+ 63 - 0
examples/jsm/tsl/display/depthAwareBlur.js

@@ -0,0 +1,63 @@
+import { abs, exp, float, Fn, int, logarithmicDepthToViewZ, Loop, max, perspectiveDepthToViewZ, reference, uv } from 'three/tsl';
+
+/**
+ * Applies one pass of a separable, depth-aware (bilateral) blur to a screen-space signal.
+ *
+ * The spatial term is a fixed 5-tap gaussian along `directionNode`; the edge-stopping term
+ * rejects neighbours whose view-space depth differs from the center, so the signal is smoothed
+ * across surfaces but not across silhouettes. Depth rejection is measured relative to `radius`,
+ * which keeps it scale invariant. Run it twice (horizontal then vertical) for a full separable
+ * blur. Handy for denoising a half-resolution effect such as ambient occlusion.
+ *
+ * @tsl
+ * @function
+ * @param {Node<vec4>} inputNode - The texture node to blur; its red channel is filtered.
+ * @param {Node<float>} depthNode - The scene depth texture node.
+ * @param {Node<vec2>} directionNode - One texel step along the blur axis, e.g. `vec2( 1 / width, 0 )`.
+ * @param {Camera} camera - The camera the scene is rendered with.
+ * @param {Node<float> | number} [sharpness=2] - How strongly a depth difference rejects a neighbour.
+ * @param {Node<float> | number} [radius=1] - The world-space scale the depth rejection is relative to.
+ * @returns {Node<float>} The blurred value.
+ */
+export const depthAwareBlur = /*#__PURE__*/ Fn( ( [ inputNode, depthNode, directionNode, camera, sharpness = 2, radius = 1 ], builder ) => {
+
+	const cameraNear = reference( 'near', 'float', camera );
+	const cameraFar = reference( 'far', 'float', camera );
+
+	const viewZ = ( uvNode ) => {
+
+		const depth = depthNode.sample( uvNode ).r;
+
+		if ( builder.renderer.logarithmicDepthBuffer === true ) {
+
+			return logarithmicDepthToViewZ( depth, cameraNear, cameraFar );
+
+		}
+
+		return perspectiveDepthToViewZ( depth, cameraNear, cameraFar );
+
+	};
+
+	const uvNode = uv();
+	const centerViewZ = viewZ( uvNode ).toVar();
+
+	const sum = float( 0 ).toVar();
+	const weightSum = float( 0 ).toVar();
+
+	Loop( { start: int( - 2 ), end: int( 3 ), type: 'int', condition: '<' }, ( { i } ) => {
+
+		const fi = float( i );
+		const sampleUv = uvNode.add( directionNode.mul( fi ) );
+
+		const spatialWeight = exp( fi.mul( fi ).mul( - 0.5 ) );
+		const depthWeight = exp( abs( viewZ( sampleUv ).sub( centerViewZ ) ).div( radius ).mul( sharpness ).negate() );
+		const weight = spatialWeight.mul( depthWeight );
+
+		sum.addAssign( inputNode.sample( sampleUv ).r.mul( weight ) );
+		weightSum.addAssign( weight );
+
+	} );
+
+	return sum.div( max( weightSum, float( 0.0001 ) ) );
+
+} );

+ 89 - 31
examples/webgpu_postprocessing_ao.html

@@ -20,7 +20,7 @@
 			</div>
 
 			<small>
-				Ambient Occlusion based on GTAO.<br />
+				Ambient Occlusion: switchable between GTAO and SSAO.<br />
 				Tennyson bust from <a href="https://threedscans.com/lincoln/tennyson/" target="_blank" rel="noopener">Three D Scans</a>.
 			</small>
 		</div>
@@ -41,6 +41,7 @@
 			import * as THREE from 'three/webgpu';
 			import { sample, pass, mrt, screenUV, normalView, velocity, vec3, vec4, packNormalToRGB, unpackRGBToNormal, colorSpaceToWorking, builtinAOContext } from 'three/tsl';
 			import { ao } from 'three/addons/tsl/display/GTAONode.js';
+			import { ssao } from 'three/addons/tsl/display/SSAONode.js';
 			import { traa } from 'three/addons/tsl/display/TRAANode.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -56,10 +57,15 @@
 			let aoPass, traaPass, transparentMesh;
 
 			const params = {
+				aoType: 'GTAO',
 				samples: 16,
 				radius: 0.5,
-				scale: 0.8,
-				thickness: 1,
+				scale: 0.8, // GTAO
+				thickness: 1, // GTAO
+				temporalFiltering: true, // GTAO
+				intensity: 2, // SSAO
+				bias: 0.025, // SSAO
+				blurSharpness: 2, // SSAO
 				aoOnly: false,
 				transparentOpacity: 0.3
 			};
@@ -135,24 +141,43 @@
 
 				const scenePass = pass( scene, camera ).toInspector( 'Color' );
 
-				// ao
+				// final output + traa
 
-				aoPass = ao( prePassDepth, prePassNormal, camera ).toInspector( 'GTAO', ( inspectNode ) => inspectNode.r );
-				aoPass.resolutionScale = 0.5; // running AO in half resolution is often sufficient
-				aoPass.useTemporalFiltering = true;
+				traaPass = traa( scenePass, prePassDepth, prePassVelocity, camera );
+				traaPass.useSubpixelCorrection = false;
 
-				const aoPassOutput = aoPass.getTextureNode();
+				// ao: the AO node feeds the scene's ambient term via `builtinAOContext`, and
+				// can be switched between GTAO ( ground-truth, needs temporal denoise ) and the
+				// cheaper SSAO ( self-denoised )
 
-				// scene context
+				function updateOutput() {
 
-				scenePass.contextNode = builtinAOContext( aoPassOutput.sample( screenUV ).r );
+					renderPipeline.outputNode = params.aoOnly ? vec4( vec3( aoPass.getTextureNode().sample( screenUV ).r ), 1 ) : traaPass;
+					renderer.toneMapping = params.aoOnly ? THREE.NoToneMapping : THREE.NeutralToneMapping;
+					renderPipeline.needsUpdate = true;
 
-				// final output + traa
+				}
 
-				traaPass = traa( scenePass, prePassDepth, prePassVelocity, camera );
-				traaPass.useSubpixelCorrection = false;
+				function createAO() {
+
+					if ( aoPass ) aoPass.dispose();
+
+					aoPass = ( params.aoType === 'SSAO' )
+						? ssao( prePassDepth, prePassNormal, camera ).toInspector( 'SSAO', ( inspectNode ) => inspectNode.r )
+						: ao( prePassDepth, prePassNormal, camera ).toInspector( 'GTAO', ( inspectNode ) => inspectNode.r );
 
-				renderPipeline.outputNode = traaPass;
+					aoPass.resolutionScale = 0.5; // running AO in half resolution is often sufficient
+
+					updateParameters();
+
+					scenePass.contextNode = builtinAOContext( aoPass.getTextureNode().sample( screenUV ).r );
+					scenePass.needsUpdate = true;
+
+					updateOutput();
+
+				}
+
+				createAO();
 
 				// models
 
@@ -496,32 +521,55 @@
 				// GUI
 
 				const gui = renderer.inspector.createParameters( 'Settings' );
+				gui.add( params, 'aoType', [ 'GTAO', 'SSAO' ] ).name( 'AO type' ).onChange( () => {
+
+					createAO();
+					updateControls();
+
+				} );
 				gui.add( params, 'samples', 4, 32, 1 ).onChange( updateParameters );
 				gui.add( params, 'radius', 0.1, 1 ).onChange( updateParameters );
-				gui.add( params, 'scale', 0.01, 1 ).onChange( updateParameters );
-				gui.add( params, 'thickness', 0.01, 2 ).onChange( updateParameters );
-				gui.add( aoPass, 'resolutionScale', 0, 1 ).name( 'resolution scale' );
-				gui.add( aoPass, 'useTemporalFiltering' ).name( 'temporal filtering' );
+
+				const gtaoControls = [
+					gui.add( params, 'scale', 0.01, 1 ).onChange( updateParameters ),
+					gui.add( params, 'thickness', 0.01, 2 ).onChange( updateParameters ),
+					gui.add( aoPass, 'resolutionScale', 0, 1 ).name( 'resolution scale' ),
+					gui.add( params, 'temporalFiltering' ).name( 'temporal filtering' ).onChange( updateParameters )
+				];
+
+				const ssaoControls = [
+					gui.add( params, 'intensity', 0, 4 ).onChange( updateParameters ),
+					gui.add( params, 'bias', 0, 0.2 ).onChange( updateParameters ),
+					gui.add( params, 'blurSharpness', 0, 8 ).name( 'blur sharpness' ).onChange( updateParameters )
+				];
+
 				gui.add( transparentMesh, 'visible' ).name( 'show transparent mesh' );
-				gui.add( params, 'transparentOpacity', 0, 1, 0.01 ).name( 'transparent opacity' ).onChange( updateParameters );
-				gui.add( params, 'aoOnly' ).onChange( ( value ) => {
+				gui.add( params, 'transparentOpacity', 0, 1, 0.01 ).name( 'transparent opacity' ).onChange( ( value ) => {
 
-					if ( value === true ) {
+					transparentMesh.material.opacity = value;
 
-						renderPipeline.outputNode = vec4( vec3( aoPass.r ), 1 );
-						renderer.toneMapping = THREE.NoToneMapping;
+				} );
+				gui.add( params, 'aoOnly' ).onChange( updateOutput );
 
-					} else {
+				// show only the controls that apply to the active AO type
+
+				function updateControls() {
 
+					if ( params.aoType === 'GTAO' ) {
 
-						renderPipeline.outputNode = traaPass;
-						renderer.toneMapping = THREE.NeutralToneMapping;
+						gtaoControls.forEach( ( control ) => control.show() );
+						ssaoControls.forEach( ( control ) => control.hide() );
+
+					} else {
+
+						gtaoControls.forEach( ( control ) => control.hide() );
+						ssaoControls.forEach( ( control ) => control.show() );
 
 					}
 
-					renderPipeline.needsUpdate = true;
+				}
 
-				} );
+				updateControls();
 
 			}
 
@@ -529,10 +577,20 @@
 
 				aoPass.samples.value = params.samples;
 				aoPass.radius.value = params.radius;
-				aoPass.scale.value = params.scale;
-				aoPass.thickness.value = params.thickness;
 
-				transparentMesh.material.opacity = params.transparentOpacity;
+				if ( params.aoType === 'SSAO' ) {
+
+					aoPass.intensity.value = params.intensity;
+					aoPass.bias.value = params.bias;
+					aoPass.blurSharpness.value = params.blurSharpness;
+
+				} else {
+
+					aoPass.scale.value = params.scale;
+					aoPass.thickness.value = params.thickness;
+					aoPass.useTemporalFiltering = params.temporalFiltering;
+
+				}
 
 			}
 

粤ICP备19079148号