358 lines
15 KiB
HLSL
358 lines
15 KiB
HLSL
////-------------//
|
|
///**NFAA Fast**///
|
|
//-------------////
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//* Normal Filter Anti Aliasing.
|
|
//* For ReShade 3.0+ & Freestyle
|
|
//* ---------------------------------
|
|
//* NFAA
|
|
//* Due Diligence
|
|
//* Based on port by b34r
|
|
//* https://www.gamedev.net/forums/topic/580517-nfaa---a-post-process-anti-aliasing-filter-results-implementation-details/?page=2
|
|
//* Later rewritten by Eric B. AKA Kourinn
|
|
//* https://github.com/BlueSkyDefender/AstrayFX/pull/17
|
|
//* If I missed any please tell me.
|
|
//*
|
|
//* LICENSE
|
|
//* ============
|
|
//* Normal Filter Anti Aliasing is licenses under: Attribution-NoDerivatives 4.0 International
|
|
//*
|
|
//* You are free to:
|
|
//* Share - copy and redistribute the material in any medium or format
|
|
//* for any purpose, even commercially.
|
|
//* The licensor cannot revoke these freedoms as long as you follow the license terms.
|
|
//* Under the following terms:
|
|
//* Attribution - You must give appropriate credit, provide a link to the license, and indicate if changes were made.
|
|
//* You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
|
//*
|
|
//* NoDerivatives - If you remix, transform, or build upon the material, you may not distribute the modified material.
|
|
//*
|
|
//* No additional restrictions - You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
|
|
//*
|
|
//* https://creativecommons.org/licenses/by-nd/4.0/
|
|
//*
|
|
//* Have fun,
|
|
//* Jose Negrete AKA BlueSkyDefender
|
|
//*
|
|
//* https://github.com/BlueSkyDefender/Depth3D
|
|
//*
|
|
//* Have fun,
|
|
//* Jose Negrete AKA BlueSkyDefender
|
|
//*
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define BUFFER_PIXEL_SIZE float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT)
|
|
#define BUFFER_ASPECT_RATIO (BUFFER_WIDTH * BUFFER_RCP_HEIGHT)
|
|
|
|
uniform int EdgeDetectionType <
|
|
ui_type = "combo";
|
|
ui_items = "Luminance edge detection\0Perceived Luminance edge detection\0Color edge detection\0Perceived Color edge detection\0";
|
|
ui_label = "Edge Detection Type";
|
|
> = 3;
|
|
|
|
uniform float EdgeDetectionThreshold <
|
|
ui_type = "drag";
|
|
ui_label = "Edge Detection Threshold";
|
|
ui_tooltip = "The difference in Luminence/Color that would be perceived as an edge.\n"
|
|
"Try lowering this slightly if the Edge Mask misses some edges.\n"
|
|
"Default is 0.063";
|
|
ui_min = 0.050; ui_max = 0.200; ui_step = 0.001;
|
|
> = 0.100;
|
|
|
|
uniform float EdgeSearchRadius <
|
|
ui_type = "drag";
|
|
ui_label = "Edge Search Radius";
|
|
ui_tooltip = "The radius to search for edges.\n"
|
|
"Try raising this if using in-game upscaling.\n"
|
|
"Default is 1.000";
|
|
ui_min = 0.000; ui_max = 4.000; ui_step = 0.001;
|
|
> = 1.000;
|
|
|
|
uniform float UnblurFilterStrength <
|
|
ui_type = "drag";
|
|
ui_label = "Unblur Filter Strength";
|
|
ui_tooltip = "Adjusts the Edge Mask and Corner Mask contrast for filtering unwanted edge blur.\n"
|
|
"Try raising this if text or icons become blurry.\n"
|
|
"Try lowering this if edges are still too aliased.\n"
|
|
"Default is 1.000";
|
|
ui_min = 0.000; ui_max = 2.000; ui_step = 0.001;
|
|
> = 1.000;
|
|
|
|
uniform float BlurStrength <
|
|
ui_type = "drag";
|
|
ui_label = "Blur Strength";
|
|
ui_tooltip = "Adjusts the Normal Map weights for stronger edge blur.\n"
|
|
"Try raising this if edges are still too aliased.\n"
|
|
"Try lowering this if text or icons become blurry.\n"
|
|
"Default is 1.000";
|
|
ui_min = 0.000; ui_max = 2.000; ui_step = 0.001;
|
|
> = 1.000;
|
|
|
|
uniform float2 BlurSize <
|
|
ui_type = "drag";
|
|
ui_label = "Blur Size";
|
|
ui_tooltip = "Adjusts the Normal Map depth for larger/longer edge blur.\n"
|
|
"Inputs are blur size parallel and perpendicular to the edge respectively.\n"
|
|
"Try raising this if edges are still too aliased.\n"
|
|
"Try lowering this if text or icons become blurry.\n"
|
|
"Defaults are 2.000 and 1.000";
|
|
ui_min = 0.000; ui_max = 4.000; ui_step = 0.001;
|
|
> = float2(2.000, 1.000);
|
|
|
|
uniform int DebugOutput <
|
|
ui_type = "combo";
|
|
ui_label = "Debug Output";
|
|
ui_items = "None\0Edge Mask View\0Corner Mask View\0Normal Map View\0Pre-Blur Mask View\0Post-Blur Mask View\0";
|
|
ui_tooltip = "Edge Mask View shows the Edge Detection and Unblur Filter Strength.\n"
|
|
"Corner Mask View shows the Corner Detection and Unblur Filter Strength.\n"
|
|
"Normal Map View shows the Normal Map depth, used for Blur Size and Blur Direction.\n"
|
|
"Pre-Blur Mask View shows just the edges that will be blurred.\n"
|
|
"Post-Blur Mask View shows just the edges have been blurred.";
|
|
ui_spacing = 2;
|
|
> = 0;
|
|
|
|
////////////////////////////////////////////////////////////Variables////////////////////////////////////////////////////////////////////
|
|
|
|
// sRGB Luminance
|
|
static const float3 LinearizeVector[4] = { float3(0.2126, 0.7152, 0.0722), float3(0.299, 0.587, 0.114), float3(0.3333333, 0.3333333, 0.3333333), float3(0.299, 0.587, 0.114) };
|
|
|
|
static const float Cos45 = 0.70710678118654752440084436210485;
|
|
|
|
static const float MaxSlope = 1024.0;
|
|
|
|
texture BackBufferTex : COLOR;
|
|
|
|
sampler BackBuffer { Texture = BackBufferTex; };
|
|
|
|
////////////////////////////////////////////////////////////Functions////////////////////////////////////////////////////////////////////
|
|
|
|
float LinearDifference(float3 A, float3 B)
|
|
{
|
|
float lumDiff = dot(A, LinearizeVector[EdgeDetectionType]) - dot(B, LinearizeVector[EdgeDetectionType]);
|
|
if (EdgeDetectionType < 2)
|
|
return lumDiff;
|
|
|
|
float3 C = abs(A - B);
|
|
return max(max(C.r, C.g), C.b) * (lumDiff < 0.0 ? -1.0 : 1.0); // sign intrinsic can return 0, which we don't want. Plus this is faster.
|
|
}
|
|
|
|
float2 Rotate45(float2 p) {
|
|
return float2(mad(p.x, Cos45, -p.y * Cos45), mad(p.x, Cos45, p.y * Cos45));
|
|
// return float2(p.x * Cos45 - p.y * Cos45, p.x * Cos45 + p.y * Cos45);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////NFAA////////////////////////////////////////////////////////////////////
|
|
|
|
float4 NFAA(float2 texcoord, float4 offsets[4])
|
|
{
|
|
float4 color = tex2Dlod(BackBuffer, float4(texcoord, 0.0, 0.0));
|
|
|
|
// Find Edges
|
|
// +---+---+---+---+---+
|
|
// | | | | | |
|
|
// +---+---+---+---+---+
|
|
// | | e | f | g | |
|
|
// +---+--(a)-(b)--+---+
|
|
// | | h | P | i | |
|
|
// +---+--(c)-(d)--+---+
|
|
// | | j | k | l | |
|
|
// +---+---+---+---+---+
|
|
// | | | | | |
|
|
// +---+---+---+---+---+
|
|
// Much better at horizontal/vertical lines, slightly better diagonals, always compares 6 pixels, not 2.
|
|
float3 a = tex2Dlod(BackBuffer, offsets[0]).rgb;
|
|
float3 b = tex2Dlod(BackBuffer, offsets[1]).rgb;
|
|
float3 c = tex2Dlod(BackBuffer, offsets[2]).rgb;
|
|
float3 d = tex2Dlod(BackBuffer, offsets[3]).rgb;
|
|
|
|
// Original edge detection from b34r & BlueSkyDefender
|
|
// +---+---+---+---+---+
|
|
// | | | | | |
|
|
// +---+---+---+---+---+
|
|
// | | | t | | |
|
|
// +---+---+---+---+---+
|
|
// | | l | C | r | |
|
|
// +---+---+---+---+---+
|
|
// | | | b | | |
|
|
// +---+---+---+---+---+
|
|
// | | | | | |
|
|
// +---+---+---+---+---+
|
|
// float angle = 0.0;
|
|
// float3 t = tex2Dlod(BackBuffer, float4(mad(float2(0.0, -EdgeSearchRadius), BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
// float3 b = tex2Dlod(BackBuffer, float4(mad(float2(-0.0, EdgeSearchRadius), BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
// float3 r = tex2Dlod(BackBuffer, float4(mad(float2(EdgeSearchRadius, 0.0), BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
// float3 l = tex2Dlod(BackBuffer, float4(mad(float2(-EdgeSearchRadius, 0.0), BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
// float2 n = float2(LinearDifference(t, b), LinearDifference(r, l));
|
|
|
|
// i.e. top vs bottom = a + b - (c + d) = (e + 2*f + g) / 4 - (j + 2*k + l) / 4
|
|
float2 normal = float2(LinearDifference(b + d, a + c), LinearDifference(c + d, a + b)); // right - left, bottom - top
|
|
float edge = length(normal);
|
|
|
|
float edgeMask = 1.0;
|
|
float cornerMask = 1.0;
|
|
if (edge > EdgeDetectionThreshold)
|
|
{
|
|
// Lets make that edgeMask for a sharper image.
|
|
float edgeConfidence = log2(edge / EdgeDetectionThreshold);
|
|
edgeMask = saturate(mad(edgeConfidence, UnblurFilterStrength - 2.0, 1.0));
|
|
// edgeMask = saturate((1.0 - (UnblurFilterStrength - 2.0) * edgeConfidence);
|
|
|
|
// Then subtract corners from edge mask to avoid bluring text and detailed icons
|
|
float4 corners = float4(LinearDifference(a + b + c, 3.0 * d), LinearDifference(a + b + d, 3.0 * c), LinearDifference(a + c + d, 3.0 * b), LinearDifference(c + d + b, 3.0 * a));
|
|
float corner = dot(abs(corners), 0.25);
|
|
cornerMask = saturate(edgeMask + corner);
|
|
|
|
// calculate x/y coordinates along the edge at specified distances and offsets
|
|
// +---+---+---+---+---+ +---+---+---+---+---+
|
|
// | | | | | | | | | | | |
|
|
// +---+---+---+---+---+ +---+---+---+---+---+
|
|
// | | | | | | | | | (e) | |
|
|
// +---+(g)a---b(e)+---+ +---+---a---b(f)+---+
|
|
// | |-O | P | O | | | | | P | | |
|
|
// +---+(h)c---d(f)+---+ +---+(g)c---d---+---+
|
|
// | | | | | | | | (h) | | |
|
|
// +---+---+---+---+---+ +---+---+---+---+---+
|
|
// | | | | | | | | | | | |
|
|
// +---+---+---+---+---+ +---+---+---+---+---+
|
|
|
|
// slope m = normal.r / normal.g
|
|
// distance d
|
|
// y = mx
|
|
// d^2 = y^2 + x^2 = (mx)^2+x^2
|
|
// d^2 = x^2(1 + m^2)
|
|
// x^2 = d^2/(1 + m^2)
|
|
|
|
// Follow the edge for 1/2 of BlurSize.x
|
|
float4 offset;
|
|
float m = normal.g != 0 ? normal.r / normal.g : MaxSlope;
|
|
float d = 0.5 * BlurSize.x;
|
|
offset.x = sqrt(d *d / (1.0 + m * m));
|
|
offset.y = m * offset.x;
|
|
// Then move perpendicular to the edge for 1/2 of BlurSize.y
|
|
m = normal.r != 0 ? -normal.g / normal.r : MaxSlope;
|
|
d = 0.5 * BlurSize.y;
|
|
offset.z = sqrt(d * d / (1.0 + m * m));
|
|
offset.w = m * offset.z;
|
|
|
|
float3 e = tex2Dlod(BackBuffer, float4(mad(offset.xy + offset.zw, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
float3 f = tex2Dlod(BackBuffer, float4(mad(offset.xy - offset.zw, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
float3 g = tex2Dlod(BackBuffer, float4(mad(-offset.xy - offset.zw, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
float3 h = tex2Dlod(BackBuffer, float4(mad(-offset.xy + offset.zw, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0)).rgb;
|
|
|
|
// It's possible to reduce taps by re-using edge detection taps a, b, c, d,
|
|
// but it's not worth it. Nearby pixels should already be cached, and it would need more math.
|
|
|
|
// apply blur
|
|
if (DebugOutput != 4)
|
|
color.rgb = lerp(color.rgb, (e + f + g + h) * 0.25, BlurStrength * 0.5 * (1.0 - cornerMask));
|
|
|
|
// Pre/Post Blur Mask
|
|
if (DebugOutput > 3)
|
|
color.a = cornerMask;
|
|
|
|
// original blur from b34r & BlueSkyDefender
|
|
// may need some work to be functional again, due to some variable name refactoring after I reversed how/why it worked
|
|
// +---+---+---+---+---+
|
|
// | | | | | |
|
|
// +---+---+---+---+---+
|
|
// |\\\| | x | x | |
|
|
// +---+---+---+---+---+
|
|
// | |\\\|\\\| x | |
|
|
// +---+---+---+---+---+
|
|
// | | | x |\\\|\\\|
|
|
// +---+---+---+---+---+
|
|
// | | | | | |
|
|
// +---+---+---+---+---+
|
|
// y = -0.5x; n.x = 1; n.y = 0.5; nl = 1.18; dn.x = 0.85; dn.y = 0.42;
|
|
// t0/1 = 0.425, 0.21; d ~= 0.5
|
|
// t2/3 = 0.765, -0.38; d ~= 0.85
|
|
// float2 dn = n / nl * BlurSize;
|
|
// float4 t0 = tex2Dlod(BackBuffer, float4(mad(-dn * 0.5, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0));
|
|
// float4 t1 = tex2Dlod(BackBuffer, float4(mad(dn * 0.5, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0));
|
|
// float4 t2 = tex2Dlod(BackBuffer, float4(mad(float2(dn.x, -dn.y) * 0.9, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0));
|
|
// float4 t3 = tex2Dlod(BackBuffer, float4(mad(float2(-dn.x, dn.y) * 0.9, BUFFER_PIXEL_SIZE, texcoord), 0.0, 0.0));
|
|
// color = lerp(mad(color, 0.23, 0.175 * (t2 + t3) + 0.21 * (t0 + t1)), color, edgeMask);
|
|
}
|
|
else {
|
|
color.a = 0.0;
|
|
}
|
|
|
|
if(DebugOutput == 1) // Edge Mask
|
|
{
|
|
color.rgb = edgeMask;
|
|
}
|
|
if(DebugOutput == 2) // Corner Mask
|
|
{
|
|
color.rgb = cornerMask;
|
|
}
|
|
else if (DebugOutput == 3) // Normal Map
|
|
{
|
|
// Normal map, right = red, green = top, configured using white box with black background
|
|
float3 normalMap;
|
|
normalMap.b = 1.0;
|
|
normalMap.rg = mad(float2(-normal.r, normal.g) * BlurSize.x * BlurStrength, 0.25, 0.5);
|
|
normalMap.rg = lerp(float2(0.5, 0.5), saturate(normalMap.rg), (1.0 - edgeMask) * BlurSize.y);
|
|
color.rgb = normalMap;
|
|
}
|
|
else if (DebugOutput > 3) { // Pre/Post Blur Mask
|
|
uint row = floor(texcoord.y / 0.1);
|
|
uint col = floor(texcoord.x * BUFFER_ASPECT_RATIO / 0.1);
|
|
|
|
float3 lightGray = 0.5;
|
|
float3 darkGray = 0.1667;
|
|
|
|
lightGray = lerp(lightGray, color.rgb, color.a);
|
|
darkGray = lerp(darkGray, color.rgb, color.a);
|
|
|
|
// Create checkerboad background to depict transparency
|
|
if (row % 2 == 0) {
|
|
if (col % 2 == 0) {
|
|
color.rgb = lightGray;
|
|
}
|
|
else {
|
|
color.rgb = darkGray;
|
|
}
|
|
}
|
|
else {
|
|
if (col % 2 == 0) {
|
|
color.rgb = darkGray;
|
|
}
|
|
else {
|
|
color.rgb = lightGray;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
color.a = 1.0;
|
|
return color;
|
|
}
|
|
|
|
void NFAA_VS(in uint id : SV_VertexID, out float4 position : SV_POSITION, out float2 texcoord : TEXCOORD, out float4 offsets[4] : TEXCOORD1 )
|
|
{
|
|
texcoord.x = (id == 2) ? 2.0 : 0.0;
|
|
texcoord.y = (id == 1) ? 2.0 : 0.0;
|
|
position = float4(texcoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
|
|
float2 offset = Cos45 * EdgeSearchRadius * BUFFER_PIXEL_SIZE;
|
|
offsets[0] = float4(mad(float2(-1.0, -1.0), offset, texcoord), 0.0, 0.0);
|
|
offsets[1] = float4(mad(float2(1.0, -1.0), offset, texcoord), 0.0, 0.0);
|
|
offsets[2] = float4(mad(float2(-1.0, 1.0), offset, texcoord), 0.0, 0.0);
|
|
offsets[3] = float4(mad(float2(1.0, 1.0), offset, texcoord), 0.0, 0.0);
|
|
}
|
|
|
|
float4 NFAA_PS(in float4 position : SV_Position, in float2 texcoord : TEXCOORD, in float4 offsets[4] : TEXCOORD1) : SV_Target
|
|
{
|
|
return NFAA(texcoord, offsets);
|
|
}
|
|
|
|
technique NFAA
|
|
{
|
|
pass NFAA_Fast
|
|
{
|
|
VertexShader = NFAA_VS;
|
|
PixelShader = NFAA_PS;
|
|
}
|
|
}
|