diff --git a/examples/jsm/tsl/display/GaussianBlurNode.js b/examples/jsm/tsl/display/GaussianBlurNode.js index 517cc504925f85..c7aa0ab0e66281 100644 --- a/examples/jsm/tsl/display/GaussianBlurNode.js +++ b/examples/jsm/tsl/display/GaussianBlurNode.js @@ -1,5 +1,5 @@ import { RenderTarget, Vector2, PostProcessingUtils } from 'three'; -import { TempNode, nodeObject, Fn, float, NodeUpdateType, uv, uniform, convertToTexture, vec2, vec4, QuadMesh, passTexture, mul, NodeMaterial } from 'three/tsl'; +import { TempNode, nodeObject, Fn, If, float, NodeUpdateType, uv, uniform, convertToTexture, vec2, vec4, QuadMesh, passTexture, mul, NodeMaterial } from 'three/tsl'; // WebGPU: The use of a single QuadMesh for both gaussian blur passes results in a single RenderObject with a SampledTexture binding that // alternates between source textures and triggers creation of new BindGroups and BindGroupLayouts every frame. @@ -9,6 +9,32 @@ const _quadMesh2 = /*@__PURE__*/ new QuadMesh(); let _rendererState; +const premult = /*@__PURE__*/ Fn( ( [ color ] ) => { + + return vec4( color.rgb.mul( color.a ), color.a ); + +} ).setLayout( { + name: 'premult', + type: 'vec4', + inputs: [ + { name: 'color', type: 'vec4' } + ] +} ); + +const unpremult = /*@__PURE__*/ Fn( ( [ color ] ) => { + + If( color.a.equal( 0.0 ), () => vec4( 0.0 ) ); + + return vec4( color.rgb.div( color.a ), color.a ); + +} ).setLayout( { + name: 'unpremult', + type: 'vec4', + inputs: [ + { name: 'color', type: 'vec4' } + ] +} ); + class GaussianBlurNode extends TempNode { static get type() { @@ -39,6 +65,22 @@ class GaussianBlurNode extends TempNode { this.resolution = new Vector2( 1, 1 ); + this.premultipliedAlpha = false; + + } + + setPremultipliedAlpha( value ) { + + this.premultipliedAlpha = value; + + return this; + + } + + getPremultipliedAlpha() { + + return this.premultipliedAlpha; + } setSize( width, height ) { @@ -123,7 +165,21 @@ class GaussianBlurNode extends TempNode { const uvNode = textureNode.uvNode || uv(); const directionNode = vec2( this.directionNode || 1 ); - const sampleTexture = ( uv ) => textureNode.uv( uv ); + let sampleTexture, output; + + if ( this.premultipliedAlpha ) { + + // https://lisyarus.github.io/blog/posts/blur-coefficients-generator.html + + sampleTexture = ( uv ) => premult( textureNode.uv( uv ) ); + output = ( color ) => unpremult( color ); + + } else { + + sampleTexture = ( uv ) => textureNode.uv( uv ); + output = ( color ) => color; + + } const blur = Fn( () => { @@ -143,15 +199,15 @@ class GaussianBlurNode extends TempNode { const uvOffset = vec2( direction.mul( invSize.mul( x ) ) ).toVar(); - const sample1 = vec4( sampleTexture( uvNode.add( uvOffset ) ) ); - const sample2 = vec4( sampleTexture( uvNode.sub( uvOffset ) ) ); + const sample1 = sampleTexture( uvNode.add( uvOffset ) ); + const sample2 = sampleTexture( uvNode.sub( uvOffset ) ); diffuseSum.addAssign( sample1.add( sample2 ).mul( w ) ); weightSum.addAssign( mul( 2.0, w ) ); } - return diffuseSum.div( weightSum ); + return output( diffuseSum.div( weightSum ) ); } ); @@ -199,3 +255,4 @@ class GaussianBlurNode extends TempNode { export default GaussianBlurNode; export const gaussianBlur = ( node, directionNode, sigma ) => nodeObject( new GaussianBlurNode( convertToTexture( node ), directionNode, sigma ) ); +export const premultipliedGaussianBlur = ( node, directionNode, sigma ) => nodeObject( new GaussianBlurNode( convertToTexture( node ), directionNode, sigma ).setPremultipliedAlpha( true ) );