Welcome, Guest.
Username: Password: Remember me

TOPIC: Grey dither and crush (Only use pre-downscale)

Grey dither and crush (Only use pre-downscale) 2 months 1 week ago #1





This one uses a new dithering function I've developed that outputs a uniformly distributed (almost, actually has a mode of zero) value between 0 and 1 and uses that to change the image i.e. greyDitherSdv changes the standard deviation of the max RGB value, on average by a specified amount; greyDitherAmnt changes the mean of the max RGB value; and greyDitherScurve applies an S-curve with a specified power to the max RGB value, on average. The non-maximum are adjusted by a fraction of themselves over the max RGB.

Note that this works much better on material that will be downscaled after it is applied, so that detail is retained. This is why I was in two minds about porting this one.

Crushing remaps the RGB values to a smaller range and then back. Due to floating point rounding errors, this loses colour information and thus simplifies the colour information. Crushing to 0.5 has a stronger de-dithering effect but is more lossy than the default crushing to the RGB average.
#include "ReShadeUI.fxh"

uniform float greyDitherAmnt < __UNIFORM_DRAG_FLOAT1
	ui_min = -255.0; ui_max = 255.0;
> = 0;

uniform float greyDitherSdv < __UNIFORM_DRAG_FLOAT1
	ui_min = 0.0; ui_max = 255.0;
> = 0;

uniform float greyDitherScurve < __UNIFORM_DRAG_FLOAT1
	ui_min = -1.0; ui_max = 5.0;
> = 1;

uniform int Crushing_type < __UNIFORM_COMBO_INT1
    ui_items = "Crush to RGB average\0Crush to 0.5\0";
    ui_tooltip = "Crushing to 0.5 has a stronger de-dither effect but loses more information.";
> = 0;

uniform float Crushing_amnt < __UNIFORM_DRAG_FLOAT1
	ui_min = 0.0; ui_max = 1.0;  ui_tooltip = "Disabled if =0";
> = 0;

uniform bool Dark_dither <> = false;

uniform float Dark_dither_pwr < __UNIFORM_DRAG_FLOAT1
	ui_min = 0.0; ui_max = 1.0;
> = 0;

uniform bool Crushing_debug <> = false;

uniform bool Split <> = false;

uniform bool Flip_split <> = false;

uniform float Split_position < __UNIFORM_SLIDER_FLOAT1
	ui_min = 0; ui_max =1;
	ui_tooltip = "0 is on the far left, 1 on the far right.";
> = 0.5;

#include "ReShade.fxh"

	float3 rgb2hsv(float3 c)
{
    float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
    float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));
 
    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}


float3 hsv2rgb(float3 c)
{
    float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * lerp(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
//Source: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl


float random( float2 p )
{
// We need irrationals for pseudo randomness.
// Most (all?) known transcendental numbers will (generally) work.
const float2 r = float2(
23.1406926327792690,  // e^pi (Gelfond's constant)
 2.6651441426902251); // 2^sqrt(2) (Gelfond-Schneider constant)

float t=frac(acos(p.x/p.y)+sin(p.x)*r.y+cos(p.y)*r.x+p.x*p.y*r.y);

t= frac((800*cos(t/20)+1400)*t);  
t= frac(pow( frac((0.01*t+sin(500*t*t))+tan(t*500)*500),2));

float rMap =3.98;
float tOld=t;
int k=0;

for (k=0;k<100;k++){
tOld=rMap*tOld*(1-tOld);
}
 
 float w = frac(10000*tOld+0.597*tOld);

#define dither_points 7
float2 d[dither_points] = {
float2(0,0),
float2(1,0),
float2(64.90915,63.75),
float2(131.4898,127.5),
float2(194.1036,191.25),
float2(254,255),
float2(255,255)
};


float2 d_x_b=float2(0,1);float2 d_y_b=float2(0,1);
float dither=w;
int i=0; int exact=0; 
for(i=0;i<dither_points;i++){
[branch]if(d[i].x/255==dither) {dither=d[i].y/255;exact=1;i=dither_points-1;}else{if(d[i].x/255<dither&&d[i].x/255>=d_x_b.x){d_x_b.x=d[i].x/255;d_y_b.x=d[i].y/255;} if(d[i].x/255<=d_x_b.y&&dither<d[i].x/255){d_x_b.y=d[i].x/255;d_y_b.y=d[i].y/255;}}} if(exact==0){dither=d_y_b.x+(dither-d_x_b.x)*((d_y_b.y-d_y_b.x)/(d_x_b.y-d_x_b.x));};i=0;exact=0;

return dither;

}


float grey_dither(float color,float2 tex,float rnd,float sdv, float gamma){

float rand=random(float2((tex.x+BUFFER_WIDTH*BUFFER_RCP_HEIGHT)*color,(tex.y+BUFFER_HEIGHT*BUFFER_RCP_WIDTH)*color));
float randm=rnd*-1*((rand*-4)+1); // averages to color + rnd

color =(rnd!=0)?color+(randm/255):color;

float sAB=sdv*sqrt(12)*0.5;
randm=sAB*(2*rand-1)*-1;

color =(sdv!=0)?color+(randm/255):color;

float colorSc=color*2;
randm=(colorSc<0.5)?(pow(abs(0.5*colorSc),gamma)-color)*-1*((rand*-4)+1):((1-(0.5*pow(abs(2-colorSc),gamma)))-color)*-1*((rand*-4)+1);
color =(gamma!=1)?color+randm:color;

return color;
}


float4 crusher(float4 color){
float4 c0=color;
float3 colorHSV=rgb2hsv(c0.rgb);

float rgbAvg=dot(c0.rgb,pow(3,-1));

float distGrey=sqrt(pow(rgbAvg-c0.r,2)+pow(rgbAvg-c0.g,2)+pow(rgbAvg-c0.b,2));
float normDistGrey=distGrey*pow(0.5*sqrt(3),-1);
float crsh=1-Crushing_amnt;
float lerper=pow(normDistGrey,Crushing_amnt);

float3 crshAvg=lerp(c0.rgb,rgbAvg,lerper);


float mxOld=max(c0.r,max(c0.g,c0.b));
//float mnOld=min(c0.r,min(c0.g,c0.b));
float mxNew=max(crshAvg.r,max(crshAvg.g,crshAvg.b));
//float mnNew=min(crshAvg.r,min(crshAvg.g,crshAvg.b));

float3 avgRevert=(mxNew==0)?0:c0.rgb*(crshAvg.rgb/mxNew);

float3 crshHalf=(0.5-0.5*crsh) + ((0.5+0.5*crsh) - (0.5-0.5*crsh)) * c0.rgb;

colorHSV.yz=rgb2hsv(crshHalf.rgb).yz;

float fromMin=0.5-0.5*crsh;
float fromMax=0.5+0.5*crsh;

float3 halfRevert=	 ((c0.rgb - fromMin) / (fromMax - fromMin));

float3 c1=(Crushing_debug==1)?crshAvg:avgRevert;
float3 c2=(Crushing_debug==1)?hsv2rgb(colorHSV):halfRevert;

float3 c3=(Crushing_type==0)?c1:c2;

return float4(c3.xyz,color.w);
}

float4 PS_GreyDither(float4 pos : SV_Position, float2 texcoord : TEXCOORD0) : SV_Target
{	

	float4 c0 = tex2D(ReShade::BackBuffer, texcoord);
	float c0Max=max(c0.r,max(c0.g,c0.b));
	float4 c1 = c0;

c1.rgb =saturate(grey_dither(c0Max,texcoord,greyDitherAmnt,greyDitherSdv,greyDitherScurve)*(c1.rgb/c0Max));
	float c1Max=max(c1.r,max(c1.g,c1.b));

c1.rgb=(Dark_dither==1)?lerp(c1.rgb,c0.rgb,pow(c1Max,1-Dark_dither_pwr)):c1.rgb;

c1.rgb=(Crushing_amnt==0)?c1.rgb:crusher(c1).rgb;

float4 c2=(texcoord.x>=Split_position*Split)?c1:c0;
float4 c3=(texcoord.x<=Split_position*Split)?c1:c0;

float4 c4=(Flip_split==1 && Split==1)?c3:c2;

float divLine = abs(texcoord.x - Split_position) < BUFFER_RCP_WIDTH;
c4 =(Split==0)?c4: c4*(1.0 - divLine); //invert divline

return c4;

}

technique GreyDither {
	pass GreyDither {
		VertexShader=PostProcessVS;
		PixelShader=PS_GreyDither;
	}
}

The video shader versions are here, I've only ported the grey dither from it so far as I find this the most useful, and crushing here.
Last Edit: 3 weeks 5 hours ago by crabshank. Reason: Fixed OpenGL compatibility issue
The administrator has disabled public write access.

Grey dither (Only use pre-downscale) 2 months 1 week ago #2

I don't understand why you're calling this dither. It's just acting like a noise overlay... That's not really the purpose of dither. Dither should be (in ideal cases) nearly imperceptible and is designed to fix banding before truncation to lower bit depth.
The administrator has disabled public write access.

Grey dither (Only use pre-downscale) 2 months 1 week ago #3

It actually destroys macroblocks in video, which is what I made it for (mine on bottom):


EDIT: Also, re. truncation to a lower bit depth I have a crushing filter that reduces the effect of the dither afterwards to clean up and boost the image (mine on bottom again):

I just put it way up high for demo purposes lol.

EDIT: Games don't suffer from macroblocking AFAIK, so that's another reason I wasn't sure whether to port this and still unsure whether or not to port the crushing part (cos they go together really).
Last Edit: 1 month 4 weeks ago by crabshank. Reason: Better examples
The administrator has disabled public write access.

Grey dither (Only use pre-downscale) 2 months 1 week ago #4

Games do not suffer from macro blocking... this has no real purpose for gaming...
The administrator has disabled public write access.

Grey dither (Only use pre-downscale) 2 months 1 week ago #5

Games do suffer from banding though, and also if someone managed to hook Reshade into a video player it would provide a version with a GUI instead of text editing the .hlsl version.
Last Edit: 2 months 1 week ago by crabshank.
The administrator has disabled public write access.

Grey dither (Only use pre-downscale) 2 months 1 week ago #6

If a game output is already banding, dither will not fix it. Dither is not debanding. Dither is meant to be used to PREVENT banding before it happens. If you dither over something that is already banded, you're just adding noise to the image and solving nothing.
The administrator has disabled public write access.
The following user(s) said Thank You: turgor128

Grey dither and crush (Only use pre-downscale) 2 months 5 days ago #7

I've optimised all the code apart from the dither rempping loop, I may come up with a solution when I re-write my remapping shader but this code is expensive anyway and IDK how much in the way of gains I can get.
The administrator has disabled public write access.