Tweaked dithering with standard deviation

This commit is contained in:
Robert Beckebans 2024-01-11 22:06:55 +01:00
parent 4bc81a1cd7
commit bbbb14159f
8 changed files with 270 additions and 182 deletions

View file

@ -611,8 +611,8 @@ void idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::AdjustFi
// RB begin
case SYSTEM_FIELD_RENDERMODE:
{
static const int numValues = 6;
static const int values[numValues] = { 0, 1, 2, 3, 4, 5 };
static const int numValues = 8;
static const int values[numValues] = { 0, 1, 2, 3, 4, 5, 6, 7 };
r_renderMode.SetInteger( AdjustOption( r_renderMode.GetInteger(), values, numValues, adjustAmount ) );
break;
}
@ -796,9 +796,9 @@ idSWFScriptVar idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings
{
"Doom 3",
"Commodore 64",
"Commodore 64 Highres",
"Commodore 64 Hi",
"Amstrad CPC 6128",
"Amstrad CPC 6128 Highres",
"Amstrad CPC 6128 Hi",
"Sega Genesis",
"Sega Genesis Highres",
"Sony PSX",

View file

@ -6143,7 +6143,8 @@ void idRenderBackend::PostProcess( const void* data )
renderProgManager.BindShader_PostProcess();
}
SetFragmentParm( RENDERPARM_JITTERTEXSCALE, jitterTexScale ); // rpWindowCoord
jitterTexScale[1] = r_retroDitherScale.GetFloat();
SetFragmentParm( RENDERPARM_JITTERTEXSCALE, jitterTexScale ); // rpJitterTexScale
float jitterTexOffset[4];
jitterTexOffset[0] = 1.0f / globalImages->blueNoiseImage256->GetUploadWidth();

View file

@ -1308,6 +1308,8 @@ enum RenderMode
RENDERMODE_PSX,
};
extern idCVar r_retroDitherScale;
extern idCVar r_renderMode;
extern idCVar image_pixelLook;
// RB end

View file

@ -302,6 +302,8 @@ idCVar r_useCRTPostFX( "r_useCRTPostFX", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVA
idCVar r_crtCurvature( "r_crtCurvature", "2", CVAR_RENDERER | CVAR_FLOAT, "rounded borders" );
idCVar r_crtVignette( "r_crtVignette", "0.8", CVAR_RENDERER | CVAR_FLOAT, "fading into the borders" );
idCVar r_retroDitherScale( "r_retroDitherScale", "0.3", CVAR_RENDERER | CVAR_FLOAT, "" );
idCVar r_renderMode( "r_renderMode", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "0 = Doom, 1 = Commodore 64, 2 = Commodore 64 Highres, 3 = Amstrad CPC 6128, 4 = Amstrad CPC 6128 Highres, 5 = Sega Genesis, 6 = Sega Genesis Highres, 7 = Sony PSX", 0, 7 );
// RB end

View file

@ -3,7 +3,7 @@
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2023 Robert Beckebans
Copyright (C) 2024 Robert Beckebans
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -54,26 +54,59 @@ struct PS_OUT
#define NUM_COLORS 16
float3 Average( float3 pal[NUM_COLORS] )
{
float3 sum = _float3( 0 );
for( int i = 0; i < NUM_COLORS; i++ )
{
sum += pal[i];
}
return sum / float( NUM_COLORS );
}
float3 Deviation( float3 pal[NUM_COLORS] )
{
float3 sum = _float3( 0 );
float3 avg = Average( pal );
for( int i = 0; i < NUM_COLORS; i++ )
{
sum += abs( pal[i] - avg );
}
return sum / float( NUM_COLORS );
}
// squared distance to avoid the sqrt of distance function
float ColorCompare( float3 a, float3 b )
{
float3 diff = b - a;
return dot( diff, diff );
}
// find nearest palette color using Euclidean distance
float3 LinearSearch( float3 c, float3 pal[NUM_COLORS] )
{
int idx = 0;
float nd = distance( c, pal[0] );
int index = 0;
float minDist = ColorCompare( c, pal[0] );
for( int i = 1; i < NUM_COLORS; i++ )
{
float d = distance( c, pal[i] );
float dist = ColorCompare( c, pal[i] );
if( d < nd )
if( dist < minDist )
{
nd = d;
idx = i;
minDist = dist;
index = i;
}
}
return pal[idx];
return pal[index];
}
/*
float3 GetClosest( float3 val1, float3 val2, float3 target )
{
if( distance( target, val1 ) >= distance( val2, target ) )
@ -138,47 +171,13 @@ float3 BinarySearch( float3 target, float3 pal[NUM_COLORS] )
// only single element left after search
return pal[mid];
}
*/
#define RGB(r, g, b) float3(float(r)/255.0, float(g)/255.0, float(b)/255.0)
void main( PS_IN fragment, out PS_OUT result )
{
float2 uv = ( fragment.texcoord0 );
float2 uvPixelated = floor( fragment.position.xy / RESOLUTION_DIVISOR ) * RESOLUTION_DIVISOR;
float3 quantizationPeriod = _float3( 1.0 / NUM_COLORS );
// get pixellated base color
float3 color = t_BaseColor.Sample( samp0, uvPixelated * rpWindowCoord.xy ).rgb;
#if 0
if( uv.y < 0.125 )
{
color = HSVToRGB( float3( uv.x, 1.0, uv.y * 8.0 ) );
color = floor( color * NUM_COLORS ) * ( 1.0 / ( NUM_COLORS - 1.0 ) );
//result.color = float4( color, 1.0 );
//return;
}
else if( uv.y < 0.1875 )
{
color = _float3( uv.x );
color = floor( color * NUM_COLORS ) * ( 1.0 / ( NUM_COLORS - 1.0 ) );
}
#endif
// add Bayer 8x8 dithering
float2 uvDither = uvPixelated;
//if( rpJitterTexScale.x > 1.0 )
{
uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / rpJitterTexScale.x );
}
float dither = DitherArray8x8( uvDither ) - 0.5;
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
// C64 colors http://unusedino.de/ec64/technical/misc/vic656x/colors/
#if 0
const float3 palette[NUM_COLORS] =
@ -223,6 +222,59 @@ void main( PS_IN fragment, out PS_OUT result )
};
#endif
float2 uv = ( fragment.texcoord0 );
float2 uvPixelated = floor( fragment.position.xy / RESOLUTION_DIVISOR ) * RESOLUTION_DIVISOR;
float3 quantizationPeriod = _float3( 1.0 / NUM_COLORS );
// get pixellated base color
float3 color = t_BaseColor.Sample( samp0, uvPixelated * rpWindowCoord.xy ).rgb;
float2 uvDither = uvPixelated;
//if( rpJitterTexScale.x > 1.0 )
{
uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / rpJitterTexScale.x );
}
float dither = DitherArray8x8( uvDither ) - 0.5;
#if 0
if( uv.y < 0.0625 )
{
color = HSVToRGB( float3( uv.x, 1.0, uv.y * 16.0 ) );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.125 )
{
// quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.0625 ) * 16.0 ) );
color = LinearSearch( color, palette );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.1875 )
{
// dithered quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.125 ) * 16.0 ) );
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
color = LinearSearch( color, palette );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.25 )
{
color = _float3( uv.x );
color = floor( color * NUM_COLORS ) * ( 1.0 / ( NUM_COLORS - 1.0 ) );
}
#endif
//color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
color.rgb += float3( dither, dither, dither ) * Deviation( palette ) * rpJitterTexScale.y;
// find closest color match from C64 color palette
color = LinearSearch( color.rgb, palette );
//color = float4( BinarySearch( color.rgb, palette ), 1.0 );

View file

@ -53,7 +53,7 @@ struct PS_OUT
#define RESOLUTION_DIVISOR 4.0
#define NUM_COLORS 32 // original 27
/*
float LinearTweak1( float c )
{
return ( c <= 0.04045 ) ? c / 12.92 : pow( ( c + 0.055 ) / 1.055, 1.4 );
@ -63,26 +63,58 @@ float3 LinearTweak3( float3 c )
{
return float3( Linear1( c.r ), Linear1( c.g ), Linear1( c.b ) );
}
*/
float3 Average( float3 pal[NUM_COLORS] )
{
float3 sum = _float3( 0 );
for( int i = 0; i < NUM_COLORS; i++ )
{
sum += pal[i];
}
return sum / float( NUM_COLORS );
}
float3 Deviation( float3 pal[NUM_COLORS] )
{
float3 sum = _float3( 0 );
float3 avg = Average( pal );
for( int i = 0; i < NUM_COLORS; i++ )
{
sum += abs( pal[i] - avg );
}
return sum / float( NUM_COLORS );
}
// squared distance to avoid the sqrt of distance function
float ColorCompare( float3 a, float3 b )
{
float3 diff = b - a;
return dot( diff, diff );
}
// find nearest palette color using Euclidean distance
float3 LinearSearch( float3 c, float3 pal[NUM_COLORS] )
{
int idx = 0;
float nd = distance( c, pal[0] );
int index = 0;
float minDist = ColorCompare( c, pal[0] );
for( int i = 1; i < NUM_COLORS; i++ )
{
//float d = distance( c, pal[i] );
float d = distance( c, ( pal[i] ) );
float dist = ColorCompare( c, pal[i] );
if( d < nd )
if( dist < minDist )
{
nd = d;
idx = i;
minDist = dist;
index = i;
}
}
return pal[idx];
return pal[index];
}
#define RGB(r, g, b) float3(float(r)/255.0, float(g)/255.0, float(b)/255.0)
@ -91,42 +123,7 @@ float3 LinearSearch( float3 c, float3 pal[NUM_COLORS] )
void main( PS_IN fragment, out PS_OUT result )
{
float2 uv = ( fragment.texcoord0 );
float2 uvPixelated = floor( fragment.position.xy / RESOLUTION_DIVISOR ) * RESOLUTION_DIVISOR;
float3 quantizationPeriod = _float3( 1.0 / NUM_COLORS );
// get pixellated base color
float3 color = t_BaseColor.Sample( samp0, uvPixelated * rpWindowCoord.xy ).rgb;
#if 0
if( uv.y < 0.125 )
{
color = HSVToRGB( float3( uv.x, 1.0, uv.y * 8.0 ) );
color = floor( color * NUM_COLORS ) * ( 1.0 / ( NUM_COLORS - 1.0 ) );
//result.color = float4( color, 1.0 );
//return;
}
else if( uv.y < 0.1875 )
{
color = _float3( uv.x );
color = floor( color * NUM_COLORS ) * ( 1.0 / ( NUM_COLORS - 1.0 ) );
}
#endif
// add Bayer 8x8 dithering
float2 uvDither = uvPixelated;
//if( rpJitterTexScale.x > 1.0 )
{
uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / rpJitterTexScale.x );
}
float dither = ( DitherArray8x8( uvDither ) - 0.5 ) * 1.0;
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
#if 1
// Amstrad CPC colors https://www.cpcwiki.eu/index.php/CPC_Palette
const float3 palette[NUM_COLORS] =
{
@ -158,29 +155,14 @@ void main( PS_IN fragment, out PS_OUT result )
RGB( 255, 255, 128 ), // pastel yellow
RGB( 255, 255, 255 ), // bright white
//RGB( 79, 69, 0 ), // brown
//RGB( 120, 120, 120 ), // dark grey
//RGB( 164, 215, 142 ), // grey
#if 0
RGB( 68, 68, 68 ), // dark grey
RGB( 80, 80, 80 ),
RGB( 108, 108, 108 ), // grey
RGB( 120, 120, 120 ),
RGB( 149, 149, 149 ), // light grey
#else
//RGB( 4, 4, 4 ), // black
#if 1
RGB( 16, 16, 16 ), // black
RGB( 0, 28, 28 ), // dark cyan
RGB( 128, 0, 255 ) * 0.9, // mauve
RGB( 111, 79, 37 ) * 1.2, // orange
//RGB( 149, 149, 149 ), // light grey
//RGB( 154, 210, 132 ), // light green
RGB( 112, 164, 178 ) * 1.3, // cyan
#endif
};
#elif 0
#elif 1
// Tweaked LOSPEC CPC BOY PALETTE which is less saturated by Arne Niklas Jansson
// https://lospec.com/palette-list/cpc-boy
@ -220,24 +202,14 @@ void main( PS_IN fragment, out PS_OUT result )
RGB( 36, 49, 55 ),
};
#elif 1
#elif 0
// NES 1 very good
// NES 1
// https://lospec.com/palette-list/nintendo-entertainment-system
const float3 palette[NUM_COLORS] = // 55
{
RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
//RGB( 0, 0, 0 ),
RGB( 252, 252, 252 ),
RGB( 248, 248, 248 ),
RGB( 188, 188, 188 ),
@ -360,6 +332,60 @@ void main( PS_IN fragment, out PS_OUT result )
#endif
float2 uv = ( fragment.texcoord0 );
float2 uvPixelated = floor( fragment.position.xy / RESOLUTION_DIVISOR ) * RESOLUTION_DIVISOR;
float3 quantizationPeriod = _float3( 1.0 / NUM_COLORS );
float3 quantDeviation = Deviation( palette );
// get pixellated base color
float3 color = t_BaseColor.Sample( samp0, uvPixelated * rpWindowCoord.xy ).rgb;
float2 uvDither = uvPixelated;
//if( rpJitterTexScale.x > 1.0 )
{
uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / rpJitterTexScale.x );
}
float dither = DitherArray8x8( uvDither ) - 0.5;
#if 0
if( uv.y < 0.0625 )
{
color = HSVToRGB( float3( uv.x, 1.0, uv.y * 16.0 ) );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.125 )
{
// quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.0625 ) * 16.0 ) );
color = LinearSearch( color, palette );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.1875 )
{
// dithered quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.125 ) * 16.0 ) );
color.rgb += float3( dither, dither, dither ) * quantDeviation * rpJitterTexScale.y;
color = LinearSearch( color, palette );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.25 )
{
color = _float3( uv.x );
color = floor( color * NUM_COLORS ) * ( 1.0 / ( NUM_COLORS - 1.0 ) );
}
#endif
//color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
color.rgb += float3( dither, dither, dither ) * quantDeviation * rpJitterTexScale.y;
// find closest color match from CPC color palette
color = LinearSearch( color.rgb, palette );

View file

@ -51,7 +51,6 @@ struct PS_OUT
#define RESOLUTION_DIVISOR 4.0
#define NUM_COLORS 64
#define Dithering_QuantizationSteps 8.0 // 8.0 = 2 ^ 3 quantization bits
float3 Quantize( float3 color, float3 period )
@ -79,26 +78,6 @@ float3 BlueNoise3( float2 n, float x )
return noise;
}
// find nearest palette color using Euclidean distance
float3 LinearSearch( float3 c, float3 pal[NUM_COLORS] )
{
int idx = 0;
float nd = distance( c, pal[0] );
for( int i = 1; i < NUM_COLORS; i++ )
{
float d = distance( c, pal[i] );
if( d < nd )
{
nd = d;
idx = i;
}
}
return pal[idx];
}
#define RGB(r, g, b) float3(float(r)/255.0, float(g)/255.0, float(b)/255.0)
void main( PS_IN fragment, out PS_OUT result )
@ -118,23 +97,6 @@ void main( PS_IN fragment, out PS_OUT result )
// get pixellated base color
float3 color = t_BaseColor.Sample( samp0, uvPixelated * rpWindowCoord.xy ).rgb;
#if 0
if( uv.y < 0.125 )
{
color = HSVToRGB( float3( uv.x, 1.0, uv.y * 8.0 ) );
color = Quantize( color, quantizationPeriod );
//result.color = float4( color, 1.0 );
//return;
}
else if( uv.y < 0.1875 )
{
color = _float3( uv.x );
color = Quantize( color, quantizationPeriod );
}
#endif
// add Bayer 8x8 dithering
float2 uvDither = uvPixelated;
//if( rpJitterTexScale.x > 1.0 )
{
@ -142,7 +104,42 @@ void main( PS_IN fragment, out PS_OUT result )
}
float dither = DitherArray8x8( uvDither ) - 0.5;
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
#if 0
if( uv.y < 0.0625 )
{
color = HSVToRGB( float3( uv.x, 1.0, uv.y * 16.0 ) );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.125 )
{
// quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.0625 ) * 16.0 ) );
color = Quantize( color, quantizationPeriod );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.1875 )
{
// dithered quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.125 ) * 16.0 ) );
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
color = Quantize( color, quantizationPeriod );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.25 )
{
color = _float3( uv.x );
color = Quantize( color, quantizationPeriod );
}
#endif
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;// * rpJitterTexScale.y;
// find closest color match from Sega Mega Drive color palette
color = Quantize( color, quantizationPeriod );

View file

@ -50,8 +50,8 @@ struct PS_OUT
// *INDENT-ON*
#define RESOLUTION_DIVISOR 4.0
#define Dithering_QuantizationSteps 32.0 // 8.0 = 2 ^ 3 quantization bits
#define RESOLUTION_DIVISOR 4.0
#define Dithering_QuantizationSteps 32.0 // 8.0 = 2 ^ 3 quantization bits
float3 Quantize( float3 color, float3 period )
{
@ -82,36 +82,44 @@ void main( PS_IN fragment, out PS_OUT result )
float dither = DitherArray8x8( uvDither ) - 0.5;
#if 0
if( uv.y < 0.05 )
if( uv.y < 0.0625 )
{
color = _float3( uv.x );
color = HSVToRGB( float3( uv.x, 1.0, uv.y * 16.0 ) );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.1 )
else if( uv.y < 0.125 )
{
// quantized signal
color = _float3( uv.x );
color = Quantize( color, quantizationPeriod );
}
else if( uv.y < 0.15 )
{
// quantized signal dithered
color = _float3( uv.x );
// quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.0625 ) * 16.0 ) );
color = Quantize( color, quantizationPeriod );
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.2 )
else if( uv.y < 0.1875 )
{
color.rgb = float3( dither, dither, dither ) * quantizationPeriod;
// dithered quantized
color = HSVToRGB( float3( uv.x, 1.0, ( uv.y - 0.125 ) * 16.0 ) );
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
color = Quantize( color, quantizationPeriod );
result.color = float4( color, 1.0 );
return;
}
else if( uv.y < 0.25 )
{
color = _float3( uv.x );
color = Quantize( color, quantizationPeriod );
}
else
#endif
{
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
// PSX color quantization
color = Quantize( color, quantizationPeriod );
}
color.rgb += float3( dither, dither, dither ) * quantizationPeriod;
// PSX color quantization with 15-bit
color = Quantize( color, quantizationPeriod );