From 779f38d84c23db158b448456f55338e98a283c7b Mon Sep 17 00:00:00 2001 From: Robert Beckebans Date: Sat, 30 Dec 2023 18:43:09 +0100 Subject: [PATCH] Tweaked PSX dithering --- neo/shaders/builtin/post/postprocess.ps.hlsl | 138 +----------------- .../builtin/post/retro_genesis.ps.hlsl | 27 ++-- neo/shaders/builtin/post/retro_ps1.ps.hlsl | 63 ++++++-- neo/shaders/global_inc.hlsl | 1 + 4 files changed, 69 insertions(+), 160 deletions(-) diff --git a/neo/shaders/builtin/post/postprocess.ps.hlsl b/neo/shaders/builtin/post/postprocess.ps.hlsl index 8684da9f..d0499027 100644 --- a/neo/shaders/builtin/post/postprocess.ps.hlsl +++ b/neo/shaders/builtin/post/postprocess.ps.hlsl @@ -72,8 +72,6 @@ struct PS_OUT #define Dithering_QuantizationSteps 16.0 // 8.0 = 2 ^ 3 quantization bits #define Dithering_NoiseBoost 1.0 #define Dithering_Wide 1.0 -#define DITHER_IN_LINEAR_SPACE 0 -#define DITHER_GENERATE_NOISE 0 float3 overlay( float3 a, float3 b ) { @@ -284,70 +282,9 @@ float3 BlueNoise3( float2 n, float x ) } -float Noise( float2 n, float x ) -{ - n += x; - -#if 1 - return frac( sin( dot( n.xy, float2( 12.9898, 78.233 ) ) ) * 43758.5453 ) * 2.0 - 1.0; -#else - //return BlueNoise( n, 55.0 ); - return BlueNoise( n, 1.0 ); - - //return InterleavedGradientNoise( n ) * 2.0 - 1.0; -#endif -} - -// Step 1 in generation of the dither source texture. -float Step1( float2 uv, float n ) -{ - float a = 1.0, b = 2.0, c = -12.0, t = 1.0; - - return ( 1.0 / ( a * 4.0 + b * 4.0 - c ) ) * ( - Noise( uv + float2( -1.0, -1.0 ) * t, n ) * a + - Noise( uv + float2( 0.0, -1.0 ) * t, n ) * b + - Noise( uv + float2( 1.0, -1.0 ) * t, n ) * a + - Noise( uv + float2( -1.0, 0.0 ) * t, n ) * b + - Noise( uv + float2( 0.0, 0.0 ) * t, n ) * c + - Noise( uv + float2( 1.0, 0.0 ) * t, n ) * b + - Noise( uv + float2( -1.0, 1.0 ) * t, n ) * a + - Noise( uv + float2( 0.0, 1.0 ) * t, n ) * b + - Noise( uv + float2( 1.0, 1.0 ) * t, n ) * a + - 0.0 ); -} - -// Step 2 in generation of the dither source texture. -float Step2( float2 uv, float n ) -{ - float a = 1.0, b = 2.0, c = -2.0, t = 1.0; - -#if DITHER_IN_LINEAR_SPACE - return ( 1.0 / ( a * 4.0 + b * 4.0 - c ) ) * ( -#else - return ( 4.0 / ( a * 4.0 + b * 4.0 - c ) ) * ( -#endif - Step1( uv + float2( -1.0, -1.0 ) * t, n ) * a + - Step1( uv + float2( 0.0, -1.0 ) * t, n ) * b + - Step1( uv + float2( 1.0, -1.0 ) * t, n ) * a + - Step1( uv + float2( -1.0, 0.0 ) * t, n ) * b + - Step1( uv + float2( 0.0, 0.0 ) * t, n ) * c + - Step1( uv + float2( 1.0, 0.0 ) * t, n ) * b + - Step1( uv + float2( -1.0, 1.0 ) * t, n ) * a + - Step1( uv + float2( 0.0, 1.0 ) * t, n ) * b + - Step1( uv + float2( 1.0, 1.0 ) * t, n ) * a + - 0.0 ); -} - // Used for stills. float3 Step3( float2 uv ) { -#if DITHER_GENERATE_NOISE - float a = Step2( uv, 0.07 ); - float b = Step2( uv, 0.11 ); - float c = Step2( uv, 0.13 ); - - return float3( a, b, c ); -#else float3 noise = BlueNoise3( uv, 0.0 ); #if 1 @@ -359,19 +296,11 @@ float3 Step3( float2 uv ) #endif return noise; -#endif } // Used for temporal dither. float3 Step3T( float2 uv ) { -#if DITHER_GENERATE_NOISE - float a = Step2( uv, 0.07 * fract( rpJitterTexOffset.z ) ); - float b = Step2( uv, 0.11 * fract( rpJitterTexOffset.z ) ); - float c = Step2( uv, 0.13 * fract( rpJitterTexOffset.z ) ); - - return float3( a, b, c ); -#else float3 noise = BlueNoise3( uv, 1.0 ); #if 1 @@ -383,7 +312,6 @@ float3 Step3T( float2 uv ) #endif return noise; -#endif } @@ -395,86 +323,32 @@ void DitheringPass( inout float4 fragColor, PS_IN fragment ) float3 color = fragColor.rgb; #if 0 -// BOTTOM: Show bands. if( uv2.y >= 0.975 ) { - // quantized signal - color = float3( uv2.x ); + // BOTTOM: Show bands. + color = _float3( uv2.x ); - // dithered still - //color = floor( color * Dithering_QuantizationSteps + Step3( uv ) * Dithering_NoiseBoost ) * ( 1.0 / ( Dithering_QuantizationSteps - 1.0 ) ); } else if( uv2.y >= 0.95 ) { // quantized signal - color = float3( uv2.x ); + color = _float3( uv2.x ); color = floor( color * Dithering_QuantizationSteps ) * ( 1.0 / ( Dithering_QuantizationSteps - 1.0 ) ); } else if( uv2.y >= 0.925 ) { // quantized signal dithered temporally - color = float3( uv2.x ); + color = _float3( uv2.x ); color = floor( color * Dithering_QuantizationSteps + Step3( uv ) * Dithering_NoiseBoost ) * ( 1.0 / ( Dithering_QuantizationSteps - 1.0 ) ); } - // TOP: Show dither texture. else if( uv2.y >= 0.9 ) { + // TOP: Show dither texture. color = Step3( uv ) * ( 0.25 * Dithering_NoiseBoost ) + 0.5; } else #endif { - -#if DITHER_IN_LINEAR_SPACE - - color = Linear3( color ); - - // Add grain in linear space. -#if 0 - // Slow more correct solutions. -#if 1 - // Too expensive. - // Helps understand the fast solutions. - float3 amount = Linear3( Srgb3( color ) + ( Dithering_NoiseBoost / Dithering_QuantizationSteps ) ) - color; -#else - // Less too expensive. - float luma = PhotoLuma( color ); - - // Implement this as a texture lookup table. - float amount = Linear1( Srgb1( luma ) + ( Dithering_NoiseBoost / Dithering_QuantizationSteps ) ) - luma; -#endif - -#else - // Fast solutions. -#if 1 - // Hack 1 (fastest). - // For HDR need saturate() around luma. - float luma = PhotoLuma( color ); - float amount = mix( - Linear1( Dithering_NoiseBoost / Dithering_QuantizationSteps ), - Linear1( ( Dithering_NoiseBoost / Dithering_QuantizationSteps ) + 1.0 ) - 1.0, - luma ); -#else - // Hack 2 (slower?). - // For HDR need saturate() around color in mix(). - float3 amount = mix( - float3( Linear1( Dithering_NoiseBoost / Dithering_QuantizationSteps ) ), - float3( Linear1( ( Dithering_NoiseBoost / Dithering_QuantizationSteps ) + 1.0 ) - 1.0 ), - color ); -#endif - -#endif - color += Step3T( uv ) * amount;// * Dithering_NoiseBoost; - - // The following represents hardware linear->sRGB xform - // which happens on sRGB formatted render targets, - // except using a lot less bits/pixel. - color = max( float3( 0.0 ), color ); - color = Srgb3( color ); - color = floor( color * Dithering_QuantizationSteps ) * ( 1.0 / ( Dithering_QuantizationSteps - 1.0 ) ); - -#else - #if 0 if( uv2.x <= 0.5 ) { @@ -486,8 +360,6 @@ void DitheringPass( inout float4 fragColor, PS_IN fragment ) { color = floor( 0.5 + color * ( Dithering_QuantizationSteps + Dithering_Wide - 1.0 ) + ( -Dithering_Wide * 0.5 ) + Step3T( uv ) * ( Dithering_Wide ) ) * ( 1.0 / ( Dithering_QuantizationSteps - 1.0 ) ); } -#endif - } fragColor.rgb = color; diff --git a/neo/shaders/builtin/post/retro_genesis.ps.hlsl b/neo/shaders/builtin/post/retro_genesis.ps.hlsl index 9e3c4db1..fcab69be 100644 --- a/neo/shaders/builtin/post/retro_genesis.ps.hlsl +++ b/neo/shaders/builtin/post/retro_genesis.ps.hlsl @@ -50,8 +50,16 @@ struct PS_OUT // *INDENT-ON* -#define RESOLUTION_DIVISOR 4.0 -#define NUM_COLORS 64 +#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 ) +{ + return floor( color * Dithering_QuantizationSteps ) * ( 1.0 / ( Dithering_QuantizationSteps - 1.0 ) ); + + //return floor( ( color + period / 2.0 ) / period ) * period; +} // find nearest palette color using Euclidean distance @@ -74,11 +82,6 @@ float3 LinearSearch( float3 c, float3 pal[NUM_COLORS] ) return pal[idx]; } -float Quantize( float inp, float period ) -{ - return floor( ( inp + period / 2.0 ) / period ) * period; -} - #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 ) @@ -92,23 +95,19 @@ void main( PS_IN fragment, out PS_OUT result ) // 8 * 8 * 8 = 512 colors // although only 61 colors were available on the screen at the same time but we ignore this for now - const int quantizationSteps = 8; + const int quantizationSteps = Dithering_QuantizationSteps; float3 quantizationPeriod = _float3( 1.0 / ( quantizationSteps - 1 ) ); // get pixellated base color float3 color = t_BaseColor.Sample( samp0, uvPixellated * rpWindowCoord.xy ).rgb; // add Bayer 8x8 dithering - float2 uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / 1.0 ); + float2 uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / 2.0 ); float dither = DitherArray8x8( uvPixellated ) - 0.5; color.rgb += float3( dither, dither, dither ) * quantizationPeriod; // find closest color match from Sega Mega Drive color palette - color = float3( - Quantize( color.r, quantizationPeriod.r ), - Quantize( color.g, quantizationPeriod.g ), - Quantize( color.b, quantizationPeriod.b ) - ); + color = Quantize( color, quantizationPeriod ); //color = LinearSearch( color.rgb, palette ); //color = float4( BinarySearch( color.rgb, palette ), 1.0 ); diff --git a/neo/shaders/builtin/post/retro_ps1.ps.hlsl b/neo/shaders/builtin/post/retro_ps1.ps.hlsl index d475f3dc..5684521e 100644 --- a/neo/shaders/builtin/post/retro_ps1.ps.hlsl +++ b/neo/shaders/builtin/post/retro_ps1.ps.hlsl @@ -51,12 +51,16 @@ struct PS_OUT #define RESOLUTION_DIVISOR 4.0 +#define Dithering_QuantizationSteps 32.0 // 8.0 = 2 ^ 3 quantization bits - -float Quantize( float inp, float period ) +float3 Quantize( float3 color, float3 period ) { - return floor( ( inp + period / 2.0 ) / period ) * period; + return floor( color * Dithering_QuantizationSteps ) * ( 1.0 / ( Dithering_QuantizationSteps - 1.0 ) ); + + //return floor( ( color + period / 2.0 ) / period ) * period; } + + void main( PS_IN fragment, out PS_OUT result ) { float2 uv = ( fragment.texcoord0 ); @@ -66,23 +70,56 @@ void main( PS_IN fragment, out PS_OUT result ) // 2^5 = 32 // 32 * 32 * 32 = 32768 colors - const int quantizationSteps = 32; + const int quantizationSteps = Dithering_QuantizationSteps; float3 quantizationPeriod = _float3( 1.0 / ( quantizationSteps - 1 ) ); // get pixellated base color float3 color = t_BaseColor.Sample( samp0, uvPixellated * rpWindowCoord.xy ).rgb; // add Bayer 8x8 dithering - float2 uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / 1.0 ); - float dither = DitherArray8x8( uvDither ) - 0.5; - color.rgb += float3( dither, dither, dither ) * quantizationPeriod; +#if 0 + // this looks awesome with 3 bits per color channel + float2 uvDither = fragment.position.xy / ( RESOLUTION_DIVISOR / 2.0 ); +#else + // more faithful to the PSX look + float2 uvDither = fragment.position.xy / RESOLUTION_DIVISOR; +#endif + + float dither = DitherArray8x8( uvDither ) - 0.5; + +#if 0 + if( uv.y < 0.05 ) + { + color = _float3( uv.x ); + } + else if( uv.y < 0.1 ) + { + // quantized signal + color = _float3( uv.x ); + color = Quantize( color, quantizationPeriod ); + } + else if( uv.y < 0.15 ) + { + // quantized signal dithered + color = _float3( uv.x ); + color = Quantize( color, quantizationPeriod ); + + color.rgb += float3( dither, dither, dither ) * quantizationPeriod; + } + else if( uv.y < 0.2 ) + { + color.rgb = float3( dither, dither, dither ) * quantizationPeriod; + } + else +#endif + { + color.rgb += float3( dither, dither, dither ) * quantizationPeriod; + + // PSX color quantization + color = Quantize( color, quantizationPeriod ); + } + - // PSX color quantization - color = float3( - Quantize( color.r, quantizationPeriod.r ), - Quantize( color.g, quantizationPeriod.g ), - Quantize( color.b, quantizationPeriod.b ) - ); //color = t_BaseColor.Sample( samp0, uv ).rgb; diff --git a/neo/shaders/global_inc.hlsl b/neo/shaders/global_inc.hlsl index c7712c92..70a09fec 100644 --- a/neo/shaders/global_inc.hlsl +++ b/neo/shaders/global_inc.hlsl @@ -366,6 +366,7 @@ float rand( float2 co ) #define _float3( x ) float3( x, x, x ) #define _float4( x ) float4( x, x, x, x ) #define _int2( x ) int2( x, x ) +#define _int3( x ) int3( x, x, x ) #define vec2 float2 #define vec3 float3 #define vec4 float4