fxaa.frag.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. export const fxaa_frag = `
  2. precision highp float;
  3. precision highp int;
  4. precision highp sampler2D;
  5. uniform sampler2D tColor;
  6. uniform vec2 uTexSizeInv;
  7. // adapted from https://github.com/kosua20/Rendu
  8. // MIT License Copyright (c) 2017 Simon Rodriguez
  9. #define QUALITY(q) ((q) < 5 ? 1.0 : ((q) > 5 ? ((q) < 10 ? 2.0 : ((q) < 11 ? 4.0 : 8.0)) : 1.5))
  10. float rgb2luma(vec3 rgb){
  11. return sqrt(dot(rgb, vec3(0.299, 0.587, 0.114)));
  12. }
  13. float sampleLuma(vec2 uv) {
  14. return rgb2luma(texture2D(tColor, uv).rgb);
  15. }
  16. float sampleLuma(vec2 uv, float uOffset, float vOffset) {
  17. uv += uTexSizeInv * vec2(uOffset, vOffset);
  18. return sampleLuma(uv);
  19. }
  20. void main(void) {
  21. vec2 coords = gl_FragCoord.xy * uTexSizeInv;
  22. vec2 inverseScreenSize = uTexSizeInv;
  23. vec4 colorCenter = texture2D(tColor, coords);
  24. // Luma at the current fragment
  25. float lumaCenter = rgb2luma(colorCenter.rgb);
  26. // Luma at the four direct neighbours of the current fragment.
  27. float lumaDown = sampleLuma(coords, 0.0, -1.0);
  28. float lumaUp = sampleLuma(coords, 0.0, 1.0);
  29. float lumaLeft = sampleLuma(coords, -1.0, 0.0);
  30. float lumaRight = sampleLuma(coords, 1.0, 0.0);
  31. // Find the maximum and minimum luma around the current fragment.
  32. float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
  33. float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
  34. // Compute the delta.
  35. float lumaRange = lumaMax - lumaMin;
  36. // If the luma variation is lower that a threshold (or if we are in a really dark area),
  37. // we are not on an edge, don't perform any AA.
  38. if (lumaRange < max(dEdgeThresholdMin, lumaMax * dEdgeThresholdMax)) {
  39. gl_FragColor = colorCenter;
  40. return;
  41. }
  42. // Query the 4 remaining corners lumas.
  43. float lumaDownLeft = sampleLuma(coords, -1.0, -1.0);
  44. float lumaUpRight = sampleLuma(coords, 1.0, 1.0);
  45. float lumaUpLeft = sampleLuma(coords, -1.0, 1.0);
  46. float lumaDownRight = sampleLuma(coords, 1.0, -1.0);
  47. // Combine the four edges lumas (using intermediary variables for future computations
  48. // with the same values).
  49. float lumaDownUp = lumaDown + lumaUp;
  50. float lumaLeftRight = lumaLeft + lumaRight;
  51. // Same for corners
  52. float lumaLeftCorners = lumaDownLeft + lumaUpLeft;
  53. float lumaDownCorners = lumaDownLeft + lumaDownRight;
  54. float lumaRightCorners = lumaDownRight + lumaUpRight;
  55. float lumaUpCorners = lumaUpRight + lumaUpLeft;
  56. // Compute an estimation of the gradient along the horizontal and vertical axis.
  57. float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) + abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 + abs(-2.0 * lumaRight + lumaRightCorners);
  58. float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) + abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 + abs(-2.0 * lumaDown + lumaDownCorners);
  59. // Is the local edge horizontal or vertical ?
  60. bool isHorizontal = (edgeHorizontal >= edgeVertical);
  61. // Choose the step size (one pixel) accordingly.
  62. float stepLength = isHorizontal ? inverseScreenSize.y : inverseScreenSize.x;
  63. // Select the two neighboring texels lumas in the opposite direction to the local edge.
  64. float luma1 = isHorizontal ? lumaDown : lumaLeft;
  65. float luma2 = isHorizontal ? lumaUp : lumaRight;
  66. // Compute gradients in this direction.
  67. float gradient1 = luma1 - lumaCenter;
  68. float gradient2 = luma2 - lumaCenter;
  69. // Which direction is the steepest ?
  70. bool is1Steepest = abs(gradient1) >= abs(gradient2);
  71. // Gradient in the corresponding direction, normalized.
  72. float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
  73. // Average luma in the correct direction.
  74. float lumaLocalAverage = 0.0;
  75. if(is1Steepest){
  76. // Switch the direction
  77. stepLength = -stepLength;
  78. lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
  79. } else {
  80. lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
  81. }
  82. // Shift UV in the correct direction by half a pixel.
  83. vec2 currentUv = coords;
  84. if(isHorizontal){
  85. currentUv.y += stepLength * 0.5;
  86. } else {
  87. currentUv.x += stepLength * 0.5;
  88. }
  89. // Compute offset (for each iteration step) in the right direction.
  90. vec2 offset = isHorizontal ? vec2(inverseScreenSize.x, 0.0) : vec2(0.0, inverseScreenSize.y);
  91. // Compute UVs to explore on each side of the edge, orthogonally.
  92. // The QUALITY allows us to step faster.
  93. vec2 uv1 = currentUv - offset * QUALITY(0);
  94. vec2 uv2 = currentUv + offset * QUALITY(0);
  95. // Read the lumas at both current extremities of the exploration segment,
  96. // and compute the delta wrt to the local average luma.
  97. float lumaEnd1 = sampleLuma(uv1);
  98. float lumaEnd2 = sampleLuma(uv2);
  99. lumaEnd1 -= lumaLocalAverage;
  100. lumaEnd2 -= lumaLocalAverage;
  101. // If the luma deltas at the current extremities is larger than the local gradient,
  102. // we have reached the side of the edge.
  103. bool reached1 = abs(lumaEnd1) >= gradientScaled;
  104. bool reached2 = abs(lumaEnd2) >= gradientScaled;
  105. bool reachedBoth = reached1 && reached2;
  106. // If the side is not reached, we continue to explore in this direction.
  107. if(!reached1){
  108. uv1 -= offset * QUALITY(1);
  109. }
  110. if(!reached2){
  111. uv2 += offset * QUALITY(1);
  112. }
  113. // If both sides have not been reached, continue to explore.
  114. if(!reachedBoth){
  115. for(int i = 2; i < dIterations; i++){
  116. // If needed, read luma in 1st direction, compute delta.
  117. if(!reached1){
  118. lumaEnd1 = sampleLuma(uv1);
  119. lumaEnd1 = lumaEnd1 - lumaLocalAverage;
  120. }
  121. // If needed, read luma in opposite direction, compute delta.
  122. if(!reached2){
  123. lumaEnd2 = sampleLuma(uv2);
  124. lumaEnd2 = lumaEnd2 - lumaLocalAverage;
  125. }
  126. // If the luma deltas at the current extremities is larger than the local gradient,
  127. // we have reached the side of the edge.
  128. reached1 = abs(lumaEnd1) >= gradientScaled;
  129. reached2 = abs(lumaEnd2) >= gradientScaled;
  130. reachedBoth = reached1 && reached2;
  131. // If the side is not reached, we continue to explore in this direction,
  132. // with a variable quality.
  133. if(!reached1){
  134. uv1 -= offset * QUALITY(i);
  135. }
  136. if(!reached2){
  137. uv2 += offset * QUALITY(i);
  138. }
  139. // If both sides have been reached, stop the exploration.
  140. if(reachedBoth){
  141. break;
  142. }
  143. }
  144. }
  145. // Compute the distances to each side edge of the edge (!).
  146. float distance1 = isHorizontal ? (coords.x - uv1.x) : (coords.y - uv1.y);
  147. float distance2 = isHorizontal ? (uv2.x - coords.x) : (uv2.y - coords.y);
  148. // In which direction is the side of the edge closer ?
  149. bool isDirection1 = distance1 < distance2;
  150. float distanceFinal = min(distance1, distance2);
  151. // Thickness of the edge.
  152. float edgeThickness = (distance1 + distance2);
  153. // Is the luma at center smaller than the local average ?
  154. bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
  155. // If the luma at center is smaller than at its neighbour,
  156. // the delta luma at each end should be positive (same variation).
  157. bool correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller;
  158. bool correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller;
  159. // Only keep the result in the direction of the closer side of the edge.
  160. bool correctVariation = isDirection1 ? correctVariation1 : correctVariation2;
  161. // UV offset: read in the direction of the closest side of the edge.
  162. float pixelOffset = - distanceFinal / edgeThickness + 0.5;
  163. // If the luma variation is incorrect, do not offset.
  164. float finalOffset = correctVariation ? pixelOffset : 0.0;
  165. // Sub-pixel shifting
  166. // Full weighted average of the luma over the 3x3 neighborhood.
  167. float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
  168. // Ratio of the delta between the global average and the center luma,
  169. // over the luma range in the 3x3 neighborhood.
  170. float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
  171. float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
  172. // Compute a sub-pixel offset based on this delta.
  173. float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * float(dSubpixelQuality);
  174. // Pick the biggest of the two offsets.
  175. finalOffset = max(finalOffset, subPixelOffsetFinal);
  176. // Compute the final UV coordinates.
  177. vec2 finalUv = coords;
  178. if(isHorizontal){
  179. finalUv.y += finalOffset * stepLength;
  180. } else {
  181. finalUv.x += finalOffset * stepLength;
  182. }
  183. // Read the color at the new UV coordinates, and use it.
  184. gl_FragColor = texture2D(tColor, finalUv);
  185. }
  186. `;