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

Added DualKawaseBloomNode. (#33920)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mrdoob 1 неделя назад
Родитель
Сommit
01cf7fe61d

+ 596 - 0
examples/jsm/tsl/display/DualKawaseBloomNode.js

@@ -0,0 +1,596 @@
+import { HalfFloatType, RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, RendererUtils, NodeUpdateType } from 'three/webgpu';
+import { nodeObject, Fn, float, vec2, uv, passTexture, uniform, texture, luminance, smoothstep, mix, vec4 } from 'three/tsl';
+
+const _quadMesh = /*@__PURE__*/ new QuadMesh();
+const _size = /*@__PURE__*/ new Vector2();
+
+let _rendererState;
+
+const luminosityHighPass = Fn( ( { input, threshold, smoothWidth } ) => {
+
+	const v = luminance( input.rgb );
+	const alpha = smoothstep( threshold, threshold.add( smoothWidth ), v );
+
+	return mix( vec4( 0 ), input, alpha );
+
+} );
+
+/**
+ * Post processing node for creating a bloom effect.
+ *
+ * The bloom is produced with a Dual Kawase blur: the bright areas are
+ * progressively downsampled with a 5-tap filter and then upsampled with an
+ * 8-tap filter, accumulating the levels back together.
+ * ```js
+ * const renderPipeline = new THREE.RenderPipeline( renderer );
+ *
+ * const scenePass = pass( scene, camera );
+ * const scenePassColor = scenePass.getTextureNode( 'output' );
+ *
+ * const bloomPass = dualKawaseBloom( scenePassColor );
+ *
+ * renderPipeline.outputNode = scenePassColor.add( bloomPass );
+ * ```
+ * By default, the node affects the entire image. For a selective bloom,
+ * use the `emissive` material property to control which objects should
+ * contribute to bloom or not. This can be achieved via MRT.
+ * ```js
+ * const renderPipeline = new THREE.RenderPipeline( renderer );
+ *
+ * const scenePass = pass( scene, camera );
+ * scenePass.setMRT( mrt( {
+ * 	output,
+ * 	emissive
+ * } ) );
+ *
+ * const scenePassColor = scenePass.getTextureNode( 'output' );
+ * const emissivePass = scenePass.getTextureNode( 'emissive' );
+ *
+ * const bloomPass = dualKawaseBloom( emissivePass );
+ * renderPipeline.outputNode = scenePassColor.add( bloomPass );
+ * ```
+ * @augments TempNode
+ * @three_import import { dualKawaseBloom } from 'three/addons/tsl/display/DualKawaseBloomNode.js';
+ */
+class DualKawaseBloomNode extends TempNode {
+
+	static get type() {
+
+		return 'DualKawaseBloomNode';
+
+	}
+
+	/**
+	 * Constructs a new bloom node.
+	 *
+	 * @param {Node<vec4>} inputNode - The node that represents the input of the effect.
+	 * @param {number} [strength=1] - The strength of the bloom.
+	 * @param {number} [radius=0] - The radius of the bloom.
+	 * @param {number} [threshold=0] - The luminance threshold limits which bright areas contribute to the bloom effect.
+	 */
+	constructor( inputNode, strength = 1, radius = 0, threshold = 0 ) {
+
+		super( 'vec4' );
+
+		/**
+		 * The node that represents the input of the effect.
+		 *
+		 * @type {Node<vec4>}
+		 */
+		this.inputNode = inputNode;
+
+		/**
+		 * The strength of the bloom.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.strength = strength.isNode ? strength : uniform( strength );
+
+		/**
+		 * The radius of the bloom. Must be in the range `[0,1]`.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.radius = radius.isNode ? radius : uniform( radius );
+
+		/**
+		 * The luminance threshold limits which bright areas contribute to the bloom effect.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.threshold = threshold.isNode ? threshold : uniform( threshold );
+
+		/**
+		 * Can be used to tweak the extracted luminance from the scene.
+		 *
+		 * @type {UniformNode<float>}
+		 */
+		this.smoothWidth = uniform( 0.01 );
+
+		/**
+		 * Scale factor for the internal render targets.
+		 *
+		 * @private
+		 * @type {number}
+		 * @default 0.5
+		 */
+		this._resolutionScale = 0.5;
+
+		/**
+		 * Can be used to inject a custom high pass filter (e.g., for anamorphic effects).
+		 *
+		 * @type {Function}
+		 */
+		this.highPassFn = luminosityHighPass;
+
+		/**
+		 * The number of downsample / upsample levels in the Dual Kawase pyramid.
+		 *
+		 * @private
+		 * @type {number}
+		 */
+		this._nMips = 6;
+
+		/**
+		 * Sample spread of the Dual Kawase filters. Kept small so each level's kernel
+		 * stays round (a wide offset makes the diamond sampling pattern visible as a
+		 * square halo); the bloom width comes from the depth of the pyramid instead.
+		 *
+		 * @private
+		 * @type {UniformNode<float>}
+		 */
+		this._offset = uniform( 3 );
+
+		/**
+		 * Per-level mixing factors, redistributed by `radius` between a tight and a wide bloom.
+		 * A linear ramp from `1.0` to `0.2` (mean `0.6`) keeps the total bloom energy constant
+		 * as `radius` shifts weight between the fine and coarse levels.
+		 *
+		 * @private
+		 * @type {Array<number>}
+		 */
+		this._bloomFactors = [];
+
+		for ( let i = 0; i < this._nMips; i ++ ) {
+
+			this._bloomFactors.push( this._nMips === 1 ? 0.6 : 1.0 - 0.8 * i / ( this._nMips - 1 ) );
+
+		}
+
+		/**
+		 * The render target for the luminance pass.
+		 *
+		 * @private
+		 * @type {RenderTarget}
+		 */
+		this._renderTargetBright = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
+		this._renderTargetBright.texture.name = 'DualKawaseBloom.bright';
+		this._renderTargetBright.texture.generateMipmaps = false;
+
+		/**
+		 * The render targets for the downsample chain.
+		 *
+		 * @private
+		 * @type {Array<RenderTarget>}
+		 */
+		this._downsampleRTs = [];
+
+		/**
+		 * The render targets for the upsample / accumulation chain.
+		 *
+		 * @private
+		 * @type {Array<RenderTarget>}
+		 */
+		this._accumRTs = [];
+
+		/**
+		 * The resolution of each pyramid level.
+		 *
+		 * @private
+		 * @type {Array<Vector2>}
+		 */
+		this._levelSizes = [];
+
+		/**
+		 * The resolution of the bright pass.
+		 *
+		 * @private
+		 * @type {Vector2}
+		 */
+		this._brightSize = new Vector2();
+
+		for ( let i = 0; i < this._nMips; i ++ ) {
+
+			const downsampleRT = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
+			downsampleRT.texture.name = 'DualKawaseBloom.down' + i;
+			downsampleRT.texture.generateMipmaps = false;
+			this._downsampleRTs.push( downsampleRT );
+
+			const accumRT = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
+			accumRT.texture.name = 'DualKawaseBloom.up' + i;
+			accumRT.texture.generateMipmaps = false;
+			this._accumRTs.push( accumRT );
+
+			this._levelSizes.push( new Vector2() );
+
+		}
+
+		/**
+		 * The material for the luminance pass.
+		 *
+		 * @private
+		 * @type {?NodeMaterial}
+		 */
+		this._highPassFilterMaterial = null;
+
+		/**
+		 * The material for the downsample pass.
+		 *
+		 * @private
+		 * @type {?NodeMaterial}
+		 */
+		this._downsampleMaterial = null;
+
+		/**
+		 * The material for the upsample / accumulation pass.
+		 *
+		 * @private
+		 * @type {?NodeMaterial}
+		 */
+		this._upsampleMaterial = null;
+
+		/**
+		 * The result of the effect is represented as a separate texture node.
+		 * The finest accumulation target holds the composited bloom.
+		 *
+		 * @private
+		 * @type {PassTextureNode}
+		 */
+		this._textureOutput = passTexture( this, this._accumRTs[ 0 ].texture );
+
+		/**
+		 * 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;
+
+	}
+
+	/**
+	 * 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._textureOutput;
+
+	}
+
+	/**
+	 * Sets the resolution scale for the pass.
+	 * The resolution scale is a factor that is multiplied with the renderer's width and height.
+	 *
+	 * @param {number} resolutionScale - The resolution scale to set. A value of `1` means full resolution.
+	 * @return {DualKawaseBloomNode} A reference to this node.
+	 */
+	setResolutionScale( resolutionScale ) {
+
+		this._resolutionScale = resolutionScale;
+
+		return this;
+
+	}
+
+	/**
+	 * Gets the current resolution scale of the pass.
+	 *
+	 * @return {number} The current resolution scale. A value of `1` means full resolution.
+	 */
+	getResolutionScale() {
+
+		return this._resolutionScale;
+
+	}
+
+	/**
+	 * 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 ) {
+
+		const resx = Math.max( 1, Math.floor( width * this._resolutionScale ) );
+		const resy = Math.max( 1, Math.floor( height * this._resolutionScale ) );
+
+		this._renderTargetBright.setSize( resx, resy );
+		this._brightSize.set( resx, resy );
+
+		let rx = resx;
+		let ry = resy;
+
+		for ( let i = 0; i < this._nMips; i ++ ) {
+
+			// Level 0 blurs in place at bright resolution; the rest halve.
+
+			if ( i > 0 ) {
+
+				rx = Math.max( 1, Math.floor( rx / 2 ) );
+				ry = Math.max( 1, Math.floor( ry / 2 ) );
+
+			}
+
+			this._downsampleRTs[ i ].setSize( rx, ry );
+			this._accumRTs[ i ].setSize( rx, ry );
+			this._levelSizes[ i ].set( rx, ry );
+
+		}
+
+	}
+
+	/**
+	 * 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 );
+
+		const nMips = this._nMips;
+		const downMaterial = this._downsampleMaterial;
+		const upMaterial = this._upsampleMaterial;
+
+		// 1. Extract bright areas
+
+		renderer.setRenderTarget( this._renderTargetBright );
+		_quadMesh.material = this._highPassFilterMaterial;
+		_quadMesh.name = 'Dual Kawase Bloom [ High Pass ]';
+		_quadMesh.render( renderer );
+
+		// 2. Downsample chain ( bright -> down0 -> down1 -> ... ). The coarsest level
+		// pre-multiplies its `radius` weight so the upsample chain can seed from it.
+
+		_quadMesh.material = downMaterial;
+
+		let sourceTexture = this._renderTargetBright.texture;
+		let sourceSize = this._brightSize;
+
+		for ( let i = 0; i < nMips; i ++ ) {
+
+			const isCoarsest = ( i === nMips - 1 );
+
+			downMaterial.colorTexture.value = sourceTexture;
+			downMaterial.texelSize.value.set( 1 / sourceSize.x, 1 / sourceSize.y );
+			downMaterial.applyWeight.value = isCoarsest ? 1 : 0;
+			downMaterial.bloomFactor.value = this._bloomFactors[ i ];
+			renderer.setRenderTarget( this._downsampleRTs[ i ] );
+			_quadMesh.name = `Dual Kawase Bloom [ Downsample - ${ i } ]`;
+			_quadMesh.render( renderer );
+
+			sourceTexture = this._downsampleRTs[ i ].texture;
+			sourceSize = this._levelSizes[ i ];
+
+		}
+
+		// 3. Upsample chain with weighted accumulation ( ... -> up1 -> up0 ).
+		// Each level adds its matching downsample weighted by `radius`; the
+		// coarsest downsample seeds the chain. The finest step ( i = 0 ) lands at
+		// bright resolution and applies `strength`, so it doubles as the composite.
+
+		_quadMesh.material = upMaterial;
+
+		for ( let i = nMips - 2; i >= 0; i -- ) {
+
+			const isSeed = ( i === nMips - 2 );
+
+			upMaterial.finalFlag.value = ( i === 0 ) ? 1 : 0;
+			upMaterial.bloomFactor.value = this._bloomFactors[ i ];
+			upMaterial.prevTexture.value = isSeed ? this._downsampleRTs[ i + 1 ].texture : this._accumRTs[ i + 1 ].texture;
+			upMaterial.addTexture.value = this._downsampleRTs[ i ].texture;
+			upMaterial.texelSize.value.set( 1 / this._levelSizes[ i + 1 ].x, 1 / this._levelSizes[ i + 1 ].y );
+
+			renderer.setRenderTarget( this._accumRTs[ i ] );
+			_quadMesh.name = ( i === 0 ) ? 'Dual Kawase Bloom [ Composite ]' : `Dual Kawase Bloom [ Upsample - ${ i } ]`;
+			_quadMesh.render( renderer );
+
+		}
+
+		// restore
+
+		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 ) {
+
+		// luminosity high pass material
+
+		this._highPassFilterMaterial = this._highPassFilterMaterial || new NodeMaterial();
+		this._highPassFilterMaterial.fragmentNode = this.highPassFn( { input: this.inputNode, threshold: this.threshold, smoothWidth: this.smoothWidth } ).context( builder.getSharedContext() );
+		this._highPassFilterMaterial.name = 'DualKawaseBloom_highPass';
+		this._highPassFilterMaterial.needsUpdate = true;
+
+		// downsample material ( Dual Kawase, 5 taps )
+
+		this._downsampleMaterial = this._downsampleMaterial || this._getDownsampleMaterial( builder );
+
+		// upsample material ( Dual Kawase, 8 taps, weighted accumulation )
+
+		this._upsampleMaterial = this._upsampleMaterial || this._getUpsampleMaterial( builder );
+
+		//
+
+		return this._textureOutput;
+
+	}
+
+	/**
+	 * Frees internal resources. This method should be called
+	 * when the effect is no longer required.
+	 */
+	dispose() {
+
+		this._renderTargetBright.dispose();
+
+		for ( let i = 0; i < this._nMips; i ++ ) {
+
+			this._downsampleRTs[ i ].dispose();
+			this._accumRTs[ i ].dispose();
+
+		}
+
+		if ( this._highPassFilterMaterial !== null ) this._highPassFilterMaterial.dispose();
+		if ( this._downsampleMaterial !== null ) this._downsampleMaterial.dispose();
+		if ( this._upsampleMaterial !== null ) this._upsampleMaterial.dispose();
+
+	}
+
+	/**
+	 * Creates the Dual Kawase downsample material. Each output texel reads the
+	 * center plus four diagonal corners of the source.
+	 *
+	 * @private
+	 * @param {NodeBuilder} builder - The current node builder.
+	 * @return {NodeMaterial}
+	 */
+	_getDownsampleMaterial( builder ) {
+
+		const colorTexture = texture( null );
+		const texelSize = uniform( new Vector2() );
+		const offset = this._offset;
+
+		const applyWeight = uniform( 0 );
+		const bloomFactor = uniform( 1 );
+
+		const uvNode = uv();
+
+		const downsamplePass = Fn( () => {
+
+			const o = texelSize.mul( 0.5 ).mul( offset );
+
+			const color = colorTexture.sample( uvNode ).mul( 4.0 ).toVar();
+
+			color.addAssign( colorTexture.sample( uvNode.add( vec2( o.x.negate(), o.y.negate() ) ) ) );
+			color.addAssign( colorTexture.sample( uvNode.add( vec2( o.x, o.y.negate() ) ) ) );
+			color.addAssign( colorTexture.sample( uvNode.add( vec2( o.x.negate(), o.y ) ) ) );
+			color.addAssign( colorTexture.sample( uvNode.add( vec2( o.x, o.y ) ) ) );
+
+			// The coarsest level pre-applies its `radius` weight so it can seed the upsample chain.
+			const weight = mix( bloomFactor, float( 1.2 ).sub( bloomFactor ), this.radius );
+
+			return vec4( color.rgb.div( 8.0 ).mul( mix( float( 1.0 ), weight, applyWeight ) ), 1.0 );
+
+		} );
+
+		const material = new NodeMaterial();
+		material.fragmentNode = downsamplePass().context( builder.getSharedContext() );
+		material.name = 'DualKawaseBloom_down';
+		material.needsUpdate = true;
+
+		material.colorTexture = colorTexture;
+		material.texelSize = texelSize;
+		material.applyWeight = applyWeight;
+		material.bloomFactor = bloomFactor;
+
+		return material;
+
+	}
+
+	/**
+	 * Creates the Dual Kawase upsample material. Each output texel reads four
+	 * edge centers and four diagonal corners of the source, then accumulates the
+	 * matching downsample level weighted by `radius`.
+	 *
+	 * @private
+	 * @param {NodeBuilder} builder - The current node builder.
+	 * @return {NodeMaterial}
+	 */
+	_getUpsampleMaterial( builder ) {
+
+		const prevTexture = texture( null );
+		const addTexture = texture( null );
+		const texelSize = uniform( new Vector2() );
+		const offset = this._offset;
+
+		const finalFlag = uniform( 0 );
+		const bloomFactor = uniform( 1 );
+
+		const uvNode = uv();
+
+		const upsamplePass = Fn( () => {
+
+			const o = texelSize.mul( 0.5 ).mul( offset );
+
+			const sum = prevTexture.sample( uvNode.add( vec2( o.x.mul( - 2.0 ), 0.0 ) ) ).rgb.toVar();
+			sum.addAssign( prevTexture.sample( uvNode.add( vec2( o.x.mul( 2.0 ), 0.0 ) ) ).rgb );
+			sum.addAssign( prevTexture.sample( uvNode.add( vec2( 0.0, o.y.mul( - 2.0 ) ) ) ).rgb );
+			sum.addAssign( prevTexture.sample( uvNode.add( vec2( 0.0, o.y.mul( 2.0 ) ) ) ).rgb );
+
+			sum.addAssign( prevTexture.sample( uvNode.add( vec2( o.x.negate(), o.y ) ) ).rgb.mul( 2.0 ) );
+			sum.addAssign( prevTexture.sample( uvNode.add( vec2( o.x, o.y ) ) ).rgb.mul( 2.0 ) );
+			sum.addAssign( prevTexture.sample( uvNode.add( vec2( o.x.negate(), o.y.negate() ) ) ).rgb.mul( 2.0 ) );
+			sum.addAssign( prevTexture.sample( uvNode.add( vec2( o.x, o.y.negate() ) ) ).rgb.mul( 2.0 ) );
+
+			const blurred = sum.div( 12.0 );
+
+			// redistribute the level's contribution between a tight and a wide bloom
+			const weight = mix( bloomFactor, float( 1.2 ).sub( bloomFactor ), this.radius );
+			const added = addTexture.sample( uvNode ).rgb.mul( weight );
+
+			// keep the total intensity independent of the level count ( the factors sum to `_nMips * 0.6` )
+			const norm = 3 / ( this._nMips * 0.6 );
+			const result = blurred.add( added ).mul( mix( float( 1.0 ), this.strength.mul( norm ), finalFlag ) );
+
+			return vec4( result, 1.0 );
+
+		} );
+
+		const material = new NodeMaterial();
+		material.fragmentNode = upsamplePass().context( builder.getSharedContext() );
+		material.name = 'DualKawaseBloom_up';
+		material.needsUpdate = true;
+
+		material.prevTexture = prevTexture;
+		material.addTexture = addTexture;
+		material.texelSize = texelSize;
+		material.finalFlag = finalFlag;
+		material.bloomFactor = bloomFactor;
+
+		return material;
+
+	}
+
+}
+
+/**
+ * TSL function for creating a bloom effect.
+ *
+ * @tsl
+ * @function
+ * @param {Node<vec4>} node - The node that represents the input of the effect.
+ * @param {number} [strength=1] - The strength of the bloom.
+ * @param {number} [radius=0] - The radius of the bloom.
+ * @param {number} [threshold=0] - The luminance threshold limits which bright areas contribute to the bloom effect.
+ * @returns {DualKawaseBloomNode}
+ */
+export const dualKawaseBloom = ( node, strength, radius, threshold ) => new DualKawaseBloomNode( nodeObject( node ), strength, radius, threshold );
+
+export default DualKawaseBloomNode;

+ 24 - 21
examples/webgpu_postprocessing_bloom.html

@@ -20,7 +20,7 @@
 			</div>
 			</div>
 
 
 			<small>
 			<small>
-				Bloom pass by <a href="http://eduperiment.com" target="_blank" rel="noopener">Prashant Sharma</a> and <a href="https://clara.io" target="_blank" rel="noopener">Ben Houston</a>.<br/>
+				Gaussian bloom by <a href="http://eduperiment.com" target="_blank" rel="noopener">Prashant Sharma</a> and <a href="https://clara.io" target="_blank" rel="noopener">Ben Houston</a>.<br/>
 				<a href="https://blog.sketchfab.com/art-spotlight-primary-ion-drive/" target="_blank" rel="noopener">Primary Ion Drive</a>
 				<a href="https://blog.sketchfab.com/art-spotlight-primary-ion-drive/" target="_blank" rel="noopener">Primary Ion Drive</a>
 				by <a href="http://mjmurdock.com/" target="_blank" rel="noopener">Mike Murdock</a> is licensed under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">Creative Commons Attribution</a>.<br />
 				by <a href="http://mjmurdock.com/" target="_blank" rel="noopener">Mike Murdock</a> is licensed under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">Creative Commons Attribution</a>.<br />
 			</small>
 			</small>
@@ -40,8 +40,9 @@
 		<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, uniform } from 'three/tsl';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
+			import { dualKawaseBloom } from 'three/addons/tsl/display/DualKawaseBloomNode.js';
 
 
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 
 
@@ -53,9 +54,7 @@
 			let renderPipeline, renderer, mixer, timer;
 			let renderPipeline, renderer, mixer, timer;
 
 
 			const params = {
 			const params = {
-				threshold: 0,
-				strength: 1,
-				radius: 0,
+				type: 'Gaussian',
 				exposure: 1
 				exposure: 1
 			};
 			};
 
 
@@ -104,9 +103,23 @@
 				const scenePass = pass( scene, camera );
 				const scenePass = pass( scene, camera );
 				const scenePassColor = scenePass.getTextureNode( 'output' ).toInspector( 'Color' );
 				const scenePassColor = scenePass.getTextureNode( 'output' ).toInspector( 'Color' );
 
 
-				const bloomPass = bloom( scenePassColor ).toInspector( 'Bloom' );
+				const strength = uniform( 1 );
+				const radius = uniform( 0 );
+				const threshold = uniform( 0 );
 
 
-				renderPipeline.outputNode = scenePassColor.add( bloomPass );
+				const bloomPasses = {
+					'Gaussian': bloom( scenePassColor, strength, radius, threshold ),
+					'Dual Kawase': dualKawaseBloom( scenePassColor, strength, radius, threshold )
+				};
+
+				function updateBloom() {
+
+					renderPipeline.outputNode = scenePassColor.add( bloomPasses[ params.type ] );
+					renderPipeline.needsUpdate = true;
+
+				}
+
+				updateBloom();
 
 
 				//
 				//
 
 
@@ -121,23 +134,13 @@
 
 
 				const bloomFolder = gui.addFolder( 'bloom' );
 				const bloomFolder = gui.addFolder( 'bloom' );
 
 
-				bloomFolder.add( params, 'threshold', 0.0, 1.0 ).onChange( function ( value ) {
-
-					bloomPass.threshold.value = value;
+				bloomFolder.add( params, 'type', [ 'Gaussian', 'Dual Kawase' ] ).onChange( updateBloom );
 
 
-				} );
+				bloomFolder.add( threshold, 'value', 0.0, 1.0 ).name( 'threshold' );
 
 
-				bloomFolder.add( params, 'strength', 0.0, 3.0 ).onChange( function ( value ) {
+				bloomFolder.add( strength, 'value', 0.0, 3.0 ).name( 'strength' );
 
 
-					bloomPass.strength.value = value;
-
-				} );
-
-				bloomFolder.add( params, 'radius', 0.0, 1.0, 0.01 ).onChange( function ( value ) {
-
-					bloomPass.radius.value = value;
-
-				} );
+				bloomFolder.add( radius, 'value', 0.0, 1.0, 0.01 ).name( 'radius' );
 
 
 				const toneMappingFolder = gui.addFolder( 'tone mapping' );
 				const toneMappingFolder = gui.addFolder( 'tone mapping' );
 
 

+ 23 - 5
examples/webgpu_postprocessing_bloom_emissive.html

@@ -36,8 +36,9 @@
 		<script type="module">
 		<script type="module">
 
 
 			import * as THREE from 'three/webgpu';
 			import * as THREE from 'three/webgpu';
-			import { pass, mrt, output, emissive, vec4 } from 'three/tsl';
+			import { pass, mrt, output, emissive, vec4, uniform } from 'three/tsl';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
+			import { dualKawaseBloom } from 'three/addons/tsl/display/DualKawaseBloomNode.js';
 
 
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
 
 
@@ -118,10 +119,26 @@
 				const outputPass = scenePass.getTextureNode().toInspector( 'Color' );
 				const outputPass = scenePass.getTextureNode().toInspector( 'Color' );
 				const emissivePass = scenePass.getTextureNode( 'emissive' ).toInspector( 'Emissive' );
 				const emissivePass = scenePass.getTextureNode( 'emissive' ).toInspector( 'Emissive' );
 
 
-				const bloomPass = bloom( emissivePass, 2.5, .5 );
+				const params = { type: 'Gaussian' };
+
+				const strength = uniform( 2.5 );
+				const radius = uniform( 0.5 );
+
+				const bloomPasses = {
+					'Gaussian': bloom( emissivePass, strength, radius ),
+					'Dual Kawase': dualKawaseBloom( emissivePass, strength, radius )
+				};
 
 
 				renderPipeline = new THREE.RenderPipeline( renderer );
 				renderPipeline = new THREE.RenderPipeline( renderer );
-				renderPipeline.outputNode = outputPass.add( bloomPass );
+
+				function updateBloom() {
+
+					renderPipeline.outputNode = outputPass.add( bloomPasses[ params.type ] );
+					renderPipeline.needsUpdate = true;
+
+				}
+
+				updateBloom();
 
 
 				//
 				//
 
 
@@ -138,8 +155,9 @@
 				const gui = renderer.inspector.createParameters( 'Settings' );
 				const gui = renderer.inspector.createParameters( 'Settings' );
 
 
 				const bloomFolder = gui.addFolder( 'Bloom' );
 				const bloomFolder = gui.addFolder( 'Bloom' );
-				bloomFolder.add( bloomPass.strength, 'value', 0.0, 5.0 ).name( 'strength' );
-				bloomFolder.add( bloomPass.radius, 'value', 0.0, 1.0 ).name( 'radius' );
+				bloomFolder.add( params, 'type', [ 'Gaussian', 'Dual Kawase' ] ).onChange( updateBloom );
+				bloomFolder.add( strength, 'value', 0.0, 5.0 ).name( 'strength' );
+				bloomFolder.add( radius, 'value', 0.0, 1.0 ).name( 'radius' );
 
 
 				const toneMappingFolder = gui.addFolder( 'Tone Mapping' );
 				const toneMappingFolder = gui.addFolder( 'Tone Mapping' );
 				toneMappingFolder.add( renderer, 'toneMappingExposure', 0.1, 2 ).name( 'exposure' );
 				toneMappingFolder.add( renderer, 'toneMappingExposure', 0.1, 2 ).name( 'exposure' );

+ 25 - 5
examples/webgpu_postprocessing_bloom_selective.html

@@ -38,6 +38,7 @@
 			import * as THREE from 'three/webgpu';
 			import * as THREE from 'three/webgpu';
 			import { pass, mrt, output, float, uniform } from 'three/tsl';
 			import { pass, mrt, output, float, uniform } from 'three/tsl';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
+			import { dualKawaseBloom } from 'three/addons/tsl/display/DualKawaseBloomNode.js';
 
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
 
@@ -96,11 +97,29 @@
 			const outputPass = scenePass.getTextureNode().toInspector( 'Color' );
 			const outputPass = scenePass.getTextureNode().toInspector( 'Color' );
 			const bloomIntensityPass = scenePass.getTextureNode( 'bloomIntensity' ).toInspector( 'Bloom Intensity' );
 			const bloomIntensityPass = scenePass.getTextureNode( 'bloomIntensity' ).toInspector( 'Bloom Intensity' );
 
 
-			const bloomPass = bloom( outputPass.mul( bloomIntensityPass ) );
+			const params = { type: 'Gaussian' };
+			const bloomInput = outputPass.mul( bloomIntensityPass );
+
+			const strength = uniform( 1 );
+			const radius = uniform( 0 );
+			const threshold = uniform( 0 );
+
+			const bloomPasses = {
+				'Gaussian': bloom( bloomInput, strength, radius, threshold ),
+				'Dual Kawase': dualKawaseBloom( bloomInput, strength, radius, threshold )
+			};
 
 
 			const renderPipeline = new THREE.RenderPipeline( renderer );
 			const renderPipeline = new THREE.RenderPipeline( renderer );
 			renderPipeline.outputColorTransform = false;
 			renderPipeline.outputColorTransform = false;
-			renderPipeline.outputNode = outputPass.add( bloomPass ).renderOutput();
+
+			function updateBloom() {
+
+				renderPipeline.outputNode = outputPass.add( bloomPasses[ params.type ] ).renderOutput();
+				renderPipeline.needsUpdate = true;
+
+			}
+
+			updateBloom();
 
 
 			// controls
 			// controls
 
 
@@ -139,9 +158,10 @@
 			const gui = renderer.inspector.createParameters( 'Settings' );
 			const gui = renderer.inspector.createParameters( 'Settings' );
 
 
 			const bloomFolder = gui.addFolder( 'Bloom' );
 			const bloomFolder = gui.addFolder( 'Bloom' );
-			bloomFolder.add( bloomPass.threshold, 'value', 0.0, 1.0 ).name( 'threshold' );
-			bloomFolder.add( bloomPass.strength, 'value', 0.0, 3 ).name( 'strength' );
-			bloomFolder.add( bloomPass.radius, 'value', 0.0, 1.0 ).name( 'radius' );
+			bloomFolder.add( params, 'type', [ 'Gaussian', 'Dual Kawase' ] ).onChange( updateBloom );
+			bloomFolder.add( threshold, 'value', 0.0, 1.0 ).name( 'threshold' );
+			bloomFolder.add( strength, 'value', 0.0, 3 ).name( 'strength' );
+			bloomFolder.add( radius, 'value', 0.0, 1.0 ).name( 'radius' );
 
 
 			const toneMappingFolder = gui.addFolder( 'Tone Mapping' );
 			const toneMappingFolder = gui.addFolder( 'Tone Mapping' );
 			toneMappingFolder.add( renderer, 'toneMappingExposure', 0.1, 3 ).name( 'exposure' );
 			toneMappingFolder.add( renderer, 'toneMappingExposure', 0.1, 3 ).name( 'exposure' );

粤ICP备19079148号