Skip to content

Commit

Permalink
FXAA: Add FXAA processing after render.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamievlin committed Feb 11, 2024
1 parent 6cb97d7 commit 4172c92
Showing 1 changed file with 179 additions and 9 deletions.
188 changes: 179 additions & 9 deletions base/shaders/fxaa.cs.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,188 @@
* @brief FXAA post-processing shader
*/

layout(binding=0, rgba8)
uniform image2D inputImage;
layout(binding=0)
uniform sampler2D inputImageSampler;

layout(binding=1, rgba8)
uniform image2D inputImage;

layout(binding=2, rgba8)
writeonly uniform image2D outputImage;

layout(local_size_x=20, local_size_y=20, local_size_z=1) in;

const float gamma=2.2;
const float invGamma=1.0/gamma;
#ifdef ENABLE_FXAA
// based on https://catlikecoding.com/unity/tutorials/advanced-rendering/fxaa/

// configuration values
const float FXAA_EDGE_THRESHOLD_MIN=1/16.0;
const float FXAA_EDGE_THRESHOLD=1/8.0;
const float FXAA_SUBPIX_TRIM=1/4.0;
const float FXAA_SUBPIX_CAP=7/8.0;

// from https://github.com/bartwronski/CSharpRenderer/blob/master/shaders/FXAA.hlsl
const float FXAA_SUBPIX_TRIM_SCALE = (1.0/(1.0 - FXAA_SUBPIX_TRIM));

/** Assumes inColor is in perceptual space */
float getLumi(vec3 inColor)
{
// from nvidia's paper at
// https://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
return inColor.y * (0.587/0.299) + inColor.x;
}

vec3 linearToPerceptual(vec3 inColor)
vec3 getColorAtOffset(ivec2 coord, ivec2 offset, ivec2 size)
{
// an actual 0.5 brightness (half amount of photons) would
// look brighter than what our eyes think is "half" light
return pow(inColor, vec3(invGamma));
return imageLoad(inputImage, clamp(coord + offset, ivec2(0,0), size-ivec2(1,1))).rgb;
}

// return color in perceptual space, no need to do any gamma correction after
vec3 fxaa(ivec2 coord, ivec2 size)
{
// fxaa code here
vec3 perceptualPixelColor=imageLoad(inputImage,coord).rgb;

// step 1: luminance
float lumi= getLumi(perceptualPixelColor);

// step 2: local contrast check

vec3 colorUp= getColorAtOffset(coord, ivec2(0, -1), size);
vec3 colorDown= getColorAtOffset(coord, ivec2(0, 1), size);
vec3 colorLeft= getColorAtOffset(coord, ivec2(-1, 0), size);
vec3 colorRight= getColorAtOffset(coord, ivec2(1, 0), size);

float lumiUp= getLumi(colorUp);
float lumiDown= getLumi(colorDown);
float lumiLeft= getLumi(colorLeft);
float lumiRight= getLumi(colorRight);

float rangeMin=min(lumi,min(min(lumiUp,lumiDown),min(lumiLeft,lumiRight)));
float rangeMax=max(lumi,max(max(lumiUp,lumiDown),max(lumiLeft,lumiRight)));
float localContrast=rangeMax-rangeMin;

if (localContrast < max(FXAA_EDGE_THRESHOLD_MIN, rangeMax * FXAA_EDGE_THRESHOLD))
{
return perceptualPixelColor;
}
// return here to see pixels that will be processed

// step 3: calculate pixel blend factor
vec3 colorNw= getColorAtOffset(coord, ivec2(-1, -1), size);
vec3 colorNe= getColorAtOffset(coord, ivec2(1, -1), size);
vec3 colorSw= getColorAtOffset(coord, ivec2(-1, 1), size);
vec3 colorSe= getColorAtOffset(coord, ivec2(1, 1), size);
float lumiNw= getLumi(colorNw);
float lumiNe= getLumi(colorNe);
float lumiSw= getLumi(colorSw);
float lumiSe= getLumi(colorSe);

float pixelBlendFactorBase=
(2*(lumiUp + lumiDown + lumiLeft + lumiRight) + (lumiNe + lumiNw + lumiSe + lumiSw)) / 12.0;
float pixelBlendFactorBeforeSmoothing = clamp(abs(pixelBlendFactorBase-lumi) / localContrast,0,1);
float pixelBlendFactorBeforeSquare=smoothstep(0, 1, pixelBlendFactorBeforeSmoothing);
float pixelBlendFactor=pixelBlendFactorBeforeSquare*pixelBlendFactorBeforeSquare;

// return here to see belnd factor

// step 4: determine if edge is horizontal or vertical
const vec3 vecCheckEdge=vec3(0.25,-0.5,0.25);
const vec3 vecCheckMid=vec3(0.5,-1.0,0.5);

float edgeVerticalFactor =
abs(dot(vecCheckEdge,vec3(lumiNw,lumiUp,lumiNe)))
+ abs(dot(vecCheckMid,vec3(lumiLeft,lumi,lumiRight)))
+ abs(dot(vecCheckEdge,vec3(lumiSw,lumiDown,lumiSe)));

float edgeHorizontalFactor =
abs(dot(vecCheckEdge,vec3(lumiNw,lumiLeft,lumiSw)))
+ abs(dot(vecCheckMid,vec3(lumiUp,lumi,lumiDown)))
+ abs(dot(vecCheckEdge,vec3(lumiNe,lumiRight,lumiSe)));

bool isHorizontalEdge=edgeHorizontalFactor >= edgeVerticalFactor;
vec2 pixelOffsetBase=isHorizontalEdge ? vec2(0, -1) : vec2(1, 0);

// return here to see if edge is horizontal or vertical

// step 4.5: determining if blend is in positive or negative direction

float pLumi=isHorizontalEdge ? lumiUp : lumiRight;
float nLumi=isHorizontalEdge ? lumiDown : lumiLeft;
float pLumiDiff=abs(pLumi-lumi);
float nLumiDiff=abs(nLumi-lumi);
bool isPositiveDirection=pLumiDiff >= nLumiDiff;
float pixelDirection= isPositiveDirection ? 1 : -1;

vec2 pixelOffset=pixelDirection * pixelOffsetBase;

// step 5: process edge blending
float oppositeLumi=isPositiveDirection ? pLumi : nLumi;
float lumiDiff=isPositiveDirection ? pLumiDiff : nLumiDiff;

vec2 middleCoord=vec2(coord)+vec2(0.5,0.5);
vec2 edgeCoordBase=middleCoord+(0.5 * pixelOffset);
vec2 edgeOffsetStep=isHorizontalEdge ? vec2(1,0) : vec2(0,-1);

float edgeLumi=(lumi + oppositeLumi) * 0.5;
float edgeLumiDeltaThreshold = 0.25 * lumiDiff;

// for positive direction
int positiveStepCount=-1;
bool endOfPosEdge=false;
float posLumiDelta=0;
while (!endOfPosEdge && positiveStepCount < 8)
{
positiveStepCount += 1; // first iteration is zero
posLumiDelta=getLumi(
texture(
inputImageSampler,
edgeCoordBase + (positiveStepCount * edgeOffsetStep)
).rgb
) -edgeLumi;

endOfPosEdge=abs(posLumiDelta) >= edgeLumiDeltaThreshold;
}

// for negative direction
int negativeStepCount=-1;
bool endOfNegEdge=false;
float negLumiDelta=0;
while (!endOfNegEdge && negativeStepCount < 8)
{
negativeStepCount += 1; // first iteration is zero
negLumiDelta=getLumi(
texture(
inputImageSampler,
edgeCoordBase - (positiveStepCount * edgeOffsetStep)
).rgb
)-edgeLumi;
endOfNegEdge=abs(negLumiDelta) >= edgeLumiDeltaThreshold;
}

bool shortestDistanceIsPositive = positiveStepCount <= negativeStepCount;
float shortestStepCount = shortestDistanceIsPositive ? positiveStepCount : negativeStepCount;
float shortestLumiDelta= shortestDistanceIsPositive ? posLumiDelta : negLumiDelta;

// if shortest luminance delta is same sign as edge lumaniance delta,
// we are on the edge that "goes away", and hence should skip
// since we already handling the pixel on the other side
// of the edge
if (shortestLumiDelta * (lumi - edgeLumi) >= 0)
{
return perceptualPixelColor;
}

float edgeBlendFactor = 0.5 - (float(shortestStepCount) / (negativeStepCount + positiveStepCount));

return texture(
inputImageSampler,
middleCoord + (max(pixelBlendFactor,edgeBlendFactor) * pixelOffset)
).rgb;
}

#endif

void main()
{
ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
Expand All @@ -34,7 +198,13 @@ void main()
// coordinate is valid

#ifdef ENABLE_FXAA
// fxaa code here

// final
imageStore(
outputImage,
coord,
vec4(fxaa(coord,size), 1)
);
#else
// already in perceptual space, no need to do any further
// gamma adjustment
Expand Down

0 comments on commit 4172c92

Please sign in to comment.