PostProcessingUtils.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import { abs, cross, float, Fn, normalize, ivec2, sub, vec2, vec3, vec4, fract, dot, cos, sin } from '../tsl/TSLBase.js';
  2. import { sqrt } from '../math/MathNode.js';
  3. import { textureSize } from '../accessors/TextureSizeNode.js';
  4. import { textureLoad } from '../accessors/TextureNode.js';
  5. import { WebGPUCoordinateSystem } from '../../constants.js';
  6. /**
  7. * Computes a position in view space based on a fragment's screen position expressed as uv coordinates, the fragments
  8. * depth value and the camera's inverse projection matrix.
  9. *
  10. * @tsl
  11. * @function
  12. * @param {Node<vec2>} screenPosition - The fragment's screen position expressed as uv coordinates.
  13. * @param {Node<float>} depth - The fragment's depth value.
  14. * @param {Node<mat4>} projectionMatrixInverse - The camera's inverse projection matrix.
  15. * @return {Node<vec3>} The fragments position in view space.
  16. */
  17. export const getViewPosition = /*@__PURE__*/ Fn( ( [ screenPosition, depth, projectionMatrixInverse ], builder ) => {
  18. let clipSpacePosition;
  19. if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem ) {
  20. screenPosition = vec2( screenPosition.x, screenPosition.y.oneMinus() ).mul( 2.0 ).sub( 1.0 );
  21. clipSpacePosition = vec4( vec3( screenPosition, depth ), 1.0 );
  22. } else {
  23. clipSpacePosition = vec4( vec3( screenPosition.x, screenPosition.y.oneMinus(), depth ).mul( 2.0 ).sub( 1.0 ), 1.0 );
  24. }
  25. const viewSpacePosition = vec4( projectionMatrixInverse.mul( clipSpacePosition ) );
  26. return viewSpacePosition.xyz.div( viewSpacePosition.w );
  27. } );
  28. /**
  29. * Computes a screen position expressed as uv coordinates based on a fragment's position in view space
  30. * and the camera's projection matrix
  31. *
  32. * @tsl
  33. * @function
  34. * @param {Node<vec3>} viewPosition - The fragments position in view space.
  35. * @param {Node<mat4>} projectionMatrix - The camera's projection matrix.
  36. * @return {Node<vec2>} The fragment's screen position expressed as uv coordinates.
  37. */
  38. export const getScreenPosition = /*@__PURE__*/ Fn( ( [ viewPosition, projectionMatrix ] ) => {
  39. const sampleClipPos = projectionMatrix.mul( vec4( viewPosition, 1.0 ) );
  40. const sampleUv = sampleClipPos.xy.div( sampleClipPos.w ).mul( 0.5 ).add( 0.5 ).toVar();
  41. return vec2( sampleUv.x, sampleUv.y.oneMinus() );
  42. } );
  43. /**
  44. * Converts a clip-space position into a screen position expressed as uv coordinates.
  45. *
  46. * @tsl
  47. * @function
  48. * @param {Node<vec4>} clipPosition - The position in clip space.
  49. * @return {Node<vec2>} The screen position expressed as uv coordinates.
  50. */
  51. export const getScreenPositionFromClip = /*@__PURE__*/ Fn( ( [ clipPosition ] ) => {
  52. const screen = clipPosition.xy.div( clipPosition.w ).mul( 0.5 ).add( 0.5 ).toVar();
  53. return vec2( screen.x, screen.y.oneMinus() );
  54. } ).setLayout( {
  55. name: 'getScreenPositionFromClip',
  56. type: 'vec2',
  57. inputs: [
  58. { name: 'clipPosition', type: 'vec4' }
  59. ]
  60. } );
  61. /**
  62. * Computes a normal vector based on depth data. Can be used as a fallback when no normal render
  63. * target is available or if flat surface normals are required.
  64. *
  65. * @tsl
  66. * @function
  67. * @param {Node<vec2>} uv - The texture coordinate.
  68. * @param {DepthTexture} depthTexture - The depth texture.
  69. * @param {Node<mat4>} projectionMatrixInverse - The camera's inverse projection matrix.
  70. * @return {Node<vec3>} The computed normal vector.
  71. */
  72. export const getNormalFromDepth = /*@__PURE__*/ Fn( ( [ uv, depthTexture, projectionMatrixInverse ] ) => {
  73. const size = textureSize( textureLoad( depthTexture ) );
  74. const p = ivec2( uv.mul( size ) ).toVar();
  75. const c0 = textureLoad( depthTexture, p ).toVar();
  76. const l2 = textureLoad( depthTexture, p.sub( ivec2( 2, 0 ) ) ).toVar();
  77. const l1 = textureLoad( depthTexture, p.sub( ivec2( 1, 0 ) ) ).toVar();
  78. const r1 = textureLoad( depthTexture, p.add( ivec2( 1, 0 ) ) ).toVar();
  79. const r2 = textureLoad( depthTexture, p.add( ivec2( 2, 0 ) ) ).toVar();
  80. const b2 = textureLoad( depthTexture, p.add( ivec2( 0, 2 ) ) ).toVar();
  81. const b1 = textureLoad( depthTexture, p.add( ivec2( 0, 1 ) ) ).toVar();
  82. const t1 = textureLoad( depthTexture, p.sub( ivec2( 0, 1 ) ) ).toVar();
  83. const t2 = textureLoad( depthTexture, p.sub( ivec2( 0, 2 ) ) ).toVar();
  84. const dl = abs( sub( float( 2 ).mul( l1 ).sub( l2 ), c0 ) ).toVar();
  85. const dr = abs( sub( float( 2 ).mul( r1 ).sub( r2 ), c0 ) ).toVar();
  86. const db = abs( sub( float( 2 ).mul( b1 ).sub( b2 ), c0 ) ).toVar();
  87. const dt = abs( sub( float( 2 ).mul( t1 ).sub( t2 ), c0 ) ).toVar();
  88. const ce = getViewPosition( uv, c0, projectionMatrixInverse ).toVar();
  89. const dpdx = dl.lessThan( dr ).select( ce.sub( getViewPosition( uv.sub( vec2( float( 1 ).div( size.x ), 0 ) ), l1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.add( vec2( float( 1 ).div( size.x ), 0 ) ), r1, projectionMatrixInverse ) ) );
  90. const dpdy = db.lessThan( dt ).select( ce.sub( getViewPosition( uv.add( vec2( 0, float( 1 ).div( size.y ) ) ), b1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.sub( vec2( 0, float( 1 ).div( size.y ) ) ), t1, projectionMatrixInverse ) ) );
  91. return normalize( cross( dpdx, dpdy ) );
  92. } );
  93. /**
  94. * Interleaved Gradient Noise (IGN) from Jimenez 2014.
  95. *
  96. * IGN has "low discrepancy" resulting in evenly distributed samples. It's superior compared to
  97. * default white noise, blue noise or Bayer.
  98. *
  99. * References:
  100. * - {@link https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/}
  101. * - {@link https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/}
  102. *
  103. * @tsl
  104. * @function
  105. * @param {Node<vec2>} position - The input position, usually screen coordinates.
  106. * @return {Node<float>} The noise value.
  107. */
  108. export const interleavedGradientNoise = Fn( ( [ position ] ) => {
  109. return fract( float( 52.9829189 ).mul( fract( dot( position, vec2( 0.06711056, 0.00583715 ) ) ) ) );
  110. } ).setLayout( {
  111. name: 'interleavedGradientNoise',
  112. type: 'float',
  113. inputs: [
  114. { name: 'position', type: 'vec2' }
  115. ]
  116. } );
  117. /**
  118. * Vogel disk sampling for uniform circular distribution.
  119. *
  120. * This function generates sample points distributed uniformly on a disk using the golden angle,
  121. * resulting in an efficient low-discrepancy sequence for sampling. The rotation parameter (phi)
  122. * allows randomizing the pattern per-pixel when combined with IGN.
  123. *
  124. * @tsl
  125. * @function
  126. * @param {Node<int>} sampleIndex - The index of the current sample (0-based).
  127. * @param {Node<int>} samplesCount - The total number of samples.
  128. * @param {Node<float>} phi - Rotation angle in radians (typically from IGN * 2π).
  129. * @return {Node<vec2>} A 2D point on the unit disk.
  130. */
  131. export const vogelDiskSample = Fn( ( [ sampleIndex, samplesCount, phi ] ) => {
  132. const goldenAngle = float( 2.399963229728653 ); // 2π * (2 - φ) where φ is golden ratio
  133. const r = sqrt( float( sampleIndex ).add( 0.5 ).div( float( samplesCount ) ) );
  134. const theta = float( sampleIndex ).mul( goldenAngle ).add( phi );
  135. return vec2( cos( theta ), sin( theta ) ).mul( r );
  136. } ).setLayout( {
  137. name: 'vogelDiskSample',
  138. type: 'vec2',
  139. inputs: [
  140. { name: 'sampleIndex', type: 'int' },
  141. { name: 'samplesCount', type: 'int' },
  142. { name: 'phi', type: 'float' }
  143. ]
  144. } );
粤ICP备19079148号