Page 1 of 1

Triangular Dither

Posted: 04 Dec 2017, 17:57
by SandvichDISH
Updated this post with significantly better code:

Code: Select all

#define min3(v) (min(v.x, min(v.y, v.z)))
#define max3(v) (max(v.x, max(v.y, v.z)))
#define remap(v, a, b) (((v) - (a)) / ((b) - (a)))
float rand21(float2 uv)
{
    float2 noise = frac(sin(dot(uv, float2(12.9898, 78.233) * 2.0)) * 43758.5453);
    return (noise.x + noise.y) * 0.5;
}
float rand11(float x) { return frac(x * 0.024390243); }
float permute(float x) { return ((34.0 * x + 1.0) * x) % 289.0; }

#define DITHER_QUALITY_LEVEL 1
#define BIT_DEPTH 8
float3 triDither(float3 color, float2 uv, float timer)
{
    static const float bitstep = pow(2.0, BIT_DEPTH) - 1.0;
    static const float lsb = 1.0 / bitstep;
    static const float lobit = 0.5 / bitstep;
    static const float hibit = (bitstep - 0.5) / bitstep;

    float3 m = float3(uv, rand21(uv + timer)) + 1.0;
    float h = permute(permute(permute(m.x) + m.y) + m.z);

    float3 noise1, noise2;
    noise1.x = rand11(h); h = permute(h);
    noise2.x = rand11(h); h = permute(h);
    noise1.y = rand11(h); h = permute(h);
    noise2.y = rand11(h); h = permute(h);
    noise1.z = rand11(h); h = permute(h);
    noise2.z = rand11(h);

#if DITHER_QUALITY_LEVEL == 1
    float lo = saturate(remap(min3(color.xyz), 0.0, lobit));
    float hi = saturate(remap(max3(color.xyz), 1.0, hibit));
    return lerp(noise1 - 0.5, noise1 - noise2, min(lo, hi)) * lsb;
#elif DITHER_QUALITY_LEVEL == 2
    float3 lo = saturate(remap(color.xyz, 0.0, lobit));
    float3 hi = saturate(remap(color.xyz, 1.0, hibit));
    float3 uni = noise1 - 0.5;
    float3 tri = noise1 - noise2;
    return float3(
        lerp(uni.x, tri.x, min(lo.x, hi.x)),
        lerp(uni.y, tri.y, min(lo.y, hi.y)),
        lerp(uni.z, tri.z, min(lo.z, hi.z))) * lsb;
#endif
}
You need to apply dither in the OUTPUT colorspace, so if sRGB conversion happens outside your control (as is often the case) you need to apply dither like this:

Code: Select all

float3 lin2srgb_fast(float3 v) { return sqrt(v); }
float3 srgb2lin_fast(float3 v) { return v * v; }

// Somewhere at the end of your shader pipeline:
color.xyz = lin2srgb_fast(color.xyz);
color.xyz = srgb2lin_fast(color.xyz + triDither(color.xyz, uv, Timer.x));
If you're already in gamma space you can just do

Code: Select all

color.xyz += triDither(color.xyz, uv, Timer.x);
Image


You can leave DITHER_QUALITY at 1, quality level 2 is objectively correct but the difference is completely negligible.
99% of users probably don't need to change the bit depth, but you could of course replace the BIT_DEPTH define with a UI element if you want.
This dither is as close as I could reasonably come to an "objectively correct" dither and shouldn't be perceived as being too "noisy" by any users.

Re: Triangular Dither

Posted: 05 Dec 2017, 12:24
by roxahris
This is something I worked on for the last update to my ENB, though I chose to err on the side of less noise (and thus potentially more perceptual banding) in the face of some comments I received about my previous implementation.

Is there any reason you chose effect.txt? In my testing, it applied before enbgamedepthoffield.fx, so any dithering implemented there would be blurred over after a distance. I put my implementation there because of that.

Re: Triangular Dither

Posted: 05 Dec 2017, 19:21
by SandvichDISH
EDIT: Roxahris is right in observing dither in Dragon's Dogma should be applied in enbgamedepthoffield.fx, IF you have the vanilla game's anti-aliasing turned OFF (which you should, probably)!
Whether anti-aliasing is on or off changes the order shaders execute in that game for some reason.

Re: Triangular Dither

Posted: 11 Jun 2018, 02:32
by SandvichDISH
BUMP: I replaced all the code in the original post with the updated (and much better) code I use now.

Re: Triangular Dither

Posted: 21 Apr 2019, 15:18
by aaronth07
Sorry, but where do I paste this code in for use with Skyrim SE?

Re: Triangular Dither

Posted: 24 Apr 2019, 17:06
by SandvichDISH
I'd say you should use it at the end of enbeffectpostpass.fx.

If you want to be fully complete, also use it at the end enbeffect.fx but with the BIT_DEPTH set to 10.

Re: Triangular Dither

Posted: 08 Nov 2022, 12:00
by DarkDefiler
Sorry for poting here, but I don't understand how to use these. I copy-pasted the top code into enbeffectpostpass.fx but it didn't do anything :(

Re: Triangular Dither

Posted: 09 Nov 2022, 07:36
by ENBSeries
Forum is buggy after update, code formatting do not work. But you can use "view page source" and there text of the shader code will be correct.