Triangular Dither

share shaders here
Post Reply
  • Author
  • Message
Offline
User avatar
Posts: 99
Joined: 11 May 2014, 10:48

Triangular Dither

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.
Last edited by SandvichDISH on 24 Jun 2018, 22:38, edited 7 times in total.
_________________
Reforged ENB for Dragon's Dogma, The Witcher 2, Kingdom Come: Deliverance

Offline
User avatar
*master*
Posts: 136
Joined: 08 Nov 2012, 15:24

Re: Triangular Dither

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.

Offline
User avatar
Posts: 99
Joined: 11 May 2014, 10:48

Re: Triangular Dither

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.
Last edited by SandvichDISH on 11 Jun 2018, 02:35, edited 1 time in total.
_________________
Reforged ENB for Dragon's Dogma, The Witcher 2, Kingdom Come: Deliverance

Offline
User avatar
Posts: 99
Joined: 11 May 2014, 10:48

Re: Triangular Dither

BUMP: I replaced all the code in the original post with the updated (and much better) code I use now.
_________________
Reforged ENB for Dragon's Dogma, The Witcher 2, Kingdom Come: Deliverance

Offline
Posts: 4
Joined: 26 Jan 2019, 11:16

Re: Triangular Dither

Sorry, but where do I paste this code in for use with Skyrim SE?

Offline
User avatar
Posts: 99
Joined: 11 May 2014, 10:48

Re: Triangular Dither

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.
_________________
Reforged ENB for Dragon's Dogma, The Witcher 2, Kingdom Come: Deliverance

Offline
Posts: 4
Joined: 01 May 2020, 05:07

Re: Triangular Dither

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 :(

Offline
User avatar
*blah-blah-blah maniac*
Posts: 17427
Joined: 27 Dec 2011, 08:53
Location: Rather not to say

Re: Triangular Dither

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.
_________________
i9-9900k, 64Gb RAM, RTX 3060 12Gb, Win7
Post Reply