Просмотр исходного кода

FXAA: Improve quality. (#29524)

* fxaa update

* webGPU

* FXAANode NDC fix

* FXAANode ternary conditionals

* FXAANode uniformArray

* 10 > 6 samples, cleanup

* parity between WebGL and WebGPU

* formatting

* unflipping WebGPU

* move to object struct

* cleanup

* cleanup

* parameter container objects, naming consistency

* correct north south naming

---------
Jan Bláha 1 год назад
Родитель
Сommit
f22effae39
2 измененных файлов с 398 добавлено и 404 удалено
  1. 225 224
      examples/jsm/shaders/FXAAShader.js
  2. 173 180
      examples/jsm/tsl/display/FXAANode.js

+ 225 - 224
examples/jsm/shaders/FXAAShader.js

@@ -2,14 +2,6 @@ import {
 	Vector2
 } from 'three';
 
-/**
- * NVIDIA FXAA by Timothy Lottes
- * https://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
- * - WebGL port by @supereggbert
- * http://www.glge.org/demos/fxaa/
- * Further improved by Daniel Sturk
- */
-
 const FXAAShader = {
 
 	name: 'FXAAShader',
@@ -33,253 +25,262 @@ const FXAAShader = {
 		}`,
 
 	fragmentShader: /* glsl */`
-		precision highp float;
 
-		uniform sampler2D tDiffuse;
+		// FXAA algorithm from NVIDIA, C# implementation by Jasper Flick, GLSL port by Dave Hoskins
+		// http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
+		// https://catlikecoding.com/unity/tutorials/advanced-rendering/fxaa/
 
+		uniform sampler2D tDiffuse;
 		uniform vec2 resolution;
-
 		varying vec2 vUv;
 
-		// FXAA 3.11 implementation by NVIDIA, ported to WebGL by Agost Biro (biro@archilogic.com)
-
-		//----------------------------------------------------------------------------------
-		// File:        es3-kepler\FXAA\assets\shaders/FXAA_DefaultES.frag
-		// SDK Version: v3.00
-		// Email:       gameworks@nvidia.com
-		// Site:        http://developer.nvidia.com/
-		//
-		// Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
-		//
-		// Redistribution and use in source and binary forms, with or without
-		// modification, are permitted provided that the following conditions
-		// are met:
-		//  * Redistributions of source code must retain the above copyright
-		//    notice, this list of conditions and the following disclaimer.
-		//  * Redistributions in binary form must reproduce the above copyright
-		//    notice, this list of conditions and the following disclaimer in the
-		//    documentation and/or other materials provided with the distribution.
-		//  * Neither the name of NVIDIA CORPORATION nor the names of its
-		//    contributors may be used to endorse or promote products derived
-		//    from this software without specific prior written permission.
-		//
-		// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
-		// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-		// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-		// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-		// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-		// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-		// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-		// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
-		// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-		// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-		// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-		//
-		//----------------------------------------------------------------------------------
-
-		#ifndef FXAA_DISCARD
-			//
-			// Only valid for PC OpenGL currently.
-			// Probably will not work when FXAA_GREEN_AS_LUMA = 1.
-			//
-			// 1 = Use discard on pixels which don't need AA.
-			//     For APIs which enable concurrent TEX+ROP from same surface.
-			// 0 = Return unchanged color on pixels which don't need AA.
-			//
-			#define FXAA_DISCARD 0
-		#endif
-
-		/*--------------------------------------------------------------------------*/
-		#define FxaaTexTop(t, p) texture2D(t, p, -100.0)
-		#define FxaaTexOff(t, p, o, r) texture2D(t, p + (o * r), -100.0)
-		/*--------------------------------------------------------------------------*/
-
-		#define NUM_SAMPLES 5
-
-		// assumes colors have premultipliedAlpha, so that the calculated color contrast is scaled by alpha
-		float contrast( vec4 a, vec4 b ) {
-			vec4 diff = abs( a - b );
-			return max( max( max( diff.r, diff.g ), diff.b ), diff.a );
+		#define EDGE_STEP_COUNT 6
+		#define EDGE_GUESS 8.0
+		#define EDGE_STEPS 1.0, 1.5, 2.0, 2.0, 2.0, 4.0
+		const float edgeSteps[EDGE_STEP_COUNT] = float[EDGE_STEP_COUNT]( EDGE_STEPS );
+
+		float _ContrastThreshold = 0.0312;
+		float _RelativeThreshold = 0.063;
+		float _SubpixelBlending = 1.0;
+
+		vec4 Sample( sampler2D  tex2D, vec2 uv ) {
+
+			return texture( tex2D, uv );
+
+		}
+
+		float SampleLuminance( sampler2D tex2D, vec2 uv ) {
+
+			return dot( Sample( tex2D, uv ).rgb, vec3( 0.3, 0.59, 0.11 ) );
+
+		}
+
+		float SampleLuminance( sampler2D tex2D, vec2 texSize, vec2 uv, float uOffset, float vOffset ) {
+
+			uv += texSize * vec2(uOffset, vOffset);
+			return SampleLuminance(tex2D, uv);
+
+		}
+
+		struct LuminanceData {
+
+			float m, n, e, s, w;
+			float ne, nw, se, sw;
+			float highest, lowest, contrast;
+
+		};
+
+		LuminanceData SampleLuminanceNeighborhood( sampler2D tex2D, vec2 texSize, vec2 uv ) {
+
+			LuminanceData l;
+			l.m = SampleLuminance( tex2D, uv );
+			l.n = SampleLuminance( tex2D, texSize, uv,  0.0,  1.0 );
+			l.e = SampleLuminance( tex2D, texSize, uv,  1.0,  0.0 );
+			l.s = SampleLuminance( tex2D, texSize, uv,  0.0, -1.0 );
+			l.w = SampleLuminance( tex2D, texSize, uv, -1.0,  0.0 );
+
+			l.ne = SampleLuminance( tex2D, texSize, uv,  1.0,  1.0 );
+			l.nw = SampleLuminance( tex2D, texSize, uv, -1.0,  1.0 );
+			l.se = SampleLuminance( tex2D, texSize, uv,  1.0, -1.0 );
+			l.sw = SampleLuminance( tex2D, texSize, uv, -1.0, -1.0 );
+
+			l.highest = max( max( max( max( l.n, l.e ), l.s ), l.w ), l.m );
+			l.lowest = min( min( min( min( l.n, l.e ), l.s ), l.w ), l.m );
+			l.contrast = l.highest - l.lowest;
+			return l;
+
+		}
+
+		bool ShouldSkipPixel( LuminanceData l ) {
+
+			float threshold = max( _ContrastThreshold, _RelativeThreshold * l.highest );
+			return l.contrast < threshold;
+
+		}
+
+		float DeterminePixelBlendFactor( LuminanceData l ) {
+
+			float f = 2.0 * ( l.n + l.e + l.s + l.w );
+			f += l.ne + l.nw + l.se + l.sw;
+			f *= 1.0 / 12.0;
+			f = abs( f - l.m );
+			f = clamp( f / l.contrast, 0.0, 1.0 );
+
+			float blendFactor = smoothstep( 0.0, 1.0, f );
+			return blendFactor * blendFactor * _SubpixelBlending;
+
 		}
 
-		/*============================================================================
-
-									FXAA3 QUALITY - PC
-
-		============================================================================*/
-
-		/*--------------------------------------------------------------------------*/
-		vec4 FxaaPixelShader(
-			vec2 posM,
-			sampler2D tex,
-			vec2 fxaaQualityRcpFrame,
-			float fxaaQualityEdgeThreshold,
-			float fxaaQualityinvEdgeThreshold
-		) {
-			vec4 rgbaM = FxaaTexTop(tex, posM);
-			vec4 rgbaS = FxaaTexOff(tex, posM, vec2( 0.0, 1.0), fxaaQualityRcpFrame.xy);
-			vec4 rgbaE = FxaaTexOff(tex, posM, vec2( 1.0, 0.0), fxaaQualityRcpFrame.xy);
-			vec4 rgbaN = FxaaTexOff(tex, posM, vec2( 0.0,-1.0), fxaaQualityRcpFrame.xy);
-			vec4 rgbaW = FxaaTexOff(tex, posM, vec2(-1.0, 0.0), fxaaQualityRcpFrame.xy);
-			// . S .
-			// W M E
-			// . N .
-
-			bool earlyExit = max( max( max(
-					contrast( rgbaM, rgbaN ),
-					contrast( rgbaM, rgbaS ) ),
-					contrast( rgbaM, rgbaE ) ),
-					contrast( rgbaM, rgbaW ) )
-					< fxaaQualityEdgeThreshold;
-			// . 0 .
-			// 0 0 0
-			// . 0 .
-
-			#if (FXAA_DISCARD == 1)
-				if(earlyExit) FxaaDiscard;
-			#else
-				if(earlyExit) return rgbaM;
-			#endif
-
-			float contrastN = contrast( rgbaM, rgbaN );
-			float contrastS = contrast( rgbaM, rgbaS );
-			float contrastE = contrast( rgbaM, rgbaE );
-			float contrastW = contrast( rgbaM, rgbaW );
-
-			float relativeVContrast = ( contrastN + contrastS ) - ( contrastE + contrastW );
-			relativeVContrast *= fxaaQualityinvEdgeThreshold;
-
-			bool horzSpan = relativeVContrast > 0.;
-			// . 1 .
-			// 0 0 0
-			// . 1 .
-
-			// 45 deg edge detection and corners of objects, aka V/H contrast is too similar
-			if( abs( relativeVContrast ) < .3 ) {
-				// locate the edge
-				vec2 dirToEdge;
-				dirToEdge.x = contrastE > contrastW ? 1. : -1.;
-				dirToEdge.y = contrastS > contrastN ? 1. : -1.;
-				// . 2 .      . 1 .
-				// 1 0 2  ~=  0 0 1
-				// . 1 .      . 0 .
-
-				// tap 2 pixels and see which ones are "outside" the edge, to
-				// determine if the edge is vertical or horizontal
-
-				vec4 rgbaAlongH = FxaaTexOff(tex, posM, vec2( dirToEdge.x, -dirToEdge.y ), fxaaQualityRcpFrame.xy);
-				float matchAlongH = contrast( rgbaM, rgbaAlongH );
-				// . 1 .
-				// 0 0 1
-				// . 0 H
-
-				vec4 rgbaAlongV = FxaaTexOff(tex, posM, vec2( -dirToEdge.x, dirToEdge.y ), fxaaQualityRcpFrame.xy);
-				float matchAlongV = contrast( rgbaM, rgbaAlongV );
-				// V 1 .
-				// 0 0 1
-				// . 0 .
-
-				relativeVContrast = matchAlongV - matchAlongH;
-				relativeVContrast *= fxaaQualityinvEdgeThreshold;
-
-				if( abs( relativeVContrast ) < .3 ) { // 45 deg edge
-					// 1 1 .
-					// 0 0 1
-					// . 0 1
-
-					// do a simple blur
-					return mix(
-						rgbaM,
-						(rgbaN + rgbaS + rgbaE + rgbaW) * .25,
-						.4
-					);
-				}
-
-				horzSpan = relativeVContrast > 0.;
+		struct EdgeData {
+
+			bool isHorizontal;
+			float pixelStep;
+			float oppositeLuminance, gradient;
+
+		};
+
+		EdgeData DetermineEdge( vec2 texSize, LuminanceData l ) {
+
+			EdgeData e;
+			float horizontal =
+				abs( l.n + l.s - 2.0 * l.m ) * 2.0 +
+				abs( l.ne + l.se - 2.0 * l.e ) +
+				abs( l.nw + l.sw - 2.0 * l.w );
+			float vertical =
+				abs( l.e + l.w - 2.0 * l.m ) * 2.0 +
+				abs( l.ne + l.nw - 2.0 * l.n ) +
+				abs( l.se + l.sw - 2.0 * l.s );
+			e.isHorizontal = horizontal >= vertical;
+
+			float pLuminance = e.isHorizontal ? l.n : l.e;
+			float nLuminance = e.isHorizontal ? l.s : l.w;
+			float pGradient = abs( pLuminance - l.m );
+			float nGradient = abs( nLuminance - l.m );
+
+			e.pixelStep = e.isHorizontal ? texSize.y : texSize.x;
+			
+			if (pGradient < nGradient) {
+
+				e.pixelStep = -e.pixelStep;
+				e.oppositeLuminance = nLuminance;
+				e.gradient = nGradient;
+
+			} else {
+
+				e.oppositeLuminance = pLuminance;
+				e.gradient = pGradient;
+
 			}
 
-			if(!horzSpan) rgbaN = rgbaW;
-			if(!horzSpan) rgbaS = rgbaE;
-			// . 0 .      1
-			// 1 0 1  ->  0
-			// . 0 .      1
+			return e;
 
-			bool pairN = contrast( rgbaM, rgbaN ) > contrast( rgbaM, rgbaS );
-			if(!pairN) rgbaN = rgbaS;
+		}
+
+		float DetermineEdgeBlendFactor( sampler2D  tex2D, vec2 texSize, LuminanceData l, EdgeData e, vec2 uv ) {
+
+			vec2 uvEdge = uv;
+			vec2 edgeStep;
+			if (e.isHorizontal) {
+
+				uvEdge.y += e.pixelStep * 0.5;
+				edgeStep = vec2( texSize.x, 0.0 );
+
+			} else {
+
+				uvEdge.x += e.pixelStep * 0.5;
+				edgeStep = vec2( 0.0, texSize.y );
+
+			}
+
+			float edgeLuminance = ( l.m + e.oppositeLuminance ) * 0.5;
+			float gradientThreshold = e.gradient * 0.25;
+
+			vec2 puv = uvEdge + edgeStep * edgeSteps[0];
+			float pLuminanceDelta = SampleLuminance( tex2D, puv ) - edgeLuminance;
+			bool pAtEnd = abs( pLuminanceDelta ) >= gradientThreshold;
+
+			for ( int i = 1; i < EDGE_STEP_COUNT && !pAtEnd; i++ ) {
 
-			vec2 offNP;
-			offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x;
-			offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y;
+				puv += edgeStep * edgeSteps[i];
+				pLuminanceDelta = SampleLuminance( tex2D, puv ) - edgeLuminance;
+				pAtEnd = abs( pLuminanceDelta ) >= gradientThreshold;
 
-			bool doneN = false;
-			bool doneP = false;
+			}
 
-			float nDist = 0.;
-			float pDist = 0.;
+			if ( !pAtEnd ) {
 
-			vec2 posN = posM;
-			vec2 posP = posM;
+				puv += edgeStep * EDGE_GUESS;
 
-			int iterationsUsedN = 0;
-			int iterationsUsedP = 0;
-			for( int i = 0; i < NUM_SAMPLES; i++ ) {
+			}
 
-				float increment = float(i + 1);
+			vec2 nuv = uvEdge - edgeStep * edgeSteps[0];
+			float nLuminanceDelta = SampleLuminance( tex2D, nuv ) - edgeLuminance;
+			bool nAtEnd = abs( nLuminanceDelta ) >= gradientThreshold;
 
-				if(!doneN) {
-					nDist += increment;
-					posN = posM + offNP * nDist;
-					vec4 rgbaEndN = FxaaTexTop(tex, posN.xy);
-					doneN = contrast( rgbaEndN, rgbaM ) > contrast( rgbaEndN, rgbaN );
-					iterationsUsedN = i;
-				}
+			for ( int i = 1; i < EDGE_STEP_COUNT && !nAtEnd; i++ ) {
 
-				if(!doneP) {
-					pDist += increment;
-					posP = posM - offNP * pDist;
-					vec4 rgbaEndP = FxaaTexTop(tex, posP.xy);
-					doneP = contrast( rgbaEndP, rgbaM ) > contrast( rgbaEndP, rgbaN );
-					iterationsUsedP = i;
-				}
+				nuv -= edgeStep * edgeSteps[i];
+				nLuminanceDelta = SampleLuminance( tex2D, nuv ) - edgeLuminance;
+				nAtEnd = abs( nLuminanceDelta ) >= gradientThreshold;
 
-				if(doneN || doneP) break;
 			}
 
+			if ( !nAtEnd ) {
+
+				nuv -= edgeStep * EDGE_GUESS;
 
-			if ( !doneP && !doneN ) return rgbaM; // failed to find end of edge
+			}
+
+			float pDistance, nDistance;
+			if ( e.isHorizontal ) {
 
-			float dist = min(
-				doneN ? float( iterationsUsedN ) / float( NUM_SAMPLES - 1 ) : 1.,
-				doneP ? float( iterationsUsedP ) / float( NUM_SAMPLES - 1 ) : 1.
-			);
+				pDistance = puv.x - uv.x;
+				nDistance = uv.x - nuv.x;
 
-			// hacky way of reduces blurriness of mostly diagonal edges
-			// but reduces AA quality
-			dist = pow(dist, .5);
+			} else {
+				
+				pDistance = puv.y - uv.y;
+				nDistance = uv.y - nuv.y;
 
-			dist = 1. - dist;
+			}
+
+			float shortestDistance;
+			bool deltaSign;
+			if ( pDistance <= nDistance ) {
+
+				shortestDistance = pDistance;
+				deltaSign = pLuminanceDelta >= 0.0;
+
+			} else {
+
+				shortestDistance = nDistance;
+				deltaSign = nLuminanceDelta >= 0.0;
+
+			}
+
+			if ( deltaSign == ( l.m - edgeLuminance >= 0.0 ) ) {
+
+				return 0.0;
+
+			}
+
+			return 0.5 - shortestDistance / ( pDistance + nDistance );
 
-			return mix(
-				rgbaM,
-				rgbaN,
-				dist * .5
-			);
 		}
 
-		void main() {
-			const float edgeDetectionQuality = .2;
-			const float invEdgeDetectionQuality = 1. / edgeDetectionQuality;
+		vec4 ApplyFXAA( sampler2D  tex2D, vec2 texSize, vec2 uv ) {
+
+			LuminanceData luminance = SampleLuminanceNeighborhood( tex2D, texSize, uv );
+			if ( ShouldSkipPixel( luminance ) ) {
+
+				return Sample( tex2D, uv );
+
+			}
 
-			gl_FragColor = FxaaPixelShader(
-				vUv,
-				tDiffuse,
-				resolution,
-				edgeDetectionQuality, // [0,1] contrast needed, otherwise early discard
-				invEdgeDetectionQuality
-			);
+			float pixelBlend = DeterminePixelBlendFactor( luminance );
+			EdgeData edge = DetermineEdge( texSize, luminance );
+			float edgeBlend = DetermineEdgeBlendFactor( tex2D, texSize, luminance, edge, uv );
+			float finalBlend = max( pixelBlend, edgeBlend );
+
+			if (edge.isHorizontal) {
+
+				uv.y += edge.pixelStep * finalBlend;
+
+			} else {
+
+				uv.x += edge.pixelStep * finalBlend;
+
+			}
+
+			return Sample( tex2D, uv );
 
 		}
-	`
+
+		void main() {
+
+			gl_FragColor = ApplyFXAA( tDiffuse, resolution.xy, vUv );
+			
+		}`
 
 };
 

+ 173 - 180
examples/jsm/tsl/display/FXAANode.js

@@ -1,5 +1,5 @@
 import { Vector2 } from 'three';
-import { TempNode, nodeObject, Fn, float, NodeUpdateType, uv, uniform, convertToTexture, vec2, vec4, If, Loop, int, max, min, pow, mix, Break, abs, sub } from 'three/tsl';
+import { TempNode, nodeObject, Fn, uniformArray, select, float, NodeUpdateType, uv, dot, clamp, uniform, convertToTexture, smoothstep, bool, vec2, vec3, If, Loop, max, min, Break, abs } from 'three/tsl';
 
 class FXAANode extends TempNode {
 
@@ -34,279 +34,272 @@ class FXAANode extends TempNode {
 		const textureNode = this.textureNode.bias( - 100 );
 		const uvNode = textureNode.uvNode || uv();
 
-		// FXAA 3.11 implementation by NVIDIA, ported to WebGL by Agost Biro (biro@archilogic.com)
-
-		//----------------------------------------------------------------------------------
-		// File:        es3-kepler\FXAA\assets\shaders/FXAA_DefaultES.frag
-		// SDK Version: v3.00
-		// Email:       gameworks@nvidia.com
-		// Site:        http://developer.nvidia.com/
-		//
-		// Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
-		//
-		// Redistribution and use in source and binary forms, with or without
-		// modification, are permitted provided that the following conditions
-		// are met:
-		//  * Redistributions of source code must retain the above copyright
-		//    notice, this list of conditions and the following disclaimer.
-		//  * Redistributions in binary form must reproduce the above copyright
-		//    notice, this list of conditions and the following disclaimer in the
-		//    documentation and/or other materials provided with the distribution.
-		//  * Neither the name of NVIDIA CORPORATION nor the names of its
-		//    contributors may be used to endorse or promote products derived
-		//    from this software without specific prior written permission.
-		//
-		// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
-		// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-		// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-		// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-		// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-		// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-		// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-		// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
-		// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-		// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-		// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-		//
-		//----------------------------------------------------------------------------------
-
-		const FxaaTexTop = ( p ) => textureNode.uv( p );
-		const FxaaTexOff = ( p, o, r ) => textureNode.uv( p.add( o.mul( r ) ) );
-
-		const NUM_SAMPLES = int( 5 );
-
-		const contrast = Fn( ( [ a_immutable, b_immutable ] ) => {
-
-			// assumes colors have premultipliedAlpha, so that the calculated color contrast is scaled by alpha
-
-			const b = vec4( b_immutable ).toVar();
-			const a = vec4( a_immutable ).toVar();
-			const diff = vec4( abs( a.sub( b ) ) ).toVar();
-
-			return max( max( max( diff.r, diff.g ), diff.b ), diff.a );
+		const EDGE_STEP_COUNT = float( 6 );
+		const EDGE_GUESS = float( 8.0 );
+		const EDGE_STEPS = uniformArray( [ 1.0, 1.5, 2.0, 2.0, 2.0, 4.0 ] );
+
+		const _ContrastThreshold = float( 0.0312 );
+		const _RelativeThreshold = float( 0.063 );
+		const _SubpixelBlending = float( 1.0 );
+
+		const Sample = Fn( ( [ uv ] ) => {
+
+			return textureNode.uv( uv );
 
 		} );
 
-		// FXAA3 QUALITY - PC
+		const SampleLuminance = Fn( ( [ uv ] ) => {
+
+			return dot( Sample( uv ).rgb, vec3( 0.3, 0.59, 0.11 ) );
+
+		} );
 
-		const FxaaPixelShader = Fn( ( [ uv, fxaaQualityRcpFrame, fxaaQualityEdgeThreshold, fxaaQualityinvEdgeThreshold ] ) => {
+		const SampleLuminanceOffset = Fn( ( [ texSize, uv, uOffset, vOffset ] ) => {
 
-			const rgbaM = FxaaTexTop( uv ).toVar();
-			const rgbaS = FxaaTexOff( uv, vec2( 0.0, - 1.0 ), fxaaQualityRcpFrame.xy ).toVar();
-			const rgbaE = FxaaTexOff( uv, vec2( 1.0, 0.0 ), fxaaQualityRcpFrame.xy ).toVar();
-			const rgbaN = FxaaTexOff( uv, vec2( 0.0, 1.0 ), fxaaQualityRcpFrame.xy ).toVar();
-			const rgbaW = FxaaTexOff( uv, vec2( - 1.0, 0.0 ), fxaaQualityRcpFrame.xy ).toVar();
-			// . S .
-			// W M E
-			// . N .
+			const shiftedUv = uv.add( texSize.mul( vec2( uOffset, vOffset ) ) );
+			return SampleLuminance( shiftedUv );
 
-			const contrastN = contrast( rgbaM, rgbaN ).toVar();
-			const contrastS = contrast( rgbaM, rgbaS ).toVar();
-			const contrastE = contrast( rgbaM, rgbaE ).toVar();
-			const contrastW = contrast( rgbaM, rgbaW ).toVar();
+		} );
 
-			const maxValue = max( contrastN, max( contrastS, max( contrastE, contrastW ) ) ).toVar();
+		const ShouldSkipPixel = ( l ) => {
 
-			// . 0 .
-			// 0 0 0
-			// . 0 .
+			const threshold = max( _ContrastThreshold, _RelativeThreshold.mul( l.highest ) );
+			return l.contrast.lessThan( threshold );
 
-			If( maxValue.lessThan( fxaaQualityEdgeThreshold ), () => {
+		};
 
-				return rgbaM; // assuming define FXAA_DISCARD is always 0
+		const SampleLuminanceNeighborhood = ( texSize, uv ) => {
 
-			} );
+			const m = SampleLuminance( uv );
 
-			//
+			const n = SampleLuminanceOffset( texSize, uv, 0.0, - 1.0 );
+			const e = SampleLuminanceOffset( texSize, uv, 1.0, 0.0 );
+			const s = SampleLuminanceOffset( texSize, uv, 0.0, 1.0 );
+			const w = SampleLuminanceOffset( texSize, uv, - 1.0, 0.0 );
 
-			const relativeVContrast = sub( contrastN.add( contrastS ), ( contrastE.add( contrastW ) ) ).toVar();
-			relativeVContrast.mulAssign( fxaaQualityinvEdgeThreshold );
+			const ne = SampleLuminanceOffset( texSize, uv, 1.0, - 1.0 );
+			const nw = SampleLuminanceOffset( texSize, uv, - 1.0, - 1.0 );
+			const se = SampleLuminanceOffset( texSize, uv, 1.0, 1.0 );
+			const sw = SampleLuminanceOffset( texSize, uv, - 1.0, 1.0 );
 
-			// 45 deg edge detection and corners of objects, aka V/H contrast is too similar
+			const highest = max( max( max( max( s, e ), n ), w ), m );
+			const lowest = min( min( min( min( s, e ), n ), w ), m );
+			const contrast = highest.sub( lowest );
 
-			If( abs( relativeVContrast ).lessThan( 0.3 ), () => {
+			return { m, n, e, s, w, ne, nw, se, sw, highest, lowest, contrast };
 
-				// locate the edge
+		};
 
-				const x = contrastE.greaterThan( contrastW ).select( 1, - 1 ).toVar();
-				const y = contrastS.greaterThan( contrastN ).select( 1, - 1 ).toVar();
+		const DeterminePixelBlendFactor = ( l ) => {
 
-				const dirToEdge = vec2( x, y ).toVar();
-				// . 2 .      . 1 .
-				// 1 0 2  ~=  0 0 1
-				// . 1 .      . 0 .
+			let f = float( 2.0 ).mul( l.s.add( l.e ).add( l.n ).add( l.w ) );
+			f = f.add( l.se.add( l.sw ).add( l.ne ).add( l.nw ) );
+			f = f.mul( 1.0 / 12.0 );
+			f = abs( f.sub( l.m ) );
+			f = clamp( f.div( max( l.contrast, 0 ) ), 0.0, 1.0 );
 
-				// tap 2 pixels and see which ones are "outside" the edge, to
-				// determine if the edge is vertical or horizontal
+			const blendFactor = smoothstep( 0.0, 1.0, f );
+			return blendFactor.mul( blendFactor ).mul( _SubpixelBlending );
 
-				const rgbaAlongH = FxaaTexOff( uv, vec2( dirToEdge.x, dirToEdge.y ), fxaaQualityRcpFrame.xy );
-				const matchAlongH = contrast( rgbaM, rgbaAlongH ).toVar();
-				// . 1 .
-				// 0 0 1
-				// . 0 H
+		};
 
-				const rgbaAlongV = FxaaTexOff( uv, vec2( dirToEdge.x.negate(), dirToEdge.y.negate() ), fxaaQualityRcpFrame.xy );
-				const matchAlongV = contrast( rgbaM, rgbaAlongV ).toVar();
-				// V 1 .
-				// 0 0 1
-				// . 0 .
+		const DetermineEdge = ( texSize, l ) => {
 
-				relativeVContrast.assign( matchAlongV.sub( matchAlongH ) );
-				relativeVContrast.mulAssign( fxaaQualityinvEdgeThreshold );
+			const horizontal =
+				abs( l.s.add( l.n ).sub( l.m.mul( 2.0 ) ) ).mul( 2.0 ).add(
+					abs( l.se.add( l.ne ).sub( l.e.mul( 2.0 ) ) ).add(
+						abs( l.sw.add( l.nw ).sub( l.w.mul( 2.0 ) ) )
+					)
+				);
 
-				If( abs( relativeVContrast ).lessThan( 0.3 ), () => { // 45 deg edge
+			const vertical =
+				abs( l.e.add( l.w ).sub( l.m.mul( 2.0 ) ) ).mul( 2.0 ).add(
+					abs( l.se.add( l.sw ).sub( l.s.mul( 2.0 ) ) ).add(
+						abs( l.ne.add( l.nw ).sub( l.n.mul( 2.0 ) ) )
+					)
+				);
 
-					// 1 1 .
-					// 0 0 1
-					// . 0 1
+			const isHorizontal = horizontal.greaterThanEqual( vertical );
 
-					// do a simple blur
-					const sum = rgbaN.add( rgbaS ).add( rgbaE ).add( rgbaW );
-					return mix( rgbaM, sum.mul( 0.25 ), 0.4 );
+			const pLuminance = select( isHorizontal, l.s, l.e );
+			const nLuminance = select( isHorizontal, l.n, l.w );
+			const pGradient = abs( pLuminance.sub( l.m ) );
+			const nGradient = abs( nLuminance.sub( l.m ) );
 
-				} );
+			const pixelStep = select( isHorizontal, texSize.y, texSize.x ).toVar();
+			const oppositeLuminance = float().toVar();
+			const gradient = float().toVar();
 
-			} );
+			If( pGradient.lessThan( nGradient ), () => {
 
-			const offNP = vec2().toVar();
+				pixelStep.assign( pixelStep.negate() );
+				oppositeLuminance.assign( nLuminance );
+				gradient.assign( nGradient );
 
-			If( relativeVContrast.lessThanEqual( 0 ), () => {
+			} ).Else( () => {
 
-				rgbaN.assign( rgbaW );
-				rgbaS.assign( rgbaE );
+				oppositeLuminance.assign( pLuminance );
+				gradient.assign( pGradient );
 
-				// . 0 .      1
-				// 1 0 1  ->  0
-				// . 0 .      1
+			} );
 
-				offNP.x.assign( 0 );
-				offNP.y.assign( fxaaQualityRcpFrame.y );
+			return { isHorizontal, pixelStep, oppositeLuminance, gradient };
 
-			 } ).Else( () => {
+		};
 
-				offNP.x.assign( fxaaQualityRcpFrame.x );
-				offNP.y.assign( 0 );
+		const DetermineEdgeBlendFactor = ( texSize, l, e, uv ) => {
 
-			 } );
+			const uvEdge = uv.toVar();
+			const edgeStep = vec2().toVar();
+			If( e.isHorizontal, () => {
 
-			const mn = contrast( rgbaM, rgbaN ).toVar();
-			const ms = contrast( rgbaM, rgbaS ).toVar();
+				uvEdge.y.addAssign( e.pixelStep.mul( 0.5 ) );
+				edgeStep.assign( vec2( texSize.x, 0.0 ) );
 
-			If( mn.lessThanEqual( ms ), () => {
+			} ).Else( () => {
 
-				rgbaN.assign( rgbaS );
+				uvEdge.x.addAssign( e.pixelStep.mul( 0.5 ) );
+				edgeStep.assign( vec2( 0.0, texSize.y ) );
 
 			} );
 
-			const doneN = int( 0 ).toVar();
-			const doneP = int( 0 ).toVar();
+			const edgeLuminance = l.m.add( e.oppositeLuminance ).mul( 0.5 );
+			const gradientThreshold = e.gradient.mul( 0.25 );
 
-			const nDist = float( 0 ).toVar();
-			const pDist = float( 0 ).toVar();
+			const puv = uvEdge.add( edgeStep.mul( EDGE_STEPS.element( 0 ) ) ).toVar();
+			const pLuminanceDelta = SampleLuminance( puv ).sub( edgeLuminance ).toVar();
+			const pAtEnd = abs( pLuminanceDelta ).greaterThanEqual( gradientThreshold ).toVar();
 
-			const posN = vec2( uv ).toVar();
-			const posP = vec2( uv ).toVar();
+			Loop( { start: 1, end: EDGE_STEP_COUNT }, ( { i } ) => {
 
-			const iterationsUsedN = int( 0 ).toVar();
-			const iterationsUsedP = int( 0 ).toVar();
+				If( pAtEnd, () => {
 
-			Loop( NUM_SAMPLES, ( { i } ) => {
+					Break();
 
-				const increment = i.add( 1 ).toVar();
+				} );
 
-				If( doneN.equal( 0 ), () => {
+				puv.addAssign( edgeStep.mul( EDGE_STEPS.element( i ) ) );
+				pLuminanceDelta.assign( SampleLuminance( puv ).sub( edgeLuminance ) );
+				pAtEnd.assign( abs( pLuminanceDelta ).greaterThanEqual( gradientThreshold ) );
 
-					nDist.addAssign( increment );
-					posN.assign( uv.add( offNP.mul( nDist ) ) );
-					const rgbaEndN = FxaaTexTop( posN.xy );
+			} );
 
-					const nm = contrast( rgbaEndN, rgbaM ).toVar();
-					const nn = contrast( rgbaEndN, rgbaN ).toVar();
+			If( pAtEnd.not(), () => {
 
-					If( nm.greaterThan( nn ), () => {
+				puv.addAssign( edgeStep.mul( EDGE_GUESS ) );
 
-						doneN.assign( 1 );
+			} );
 
-					} );
+			const nuv = uvEdge.sub( edgeStep.mul( EDGE_STEPS.element( 0 ) ) ).toVar();
+			const nLuminanceDelta = SampleLuminance( nuv ).sub( edgeLuminance ).toVar();
+			const nAtEnd = abs( nLuminanceDelta ).greaterThanEqual( gradientThreshold ).toVar();
 
-					iterationsUsedN.assign( i );
+			Loop( { start: 1, end: EDGE_STEP_COUNT }, ( { i } ) => {
 
-				} );
+				If( nAtEnd, () => {
 
-				If( doneP.equal( 0 ), () => {
+					Break();
 
-					pDist.addAssign( increment );
-					posP.assign( uv.sub( offNP.mul( pDist ) ) );
-					const rgbaEndP = FxaaTexTop( posP.xy );
+				} );
 
-					const pm = contrast( rgbaEndP, rgbaM ).toVar();
-					const pn = contrast( rgbaEndP, rgbaN ).toVar();
+				nuv.subAssign( edgeStep.mul( EDGE_STEPS.element( i ) ) );
+				nLuminanceDelta.assign( SampleLuminance( nuv ).sub( edgeLuminance ) );
+				nAtEnd.assign( abs( nLuminanceDelta ).greaterThanEqual( gradientThreshold ) );
 
-					If( pm.greaterThan( pn ), () => {
+			} );
 
-						doneP.assign( 1 );
+			If( nAtEnd.not(), () => {
 
-					} );
+				nuv.subAssign( edgeStep.mul( EDGE_GUESS ) );
 
-					iterationsUsedP.assign( i );
+			} );
 
-				} );
+			const pDistance = float().toVar();
+			const nDistance = float().toVar();
 
-				If( doneN.equal( 1 ).or( doneP.equal( 1 ) ), () => {
+			If( e.isHorizontal, () => {
 
-					Break();
+				pDistance.assign( puv.x.sub( uv.x ) );
+				nDistance.assign( uv.x.sub( nuv.x ) );
 
-				} );
+			} ).Else( () => {
+
+				pDistance.assign( puv.y.sub( uv.y ) );
+				nDistance.assign( uv.y.sub( nuv.y ) );
 
 			} );
 
-			If( doneN.equal( 0 ).and( doneP.equal( 0 ) ), () => {
+			const shortestDistance = float().toVar();
+			const deltaSign = bool().toVar();
+
+			If( pDistance.lessThanEqual( nDistance ), () => {
 
-				return rgbaM; // failed to find end of edge
+				shortestDistance.assign( pDistance );
+				deltaSign.assign( pLuminanceDelta.greaterThanEqual( 0.0 ) );
+
+			} ).Else( () => {
+
+				shortestDistance.assign( nDistance );
+				deltaSign.assign( nLuminanceDelta.greaterThanEqual( 0.0 ) );
 
 			} );
 
-			const distN = float( 1 ).toVar();
-			const distP = float( 1 ).toVar();
+			const blendFactor = float().toVar();
+
+			If( deltaSign.equal( l.m.sub( edgeLuminance ).greaterThanEqual( 0.0 ) ), () => {
 
-			If( doneN.equal( 1 ), () => {
+				blendFactor.assign( 0.0 );
 
-				distN.assign( float( iterationsUsedN ).div( float( NUM_SAMPLES.sub( 1 ) ) ) );
+			} ).Else( () => {
+
+				blendFactor.assign( float( 0.5 ).sub( shortestDistance.div( pDistance.add( nDistance ) ) ) );
 
 			} );
 
-			If( doneP.equal( 1 ), () => {
+			return blendFactor;
+
+		};
 
-				distP.assign( float( iterationsUsedP ).div( float( NUM_SAMPLES.sub( 1 ) ) ) );
+		const ApplyFXAA = Fn( ( [ uv, texSize ] ) => {
+
+			const luminance = SampleLuminanceNeighborhood( texSize, uv );
+			If( ShouldSkipPixel( luminance ), () => {
+
+				return Sample( uv );
 
 			} );
 
-			const dist = min( distN, distP );
+			const pixelBlend = DeterminePixelBlendFactor( luminance );
+			const edge = DetermineEdge( texSize, luminance );
+			const edgeBlend = DetermineEdgeBlendFactor( texSize, luminance, edge, uv );
+
+			const finalBlend = max( pixelBlend, edgeBlend );
+			const finalUv = uv.toVar();
+
+			If( edge.isHorizontal, () => {
+
+				finalUv.y.addAssign( edge.pixelStep.mul( finalBlend ) );
 
-			// hacky way of reduces blurriness of mostly diagonal edges
-			// but reduces AA quality
-			dist.assign( pow( dist, 0.5 ) );
-			dist.assign( float( 1 ).sub( dist ) );
+			} ).Else( () => {
 
-			return mix( rgbaM, rgbaN, dist.mul( 0.5 ) );
+				finalUv.x.addAssign( edge.pixelStep.mul( finalBlend ) );
+
+			} );
+
+			return Sample( finalUv );
 
 		} ).setLayout( {
 			name: 'FxaaPixelShader',
 			type: 'vec4',
 			inputs: [
 				{ name: 'uv', type: 'vec2' },
-				{ name: 'fxaaQualityRcpFrame', type: 'vec2' },
-				{ name: 'fxaaQualityEdgeThreshold', type: 'float' },
-				{ name: 'fxaaQualityinvEdgeThreshold', type: 'float' },
+				{ name: 'texSize', type: 'vec2' },
 			]
 		} );
 
 		const fxaa = Fn( () => {
 
-			const edgeDetectionQuality = float( 0.2 );
-			const invEdgeDetectionQuality = float( 1 ).div( edgeDetectionQuality );
-
-			return FxaaPixelShader( uvNode, this._invSize, edgeDetectionQuality, invEdgeDetectionQuality );
+			return ApplyFXAA( uvNode, this._invSize );
 
 		} );
 

粤ICP备19079148号