Added Blue Noise based Filmic Dithering by Timothy Lottes and Chromatic Aberration

This commit is contained in:
Robert Beckebans 2020-04-22 22:49:24 +02:00
parent f06484bc61
commit 1c828aee6f
2 changed files with 314 additions and 26 deletions

View file

@ -3,7 +3,8 @@
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2015 Robert Beckebans
Copyright (C) 2015-2020 Robert Beckebans
Copyright (C) 2014 Timothy Lottes (AMD)
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -45,7 +46,9 @@ struct PS_OUT
};
// *INDENT-ON*
#define USE_TECHNICOLOR 1 // [0 or 1]
#define USE_CHROMATIC_ABERRATION 1
#define USE_TECHNICOLOR 0 // [0 or 1]
#define Technicolor_Amount 0.5 // [0.00 to 1.00]
#define Technicolor_Power 4.0 // [0.00 to 8.00]
@ -53,11 +56,11 @@ struct PS_OUT
#define Technicolor_GreenNegativeAmount 0.88 // [0.00 to 1.00]
#define Technicolor_BlueNegativeAmount 0.88 // [0.00 to 1.00]
#define USE_VIBRANCE 1
#define USE_VIBRANCE 0
#define Vibrance 0.5 // [-1.00 to 1.00]
#define Vibrance_RGB_Balance float3( 1.0, 1.0, 1.0 )
#define USE_FILMGRAIN 1
#define USE_DITHERING 1
float3 overlay( float3 a, float3 b )
{
@ -111,29 +114,307 @@ void VibrancePass( inout float4 color )
}
void FilmgrainPass( inout float4 color )
// CHROMATIC ABBERATION
float2 BarrelDistortion( float2 xy, float amount )
{
float4 jitterTC = ( fragment.position * rpScreenCorrectionFactor ) + rpJitterTexOffset;
//float4 jitterTC = ( fragment.position * ( 1.0 / 128.0 ) ) + rpJitterTexOffset;
//float2 jitterTC = fragment.position.xy * 2.0;
//jitterTC.x *= rpWindowCoord.y / rpWindowCoord.x;
float2 cc = xy - 0.5;
float dist = dot2( cc, cc );
float4 noiseColor = tex2D( samp1, fragment.position.xy + jitterTC.xy );
float Y = noiseColor.r;
//float Y = dot( LUMINANCE_VECTOR, noiseColor );
//noiseColor.rgb = float3( Y, Y, Y );
float exposureFactor = 1.0;
exposureFactor = sqrt( exposureFactor );
const float noiseIntensity = 1.7; //rpScreenCorrectionFactor.z;
float t = lerp( 3.5 * noiseIntensity, 1.13 * noiseIntensity, exposureFactor );
color.rgb = overlay( color.rgb, lerp( float3( 0.5 ), noiseColor.rgb, t ) );
//color.rgb = noiseColor.rgb;
//color.rgb = lerp( color.rgb, noiseColor.rgb, 0.3 );
return xy + cc * dist * amount;
}
float Linterp( float t )
{
return saturate( 1.0 - abs( 2.0 * t - 1.0 ) );
}
float Remap( float t, float a, float b )
{
return saturate( ( t - a ) / ( b - a ) );
}
float3 SpectrumOffset( float t )
{
float lo = step( t, 0.5 );
float hi = 1.0 - lo;
float w = Linterp( Remap( t, 1.0 / 6.0, 5.0 / 6.0 ) );
float3 ret = float3( lo, 1.0, hi ) * float3( 1.0 - w, w, 1.0 - w );
return pow( ret, float3( 1.0 / 2.2 ) );
}
void ChromaticAberrationPass( inout float4 color )
{
float amount = 0.1; //color.a * 1.0; //rpUser0.x;
float3 sum = float3( 0.0 );
float3 sumColor = float3( 0.0 );
//float samples = rpOverbright.x;
float samples = 12.0; // * 2;
for( float i = 0.0; i < samples; i = i + 1.0 )
{
//float t = ( ( i / ( samples - 1.0 ) ) - 0.5 );
float t = ( i / ( samples - 1.0 ) );
//float t = log( i / ( samples - 1.0 ) );
float3 so = SpectrumOffset( t );
sum += so.xyz;
sumColor += so * tex2D( samp0, BarrelDistortion( fragment.texcoord0, ( 0.5 * amount * t ) ) ).rgb;
}
color.rgb = ( sumColor / sum );
//color.rgb = lerp(color.rgb, (sumColor / sum), Technicolor_Amount);
}
// https://gpuopen.com/vdr-follow-up-fine-art-of-film-grain/
//
// TEMPORAL DITHERING TEST IN LINEAR
//
//
// This is biased (saturates + adds contrast) because dithering done in non-linear space.
//
// Shows proper dithering of a signal (overlapping of dither between bands).
// Results in about a 1-stop improvement in dynamic range over conventional dither
// which does not overlap dither across bands
// (try "#define WIDE 0.5" to see the difference below).
//
// This would work a lot better with a proper random number generator (flicker etc is bad).
// Sorry there is a limit to what can be done easily in shadertoy.
//
// Proper dithering algorithm,
//
// color = floor(color * steps + noise) * (1.0/(steps-1.0))
//
// Where,
//
// color ... output color {0 to 1}
// noise ... random number between {-1 to 1}
// steps ... quantization steps, ie 8-bit = 256
//
// The noise in this case is shaped by a high pass filter.
// This is to produce a better quality temporal dither.
// Scale the width of the dither
//-----------------------------------------------------------------------
float Linear1( float c )
{
return ( c <= 0.04045 ) ? c / 12.92 : pow( ( c + 0.055 ) / 1.055, 2.4 );
}
float3 Linear3( float3 c )
{
return float3( Linear1( c.r ), Linear1( c.g ), Linear1( c.b ) );
}
float Srgb1( float c )
{
return ( c < 0.0031308 ? c * 12.92 : 1.055 * pow( c, 0.41666 ) - 0.055 );
}
float3 Srgb3( float3 c )
{
return float3( Srgb1( c.r ), Srgb1( c.g ), Srgb1( c.b ) );
}
float3 photoLuma = float3( 0.2126, 0.7152, 0.0722 );
float PhotoLuma( float3 c )
{
return dot( c, photoLuma );
}
//note: works for structured patterns too
// [0;1[
float RemapNoiseTriErp( const float v )
{
float r2 = 0.5 * v;
float f1 = sqrt( r2 );
float f2 = 1.0 - sqrt( r2 - 0.25 );
return ( v < 0.5 ) ? f1 : f2;
}
#if 1
float Noise( float2 n, float x )
{
// golden ratio
n += x;// * 1.61803398875;
return fract( sin( dot( n.xy, float2( 12.9898, 78.233 ) ) ) * 43758.5453 ) * 2.0 - 1.0;
}
#else
//note: returns [-intensity;intensity[, magnitude of 2x intensity
//note: from "NEXT GENERATION POST PROCESSING IN CALL OF DUTY: ADVANCED WARFARE"
// http://advances.realtimerendering.com/s2014/index.html
//float InterleavedGradientNoise( vec2 uv )
float Noise( float2 uv, float x )
{
// RB: golden ratio
uv += x;// * 1.61803398875;
const float3 magic = vec3( 0.06711056, 0.00583715, 52.9829189 );
float rnd = fract( magic.z * fract( dot( uv, magic.xy ) ) );
//rnd = RemapNoiseTriErp(rnd) * 2.0 - 0.5;
return rnd;
}
#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;
return ( 1.0 / ( a * 4.0 + b * 4.0 - c ) ) * (
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 )
{
float a = Step2( uv, 0.07 );
float b = Step2( uv, 0.11 );
float c = Step2( uv, 0.13 );
#if 0
// Monochrome can look better on stills.
return float3( a );
#else
return float3( a, b, c );
#endif
}
// Used for temporal dither.
float3 Step3T( float2 uv )
{
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 );
}
#define STEPS 12.0
void DitheringPass( inout float4 fragColor )
{
float2 uv = fragment.position.xy;
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
// BOTTOM: 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 ) );
}
// TOP: Show dither texture.
else if( uv2.y >= 0.9 )
{
color = Step3( uv ) * 1.0 + 0.5;
}
else
#endif
{
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 ) + ( 4.0 / STEPS ) ) - color;
#else
// Less too expensive.
float luma = PhotoLuma( color );
// Implement this as a texture lookup table.
float amount = Linear1( Srgb1( luma ) + ( 4.0 / STEPS ) ) - luma;
#endif
#else
// Fast solutions.
#if 1
// Hack 1 (fastest).
// For HDR need saturate() around luma.
float luma = PhotoLuma( color );
float amount = mix(
Linear1( 4.0 / STEPS ),
Linear1( ( 4.0 / STEPS ) + 1.0 ) - 1.0,
luma );
#else
// Hack 2 (slower?).
// For HDR need saturate() around color in mix().
float3 amount = mix(
float3( Linear1( 4.0 / STEPS ) ),
float3( Linear1( ( 4.0 / STEPS ) + 1.0 ) - 1.0 ),
color );
#endif
#endif
color += Step3T( uv ) * amount;
// 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 * STEPS ) * ( 1.0 / ( STEPS - 1.0 ) );
}
fragColor.rgb = color;
}
void main( PS_IN fragment, out PS_OUT result )
{
@ -142,6 +423,10 @@ void main( PS_IN fragment, out PS_OUT result )
// base color with tone mapping and other post processing applied
float4 color = tex2D( samp0, tCoords );
#if USE_CHROMATIC_ABERRATION
ChromaticAberrationPass( color );
#endif
#if USE_TECHNICOLOR
TechnicolorPass( color );
#endif
@ -150,8 +435,8 @@ void main( PS_IN fragment, out PS_OUT result )
VibrancePass( color );
#endif
#if USE_FILMGRAIN
FilmgrainPass( color );
#if USE_DITHERING
DitheringPass( color );
#endif
result.color = color;

View file

@ -5801,6 +5801,7 @@ void idRenderBackend::MotionBlur()
GL_SelectTexture( 0 );
globalImages->currentRenderImage->Bind();
GL_SelectTexture( 1 );
globalImages->currentDepthImage->Bind();
@ -6066,13 +6067,15 @@ void idRenderBackend::PostProcess( const void* data )
{
jitterTexOffset[0] = ( rand() & 255 ) / 255.0;
jitterTexOffset[1] = ( rand() & 255 ) / 255.0;
jitterTexOffset[2] = Sys_Milliseconds() / 1000.0f;
}
else
{
jitterTexOffset[0] = 0;
jitterTexOffset[1] = 0;
jitterTexOffset[2] = 0.0f;
}
jitterTexOffset[2] = 0.0f;
jitterTexOffset[3] = 0.0f;
SetFragmentParm( RENDERPARM_JITTERTEXOFFSET, jitterTexOffset ); // rpJitterTexOffset