PoissonDenoiseShader.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import {
  2. Matrix4,
  3. Vector2,
  4. } from 'three';
  5. /**
  6. * References:
  7. * https://github.com/0beqz/realism-effects
  8. * https://github.com/N8python/n8ao
  9. */
  10. const PoissonDenoiseShader = {
  11. name: 'PoissonDenoiseShader',
  12. defines: {
  13. 'SAMPLES': 16,
  14. 'SAMPLE_VECTORS': generatePdSamplePointInitializer( 16, 4 ),
  15. 'NORMAL_VECTOR_TYPE': 1,
  16. 'DEPTH_VALUE_SOURCE': 0,
  17. },
  18. uniforms: {
  19. 'tDiffuse': { value: null },
  20. 'tNormal': { value: null },
  21. 'tDepth': { value: null },
  22. 'tNoise': { value: null },
  23. 'resolution': { value: new Vector2() },
  24. 'cameraProjectionMatrixInverse': { value: new Matrix4() },
  25. 'lumaPhi': { value: 5. },
  26. 'depthPhi': { value: 5. },
  27. 'normalPhi': { value: 5. },
  28. 'radius': { value: 10. },
  29. 'index': { value: 0 }
  30. },
  31. vertexShader: /* glsl */`
  32. varying vec2 vUv;
  33. void main() {
  34. vUv = uv;
  35. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  36. }`,
  37. fragmentShader: /* glsl */`
  38. varying vec2 vUv;
  39. uniform sampler2D tDiffuse;
  40. uniform sampler2D tNormal;
  41. uniform sampler2D tDepth;
  42. uniform sampler2D tNoise;
  43. uniform vec2 resolution;
  44. uniform mat4 cameraProjectionMatrixInverse;
  45. uniform float lumaPhi;
  46. uniform float depthPhi;
  47. uniform float normalPhi;
  48. uniform float radius;
  49. uniform int index;
  50. #include <common>
  51. #include <packing>
  52. #ifndef SAMPLE_LUMINANCE
  53. #define SAMPLE_LUMINANCE dot(vec3(0.2125, 0.7154, 0.0721), a)
  54. #endif
  55. #ifndef FRAGMENT_OUTPUT
  56. #define FRAGMENT_OUTPUT vec4(denoised, 1.)
  57. #endif
  58. float getLuminance(const in vec3 a) {
  59. return SAMPLE_LUMINANCE;
  60. }
  61. const vec2 poissonDisk[SAMPLES] = SAMPLE_VECTORS;
  62. vec3 getViewPosition(const in vec2 screenPosition, const in float depth) {
  63. vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0);
  64. vec4 viewSpacePosition = cameraProjectionMatrixInverse * clipSpacePosition;
  65. return viewSpacePosition.xyz / viewSpacePosition.w;
  66. }
  67. float getDepth(const vec2 uv) {
  68. #if DEPTH_VALUE_SOURCE == 1
  69. return textureLod(tDepth, uv.xy, 0.0).a;
  70. #else
  71. return textureLod(tDepth, uv.xy, 0.0).r;
  72. #endif
  73. }
  74. float fetchDepth(const ivec2 uv) {
  75. #if DEPTH_VALUE_SOURCE == 1
  76. return texelFetch(tDepth, uv.xy, 0).a;
  77. #else
  78. return texelFetch(tDepth, uv.xy, 0).r;
  79. #endif
  80. }
  81. vec3 computeNormalFromDepth(const vec2 uv) {
  82. vec2 size = vec2(textureSize(tDepth, 0));
  83. ivec2 p = ivec2(uv * size);
  84. float c0 = fetchDepth(p);
  85. float l2 = fetchDepth(p - ivec2(2, 0));
  86. float l1 = fetchDepth(p - ivec2(1, 0));
  87. float r1 = fetchDepth(p + ivec2(1, 0));
  88. float r2 = fetchDepth(p + ivec2(2, 0));
  89. float b2 = fetchDepth(p - ivec2(0, 2));
  90. float b1 = fetchDepth(p - ivec2(0, 1));
  91. float t1 = fetchDepth(p + ivec2(0, 1));
  92. float t2 = fetchDepth(p + ivec2(0, 2));
  93. float dl = abs((2.0 * l1 - l2) - c0);
  94. float dr = abs((2.0 * r1 - r2) - c0);
  95. float db = abs((2.0 * b1 - b2) - c0);
  96. float dt = abs((2.0 * t1 - t2) - c0);
  97. vec3 ce = getViewPosition(uv, c0).xyz;
  98. vec3 dpdx = (dl < dr) ? ce - getViewPosition((uv - vec2(1.0 / size.x, 0.0)), l1).xyz
  99. : -ce + getViewPosition((uv + vec2(1.0 / size.x, 0.0)), r1).xyz;
  100. vec3 dpdy = (db < dt) ? ce - getViewPosition((uv - vec2(0.0, 1.0 / size.y)), b1).xyz
  101. : -ce + getViewPosition((uv + vec2(0.0, 1.0 / size.y)), t1).xyz;
  102. return normalize(cross(dpdx, dpdy));
  103. }
  104. vec3 getViewNormal(const vec2 uv) {
  105. #if NORMAL_VECTOR_TYPE == 2
  106. return normalize(textureLod(tNormal, uv, 0.).rgb);
  107. #elif NORMAL_VECTOR_TYPE == 1
  108. return unpackRGBToNormal(textureLod(tNormal, uv, 0.).rgb);
  109. #else
  110. return computeNormalFromDepth(uv);
  111. #endif
  112. }
  113. float distToPlane(const vec3 viewPos, const vec3 neighborViewPos, const vec3 viewNormal) {
  114. return abs(dot(viewPos - neighborViewPos, viewNormal));
  115. }
  116. void main() {
  117. float depth = getDepth(vUv.xy);
  118. vec3 viewNormal = getViewNormal(vUv);
  119. if (depth == 1. || dot(viewNormal, viewNormal) == 0.) {
  120. discard;
  121. return;
  122. }
  123. vec4 texel = textureLod(tDiffuse, vUv, 0.0);
  124. vec3 denoised = texel.rgb;
  125. vec3 center = texel.rgb;
  126. vec3 viewPos = getViewPosition(vUv, depth);
  127. vec2 noiseResolution = vec2(textureSize(tNoise, 0));
  128. vec2 noiseUv = vUv * resolution / noiseResolution;
  129. vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0);
  130. //vec2 noiseVec = normalize((index % 2 == 0 ? noiseTexel.xy : noiseTexel.yz) * 2.0 - 1.0);
  131. vec2 noiseVec = vec2(sin(noiseTexel[index % 4] * 2. * PI), cos(noiseTexel[index % 4] * 2. * PI));
  132. mat2 rotationMatrix = mat2(noiseVec.x, -noiseVec.y, noiseVec.x, noiseVec.y);
  133. float totalWeight = 1.0;
  134. for (int i = 0; i < SAMPLES; i++) {
  135. vec2 offset = rotationMatrix * (poissonDisk[i] * radius / resolution);
  136. vec2 sampleUv = vUv + offset;
  137. vec4 sampleTexel = textureLod(tDiffuse, sampleUv, 0.0);
  138. float sampleDepth = getDepth(sampleUv);
  139. vec3 sampleNormal = getViewNormal(sampleUv);
  140. vec3 neighborColor = sampleTexel.rgb;
  141. vec3 viewPosSample = getViewPosition(sampleUv, sampleDepth);
  142. float normalDiff = dot(viewNormal, sampleNormal);
  143. float normalSimilarity = pow(max(normalDiff, 0.), normalPhi);
  144. float lumaDiff = abs(getLuminance(neighborColor) - getLuminance(center));
  145. float lumaSimilarity = max(1.0 - lumaDiff / lumaPhi, 0.0);
  146. float depthDiff = 1. - distToPlane(viewPos, viewPosSample, viewNormal);
  147. float depthSimilarity = max(depthDiff / depthPhi, 0.);
  148. float w = lumaSimilarity * depthSimilarity * normalSimilarity;
  149. denoised += w * neighborColor;
  150. totalWeight += w;
  151. }
  152. if (totalWeight > 0.) {
  153. denoised /= totalWeight;
  154. }
  155. gl_FragColor = FRAGMENT_OUTPUT;
  156. }`
  157. };
  158. function generatePdSamplePointInitializer( samples, rings ) {
  159. const poissonDisk = generateDenoiseSamples(
  160. samples,
  161. rings,
  162. );
  163. let glslCode = 'vec2[SAMPLES](';
  164. for ( let i = 0; i < samples; i ++ ) {
  165. const sample = poissonDisk[ i ];
  166. glslCode += `vec2(${sample.x}, ${sample.y})`;
  167. if ( i < samples - 1 ) {
  168. glslCode += ',';
  169. }
  170. }
  171. glslCode += ')';
  172. return glslCode;
  173. }
  174. function generateDenoiseSamples( numSamples, numRings ) {
  175. const angleStep = ( 2 * Math.PI * numRings ) / numSamples;
  176. const invNumSamples = 1.0 / numSamples;
  177. const radiusStep = invNumSamples;
  178. const samples = [];
  179. let radius = invNumSamples;
  180. let angle = 0;
  181. for ( let i = 0; i < numSamples; i ++ ) {
  182. const v = new Vector2( Math.cos( angle ), Math.sin( angle ) )
  183. .multiplyScalar( Math.pow( radius, 0.75 ) );
  184. samples.push( v );
  185. radius += radiusStep;
  186. angle += angleStep;
  187. }
  188. return samples;
  189. }
  190. export { generatePdSamplePointInitializer, PoissonDenoiseShader };