|
| 1 | +import { Fn, vec2, uv, Loop, vec4, premultiplyAlpha, unpremultiplyAlpha, max, int, textureSize, nodeObject, convertToTexture } from 'three/tsl'; |
| 2 | + |
| 3 | +/** |
| 4 | + * Applies a box blur effect to the given texture node. |
| 5 | + * |
| 6 | + * Compared to Gaussian blur, box blur produces a more blocky result but with better performance when correctly |
| 7 | + * configured. It is intended for mobile devices or performance restricted use cases where Gaussian is too heavy. |
| 8 | + * |
| 9 | + * The (kernel) `size` parameter should be small (1, 2 or 3) since it determines the number of samples based on (size * 2 + 1)^2. |
| 10 | + * This implementation uses a single pass approach so the kernel is not applied as a seprabable filter. That means larger |
| 11 | + * kernels won't perform well. Use Gaussian instead if you need a more high-quality blur. |
| 12 | + * |
| 13 | + * To produce wider blurs, increase the `separation` parameter instead which has no influence on the performance. |
| 14 | + * |
| 15 | + * Reference: {@link https://github.com/lettier/3d-game-shaders-for-beginners/blob/master/demonstration/shaders/fragment/box-blur.frag}. |
| 16 | + * |
| 17 | + * @function |
| 18 | + * @param {Node<vec4>} textureNode - The texture node that should be blurred. |
| 19 | + * @param {Object} [options={}] - Additional options for the hash blur effect. |
| 20 | + * @param {Node<int>} [options.size=int(1)] - Controls the blur's kernel. For performant results, the range should within [1, 3]. |
| 21 | + * @param {Node<int>} [options.separation=int(1)] - Spreads out the blur without having to sample additional fragments. Ranges from [1, Infinity]. |
| 22 | + * @param {boolean} [options.premultipliedAlpha=false] - Whether to use premultiplied alpha for the blur effect. |
| 23 | + * @return {Node<vec4>} The blurred texture node. |
| 24 | + */ |
| 25 | +export const boxBlur = /*#__PURE__*/ Fn( ( [ textureNode, options = {} ] ) => { |
| 26 | + |
| 27 | + textureNode = convertToTexture( textureNode ); |
| 28 | + const size = nodeObject( options.size ) || int( 1 ); |
| 29 | + const separation = nodeObject( options.separation ) || int( 1 ); |
| 30 | + const premultipliedAlpha = options.premultipliedAlpha || false; |
| 31 | + |
| 32 | + const tap = ( uv ) => { |
| 33 | + |
| 34 | + const sample = textureNode.sample( uv ); |
| 35 | + return premultipliedAlpha ? premultiplyAlpha( sample ) : sample; |
| 36 | + |
| 37 | + }; |
| 38 | + |
| 39 | + const targetUV = textureNode.uvNode || uv(); |
| 40 | + |
| 41 | + const result = vec4( 0 ); |
| 42 | + const sep = max( separation, 1 ); |
| 43 | + const count = int( 0 ); |
| 44 | + const pixelStep = vec2( 1 ).div( textureSize( textureNode ) ); |
| 45 | + |
| 46 | + Loop( { start: size.negate(), end: size, name: 'i', condition: '<=' }, ( { i } ) => { |
| 47 | + |
| 48 | + Loop( { start: size.negate(), end: size, name: 'j', condition: '<=' }, ( { j } ) => { |
| 49 | + |
| 50 | + const uvs = targetUV.add( vec2( i, j ).mul( pixelStep ).mul( sep ) ); |
| 51 | + result.addAssign( tap( uvs ) ); |
| 52 | + count.addAssign( 1 ); |
| 53 | + |
| 54 | + } ); |
| 55 | + |
| 56 | + } ); |
| 57 | + |
| 58 | + result.divAssign( count ); |
| 59 | + |
| 60 | + return premultipliedAlpha ? unpremultiplyAlpha( result ) : result; |
| 61 | + |
| 62 | +} ); |
0 commit comments