depthAwareBlur.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. import { abs, exp, float, Fn, int, logarithmicDepthToViewZ, Loop, max, perspectiveDepthToViewZ, reference, uv } from 'three/tsl';
  2. /**
  3. * Applies one pass of a separable, depth-aware (bilateral) blur to a screen-space signal.
  4. *
  5. * The spatial term is a fixed 5-tap gaussian along `directionNode`; the edge-stopping term
  6. * rejects neighbours whose view-space depth differs from the center, so the signal is smoothed
  7. * across surfaces but not across silhouettes. Depth rejection is measured relative to `radius`,
  8. * which keeps it scale invariant. Run it twice (horizontal then vertical) for a full separable
  9. * blur. Handy for denoising a half-resolution effect such as ambient occlusion.
  10. *
  11. * @tsl
  12. * @function
  13. * @param {Node<vec4>} inputNode - The texture node to blur; its red channel is filtered.
  14. * @param {Node<float>} depthNode - The scene depth texture node.
  15. * @param {Node<vec2>} directionNode - One texel step along the blur axis, e.g. `vec2( 1 / width, 0 )`.
  16. * @param {Camera} camera - The camera the scene is rendered with.
  17. * @param {Node<float> | number} [sharpness=2] - How strongly a depth difference rejects a neighbour.
  18. * @param {Node<float> | number} [radius=1] - The world-space scale the depth rejection is relative to.
  19. * @returns {Node<float>} The blurred value.
  20. */
  21. export const depthAwareBlur = /*#__PURE__*/ Fn( ( [ inputNode, depthNode, directionNode, camera, sharpness = 2, radius = 1 ], builder ) => {
  22. const cameraNear = reference( 'near', 'float', camera );
  23. const cameraFar = reference( 'far', 'float', camera );
  24. const viewZ = ( uvNode ) => {
  25. const depth = depthNode.sample( uvNode ).r;
  26. if ( builder.renderer.logarithmicDepthBuffer === true ) {
  27. return logarithmicDepthToViewZ( depth, cameraNear, cameraFar );
  28. }
  29. return perspectiveDepthToViewZ( depth, cameraNear, cameraFar );
  30. };
  31. const uvNode = uv();
  32. const centerViewZ = viewZ( uvNode ).toVar();
  33. const sum = float( 0 ).toVar();
  34. const weightSum = float( 0 ).toVar();
  35. Loop( { start: int( - 2 ), end: int( 3 ), type: 'int', condition: '<' }, ( { i } ) => {
  36. const fi = float( i );
  37. const sampleUv = uvNode.add( directionNode.mul( fi ) );
  38. const spatialWeight = exp( fi.mul( fi ).mul( - 0.5 ) );
  39. const depthWeight = exp( abs( viewZ( sampleUv ).sub( centerViewZ ) ).div( radius ).mul( sharpness ).negate() );
  40. const weight = spatialWeight.mul( depthWeight );
  41. sum.addAssign( inputNode.sample( sampleUv ).r.mul( weight ) );
  42. weightSum.addAssign( weight );
  43. } );
  44. return sum.div( max( weightSum, float( 0.0001 ) ) );
  45. } );
粤ICP备19079148号