Fast Approximate Anti-Aliasing (FXAA) in Metal?

Is there a FXAA equivalent in Metal?


If not, what's the typical way people handle aliasing issues in Metal? Just ignore it? 🙂


Thanks!

Replies

It shouldn't be too much trouble to port FXAA's code to Metal.


There is also MSAA, of course. Or other types of post-processing antialiasing (SMAA etc.)

Can you point me in the right direction of SMAA for Metal? That would be really helpful!


Thanks!

You could just use multisampling which is supported by MTKView, however I've implemented FXAA in my rendering engine at

https://github.com/tokyovigilante/CesiumKit/blob/master/CesiumKit/Scene/FXAA.swift


There's a lot of engine-specific code there, but in a nutshell it just renders my composited frame to a texture backing a MTKView drawable using the FXAAFS shader in https://github.com/tokyovigilante/CesiumKit/blob/master/CesiumKit/Shaders/Shaders.swiftand a viewport aligned quad.


The accompanying shader is written in GLSL, and largely based on the original NVIDIA implementation, but you can use the accompanying GLSL -> MSL compiler to generate a Metal FXAA shader either online or offline.


(Update - fixed links)

Why you can't just use MSAA? Check MetalBasic3D sample from apple.


If you want to port FXAA, here is common version of glsl shader:


float FXAA_SPAN_MAX = 8.0;
float FXAA_REDUCE_MUL = 1.0/8.0;
float FXAA_REDUCE_MIN = 1.0/128.0;
vec3 rgbNW=texture2D(buf0,texCoords+(vec2(-1.0,-1.0)/frameBufSize)).xyz;
vec3 rgbNE=texture2D(buf0,texCoords+(vec2(1.0,-1.0)/frameBufSize)).xyz;
vec3 rgbSW=texture2D(buf0,texCoords+(vec2(-1.0,1.0)/frameBufSize)).xyz;
vec3 rgbSE=texture2D(buf0,texCoords+(vec2(1.0,1.0)/frameBufSize)).xyz;
vec3 rgbM=texture2D(buf0,texCoords).xyz;
vec3 luma=vec3(0.299, 0.587, 0.114);
float lumaNW = dot(rgbNW, luma);
float lumaNE = dot(rgbNE, luma);
float lumaSW = dot(rgbSW, luma);
float lumaSE = dot(rgbSE, luma);
float lumaM  = dot(rgbM,  luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));
float dirReduce = max(
                      (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL),
                      FXAA_REDUCE_MIN);
float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2( FXAA_SPAN_MAX,  FXAA_SPAN_MAX),
          max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
              dir * rcpDirMin)) / frameBufSize;
vec3 rgbA = (1.0/2.0) * (
                         texture2D(buf0, texCoords.xy + dir * (1.0/3.0 - 0.5)).xyz +
                         texture2D(buf0, texCoords.xy + dir * (2.0/3.0 - 0.5)).xyz);
vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * (
                                            texture2D(buf0, texCoords.xy + dir * (0.0/3.0 - 0.5)).xyz +
                                            texture2D(buf0, texCoords.xy + dir * (3.0/3.0 - 0.5)).xyz);
float lumaB = dot(rgbB, luma);
if((lumaB < lumaMin) || (lumaB > lumaMax)) {
    gl_FragColor.xyz=rgbA;
} else {
    gl_FragColor.xyz=rgbB;
}




Here is SMAA for hlsl and glsl, if you are interested.