Optimized Filmic Dithering with precalculated Blue Noise

This commit is contained in:
Robert Beckebans 2020-05-10 22:10:32 +02:00
parent 9931fab681
commit 99235ec92c
7 changed files with 66058 additions and 92 deletions

View file

@ -285,3 +285,17 @@ float InterleavedGradientNoise( float2 uv )
return rnd;
}
// RB: the golden ratio is useful to animate Blue noise
const float c_goldenRatioConjugate = 0.61803398875;
// RB: very efficient white noise without sine https://www.shadertoy.com/view/4djSRW
#define HASHSCALE3 float3(443.897, 441.423, 437.195)
float3 Hash33( float3 p3 )
{
p3 = fract( p3 * HASHSCALE3 );
p3 += dot( p3, p3.yxz + 19.19 );
return fract( ( p3.xxy + p3.yxx ) * p3.zyx );
}

View file

@ -63,11 +63,13 @@ struct PS_OUT
float BlueNoise( float2 n, float x )
{
float noise = tex2D( samp6, ( n.xy / 256.0 ) ).r;
float2 uv = n.xy * rpJitterTexOffset.xy;
noise = fract( noise + 0.61803398875 * rpJitterTexOffset.z * x );
float noise = tex2D( samp6, uv ).r;
noise = RemapNoiseTriErp( noise );
noise = fract( noise + c_goldenRatioConjugate * rpJitterTexOffset.w * x );
//noise = RemapNoiseTriErp( noise );
//noise = noise * 2.0 - 1.0;
@ -309,7 +311,7 @@ void main( PS_IN fragment, out PS_OUT result )
//float4 jitterTC = ( fragment.position * rpScreenCorrectionFactor ) + rpJitterTexOffset;
//float random = tex2D( samp6, jitterTC.xy ).x;
float random = BlueNoise( fragment.position.xy * 1.0, 100.0 );
float random = BlueNoise( fragment.position.xy, 100.0 );
//float random = InterleavedGradientNoise( fragment.position.xy );
@ -372,6 +374,7 @@ void main( PS_IN fragment, out PS_OUT result )
half3 specularColor = specMapSRGB.rgb; // RB: should be linear but it looks too flat
#endif
//diffuseColor = half3( 1.0 );
// RB: compensate r_lightScale 3 and the division of Pi
//lambert *= 1.3;

View file

@ -46,7 +46,7 @@ struct PS_OUT
};
// *INDENT-ON*
#define USE_CHROMATIC_ABERRATION 1
#define USE_CHROMATIC_ABERRATION 0
#define Chromatic_Amount 0.075
#define USE_TECHNICOLOR 0 // [0 or 1]
@ -238,7 +238,7 @@ float PhotoLuma( float3 c )
float BlueNoise( float2 n, float x )
{
float noise = tex2D( samp1, ( n.xy / 512.0 ) * 1.0 ).r;
float noise = tex2D( samp1, ( n.xy * rpJitterTexOffset.xy ) * 1.0 ).r;
noise = fract( noise + 0.61803398875 * rpJitterTexOffset.z * x );
@ -253,15 +253,17 @@ float BlueNoise( float2 n, float x )
float3 BlueNoise3( float2 n, float x )
{
float3 noise = tex2D( samp1, ( n.xy / 512.0 ) * 1.0 ).rgb;
float2 uv = n.xy * rpJitterTexOffset.xy;
noise = fract( noise + 0.61803398875 * rpJitterTexOffset.z * x );
float3 noise = tex2D( samp1, uv ).rgb;
noise.x = RemapNoiseTriErp( noise.x );
noise.y = RemapNoiseTriErp( noise.y );
noise.z = RemapNoiseTriErp( noise.z );
noise = fract( noise + c_goldenRatioConjugate * rpJitterTexOffset.w * x );
noise = noise * 2.0 - 1.0;
//noise.x = RemapNoiseTriErp( noise.x );
//noise.y = RemapNoiseTriErp( noise.y );
//noise.z = RemapNoiseTriErp( noise.z );
//noise = noise * 2.0 - 1.0;
return noise;
}
@ -320,44 +322,44 @@ float Step2( float2 uv, float n )
// Used for stills.
float3 Step3( float2 uv )
{
#if 1
#if 0
float a = Step2( uv, 0.07 );
float b = Step2( uv, 0.11 );
float c = Step2( uv, 0.13 );
return float3( a, b, c );
#else
//float a = BlueNoise( uv, 0.07 );
//float b = BlueNoise( uv, 0.11 );
//float c = BlueNoise( uv, 0.13 );
float3 noise = BlueNoise3( uv, 0.0 );
//float a = 1.0, b = 2.0, c = -12.0;
//return ( 1.0 / ( a * 4.0 + b * 4.0 - c ) ) * float3( BlueNoise( uv, 0.0 ) );
return BlueNoise3( uv, 0.0 );
noise.x = RemapNoiseTriErp( noise.x );
noise.y = RemapNoiseTriErp( noise.y );
noise.z = RemapNoiseTriErp( noise.z );
noise = noise * 2.0 - 1.0;
return noise;
#endif
}
// Used for temporal dither.
float3 Step3T( float2 uv )
{
#if 1
#if 0
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
float a = BlueNoise( uv + 0.07, 1.0 );
float b = BlueNoise( uv + 0.11, 1.0 );
float c = BlueNoise( uv + 0.13, 1.0 );
float3 noise = BlueNoise3( uv, 1.0 );
//return BlueNoise3( uv + 0.07, 1.0 );
return float3( a, b, c );
noise.x = RemapNoiseTriErp( noise.x );
noise.y = RemapNoiseTriErp( noise.y );
noise.z = RemapNoiseTriErp( noise.z );
//float a = 1.0, b = 2.0, c = -2.0, d = -12.0;
noise = noise * 2.0 - 1.0;
//float3 step2 = ( 1.0 / ( a * 4.0 + b * 4.0 - c ) ) * BlueNoise3( uv, 55.0 );
//return step2 * ( 1.0 / ( a * 4.0 + b * 4.0 - d ) ) * BlueNoise3( uv, 55.0 );
return noise;
#endif
}
@ -368,12 +370,10 @@ void DitheringPass( inout float4 fragColor )
{
float2 uv = fragment.position.xy * 1.0;
float2 uv2 = fragment.texcoord0;
//float2 uv3 = float2( uv2.x, 1.0 - uv2.y );
float3 color = fragColor.rgb;
//float3 color = tex2D(samp0, uv2).rgb;
#if 0
#if 1
// BOTTOM: Show bands.
if( uv2.y >= 0.975 )
{
@ -450,6 +450,205 @@ void DitheringPass( inout float4 fragColor )
#define ANIMATE_NOISE 1
#define TARGET_BITS 4 // 2^3 = 8 dithered to this many bits
#define DITHER_IN_LINEAR_SPACE 0
//----------------------------------------------------------------------------------------
/*
Items of note!
* The blue noise texture sampling should be set to "nearest" (not mip map!) and repeat
* you should calculate the uv to use based on the pixel coordinate and the size of the blue noise texture.
* aka you should tile the blue noise texture across the screen.
* blue noise actually tiles really well unlike white noise.
* A blue noise texture is "low discrepancy over space" which means there are fewer visible patterns than white noise
* it also gives more even coverage vs white noise. no clumps or voids.
* In an attempt to make it also blue noise over time, you can add the golden ratio and frac it.
* that makes it lower discrepancy over time, but makes it less good over space.
* thanks to r4unit for that tip! https://twitter.com/R4_Unit
* Animating the noise in this demo makes the noise basically disappear imo, it's really nice!
For more information:
What the heck is blue nois:
https://blog.demofox.org/2018/01/30/what-the-heck-is-blue-noise/
Low discrepancy sequences:
https://blog.demofox.org/2017/05/29/when-random-numbers-are-too-random-low-discrepancy-sequences/
You can get your own blue noise textures here:
http://momentsingraphics.de/?p=127
*/
void DitheringPassDemoFox( inout float4 fragColor )
{
// texture color
float2 uv = fragment.position.xy;
float2 uv2 = fragment.texcoord0;
float3 fg = fragColor.rgb;
float3 color = fg;
#if 1
// TOP: show bands
if( uv2.y >= 0.975 )
{
color = float3( uv2.x );
color = floor( color * STEPS + Step3( uv ) * 4.0 ) * ( 1.0 / ( STEPS - 1.0 ) );
}
else if( uv2.y >= 0.95 )
{
color = float3( uv2.x );
color = floor( color * STEPS ) * ( 1.0 / ( STEPS - 1.0 ) );
}
else if( uv2.y >= 0.925 )
{
color = float3( uv2.x );
color = floor( color * STEPS + Step3T( uv ) * 4.0 ) * ( 1.0 / ( STEPS - 1.0 ) );
}
// BOTTOM: show dither texture
else if( uv2.y >= 0.9 )
{
color = Step3( uv ).rgb;
}
else
#endif
{
// right of the screen is dithered using white noise to a fewer number of bits per color channel
if( uv2.x > 3.0 / 4.0 )
{
// get white noise "random" number
#if ANIMATE_NOISE
float3 whiteNoise = Hash33( float3( uv, rpJitterTexOffset.w / 256.0 ) );
#else
float3 whiteNoise = Hash33( float3( uv, 0.0 ) );
#endif
// dither to the specified number of bits, using sRGB conversions if desired
#if DITHER_IN_LINEAR_SPACE
fg = pow( fg, float3( 2.2 ) );
#endif
float scale = exp2( float( TARGET_BITS ) ) - 1.0;
color = floor( fg * scale + whiteNoise ) / scale;
#if DITHER_IN_LINEAR_SPACE
color = pow( col, 1.0 / float3( 2.2 ) );
#endif
}
// middle right of the screen is dithered using blue noise to a fewer number of bits per color channel
else if( uv2.x > 2.0 / 4.0 )
{
float3 blueNoise = BlueNoise3( uv, 1.0 );
// dither to the specified number of bits, using sRGB conversions if desired
#if DITHER_IN_LINEAR_SPACE
fg = pow( fg, float3( 2.2 ) );
#endif
float scale = exp2( float( TARGET_BITS ) ) - 1.0;
color = floor( fg * scale + blueNoise ) / scale;
#if DITHER_IN_LINEAR_SPACE
color = pow( color, 1.0 / float3( 2.2 ) );
#endif
}
// middle left of the screen is quantized but not dithered
else if( uv2.x > 1.0 / 4.0 )
{
// dither to the specified number of bits, using sRGB conversions if desired
#if DITHER_IN_LINEAR_SPACE
fg = pow( fg, float3( 2.2 ) );
#endif
float scale = exp2( float( TARGET_BITS ) ) - 1.0;
color = floor( fg * scale + 0.5f ) / scale;
#if DITHER_IN_LINEAR_SPACE
color = pow( color, 1.0 / float3( 2.2 ) );
#endif
}
// left side of screen is left alone for comparison
else
{
color = fg;
}
if( abs( uv2.x - 1.0 / 4.0 ) < 0.001 || abs( uv2.x - 2.0 / 4.0 ) < 0.001 || abs( uv2.x - 3.0 / 4.0 ) < 0.001 )
{
color = float3( 0.0, 1.0, 0.0 );
}
}
fragColor.rgb = color;
}
void DitheringPassSlim( inout float4 fragColor )
{
// texture color
float2 uv = fragment.position.xy;
float2 uv2 = fragment.texcoord0;
float3 fg = fragColor.rgb;
float3 color = fg;
#if 0
if( uv2.y >= 0.975 )
{
// source signal
color = float3( uv2.x );
}
else if( uv2.y >= 0.95 )
{
color = float3( uv2.x );
// quantized signal
float scale = exp2( float( TARGET_BITS ) ) - 1.0;
color = floor( color * scale + 0.0f ) / scale;
}
else if( uv2.y >= 0.925 )
{
// dithered quantized signal
color = float3( uv2.x );
color = floor( color * STEPS + Step3T( uv ) * 4.0 ) * ( 1.0 / ( STEPS - 1.0 ) );
}
else if( uv2.y >= 0.9 )
{
// dither texture
color = Step3( uv ).rgb;
}
else
#endif
{
float3 noise = BlueNoise3( uv, 1.0 );
//noise.x = RemapNoiseTriErp( noise.x );
//noise.y = RemapNoiseTriErp( noise.y );
//noise.z = RemapNoiseTriErp( noise.z );
// dither to the specified number of bits, using sRGB conversions if desired
#if DITHER_IN_LINEAR_SPACE
fg = pow( fg, float3( 2.2 ) );
#endif
float scale = exp2( float( TARGET_BITS ) ) - 1.0;
color = floor( fg * scale + noise ) / scale;
#if DITHER_IN_LINEAR_SPACE
color = pow( color, 1.0 / float3( 2.2 ) );
#endif
}
fragColor.rgb = color;
}
void main( PS_IN fragment, out PS_OUT result )
{
float2 tCoords = fragment.texcoord0;
@ -470,7 +669,7 @@ void main( PS_IN fragment, out PS_OUT result )
#endif
#if USE_DITHERING
DitheringPass( color );
DitheringPassSlim( color );
#endif
result.color = color;

File diff suppressed because it is too large Load diff

View file

@ -36,8 +36,8 @@ If you have questions concerning this license or the applicable additional terms
#include "SMAA/AreaTex.h"
#include "SMAA/SearchTex.h"
#include "Image_brdfLut.h"
#include "Image_blueNoiseVC_1M.h" // 256^2 R8 data
//#include "Image_blueNoiseVC_2.h" // 512^2 RGB8 data
//#include "Image_blueNoiseVC_1M.h" // 256^2 R8 data
#include "Image_blueNoiseVC_2.h" // 512^2 RGB8 data
#define DEFAULT_SIZE 16
@ -695,7 +695,7 @@ static void R_CreateBlueNoise256Image( idImage* image )
{
for( int y = 0; y < BLUENOISE_TEX_HEIGHT; y++ )
{
#if 0
#if 1
data[x][y][0] = blueNoiseTexBytes[ y * BLUENOISE_TEX_PITCH + x * 3 + 0 ];
data[x][y][1] = blueNoiseTexBytes[ y * BLUENOISE_TEX_PITCH + x * 3 + 1 ];
data[x][y][2] = blueNoiseTexBytes[ y * BLUENOISE_TEX_PITCH + x * 3 + 2 ];

View file

@ -1681,18 +1681,16 @@ void idRenderBackend::RenderInteractions( const drawSurf_t* surfList, const view
SetFragmentParm( RENDERPARM_JITTERTEXSCALE, jitterTexScale ); // rpJitterTexScale
float jitterTexOffset[4];
jitterTexOffset[0] = 1.0f / globalImages->blueNoiseImage256->GetUploadWidth();
jitterTexOffset[1] = 1.0f / globalImages->blueNoiseImage256->GetUploadWidth();
if( r_shadowMapRandomizeJitter.GetBool() )
{
jitterTexOffset[0] = ( rand() & 255 ) / 255.0;
jitterTexOffset[1] = ( rand() & 255 ) / 255.0;
jitterTexOffset[2] = Sys_Milliseconds() / 1000.0f;
jitterTexOffset[3] = tr.frameCount % 64;
}
else
{
jitterTexOffset[0] = 0;
jitterTexOffset[1] = 0;
jitterTexOffset[2] = 0.0f;
jitterTexOffset[3] = 0.0f;
}
@ -4990,7 +4988,6 @@ void idRenderBackend::DrawScreenSpaceAmbientOcclusion( const viewDef_t* _viewDef
#endif
SetVertexParms( RENDERPARM_MODELMATRIX_X, viewDef->unprojectionToCameraRenderMatrix[0], 4 );
const static int BLUENOISE_SIZE = 256;
const float jitterSampleScale = 1.0f;
float jitterTexScale[4];
@ -5001,13 +4998,13 @@ void idRenderBackend::DrawScreenSpaceAmbientOcclusion( const viewDef_t* _viewDef
SetFragmentParm( RENDERPARM_JITTERTEXSCALE, jitterTexScale ); // rpJitterTexScale
float jitterTexOffset[4];
jitterTexOffset[0] = 1.0f / BLUENOISE_SIZE;
jitterTexOffset[1] = 1.0f / BLUENOISE_SIZE;
jitterTexOffset[0] = 1.0f / globalImages->blueNoiseImage256->GetUploadWidth();
jitterTexOffset[1] = 1.0f / globalImages->blueNoiseImage256->GetUploadHeight();
if( r_shadowMapRandomizeJitter.GetBool() )
{
jitterTexOffset[2] = Sys_Milliseconds() / 1000.0f;
jitterTexOffset[3] = tr.frameCount % 64;
jitterTexOffset[3] = tr.frameCount % 256;
}
else
{
@ -6147,29 +6144,17 @@ void idRenderBackend::PostProcess( const void* data )
renderProgManager.BindShader_PostProcess();
const static int BLUENOISE_SIZE = 256;
// screen power of two correction factor
float screenCorrectionParm[4];
screenCorrectionParm[0] = 1.0f / BLUENOISE_SIZE;
screenCorrectionParm[1] = 1.0f / BLUENOISE_SIZE;
screenCorrectionParm[2] = 1.0f;
screenCorrectionParm[3] = 1.0f;
SetFragmentParm( RENDERPARM_SCREENCORRECTIONFACTOR, screenCorrectionParm ); // rpScreenCorrectionFactor
float jitterTexOffset[4];
jitterTexOffset[0] = 1.0f / globalImages->blueNoiseImage256->GetUploadWidth();
jitterTexOffset[1] = 1.0f / globalImages->blueNoiseImage256->GetUploadHeight();
if( r_shadowMapRandomizeJitter.GetBool() )
{
jitterTexOffset[0] = ( rand() & 255 ) / 255.0;
jitterTexOffset[1] = ( rand() & 255 ) / 255.0;
jitterTexOffset[2] = Sys_Milliseconds() / 1000.0f;
jitterTexOffset[3] = tr.frameCount % 64;
jitterTexOffset[3] = tr.frameCount % 256;
}
else
{
jitterTexOffset[0] = 0;
jitterTexOffset[1] = 0;
jitterTexOffset[2] = 0.0f;
jitterTexOffset[3] = 0.0f;
}

View file

@ -299,6 +299,20 @@ static const cgShaderDef_t cg_renderprogs[] =
" return rnd;\n"
"}\n"
"\n"
"// RB: the golden ratio is useful to animate Blue noise\n"
"const float c_goldenRatioConjugate = 0.61803398875;\n"
"\n"
"\n"
"// RB: very efficient white noise without sine https://www.shadertoy.com/view/4djSRW\n"
"#define HASHSCALE3 float3(443.897, 441.423, 437.195)\n"
"\n"
"float3 Hash33( float3 p3 )\n"
"{\n"
" p3 = fract( p3 * HASHSCALE3 );\n"
" p3 += dot( p3, p3.yxz + 19.19 );\n"
" return fract( ( p3.xxy + p3.yxx ) * p3.zyx );\n"
"}\n"
"\n"
"\n"
},
@ -9851,11 +9865,13 @@ static const cgShaderDef_t cg_renderprogs[] =
"\n"
"float BlueNoise( float2 n, float x )\n"
"{\n"
" float noise = tex2D( samp6, ( n.xy / 256.0 ) ).r;\n"
" float2 uv = n.xy * rpJitterTexOffset.xy;\n"
"\n"
" noise = fract( noise + 0.61803398875 * rpJitterTexOffset.z * x );\n"
" float noise = tex2D( samp6, uv ).r;\n"
"\n"
" noise = RemapNoiseTriErp( noise );\n"
" noise = fract( noise + c_goldenRatioConjugate * rpJitterTexOffset.w * x );\n"
"\n"
" //noise = RemapNoiseTriErp( noise );\n"
"\n"
" //noise = noise * 2.0 - 1.0;\n"
"\n"
@ -10097,7 +10113,7 @@ static const cgShaderDef_t cg_renderprogs[] =
" //float4 jitterTC = ( fragment.position * rpScreenCorrectionFactor ) + rpJitterTexOffset;\n"
" //float random = tex2D( samp6, jitterTC.xy ).x;\n"
"\n"
" float random = BlueNoise( fragment.position.xy * 1.0, 100.0 );\n"
" float random = BlueNoise( fragment.position.xy, 100.0 );\n"
"\n"
" //float random = InterleavedGradientNoise( fragment.position.xy );\n"
"\n"
@ -10160,6 +10176,7 @@ static const cgShaderDef_t cg_renderprogs[] =
" half3 specularColor = specMapSRGB.rgb; // RB: should be linear but it looks too flat\n"
"#endif\n"
"\n"
" //diffuseColor = half3( 1.0 );\n"
"\n"
" // RB: compensate r_lightScale 3 and the division of Pi\n"
" //lambert *= 1.3;\n"
@ -10638,7 +10655,7 @@ static const cgShaderDef_t cg_renderprogs[] =
"};\n"
"// *INDENT-ON*\n"
"\n"
"#define USE_CHROMATIC_ABERRATION 1\n"
"#define USE_CHROMATIC_ABERRATION 0\n"
"#define Chromatic_Amount 0.075\n"
"\n"
"#define USE_TECHNICOLOR 0 // [0 or 1]\n"
@ -10830,7 +10847,7 @@ static const cgShaderDef_t cg_renderprogs[] =
"\n"
"float BlueNoise( float2 n, float x )\n"
"{\n"
" float noise = tex2D( samp1, ( n.xy / 512.0 ) * 1.0 ).r;\n"
" float noise = tex2D( samp1, ( n.xy * rpJitterTexOffset.xy ) * 1.0 ).r;\n"
"\n"
" noise = fract( noise + 0.61803398875 * rpJitterTexOffset.z * x );\n"
"\n"
@ -10845,15 +10862,17 @@ static const cgShaderDef_t cg_renderprogs[] =
"\n"
"float3 BlueNoise3( float2 n, float x )\n"
"{\n"
" float3 noise = tex2D( samp1, ( n.xy / 512.0 ) * 1.0 ).rgb;\n"
" float2 uv = n.xy * rpJitterTexOffset.xy;\n"
"\n"
" noise = fract( noise + 0.61803398875 * rpJitterTexOffset.z * x );\n"
" float3 noise = tex2D( samp1, uv ).rgb;\n"
"\n"
" noise.x = RemapNoiseTriErp( noise.x );\n"
" noise.y = RemapNoiseTriErp( noise.y );\n"
" noise.z = RemapNoiseTriErp( noise.z );\n"
" noise = fract( noise + c_goldenRatioConjugate * rpJitterTexOffset.w * x );\n"
"\n"
" noise = noise * 2.0 - 1.0;\n"
" //noise.x = RemapNoiseTriErp( noise.x );\n"
" //noise.y = RemapNoiseTriErp( noise.y );\n"
" //noise.z = RemapNoiseTriErp( noise.z );\n"
"\n"
" //noise = noise * 2.0 - 1.0;\n"
"\n"
" return noise;\n"
"}\n"
@ -10912,44 +10931,44 @@ static const cgShaderDef_t cg_renderprogs[] =
"// Used for stills.\n"
"float3 Step3( float2 uv )\n"
"{\n"
"#if 1\n"
"#if 0\n"
" float a = Step2( uv, 0.07 );\n"
" float b = Step2( uv, 0.11 );\n"
" float c = Step2( uv, 0.13 );\n"
"\n"
" return float3( a, b, c );\n"
"#else\n"
" //float a = BlueNoise( uv, 0.07 );\n"
" //float b = BlueNoise( uv, 0.11 );\n"
" //float c = BlueNoise( uv, 0.13 );\n"
" float3 noise = BlueNoise3( uv, 0.0 );\n"
"\n"
" //float a = 1.0, b = 2.0, c = -12.0;\n"
" //return ( 1.0 / ( a * 4.0 + b * 4.0 - c ) ) * float3( BlueNoise( uv, 0.0 ) );\n"
" return BlueNoise3( uv, 0.0 );\n"
" noise.x = RemapNoiseTriErp( noise.x );\n"
" noise.y = RemapNoiseTriErp( noise.y );\n"
" noise.z = RemapNoiseTriErp( noise.z );\n"
"\n"
" noise = noise * 2.0 - 1.0;\n"
"\n"
" return noise;\n"
"#endif\n"
"}\n"
"\n"
"// Used for temporal dither.\n"
"float3 Step3T( float2 uv )\n"
"{\n"
"#if 1\n"
"#if 0\n"
" float a = Step2( uv, 0.07 * fract( rpJitterTexOffset.z ) );\n"
" float b = Step2( uv, 0.11 * fract( rpJitterTexOffset.z ) );\n"
" float c = Step2( uv, 0.13 * fract( rpJitterTexOffset.z ) );\n"
"\n"
" return float3( a, b, c );\n"
"#else\n"
" float a = BlueNoise( uv + 0.07, 1.0 );\n"
" float b = BlueNoise( uv + 0.11, 1.0 );\n"
" float c = BlueNoise( uv + 0.13, 1.0 );\n"
" float3 noise = BlueNoise3( uv, 1.0 );\n"
"\n"
" //return BlueNoise3( uv + 0.07, 1.0 );\n"
" return float3( a, b, c );\n"
" noise.x = RemapNoiseTriErp( noise.x );\n"
" noise.y = RemapNoiseTriErp( noise.y );\n"
" noise.z = RemapNoiseTriErp( noise.z );\n"
"\n"
" //float a = 1.0, b = 2.0, c = -2.0, d = -12.0;\n"
" noise = noise * 2.0 - 1.0;\n"
"\n"
" //float3 step2 = ( 1.0 / ( a * 4.0 + b * 4.0 - c ) ) * BlueNoise3( uv, 55.0 );\n"
" //return step2 * ( 1.0 / ( a * 4.0 + b * 4.0 - d ) ) * BlueNoise3( uv, 55.0 );\n"
" return noise;\n"
"#endif\n"
"}\n"
"\n"
@ -10960,12 +10979,10 @@ static const cgShaderDef_t cg_renderprogs[] =
"{\n"
" float2 uv = fragment.position.xy * 1.0;\n"
" float2 uv2 = fragment.texcoord0;\n"
" //float2 uv3 = float2( uv2.x, 1.0 - uv2.y );\n"
"\n"
" float3 color = fragColor.rgb;\n"
" //float3 color = tex2D(samp0, uv2).rgb;\n"
"\n"
"#if 0\n"
"#if 1\n"
"// BOTTOM: Show bands.\n"
" if( uv2.y >= 0.975 )\n"
" {\n"
@ -11042,6 +11059,205 @@ static const cgShaderDef_t cg_renderprogs[] =
"\n"
"\n"
"\n"
"#define ANIMATE_NOISE 1\n"
"#define TARGET_BITS 4 // 2^3 = 8 dithered to this many bits\n"
"#define DITHER_IN_LINEAR_SPACE 0\n"
"\n"
"//----------------------------------------------------------------------------------------\n"
"\n"
"/*\n"
"Items of note!\n"
"\n"
"* The blue noise texture sampling should be set to \"nearest\" (not mip map!) and repeat\n"
"\n"
"* you should calculate the uv to use based on the pixel coordinate and the size of the blue noise texture.\n"
" * aka you should tile the blue noise texture across the screen.\n"
" * blue noise actually tiles really well unlike white noise.\n"
"\n"
"* A blue noise texture is \"low discrepancy over space\" which means there are fewer visible patterns than white noise\n"
" * it also gives more even coverage vs white noise. no clumps or voids.\n"
"\n"
"* In an attempt to make it also blue noise over time, you can add the golden ratio and frac it.\n"
" * that makes it lower discrepancy over time, but makes it less good over space.\n"
" * thanks to r4unit for that tip! https://twitter.com/R4_Unit\n"
"\n"
"* Animating the noise in this demo makes the noise basically disappear imo, it's really nice!\n"
"\n"
"For more information:\n"
"\n"
"What the heck is blue nois:\n"
"https://blog.demofox.org/2018/01/30/what-the-heck-is-blue-noise/\n"
"\n"
"Low discrepancy sequences:\n"
"https://blog.demofox.org/2017/05/29/when-random-numbers-are-too-random-low-discrepancy-sequences/\n"
"\n"
"You can get your own blue noise textures here:\n"
"http://momentsingraphics.de/?p=127\n"
"\n"
"*/\n"
"void DitheringPassDemoFox( inout float4 fragColor )\n"
"{\n"
" // texture color\n"
" float2 uv = fragment.position.xy;\n"
" float2 uv2 = fragment.texcoord0;\n"
" float3 fg = fragColor.rgb;\n"
"\n"
" float3 color = fg;\n"
"\n"
"#if 1\n"
" // TOP: show bands\n"
" if( uv2.y >= 0.975 )\n"
" {\n"
" color = float3( uv2.x );\n"
" color = floor( color * STEPS + Step3( uv ) * 4.0 ) * ( 1.0 / ( STEPS - 1.0 ) );\n"
" }\n"
" else if( uv2.y >= 0.95 )\n"
" {\n"
" color = float3( uv2.x );\n"
" color = floor( color * STEPS ) * ( 1.0 / ( STEPS - 1.0 ) );\n"
" }\n"
" else if( uv2.y >= 0.925 )\n"
" {\n"
" color = float3( uv2.x );\n"
" color = floor( color * STEPS + Step3T( uv ) * 4.0 ) * ( 1.0 / ( STEPS - 1.0 ) );\n"
" }\n"
" // BOTTOM: show dither texture\n"
" else if( uv2.y >= 0.9 )\n"
" {\n"
" color = Step3( uv ).rgb;\n"
" }\n"
" else\n"
"#endif\n"
" {\n"
" // right of the screen is dithered using white noise to a fewer number of bits per color channel\n"
" if( uv2.x > 3.0 / 4.0 )\n"
" {\n"
" // get white noise \"random\" number\n"
"#if ANIMATE_NOISE\n"
" float3 whiteNoise = Hash33( float3( uv, rpJitterTexOffset.w / 256.0 ) );\n"
"#else\n"
" float3 whiteNoise = Hash33( float3( uv, 0.0 ) );\n"
"#endif\n"
"\n"
" // dither to the specified number of bits, using sRGB conversions if desired\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" fg = pow( fg, float3( 2.2 ) );\n"
"#endif\n"
"\n"
" float scale = exp2( float( TARGET_BITS ) ) - 1.0;\n"
" color = floor( fg * scale + whiteNoise ) / scale;\n"
"\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" color = pow( col, 1.0 / float3( 2.2 ) );\n"
"#endif\n"
" }\n"
" // middle right of the screen is dithered using blue noise to a fewer number of bits per color channel\n"
" else if( uv2.x > 2.0 / 4.0 )\n"
" {\n"
" float3 blueNoise = BlueNoise3( uv, 1.0 );\n"
"\n"
" // dither to the specified number of bits, using sRGB conversions if desired\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" fg = pow( fg, float3( 2.2 ) );\n"
"#endif\n"
"\n"
" float scale = exp2( float( TARGET_BITS ) ) - 1.0;\n"
" color = floor( fg * scale + blueNoise ) / scale;\n"
"\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" color = pow( color, 1.0 / float3( 2.2 ) );\n"
"#endif\n"
" }\n"
" // middle left of the screen is quantized but not dithered\n"
" else if( uv2.x > 1.0 / 4.0 )\n"
" {\n"
" // dither to the specified number of bits, using sRGB conversions if desired\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" fg = pow( fg, float3( 2.2 ) );\n"
"#endif\n"
"\n"
" float scale = exp2( float( TARGET_BITS ) ) - 1.0;\n"
" color = floor( fg * scale + 0.5f ) / scale;\n"
"\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" color = pow( color, 1.0 / float3( 2.2 ) );\n"
"#endif\n"
" }\n"
" // left side of screen is left alone for comparison\n"
" else\n"
" {\n"
" color = fg;\n"
" }\n"
"\n"
" if( abs( uv2.x - 1.0 / 4.0 ) < 0.001 || abs( uv2.x - 2.0 / 4.0 ) < 0.001 || abs( uv2.x - 3.0 / 4.0 ) < 0.001 )\n"
" {\n"
" color = float3( 0.0, 1.0, 0.0 );\n"
" }\n"
" }\n"
"\n"
" fragColor.rgb = color;\n"
"}\n"
"\n"
"void DitheringPassSlim( inout float4 fragColor )\n"
"{\n"
" // texture color\n"
" float2 uv = fragment.position.xy;\n"
" float2 uv2 = fragment.texcoord0;\n"
" float3 fg = fragColor.rgb;\n"
"\n"
" float3 color = fg;\n"
"\n"
"#if 0\n"
" if( uv2.y >= 0.975 )\n"
" {\n"
" // source signal\n"
" color = float3( uv2.x );\n"
" }\n"
" else if( uv2.y >= 0.95 )\n"
" {\n"
" color = float3( uv2.x );\n"
"\n"
" // quantized signal\n"
" float scale = exp2( float( TARGET_BITS ) ) - 1.0;\n"
" color = floor( color * scale + 0.0f ) / scale;\n"
" }\n"
" else if( uv2.y >= 0.925 )\n"
" {\n"
" // dithered quantized signal\n"
" color = float3( uv2.x );\n"
" color = floor( color * STEPS + Step3T( uv ) * 4.0 ) * ( 1.0 / ( STEPS - 1.0 ) );\n"
" }\n"
" else if( uv2.y >= 0.9 )\n"
" {\n"
" // dither texture\n"
" color = Step3( uv ).rgb;\n"
" }\n"
" else\n"
"#endif\n"
" {\n"
" float3 noise = BlueNoise3( uv, 1.0 );\n"
"\n"
" //noise.x = RemapNoiseTriErp( noise.x );\n"
" //noise.y = RemapNoiseTriErp( noise.y );\n"
" //noise.z = RemapNoiseTriErp( noise.z );\n"
"\n"
" // dither to the specified number of bits, using sRGB conversions if desired\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" fg = pow( fg, float3( 2.2 ) );\n"
"#endif\n"
"\n"
" float scale = exp2( float( TARGET_BITS ) ) - 1.0;\n"
" color = floor( fg * scale + noise ) / scale;\n"
"\n"
"#if DITHER_IN_LINEAR_SPACE\n"
" color = pow( color, 1.0 / float3( 2.2 ) );\n"
"#endif\n"
" }\n"
"\n"
" fragColor.rgb = color;\n"
"}\n"
"\n"
"\n"
"void main( PS_IN fragment, out PS_OUT result )\n"
"{\n"
" float2 tCoords = fragment.texcoord0;\n"
@ -11062,7 +11278,7 @@ static const cgShaderDef_t cg_renderprogs[] =
"#endif\n"
"\n"
"#if USE_DITHERING\n"
" DitheringPass( color );\n"
" DitheringPassSlim( color );\n"
"#endif\n"
"\n"
" result.color = color;\n"