
Hi everyone,
I know it has been a long time. I have been busy with other programming topics, mostly CPU oriented stuff. Very recently I have been playing with the deband shader now that I am more experienced in general programming topics. While at it I have decided to try to improve it with my still limited GPU programming knowledge. The deband shader is actually based on the popular flash3kyuu_deband. A while back, madshi made some improvements to the original design and shared them publicly. And I have implemented some of them to increase debanding quality. Basically now there is more sanity checking, which means higher parameters should yield lesser decrease in detail while being able to deband more color banding. I have also simplified the settings, the grain setting no longer exists, we are now using CeeJay's ordered dithering algorithm and it is on by default since turning off just undoes the effect. Other than that the ranges are reduced, I have tested different parameters and basically limited each setting to a range where it makes a significant difference. Before making it public, I want to release it here as a beta and get your feedback. I'd really appreciate it if you could compare it to previous version and tell me what do you think. Thanks.
/**
* Deband shader by haasn
* https://github.com/haasn/gentooconf/blob/xor/home/nand/.mpv/shaders/debandpre.glsl
*
* Copyright (c) 2015 Niklas Haas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Modified and optimized for ReShade by JPulowski
* https://reshade.me/forum/shaderpresentation/768deband
*
* Do not distribute without giving credit to the original author(s).
*
* 1.0  Initial release
* 1.1  Replaced the algorithm with the one from MPV
* 1.1a  Minor optimizations
* Removed unnecessary lines and replaced them with ReShadeFX intrinsic counterparts
* 2.0  Replaced "grain" with CeeJay.dk's ordered dithering algorithm and enabled it by default
* The configuration is now more simpler and straightforward
* Some minor code changes and optimizations
* Improved the algorithm and made it more robust by adding some of the madshi's
* improvements to flash3kyuu_deband which should cause an increase in quality. Higher
* iterations/ranges should now yield higher quality debanding without too much decrease
* in quality.
* Changed licensing text and original source code URL
*/
#include "ReShadeUI.fxh"
uniform float Threshold < __UNIFORM_SLIDER_FLOAT1
ui_min = 0.6;
ui_max = 3.4;
ui_step = 0.1;
ui_label = "CutOff Threshold";
ui_tooltip = "Higher numbers increase the debanding strength dramatically but progressively diminish image details. In pixel shaders a 8bit color step equals to 1.0/255.0";
> = 1.6;
uniform float Range < __UNIFORM_SLIDER_FLOAT1
ui_min = 1.0;
ui_max = 32.0;
ui_step = 1.0;
ui_label = "Initial Radius";
ui_tooltip = "The radius increases linearly for each iteration. A higher radius will find more gradients, but a lower radius will smooth more aggressively.";
> = 24.0;
uniform int Iterations < __UNIFORM_SLIDER_INT1
ui_min = 1; ui_max = 4;
ui_tooltip = "The number of debanding steps to perform per sample. Each step reduces a bit more banding, but takes time to compute.";
> = 1;
#include "ReShade.fxh"
uniform int drandom < source = "random"; min = 0; max = 5000; >;
float rand(float x)
{
return frac(x / 41.0);
}
float permute(float x)
{
return ((34.0 * x + 1.0) * x) % 289.0;
}
void analyze_pixels(float3 ori, sampler2D tex, float2 pos, float2 range, float2 dir, out float3 ref_avg, out float3 ref_avg_diff, out float3 ref_max_diff, out float3 ref_mid_diff1, out float3 ref_mid_diff2)
{
// Sample at quarterturn intervals around the source pixel
// Southeast
float3 ref = tex2D(tex, float2(pos + range * float2(dir.x, dir.y))).rgb;
float3 diff = abs(ori  ref);
ref_max_diff = diff;
ref_avg = ref;
ref_mid_diff1 = ref;
// Northwest
ref = tex2D(tex, float2(pos + range * float2(dir.x, dir.y))).rgb;
diff = abs(ori  ref);
ref_max_diff = max(ref_max_diff, diff);
ref_avg += ref;
ref_mid_diff1 = abs(((ref_mid_diff1 + ref) * 0.5)  ori);
// Northeast
ref = tex2D(tex, float2(pos + range * float2(dir.y, dir.x))).rgb;
diff = abs(ori  ref);
ref_max_diff = max(ref_max_diff, diff);
ref_avg += ref;
ref_mid_diff2 = ref;
// Southwest
ref = tex2D(tex, float2(pos + range * float2( dir.y, dir.x))).rgb;
diff = abs(ori  ref);
ref_max_diff = max(ref_max_diff, diff);
ref_avg += ref;
ref_mid_diff2 = abs(((ref_mid_diff2 + ref) * 0.5)  ori);
ref_avg *= 0.25; // Normalize avg
ref_avg_diff = abs(ori  ref_avg);
}
float3 PS_Deband(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target
{
// Initialize the PRNG by hashing the position + a random uniform
float3 m = float3(texcoord, drandom * 0.0002) + 1.0;
float h = permute(permute(permute(m.x) + m.y) + m.z);
float3 ref_avg; // Average of 4 reference pixels
float3 ref_avg_diff; // The difference between the average of 4 reference pixels and the original pixel
float3 ref_max_diff; // The maximum difference between one of the 4 reference pixels and the original pixel
float3 ref_mid_diff1; // The difference between the average of SE and NW reference pixels and the original pixel
float3 ref_mid_diff2; // The difference between the average of NE and SW reference pixels and the original pixel
float3 ori = tex2D(ReShade::BackBuffer, texcoord).rgb; // Original pixel
float3 res; // Final pixel
// Compute a random angle
float dir = rand(permute(h)) * 6.2831853;
float2 o = float2(cos(dir), sin(dir));
for (int i = 1; i <= Iterations; ++i)
{
// Compute a random distance
float dist = rand(h) * Range * i;
float2 pt = dist * ReShade::PixelSize;
analyze_pixels(ori, ReShade::BackBuffer, texcoord, pt, o,
ref_avg,
ref_avg_diff,
ref_max_diff,
ref_mid_diff1,
ref_mid_diff2);
// Thresholds roughly based on madshi's "medium" settings
float3 ref_avg_diff_threshold = Threshold / 255.0 * i;
float3 ref_max_diff_threshold = 4.0 / 255.0 * i;
float3 ref_mid_diff_threshold = 2.0 / 255.0 * i;
// Fuzzy logic based pixel selection
float3 factor = pow(saturate(3.0 * (1.0  ref_avg_diff / ref_avg_diff_threshold)) *
saturate(3.0 * (1.0  ref_max_diff / ref_max_diff_threshold)) *
saturate(3.0 * (1.0  ref_mid_diff1 / ref_mid_diff_threshold)) *
saturate(3.0 * (1.0  ref_mid_diff2 / ref_mid_diff_threshold)), 0.1);
res = lerp(ori, ref_avg, factor);
h = permute(h);
}
const float dither_bit = 8.0; //Number of bits per channel. Should be 8 for most monitors.
/*.
 :: Ordered Dithering :: 
'*/
//Calculate grid position
float grid_position = frac(dot(texcoord, (ReShade::ScreenSize * float2(1.0 / 16.0, 10.0 / 36.0)) + 0.25));
//Calculate how big the shift should be
float dither_shift = 0.25 * (1.0 / (pow(2, dither_bit)  1.0));
//Shift the individual colors differently, thus making it even harder to see the dithering pattern
float3 dither_shift_RGB = float3(dither_shift, dither_shift, dither_shift); //subpixel dithering
//modify shift acording to grid position.
dither_shift_RGB = lerp(2.0 * dither_shift_RGB, 2.0 * dither_shift_RGB, grid_position); //shift acording to grid position.
//shift the color by dither_shift
res += dither_shift_RGB;
return res;
}
technique Deband <
ui_tooltip = "Alleviates color banding by trying to approximate original color values.";
>
{
pass
{
VertexShader = PostProcessVS;
PixelShader = PS_Deband;
}
}
