531 lines
16 KiB
HLSL
531 lines
16 KiB
HLSL
/*------------------.
|
|
| :: Description :: |
|
|
'-------------------/
|
|
|
|
Ascii (Version 0.9)
|
|
|
|
Author: CeeJay.dk
|
|
License: MIT
|
|
|
|
About:
|
|
Converts the image to ASCII characters using a greyscale algoritm,
|
|
cherrypicked characters and a custom bitmap font stored in a set of floats.
|
|
|
|
It has 17 gray levels but uses dithering to greatly increase that number.
|
|
|
|
Ideas for future improvement:
|
|
* Cleanup code
|
|
* Maybe find a better/faster pattern - possibly blur the pixels first with a 2 pass aproach
|
|
* Try using a font atlas for more fonts or perhaps more performance
|
|
* Try making an ordered dither rather than the random one. I think the random looks a bit too noisy.
|
|
* Calculate luma from linear colorspace
|
|
|
|
History:
|
|
(*) Feature (+) Improvement (x) Bugfix (-) Information (!) Compatibility
|
|
|
|
Version 0.7 by CeeJay.dk
|
|
* Added the 3x5 font
|
|
|
|
Version 0.8 by CeeJay.dk
|
|
+ Cleaned up settings UI for Reshade 3.x
|
|
|
|
Version 0.9 by CeeJay.dk
|
|
x Fixed an issue with the settings where the 3x5 could not be selected.
|
|
- Cleaned up and commented the code. More cleanup is still needed.
|
|
* Added the ability to toggle dithering on/off
|
|
x Removed temporal dither code due to incompatibility with humans - it was giving me headaches and I didn't want to cause anyones seizure
|
|
x Fixed an uneven distribution of the greyscale shades
|
|
*/
|
|
|
|
|
|
/*---------------.
|
|
| :: Includes :: |
|
|
'---------------*/
|
|
|
|
#include "ReShade.fxh"
|
|
|
|
|
|
/*------------------.
|
|
| :: UI Settings :: |
|
|
'------------------*/
|
|
|
|
#include "ReShadeUI.fxh"
|
|
|
|
/*
|
|
uniform float Version <
|
|
ui_label = "Version";
|
|
ui_min = 0.8;
|
|
ui_max = 0.8;
|
|
ui_step = 1.0;
|
|
ui_category = "Author : CeeJay.dk\n\n"
|
|
"To increase the size of the characters on screen simply lower your resolution in-game\n\n"
|
|
"Try using this together with Nostagia. It also looks best if you first increase the contrast with Curves.\n\n";
|
|
> = float(0.8);
|
|
*/
|
|
|
|
uniform int Ascii_spacing < __UNIFORM_SLIDER_INT1
|
|
ui_min = 0;
|
|
ui_max = 5;
|
|
ui_label = "Character Spacing";
|
|
ui_tooltip = "Determines the spacing between characters. I feel 1 to 3 looks best.";
|
|
ui_category = "Font style";
|
|
> = 1;
|
|
|
|
/*
|
|
uniform int Ascii_font <
|
|
ui_type = "drag";
|
|
ui_min = 1;
|
|
ui_max = 2;
|
|
ui_label = "Font Size";
|
|
ui_tooltip = "1 = 5x5 font, 2 = 3x5 font";
|
|
ui_category = "Font style";
|
|
> = 1;
|
|
*/
|
|
|
|
uniform int Ascii_font <
|
|
ui_type = "combo";
|
|
ui_label = "Font Size";
|
|
ui_tooltip = "Choose font size";
|
|
ui_category = "Font style";
|
|
ui_items =
|
|
"Smaller 3x5 font\0"
|
|
"Normal 5x5 font\0"
|
|
;
|
|
> = 1;
|
|
|
|
|
|
uniform int Ascii_font_color_mode < __UNIFORM_SLIDER_INT1
|
|
ui_min = 0;
|
|
ui_max = 2;
|
|
ui_label = "Font Color Mode";
|
|
ui_tooltip = "0 = Foreground color on background color, 1 = Colorized grayscale, 2 = Full color";
|
|
ui_category = "Color options";
|
|
> = 1;
|
|
|
|
uniform float3 Ascii_font_color < __UNIFORM_COLOR_FLOAT3
|
|
ui_label = "Font Color";
|
|
ui_tooltip = "Choose a font color";
|
|
ui_category = "Color options";
|
|
> = float3(1.0, 1.0, 1.0);
|
|
|
|
uniform float3 Ascii_background_color < __UNIFORM_COLOR_FLOAT3
|
|
ui_label = "Background Color";
|
|
ui_tooltip = "Choose a background color";
|
|
ui_category = "Color options";
|
|
> = float3(0.0, 0.0, 0.0);
|
|
|
|
uniform bool Ascii_swap_colors <
|
|
ui_label = "Swap Colors";
|
|
ui_tooltip = "Swaps the font and background color when you are too lazy to edit the settings above (I know I am)";
|
|
ui_category = "Color options";
|
|
> = 0;
|
|
|
|
uniform bool Ascii_invert_brightness <
|
|
ui_label = "Invert Brightness";
|
|
ui_category = "Color options";
|
|
> = 0;
|
|
|
|
uniform bool Ascii_dithering <
|
|
ui_label = "Dithering";
|
|
ui_category = "Dithering";
|
|
> = 1;
|
|
|
|
uniform float Ascii_dithering_intensity < __UNIFORM_SLIDER_FLOAT1
|
|
ui_min = 0.0;
|
|
ui_max = 4.0;
|
|
ui_label = "Dither shift intensity";
|
|
ui_tooltip = "For debugging purposes";
|
|
ui_category = "Debugging";
|
|
> = 2.0;
|
|
|
|
uniform bool Ascii_dithering_debug_gradient <
|
|
ui_label = "Dither debug gradient";
|
|
ui_category = "Debugging";
|
|
> = 0;
|
|
|
|
/*-------------------------.
|
|
| :: Sampler and timers :: |
|
|
'-------------------------*/
|
|
|
|
#define asciiSampler ReShade::BackBuffer
|
|
|
|
uniform float timer < source = "timer"; >;
|
|
uniform float framecount < source = "framecount"; >;
|
|
|
|
/*-------------.
|
|
| :: Effect :: |
|
|
'-------------*/
|
|
|
|
float3 AsciiPass( float2 tex )
|
|
{
|
|
|
|
/*-------------------------.
|
|
| :: Sample and average :: |
|
|
'-------------------------*/
|
|
|
|
//if (Ascii_font != 1)
|
|
float2 Ascii_font_size = float2(3.0,5.0); //3x5
|
|
float num_of_chars = 14. ;
|
|
|
|
if (Ascii_font == 1)
|
|
{
|
|
Ascii_font_size = float2(5.0,5.0); //5x5
|
|
num_of_chars = 17.;
|
|
}
|
|
|
|
float quant = 1.0/(num_of_chars-1.0); //value used for quantization
|
|
|
|
float2 Ascii_block = Ascii_font_size + float(Ascii_spacing);
|
|
float2 cursor_position = trunc((BUFFER_SCREEN_SIZE / Ascii_block) * tex) * (Ascii_block / BUFFER_SCREEN_SIZE);
|
|
|
|
|
|
//TODO Cleanup - and maybe find a better/faster pattern - possibly blur the pixels first with a 2 pass aproach
|
|
//-- Pattern 2 - Sample ALL the pixels! --
|
|
float3 color = tex2D(asciiSampler, cursor_position + float2( 1.5, 1.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 1.5, 3.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 1.5, 5.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
//color += tex2D(asciiSampler, cursor_position + float2( 0.5, 6.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 3.5, 1.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 3.5, 3.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 3.5, 5.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
//color += tex2D(asciiSampler, cursor_position + float2( 2.5, 6.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 5.5, 1.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 5.5, 3.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
color += tex2D(asciiSampler, cursor_position + float2( 5.5, 5.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
//color += tex2D(asciiSampler, cursor_position + float2( 4.5, 6.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
//color += tex2D(asciiSampler, cursor_position + float2( 6.5, 0.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
//color += tex2D(asciiSampler, cursor_position + float2( 6.5, 2.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
//color += tex2D(asciiSampler, cursor_position + float2( 6.5, 4.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
//color += tex2D(asciiSampler, cursor_position + float2( 6.5, 6.5) * BUFFER_PIXEL_SIZE).rgb;
|
|
|
|
color /= 9.0;
|
|
|
|
/*
|
|
//-- Pattern 3 - Just one --
|
|
float3 color = tex2D(asciiSampler, cursor_position + float2(4.0,4.0) * BUFFER_PIXEL_SIZE) .rgb; //this may be fast but it's not very temporally stable
|
|
*/
|
|
|
|
/*------------------------.
|
|
| :: Make it grayscale :: |
|
|
'------------------------*/
|
|
|
|
float luma = dot(color,float3(0.2126, 0.7152, 0.0722));
|
|
|
|
float gray = luma;
|
|
|
|
if (Ascii_invert_brightness)
|
|
gray = 1.0 - gray;
|
|
|
|
|
|
/*----------------.
|
|
| :: Debugging :: |
|
|
'----------------*/
|
|
|
|
if (Ascii_dithering_debug_gradient)
|
|
{
|
|
//gray = sqrt(dot(float2((cursor_position.x - 0.5)*1.778,cursor_position.y - 0.5),float2((cursor_position.x - 0.5)*1.778,cursor_position.y - 0.5))) * 1.1;
|
|
//gray = (cursor_position.x + cursor_position.y) * 0.5; //diagonal test gradient
|
|
//gray = smoothstep(0.0,1.0,gray); //increase contrast
|
|
gray = cursor_position.x; //horizontal test gradient
|
|
//gray = cursor_position.y; //vertical test gradient
|
|
}
|
|
/*-------------------.
|
|
| :: Get position :: |
|
|
'-------------------*/
|
|
|
|
float2 p = frac((BUFFER_SCREEN_SIZE / Ascii_block) * tex); //p is the position of the current pixel inside the character
|
|
|
|
p = trunc(p * Ascii_block);
|
|
//p = trunc(p * Ascii_block - float2(1.5,1.5)) ;
|
|
|
|
float x = (Ascii_font_size.x * p.y + p.x); //x is the number of the position in the bitfield
|
|
|
|
/*----------------.
|
|
| :: Dithering :: |
|
|
'----------------*/
|
|
|
|
//TODO : Try make an ordered dither rather than the random dither. Random looks a bit too noisy for my taste.
|
|
|
|
if (Ascii_dithering != 0)
|
|
{
|
|
//Pseudo Random Number Generator
|
|
// -- PRNG 1 - Reference --
|
|
float seed = dot(cursor_position, float2(12.9898,78.233)); //I could add more salt here if I wanted to
|
|
float sine = sin(seed); //cos also works well. Sincos too if you want 2D noise.
|
|
float noise = frac(sine * 43758.5453 + cursor_position.y);
|
|
|
|
float dither_shift = (quant * Ascii_dithering_intensity); // Using noise to determine shift.
|
|
|
|
float dither_shift_half = (dither_shift * 0.5); // The noise should vary between +- 0.5
|
|
dither_shift = dither_shift * noise - dither_shift_half; // MAD
|
|
|
|
//shift the color by dither_shift
|
|
gray += dither_shift; //apply dithering
|
|
}
|
|
|
|
/*---------------------------.
|
|
| :: Convert to character :: |
|
|
'---------------------------*/
|
|
|
|
float n = 0;
|
|
|
|
if (Ascii_font == 1)
|
|
{
|
|
// -- 5x5 bitmap font by CeeJay.dk --
|
|
// .:^"~cvo*wSO8Q0#
|
|
|
|
//17 characters including space which is handled as a special case
|
|
|
|
//The serial aproach to this would take 16 cycles, so I instead used an upside down binary tree to parallelize this to only 5 cycles
|
|
|
|
float n12 = (gray < (2. * quant)) ? 4194304. : 131200. ; // . or :
|
|
float n34 = (gray < (4. * quant)) ? 324. : 330. ; // ^ or "
|
|
float n56 = (gray < (6. * quant)) ? 283712. : 12650880.; // ~ or c
|
|
float n78 = (gray < (8. * quant)) ? 4532768. : 13191552.; // v or o
|
|
float n910 = (gray < (10. * quant)) ? 10648704. : 11195936.; // * or w
|
|
float n1112 = (gray < (12. * quant)) ? 15218734. : 15255086.; // S or O
|
|
float n1314 = (gray < (14. * quant)) ? 15252014. : 32294446.; // 8 or Q
|
|
float n1516 = (gray < (16. * quant)) ? 15324974. : 11512810.; // 0 or #
|
|
|
|
float n1234 = (gray < (3. * quant)) ? n12 : n34;
|
|
float n5678 = (gray < (7. * quant)) ? n56 : n78;
|
|
float n9101112 = (gray < (11. * quant)) ? n910 : n1112;
|
|
float n13141516 = (gray < (15. * quant)) ? n1314 : n1516;
|
|
|
|
float n12345678 = (gray < (5. * quant)) ? n1234 : n5678;
|
|
float n910111213141516 = (gray < (13. * quant)) ? n9101112 : n13141516;
|
|
|
|
n = (gray < (9. * quant)) ? n12345678 : n910111213141516;
|
|
}
|
|
else // Ascii_font == 0 , the 3x5 font
|
|
{
|
|
// -- 3x5 bitmap font by CeeJay.dk --
|
|
// .:;s*oSOXH0
|
|
|
|
//14 characters including space which is handled as a special case
|
|
|
|
/* Font reference :
|
|
|
|
//The plusses are "likes". I was rating how much I liked that character over other alternatives.
|
|
|
|
3 ^ 42.
|
|
3 - 448.
|
|
3 i (short) 9232.
|
|
3 ; 5136. ++
|
|
4 " 45.
|
|
4 i 9346.
|
|
4 s 5200. ++
|
|
5 + 1488.
|
|
5 * 2728. ++
|
|
6 c 25200.
|
|
6 o 11088. ++
|
|
7 v 11112.
|
|
7 S 14478. ++
|
|
8 O 11114. ++
|
|
9 F 5071.
|
|
9 5 (rounded) 14543.
|
|
9 X 23213. ++
|
|
10 A 23530.
|
|
10 D 15211. +
|
|
11 H 23533. +
|
|
11 5 (square) 31183.
|
|
11 2 (square) 29671. ++
|
|
|
|
5 (rounded) 14543.
|
|
*/
|
|
|
|
float n12 = (gray < (2. * quant)) ? 4096. : 1040. ; // . or :
|
|
float n34 = (gray < (4. * quant)) ? 5136. : 5200. ; // ; or s
|
|
float n56 = (gray < (6. * quant)) ? 2728. : 11088.; // * or o
|
|
float n78 = (gray < (8. * quant)) ? 14478. : 11114.; // S or O
|
|
float n910 = (gray < (10. * quant)) ? 23213. : 15211.; // X or D
|
|
float n1112 = (gray < (12. * quant)) ? 23533. : 31599.; // H or 0
|
|
float n13 = 31727.; // 8
|
|
|
|
float n1234 = (gray < (3. * quant)) ? n12 : n34;
|
|
float n5678 = (gray < (7. * quant)) ? n56 : n78;
|
|
float n9101112 = (gray < (11. * quant)) ? n910 : n1112;
|
|
|
|
float n12345678 = (gray < (5. * quant)) ? n1234 : n5678;
|
|
float n910111213 = (gray < (13. * quant)) ? n9101112 : n13;
|
|
|
|
n = (gray < (9. * quant)) ? n12345678 : n910111213;
|
|
}
|
|
|
|
|
|
/*--------------------------------.
|
|
| :: Decode character bitfield :: |
|
|
'--------------------------------*/
|
|
|
|
float character = 0.0;
|
|
|
|
//Test values
|
|
//n = -(exp2(24.)-1.0); //-(2^24-1) All bits set - a white 5x5 box
|
|
|
|
float lit = (gray <= (1. * quant)) //If black then set all pixels to black (the space character)
|
|
? 0.0 //That way I don't have to use a character bitfield for space
|
|
: 1.0 ; //I simply let it to decode to the second darkest "." and turn its pixels off
|
|
|
|
float signbit = (n < 0.0) //is n negative? (I would like to test for negative 0 here too but can't)
|
|
? lit
|
|
: 0.0 ;
|
|
|
|
signbit = (x > 23.5) //is this the first pixel in the character?
|
|
? signbit //if so set to the signbit (which may be on or off depending on if the number was negative)
|
|
: 0.0 ; //else make it black
|
|
|
|
//Tenary Multiply exp2
|
|
character = ( frac( abs( n*exp2(-x-1.0))) >= 0.5) ? lit : signbit; //If the bit for the right position is set, then light up the pixel
|
|
//UNLESS gray was dark enough, then keep the pixel dark to make a space
|
|
//If the bit for the right position was not set, then turn off the pixel
|
|
//UNLESS it's the first pixel in the character AND n is negative - if so light up the pixel.
|
|
//This way I can use all 24 bits in the mantissa as well as the signbit for characters.
|
|
|
|
if (clamp(p.x, 0.0, Ascii_font_size.x - 1.0) != p.x || clamp(p.y, 0.0, Ascii_font_size.y - 1.0) != p.y) //Is this the space around the character?
|
|
character = 0.0; //If so make the pixel black.
|
|
|
|
|
|
/*---------------.
|
|
| :: Colorize :: |
|
|
'---------------*/
|
|
|
|
if (Ascii_swap_colors)
|
|
{
|
|
if (Ascii_font_color_mode == 2)
|
|
{
|
|
color = (character) ? character * color : Ascii_font_color;
|
|
}
|
|
else if (Ascii_font_color_mode == 1)
|
|
{
|
|
color = (character) ? Ascii_background_color * gray : Ascii_font_color;
|
|
}
|
|
else // Ascii_font_color_mode == 0
|
|
{
|
|
color = (character) ? Ascii_background_color : Ascii_font_color;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Ascii_font_color_mode == 2)
|
|
{
|
|
color = (character) ? character * color : Ascii_background_color;
|
|
}
|
|
else if (Ascii_font_color_mode == 1)
|
|
{
|
|
color = (character) ? Ascii_font_color * gray : Ascii_background_color;
|
|
}
|
|
else // Ascii_font_color_mode == 0
|
|
{
|
|
color = (character) ? Ascii_font_color : Ascii_background_color;
|
|
}
|
|
}
|
|
|
|
/*-------------.
|
|
| :: Return :: |
|
|
'-------------*/
|
|
|
|
//color = gray;
|
|
return saturate(color);
|
|
}
|
|
|
|
|
|
float3 PS_Ascii(float4 position : SV_Position, float2 texcoord : TEXCOORD) : SV_Target
|
|
{
|
|
float3 color = AsciiPass(texcoord);
|
|
return color.rgb;
|
|
}
|
|
|
|
|
|
technique ASCII
|
|
{
|
|
pass ASCII
|
|
{
|
|
VertexShader=PostProcessVS;
|
|
PixelShader=PS_Ascii;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
.---------------------.
|
|
| :: Character set :: |
|
|
'---------------------'
|
|
|
|
Here are some various chacters and gradients I created in my quest to get the best look
|
|
|
|
.'~:;!>+=icjtJY56SXDQKHNWM
|
|
.':!+ijY6XbKHNM
|
|
.:%oO$8@#M
|
|
.:+j6bHM
|
|
.:coCO8@
|
|
.:oO8@
|
|
.:oO8
|
|
:+#
|
|
|
|
.:^"~cso*wSO8Q0#
|
|
.:^"~csoCwSO8Q0#
|
|
.:^"~c?o*wSO8Q0#
|
|
|
|
n value // # of pixels // character
|
|
------------//----//-------------------
|
|
4194304. // 1 // . (bottom aligned) *
|
|
131200. // 2 // : (middle aligned) *
|
|
4198400. // 2 // : (bottom aligned)
|
|
132. // 2 // '
|
|
2228352. // 3 // ;
|
|
4325504. // 3 // i (short)
|
|
14336. // 3 // - (small)
|
|
324. // 3 // ^
|
|
4329476. // 4 // i (tall)
|
|
330. // 4 // "
|
|
31744. // 5 // - (larger)
|
|
283712. // 5 // ~
|
|
10627072. // 5 // x
|
|
145536. // 5 // * or + (small and centered)
|
|
6325440. // 6 // c (narrow - left aligned)
|
|
12650880. // 6 // c (narrow - center aligned)
|
|
9738240. // 6 // n (left aligned)
|
|
6557772. // 7 // s (tall)
|
|
8679696. // 7 // f
|
|
4532768. // 7 // v (1st)
|
|
4539936. // 7 // v (2nd)
|
|
4207118. // 7 // ?
|
|
-17895696. // 7 // %
|
|
6557958. // 7 // 3
|
|
6595776. // 8 // o (left aligned)
|
|
13191552. // 8 // o (right aligned)
|
|
14714304. // 8 // c (wide)
|
|
12806528. // 9 // e (right aligned)
|
|
332772. // 9 // * (top aligned)
|
|
10648704. // 9 // * (bottom aligned)
|
|
4357252. // 9 // +
|
|
-18157904. // 9 // X
|
|
11195936. // 10 // w
|
|
483548. // 10 // s (thick)
|
|
15218734. // 11 // S
|
|
31491134. // 11 // C
|
|
15238702. // 11 // C (rounded)
|
|
22730410. // 11 // M (more like a large m)
|
|
10648714. // 11 // * (larger)
|
|
4897444. // 11 // * (2nd larger)
|
|
14726438. // 11 // @ (also looks like a large e)
|
|
23385164. // 11 // &
|
|
15255086. // 12 // O
|
|
16267326. // 13 // S (slightly larger)
|
|
15252014. // 13 // 8
|
|
15259182. // 13 // 0 (O with dot in the middle)
|
|
15517230. // 13 // Q (1st)
|
|
-18405232. // 13 // M
|
|
-11196080. // 13 // W
|
|
32294446. // 14 // Q (2nd)
|
|
15521326. // 14 // Q (3rd)
|
|
32298542. // 15 // Q (4th)
|
|
15324974. // 15 // 0 or Ø
|
|
16398526. // 15 // $
|
|
11512810. // 16 // #
|
|
-33061950. // 17 // 5 or S (stylized)
|
|
-33193150. // 19 // $ (stylized)
|
|
-33150782. // 19 // 0 (stylized)
|
|
|
|
*/
|