mirror of
https://github.com/libretro/slang-shaders.git
synced 2024-05-20 05:01:06 -04:00
Point ntsc-adaptive to guest.r's version; add initial cathode-retro port (#514)
* initial cathode-retro port * move ntsc-adaptive and most associated presets over to use guest.r's modified version * fix some ctrl^H goofs * add license blocks to cathode-retro shaders * updates to cathode-retro; signal stuff is still broken but less so maybe
This commit is contained in:
parent
2a4f904bfd
commit
182766b2a1
47
crt/cathode-retro_no-signal.slangp
Normal file
47
crt/cathode-retro_no-signal.slangp
Normal file
|
@ -0,0 +1,47 @@
|
|||
shaders = 11
|
||||
|
||||
shader0 = shaders/cathode-retro/cathode-retro-util-copy.slang
|
||||
alias0 = g_sourceTexture
|
||||
filter_linear0 = true
|
||||
|
||||
shader1 = shaders/cathode-retro/cathode-retro-util-downsample-2x-horz.slang
|
||||
filter_linear1 = true
|
||||
scale_x1 = 0.5
|
||||
|
||||
shader2 = shaders/cathode-retro/cathode-retro-util-downsample-2x-vert.slang
|
||||
filter_linear2 = true
|
||||
scale_y2 = 0.5
|
||||
|
||||
shader3 = ../stock.slang
|
||||
|
||||
shader4 = shaders/cathode-retro/cathode-retro-util-tonemap-and-downsample-horz.slang
|
||||
filter_linear4 = true
|
||||
scale_x4 = 0.5
|
||||
|
||||
shader5 = shaders/cathode-retro/cathode-retro-util-tonemap-and-downsample-vert.slang
|
||||
filter_linear5 = true
|
||||
scale_y5 = 0.5
|
||||
alias5 = g_sourceTex
|
||||
|
||||
shader6 = shaders/cathode-retro/cathode-retro-util-gaussian-blur-horz.slang
|
||||
filter_linear6 = true
|
||||
|
||||
shader7 = shaders/cathode-retro/cathode-retro-util-gaussian-blur-vert.slang
|
||||
filter_linear7 = true
|
||||
alias7 = g_diffusionTexture
|
||||
|
||||
shader8 = shaders/cathode-retro/cathode-retro-crt-generate-masks.slang
|
||||
alias8 = g_maskTexture
|
||||
mipmap_input8 = true
|
||||
wrap_mode8 = repeat
|
||||
scale_type8 = viewport
|
||||
|
||||
shader9 = shaders/cathode-retro/cathode-retro-crt-generate-screen-texture.slang
|
||||
alias9 = g_screenMaskTexture
|
||||
mipmap_input9 = true
|
||||
filter_linear9 = true
|
||||
wrap_mode9 = clamp
|
||||
|
||||
shader10 = shaders/cathode-retro/cathode-retro-crt-rgb-to-crt_no-signal.slang
|
||||
mipmap_input10 = true
|
||||
filter_linear10 = true
|
|
@ -1,71 +1,74 @@
|
|||
shaders = "7"
|
||||
shader0 = "../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang"
|
||||
filter_linear0 = "false"
|
||||
wrap_mode0 = "clamp_to_border"
|
||||
frame_count_mod0 = "2"
|
||||
mipmap_input0 = "false"
|
||||
alias0 = ""
|
||||
float_framebuffer0 = "true"
|
||||
srgb_framebuffer0 = "false"
|
||||
scale_type_x0 = "source"
|
||||
scale_x0 = "4.000000"
|
||||
scale_type_y0 = "source"
|
||||
scale_y0 = "1.000000"
|
||||
shader1 = "../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang"
|
||||
filter_linear1 = "false"
|
||||
wrap_mode1 = "clamp_to_border"
|
||||
mipmap_input1 = "false"
|
||||
alias1 = ""
|
||||
float_framebuffer1 = "false"
|
||||
srgb_framebuffer1 = "true"
|
||||
scale_type_x1 = "source"
|
||||
scale_x1 = "0.500000"
|
||||
scale_type_y1 = "source"
|
||||
scale_y1 = "1.000000"
|
||||
shader2 = "../crt/shaders/hyllian/crt-hyllian-curvature.slang"
|
||||
filter_linear2 = "false"
|
||||
wrap_mode2 = "clamp_to_border"
|
||||
mipmap_input2 = "false"
|
||||
alias2 = "CRTPass"
|
||||
float_framebuffer2 = "false"
|
||||
srgb_framebuffer2 = "true"
|
||||
scale_type_x2 = "viewport"
|
||||
scale_x2 = "1.000000"
|
||||
scale_type_y2 = "viewport"
|
||||
scale_y2 = "1.000000"
|
||||
shader3 = "../crt/shaders/glow/threshold.slang"
|
||||
filter_linear3 = "true"
|
||||
wrap_mode3 = "clamp_to_border"
|
||||
mipmap_input3 = "false"
|
||||
alias3 = ""
|
||||
float_framebuffer3 = "false"
|
||||
srgb_framebuffer3 = "true"
|
||||
shader4 = "../crt/shaders/glow/blur_horiz.slang"
|
||||
filter_linear4 = "true"
|
||||
shaders = "9"
|
||||
|
||||
shader0 = ../stock.slang
|
||||
alias0 = PrePass0
|
||||
|
||||
shader1 = shaders/guest/advanced/ntsc/ntsc-pass1.slang
|
||||
alias1 = NPass1
|
||||
scale_type_x1 = source
|
||||
scale_type_y1 = source
|
||||
scale_x1 = 4.0
|
||||
scale_y1 = 1.0
|
||||
float_framebuffer1 = true
|
||||
filter_linear1 = false
|
||||
|
||||
shader2 = shaders/guest/advanced/ntsc/ntsc-pass2.slang
|
||||
filter_linear2 = true
|
||||
float_framebuffer2 = true
|
||||
scale_type2 = source
|
||||
scale_x2 = 0.5
|
||||
scale_y2 = 1.0
|
||||
|
||||
shader3 = shaders/guest/advanced/ntsc/ntsc-pass3.slang
|
||||
filter_linear3 = true
|
||||
scale_type3 = source
|
||||
scale_x3 = 1.0
|
||||
scale_y3 = 1.0
|
||||
|
||||
shader4 = "../crt/shaders/hyllian/crt-hyllian-curvature.slang"
|
||||
filter_linear4 = "false"
|
||||
wrap_mode4 = "clamp_to_border"
|
||||
mipmap_input4 = "true"
|
||||
alias4 = ""
|
||||
mipmap_input4 = "false"
|
||||
alias4 = "CRTPass"
|
||||
float_framebuffer4 = "false"
|
||||
srgb_framebuffer4 = "true"
|
||||
scale_type_x4 = "viewport"
|
||||
scale_x4 = "0.200000"
|
||||
scale_x4 = "1.000000"
|
||||
scale_type_y4 = "viewport"
|
||||
scale_y4 = "0.200000"
|
||||
shader5 = "../crt/shaders/glow/blur_vert.slang"
|
||||
scale_y4 = "1.000000"
|
||||
shader5 = "../crt/shaders/glow/threshold.slang"
|
||||
filter_linear5 = "true"
|
||||
wrap_mode5 = "clamp_to_border"
|
||||
mipmap_input5 = "false"
|
||||
alias5 = ""
|
||||
float_framebuffer5 = "false"
|
||||
srgb_framebuffer5 = "true"
|
||||
shader6 = "../crt/shaders/glow/resolve.slang"
|
||||
shader6 = "../crt/shaders/glow/blur_horiz.slang"
|
||||
filter_linear6 = "true"
|
||||
wrap_mode6 = "clamp_to_border"
|
||||
mipmap_input6 = "false"
|
||||
mipmap_input6 = "true"
|
||||
alias6 = ""
|
||||
float_framebuffer6 = "false"
|
||||
srgb_framebuffer6 = "false"
|
||||
scale_type6 = viewport
|
||||
srgb_framebuffer6 = "true"
|
||||
scale_type_x6 = "viewport"
|
||||
scale_x6 = "0.200000"
|
||||
scale_type_y6 = "viewport"
|
||||
scale_y6 = "0.200000"
|
||||
shader7 = "../crt/shaders/glow/blur_vert.slang"
|
||||
filter_linear7 = "true"
|
||||
wrap_mode7 = "clamp_to_border"
|
||||
mipmap_input7 = "false"
|
||||
alias7 = ""
|
||||
float_framebuffer7 = "false"
|
||||
srgb_framebuffer7 = "true"
|
||||
shader8 = "../crt/shaders/glow/resolve.slang"
|
||||
filter_linear8 = "true"
|
||||
wrap_mode8 = "clamp_to_border"
|
||||
mipmap_input8 = "false"
|
||||
alias8 = ""
|
||||
float_framebuffer8 = "false"
|
||||
srgb_framebuffer8 = "false"
|
||||
scale_type8 = viewport
|
||||
parameters = "linearize;quality;ntsc_sat;BEAM_PROFILE;HFILTER_PROFILE;BEAM_MIN_WIDTH;BEAM_MAX_WIDTH;SCANLINES_STRENGTH;COLOR_BOOST;PHOSPHOR_LAYOUT;MASK_INTENSITY;CRT_ANTI_RINGING;InputGamma;OutputGamma;VSCANLINES;CRT_CURVATURE;CRT_warpX;CRT_warpY;CRT_cornersize;CRT_cornersmooth;GLOW_WHITEPOINT;GLOW_ROLLOFF;BLOOM_STRENGTH;OUTPUT_GAMMA;CURVATURE;warpX;warpY;cornersize;cornersmooth;noise_amt;shadowMask;maskDark;maskLight"
|
||||
BEAM_PROFILE = "0.000000"
|
||||
HFILTER_PROFILE = "1.000000"
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
// Do a barrel distortion to a given texture coordinate to emulate a curved CRT screen.
|
||||
|
||||
#include "cathode-retro-util-language-helpers.inc"
|
||||
|
||||
vec2 ApproxAtan2(vec2 x, vec2 y)
|
||||
{
|
||||
// A simple approximation of atan2 will suffice here, just a few terms of the taylor series are good enough for the
|
||||
// angle ranges we're dealing with.
|
||||
x /= y;
|
||||
vec2 x2 = x*x;
|
||||
return x*(1.0 + x2*(x2*0.2 - 0.333333333));
|
||||
}
|
||||
|
||||
|
||||
vec2 DistortCRTCoordinates(
|
||||
// The original texture coordinate, intended to come straight from the full-render-target quad, in [-1..1] range (not
|
||||
// standard [0..1])
|
||||
vec2 texCoord,
|
||||
|
||||
// a [horizontal, vertical] distortion pair which describes the effective curvature of the virtual screen.
|
||||
vec2 distortion)
|
||||
{
|
||||
if (distortion.x == 0 && distortion.y == 0)
|
||||
{
|
||||
return texCoord;
|
||||
}
|
||||
|
||||
const float k_distance = 2.0;
|
||||
const float k_minDistortion = 0.0001;
|
||||
|
||||
// We don't want to let the distortion drop below this minimum value because at 0, the whole technique falls apart.
|
||||
// $TODO: It's definitely possible to handle distortion.x or y == 0 as a special case, but I didn't do it for now.
|
||||
distortion = max(vec2(k_minDistortion, k_minDistortion), distortion);
|
||||
|
||||
// We're going to cast a ray from a virtual camera position p0 (0, 0, -k_distance) and collide it with a unit sphere.
|
||||
// The horizontal and vertical spread of the rays is determined by the distortion values.
|
||||
vec3 ray = vec3(texCoord * distortion, -k_distance);
|
||||
|
||||
// Get the squared length of the ray.
|
||||
float rayLenSq = dot(ray, ray);
|
||||
|
||||
// we have an originating point p0 and a ray direction, so we can treat that as a parametric equation:
|
||||
// p = p0 + r*t
|
||||
// Since we have a non-translated unit sphere (1 = x^2 + y^2 + z^2) we can sub in our parametric values and solve for
|
||||
// t as a quadratic. To simplify the math, we're going to use a slightly different form of quadratic equation:
|
||||
// t^2 - 2b*t + c == 0
|
||||
float b = (k_distance * k_distance) / rayLenSq;
|
||||
float c = (k_distance * k_distance - 1.0) / rayLenSq;
|
||||
|
||||
// Given our "t^2 - 2b*t + c == 0" quadratic form, the quadratic formula simplifies to -b +/- sqrt(b^2 - c).
|
||||
// Get the nearer of the two coordinates (and do a max inside the sqrt so we still get continuous values outside of
|
||||
// the sphere, even if they're nonsensical, they'll get masked out by our mask value anyway).
|
||||
float t = b - sqrt(max(0.0, b*b - c));
|
||||
|
||||
// Get our uv coordinates (Basically, latitude and longitude).
|
||||
vec2 uv = ApproxAtan2(ray.xy * t, k_distance + ray.zz * t);
|
||||
|
||||
// maxUV could be calculated on the CPU and passed in for perf.
|
||||
// Do the same calculation as above, but for two additional rays: the x, 0, z ray pointing all the way to the right,
|
||||
// and the 0, y, z ray pointing at the bottom (Both rays are packed into the same "diagonal" ray value, but the
|
||||
// lengths are calculated separately). This gets us the uv extents for the rays that we cast, which we can use to
|
||||
// scale our output UVs.
|
||||
vec2 maxUV;
|
||||
{
|
||||
vec3 maxRayDiagonal = vec3(distortion, -k_distance);
|
||||
vec2 maxRayLenSq = vec2(dot(maxRayDiagonal.xz, maxRayDiagonal.xz), dot(maxRayDiagonal.yz, maxRayDiagonal.yz));
|
||||
|
||||
vec2 maxB = (k_distance * k_distance) / maxRayLenSq;
|
||||
vec2 maxC = (k_distance * k_distance - 1.0) / maxRayLenSq;
|
||||
vec2 maxT = maxB - sqrt(max(vec2(0.0, 0.0), maxB*maxB - maxC));
|
||||
maxUV = ApproxAtan2(maxRayDiagonal.xy * maxT, k_distance + maxRayDiagonal.zz * maxT);
|
||||
}
|
||||
|
||||
// Scale our UVs by the max value.
|
||||
return uv / maxUV;
|
||||
}
|
207
crt/shaders/cathode-retro/cathode-retro-crt-generate-masks.slang
Normal file
207
crt/shaders/cathode-retro/cathode-retro-crt-generate-masks.slang
Normal file
|
@ -0,0 +1,207 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
#include "slang_params.inc"
|
||||
|
||||
// The size of the render target we are rendering to.
|
||||
// NOTE: It is expected that width == 2 * height.
|
||||
vec2 g_texSize = vec2(params.FinalViewportSize.y * 2.0, params.FinalViewportSize.y);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader generates an approximation of a CRT slot mask (the little R, G, and B dots that you can see on some CRTs
|
||||
// when you are close to them.
|
||||
// It is intended to be rendered to a texture that has a 2:1 width:height ratio, and we are generating two sets of RGB
|
||||
// rounded rectangles, where the right-most set is offset 50% vertically (so it wraps from bottom to top).
|
||||
|
||||
vec4 aperture(vec2 inTexCoord)
|
||||
{
|
||||
uint texelIndex = uint(inTexCoord.x * g_texSize.x - 0.5);
|
||||
float x = float(texelIndex) / g_texSize.x;
|
||||
|
||||
x = fract(x * 2.) * 3.;
|
||||
|
||||
// Default to "red zone"
|
||||
vec3 color = vec3(1., 0., 0.);
|
||||
if (x >= 2.)
|
||||
{
|
||||
// blue zone
|
||||
color = vec3(0.,0.,1.);
|
||||
}
|
||||
else if (x >= 1.)
|
||||
{
|
||||
// green zone
|
||||
color = vec3(0.,1.,0.);
|
||||
}
|
||||
|
||||
// Convert our coordinate to be in [0..1/3rd]. Note that it was already multiplied by 3 above so this "frac" call
|
||||
// gets us into the [0..1] value for this third.
|
||||
x = fract(x);
|
||||
x /= 3.;
|
||||
|
||||
// These are the positions of where the border of the colored rectangles will go in our third, as well as how round
|
||||
// the edges will be.
|
||||
float border = 1.0 / 12.0;
|
||||
float smoothing = border / 3.0;
|
||||
border -= smoothing;
|
||||
|
||||
// Do similar calculations to the slot mask texture generation, except without any y component since there are no
|
||||
// breaks along the vertical axis in an aperture grille. This could likely be simplified, but I already had that
|
||||
// logic and this is fine for something that is intended to run once ever (at startup).
|
||||
x = abs(x - 1.0 / 6.0);
|
||||
x -= 1.0 / 6.0 - (smoothing + border);
|
||||
x /= smoothing;
|
||||
x = max(0.0, x);
|
||||
|
||||
float delta = 1.0 / (g_texSize.x * smoothing);
|
||||
float mul = saturate(1.0 - smoothstep(1.0 - delta * 0.5, 1.0 + delta * 0.5, x));
|
||||
|
||||
color *= mul;
|
||||
return vec4(color, 1.);
|
||||
}
|
||||
|
||||
vec4 slotmask(vec2 inTexCoord)
|
||||
{
|
||||
uvec2 texelIndex = uvec2(inTexCoord * g_texSize - 0.5);
|
||||
vec2 t = vec2(texelIndex) / g_texSize.x * vec2(1.0, 2.0);
|
||||
|
||||
// Adjust our texture coordinates based on whether we're in the left half of the output or the right half. In each
|
||||
// we'll end up with a x, y in [0..1] square that will contain the R, G, B rectangles.
|
||||
if (t.x > 0.5)
|
||||
{
|
||||
// We're in the right-most half of the texture, so offset our y coordinate by 0.5 (and wrap it to within 0..1) so
|
||||
// this set draws offset vertically.
|
||||
t.y = fract(t.y + 0.5);
|
||||
|
||||
// Scale x to be within 0..1 where 0 is the middle of our texture (and the left edge of this rightmost square) and
|
||||
// 1 is on the right edge of both.
|
||||
t.x = t.x * 2.0 - 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are in the left half of our texture, so scale the x coordinate so that it is 1.0 at the middle of the texture
|
||||
// (so in this leftmost square it goes from [0..1]). The y coordinate is unchanged.
|
||||
t.x *= 2.0;
|
||||
}
|
||||
|
||||
// Figure out which of the three "zones" we're in - R, G, or B - i.e. which horizontal third of the square we are in.
|
||||
// We'll start by multiplying by 3 so that x in [0..1) is R, [1..2) is G, and [2..3] is B.
|
||||
t.x *= 3.;
|
||||
|
||||
// Default to "red zone"
|
||||
vec3 color = vec3(1., 0., 0.);
|
||||
if (t.x >= 2.)
|
||||
{
|
||||
// blue zone
|
||||
color = vec3(0.,0.,1.);
|
||||
}
|
||||
else if (t.x >= 1.)
|
||||
{
|
||||
// green zone
|
||||
color = vec3(0.,1.,0.);
|
||||
}
|
||||
|
||||
// Convert our coordinate to be in [0..1/3rd]. Note that it was already multiplied by 3 above so this "frac" call
|
||||
// gets us into the [0..1] value for this third.
|
||||
t.x = fract(t.x);
|
||||
t.x /= 3;
|
||||
|
||||
// These are the positions of where the border of the colored rectangles will go in our third, as well as how round
|
||||
// the edges will be.
|
||||
vec2 border = vec2(1.0/3.0 * (1.0 / 4.0), 1.0/6.0);
|
||||
float rounding = border.x / 3.0;
|
||||
border -= rounding;
|
||||
|
||||
// Center the rectangle so we can do a distance calculation from center to find the bounds of the rectangle, using
|
||||
// that to make a final [0..1] multiply value to scale our chosen rectangle color down to black out the areas
|
||||
// outside of the colored portion.
|
||||
t -= vec2(1.0 / 6.0, 0.5);
|
||||
t = abs(t);
|
||||
t -= (vec2(1.0 / 6.0, 0.5)) - (rounding + border);
|
||||
t /= rounding;
|
||||
t = max(vec2(0.0, 0.0), t);
|
||||
|
||||
float distance = length(t);
|
||||
float delta = 1.0 / (g_texSize.x * rounding);
|
||||
float mul = saturate(1.0 - smoothstep(1.0 - delta * 0.5, 1.0 + delta * 0.5, distance));
|
||||
|
||||
color *= mul;
|
||||
return vec4(color, 1.0);
|
||||
}
|
||||
|
||||
vec4 shadowmask(vec2 inTexCoord)
|
||||
{
|
||||
// changing this from 6.,4. to make circles instead of ovals -HK
|
||||
vec2 hexGridF = inTexCoord * vec2(6.0, 6.0);
|
||||
|
||||
bool isOddBlock = (fract(hexGridF.y * 0.5) >= 0.5);
|
||||
if (isOddBlock)
|
||||
{
|
||||
hexGridF.x += .5;
|
||||
}
|
||||
|
||||
ivec2 hexID = ivec2(floor(hexGridF));
|
||||
|
||||
vec2 cellCoord = fract(hexGridF);
|
||||
|
||||
const float t = 0.5773502691; // tan(30 degrees)
|
||||
const float c = 0.5 * t;
|
||||
|
||||
if (cellCoord.y < (-t * cellCoord.x) + c)
|
||||
{
|
||||
hexID.y--;
|
||||
hexGridF.x += isOddBlock ? -0.5 : 0.5;
|
||||
hexID.x -= int(isOddBlock);
|
||||
}
|
||||
else if (cellCoord.y < (t * cellCoord.x) - c)
|
||||
{
|
||||
hexID.y--;
|
||||
hexGridF.x += isOddBlock ? -0.5 : 0.5;
|
||||
hexID.x += int(!isOddBlock);
|
||||
}
|
||||
|
||||
if (fract(float(hexID.y) *0.5) >= 0.5)
|
||||
{
|
||||
hexGridF.x ++;
|
||||
hexID.x ++;
|
||||
}
|
||||
|
||||
vec3 color;
|
||||
|
||||
uint hx = uint(hexID.x + 1);
|
||||
if ((hx % 3U) == 0U)
|
||||
{
|
||||
color = vec3(1., 0., 0.);
|
||||
}
|
||||
else if ((hx % 3U) == 1U)
|
||||
{
|
||||
color = vec3(0., 1., 0.);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = vec3(0., 0., 1.);
|
||||
}
|
||||
|
||||
cellCoord = ((hexGridF - vec2(hexID)) * vec2(1.0, 1.0 / (1.0 + c)));
|
||||
cellCoord = cellCoord * 2. - 1.;
|
||||
|
||||
float dist = length(cellCoord);
|
||||
dist = 1.0 - smoothstep(0.75, 0.8, dist);
|
||||
|
||||
// color *= lerp(0.1, 1.0, float(hexID.y % 2));
|
||||
return vec4(color * dist, 1.);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if(int(global.cat_mask_picker) == 1) FragColor = aperture(fract(vTexCoord * (params.FinalViewportSize.xy / 6.) / global.mask_scale));
|
||||
else if(int(global.cat_mask_picker) == 2) FragColor = slotmask(fract(vTexCoord * (params.FinalViewportSize.xy / 6.) / global.mask_scale));
|
||||
else if(int(global.cat_mask_picker) == 3) FragColor = shadowmask(fract(vTexCoord * (params.FinalViewportSize.xy / 6.) / global.mask_scale));
|
||||
// we compensate for brightness later, so better darken up the mask-less image -HK
|
||||
else FragColor = vec4(0.5);
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
#include "slang_params.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
float animate = 1.0 + mod(params.FrameCount, 46375.0) * global.anim_noise;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader generates the Screen Texture, which is effectively overlayed on top of the actual screen image in the
|
||||
// RGBToCRT shader. This includes the emulation of the shadow mask (or slot mask, aperture grill) of a CRT screen, as
|
||||
// well as masking around the edges to handle blacking out values that are outside of the visible screen.
|
||||
|
||||
|
||||
#include "cathode-retro-crt-distort-coordinates.inc"
|
||||
#include "cathode-retro-util-noise.inc"
|
||||
|
||||
// The mask texture for the CRT. That is, if you think of an old CRT and how you could see the
|
||||
// R, G, and B dots, this is that texture. Needs to be set up to wrap, as well as to have mip mapping (and, ideally,
|
||||
// anisotropic filtering too, if rendering as a curved screen, to help with sharpness and aliasing).
|
||||
//DECLARE_TEXTURE2D(g_maskTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D g_maskTexture;
|
||||
|
||||
// This is a 64-tap poisson disc filter, found here:
|
||||
// https://www.geeks3d.com/20100628/3d-programming-ready-to-use-64-sample-poisson-disc/
|
||||
const int k_samplePointCount = 64;
|
||||
const vec2 k_samplingPattern[k_samplePointCount] = vec2[k_samplePointCount](
|
||||
vec2(-0.613392, 0.617481),
|
||||
vec2(0.170019, -0.040254),
|
||||
vec2(-0.299417, 0.791925),
|
||||
vec2(0.645680, 0.493210),
|
||||
vec2(-0.651784, 0.717887),
|
||||
vec2(0.421003, 0.027070),
|
||||
vec2(-0.817194, -0.271096),
|
||||
vec2(-0.705374, -0.668203),
|
||||
vec2(0.977050, -0.108615),
|
||||
vec2(0.063326, 0.142369),
|
||||
vec2(0.203528, 0.214331),
|
||||
vec2(-0.667531, 0.326090),
|
||||
vec2(-0.098422, -0.295755),
|
||||
vec2(-0.885922, 0.215369),
|
||||
vec2(0.566637, 0.605213),
|
||||
vec2(0.039766, -0.396100),
|
||||
vec2(0.751946, 0.453352),
|
||||
vec2(0.078707, -0.715323),
|
||||
vec2(-0.075838, -0.529344),
|
||||
vec2(0.724479, -0.580798),
|
||||
vec2(0.222999, -0.215125),
|
||||
vec2(-0.467574, -0.405438),
|
||||
vec2(-0.248268, -0.814753),
|
||||
vec2(0.354411, -0.887570),
|
||||
vec2(0.175817, 0.382366),
|
||||
vec2(0.487472, -0.063082),
|
||||
vec2(-0.084078, 0.898312),
|
||||
vec2(0.488876, -0.783441),
|
||||
vec2(0.470016, 0.217933),
|
||||
vec2(-0.696890, -0.549791),
|
||||
vec2(-0.149693, 0.605762),
|
||||
vec2(0.034211, 0.979980),
|
||||
vec2(0.503098, -0.308878),
|
||||
vec2(-0.016205, -0.872921),
|
||||
vec2(0.385784, -0.393902),
|
||||
vec2(-0.146886, -0.859249),
|
||||
vec2(0.643361, 0.164098),
|
||||
vec2(0.634388, -0.049471),
|
||||
vec2(-0.688894, 0.007843),
|
||||
vec2(0.464034, -0.188818),
|
||||
vec2(-0.440840, 0.137486),
|
||||
vec2(0.364483, 0.511704),
|
||||
vec2(0.034028, 0.325968),
|
||||
vec2(0.099094, -0.308023),
|
||||
vec2(0.693960, -0.366253),
|
||||
vec2(0.678884, -0.204688),
|
||||
vec2(0.001801, 0.780328),
|
||||
vec2(0.145177, -0.898984),
|
||||
vec2(0.062655, -0.611866),
|
||||
vec2(0.315226, -0.604297),
|
||||
vec2(-0.780145, 0.486251),
|
||||
vec2(-0.371868, 0.882138),
|
||||
vec2(0.200476, 0.494430),
|
||||
vec2(-0.494552, -0.711051),
|
||||
vec2(0.612476, 0.705252),
|
||||
vec2(-0.578845, -0.768792),
|
||||
vec2(-0.772454, -0.090976),
|
||||
vec2(0.504440, 0.372295),
|
||||
vec2(0.155736, 0.065157),
|
||||
vec2(0.391522, 0.849605),
|
||||
vec2(-0.620106, -0.328104),
|
||||
vec2(0.789239, -0.419965),
|
||||
vec2(-0.545396, 0.538133),
|
||||
vec2(-0.178564, -0.596057)
|
||||
);
|
||||
|
||||
// $NOTE: The first four values here are the same as the first four in RGBToCRT.hlsl, and are expected to match.
|
||||
|
||||
// This shader is intended to render a screen of the correct shape regardless of the output render target shape,
|
||||
// effectively letterboxing or pillarboxing as needed(i.e. rendering a 4:3 screen to a 16:9 render target).
|
||||
// g_viewScale is the scale value necessary to get the resulting screen scale correct. In the event the output
|
||||
// render target is wider than the intended screen, the screen needs to be scaled down horizontally to pillarbox,
|
||||
// usually like (where screenAspectRatio is crtScreenWidth / crtScreenHeight):
|
||||
// (x: (renderTargetWidth / renderTargetHeight) * (1.0 / screenAspectRatio), y: 1.0)
|
||||
// if the output render target is taller than the intended screen, it will end up letterboxed using something like:
|
||||
// (x: 1.0, y: (renderTargetHeight / renderTargetWidth) * screenAspectRatio)
|
||||
// Note that if overscan (where the edges of the screen cover up some of the picture) is being emulated, it
|
||||
// potentially needs to be taken into account in this value too. See RGBToCRT.h for details if that's the case.
|
||||
vec2 g_viewScale = vec2(1.0);
|
||||
|
||||
// If overscan emulation is intended (where the edges of the screen cover up some of the picture), then this is the
|
||||
// amount of signal texture scaling needed to account for that. Given an overscan value "overscanAmount" that's
|
||||
// (overscanLeft + overscanRight, overscanTop + overscanBottom)
|
||||
// this value should end up being:
|
||||
// (inputImageSize.xy - overscanAmount.xy) / inputImageSize.xy
|
||||
vec2 g_overscanScale = vec2(1.0);
|
||||
|
||||
// This is the texture coordinate offset to adjust for overscan. Because the input coordinates are [-1..1] instead
|
||||
// of [0..1], this is the offset needed to recenter the value. Given an "overscanDifference" value:
|
||||
// (overscanLeft - overscanRight, overscanTop - overscanBottom)
|
||||
// this value should be:
|
||||
// overscanDifference.xy/ inputImageSize.xy * 0.5
|
||||
vec2 g_overscanOffset = vec2(0.);
|
||||
|
||||
// The amount along each axis to apply the virtual-curved screen distortion. Usually a value in [0..1]. "0" indicates
|
||||
// no curvature (a flat screen) and "1" indicates "quite curved"
|
||||
vec2 g_distortion = vec2(params.warpX, params.warpY);
|
||||
|
||||
// The distortion amount used for where the edges of the screen mask should be. Should be at least g_distortion, but
|
||||
// if emulating an older TV which had some additional bevel curvature that cut off part of the picture, this can be
|
||||
// applied by adding additional value to this.
|
||||
// dunno what this does yet. revisit it when the mask tiling is working -HK
|
||||
vec2 g_maskDistortion = vec2(0., 0.);
|
||||
|
||||
// The scale of the mask texture. Higher value means the coordinates are scaled more and, thus, the shadow mask is
|
||||
// smaller.
|
||||
vec2 g_maskScale = vec2(global.mask_scale);
|
||||
|
||||
// The dimensions of the source texture (used for aspect correction
|
||||
float g_aspect = 1.0;
|
||||
|
||||
// How much to round the corners of the screen to emulate a TV with rounded corners that cut off a little picture.
|
||||
// 0 indicates no rounding (squared-off corners), while a value of 1.0 means "the whole screen is an oval."
|
||||
// Values <= 0.2 are recommended.
|
||||
float g_roundedCornerSize = params.corner;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 pixelCoord8k = uvec2(round(inTexCoord * vec2(7680, 4320)));
|
||||
|
||||
// First thing we want to do is scale our input texture coordinate to be in [-1..1] instead of [0..1] and adjust for
|
||||
vec2 scaledTexCoord = (inTexCoord * 2. - 1.) * g_viewScale;
|
||||
|
||||
// Distort these coordinates to get the -1..1 screen area:
|
||||
vec2 t = DistortCRTCoordinates(scaledTexCoord, g_distortion);
|
||||
|
||||
// Calculate a separate set of distorted coordinates, this for the outer mask (which determines the masking off of extra-rounded screen
|
||||
// edges).
|
||||
vec2 maskT = DistortCRTCoordinates(t, g_maskDistortion.yx);
|
||||
|
||||
// Adjust for overscan
|
||||
t = t * g_overscanScale + g_overscanOffset * 2.0;
|
||||
|
||||
// Calculate the signed distance to the edge of the "screen" taking the rounded corners into account. This will be used to generate the
|
||||
// mask around the edges of the "screen" where we draw a dark color.
|
||||
float edgeDist;
|
||||
{
|
||||
// Get our coordinate as just an "upper quadrant" coordinate, scaled to have the correct display aspect ratio.
|
||||
vec2 sq = vec2(g_aspect, 1.0) / max(1.0, g_aspect);
|
||||
vec2 upperQuadrantT = abs(maskT);
|
||||
|
||||
// Get the signed distance to a rounded rectangle to get our corners.
|
||||
vec2 q = upperQuadrantT * sq - sq + g_roundedCornerSize;
|
||||
edgeDist = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - g_roundedCornerSize;
|
||||
}
|
||||
|
||||
// Use our signed distance to edge of screen along with the ddx/ddy of our mask coordinate to generate a nicely-antialiased mask where
|
||||
// 0 means "fully outside of the screen" and 1 means "fully on-screen".
|
||||
float maskAlpha = 1.0 - smoothstep(-length(ddx(maskT) + ddy(maskT)), 0.0, edgeDist);
|
||||
if (max(abs(scaledTexCoord.x), abs(scaledTexCoord.y)) > 1.1)
|
||||
{
|
||||
maskAlpha = 0.0;
|
||||
}
|
||||
|
||||
// Now supersample the mask texture with a mip-map bias to make it sharper. The supersampling will help counteract the bias and
|
||||
// give us a sharp mask with minimal-to-no aliasing.
|
||||
float angle = Noise2D(t * 1000., 10.0 * animate) * 6.28318531;
|
||||
vec2 rotX = vec2(sin(angle), cos(angle)) * 1.414;
|
||||
vec2 rotY = vec2(-rotX.y, rotX.x);
|
||||
|
||||
vec2 dxT = vec2(dot(rotX, ddx(t)), dot(rotY, ddx(t)));
|
||||
vec2 dyT = vec2(dot(rotX, ddy(t)), dot(rotY, ddy(t)));
|
||||
vec3 color = vec3(0., 0., 0.);
|
||||
for (int i = 0; i < k_samplePointCount; i++)
|
||||
{
|
||||
color += texture(g_maskTexture, 0.5 * (t + k_samplingPattern[i].x * dxT + k_samplingPattern[i].y * dyT) * 1.0 + 0.5, -2.0).rgb;
|
||||
}
|
||||
|
||||
color /= float(k_samplePointCount);
|
||||
|
||||
// Our final texture contains the rgb value from the mask, as well as the mask value in the alpha channel.
|
||||
// Note that the color channel has not been premultiplied with the mask.
|
||||
FragColor = vec4(color, maskAlpha);
|
||||
}
|
||||
|
243
crt/shaders/cathode-retro/cathode-retro-crt-rgb-to-crt.slang
Normal file
243
crt/shaders/cathode-retro/cathode-retro-crt-rgb-to-crt.slang
Normal file
|
@ -0,0 +1,243 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
#include "slang_params.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader combines the current frame, the previous frame, screen mask, and diffusion into the final render.
|
||||
// It's a relatively complex shader, and certainly if there are features that aren't needed for the current render it
|
||||
// could be simplified.
|
||||
|
||||
// $TODO: May want to do something like an ubershader version that does exactly the minimum amount of work based on how
|
||||
// many features are actually in use (pulling out the distortion or, more importantly, the sampling of the previous/
|
||||
// diffusion textures when they're unneeded).
|
||||
|
||||
#include "cathode-retro-crt-distort-coordinates.inc"
|
||||
|
||||
// This is the RGB current frame texture - the output of the NTSC decode shaders if decoding was needed.
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_currentFrameTexture, g_currentFrameSampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D g_currentFrameTexture;
|
||||
|
||||
// This is the previous frame's texture (i.e. last frame's g_currentFrameTexture).
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_previousFrameTexture, g_previousFrameSampler);
|
||||
layout(set = 0, binding = 3) uniform sampler2D PassFeedback14;
|
||||
#define g_previousFrameTexture PassFeedback14
|
||||
|
||||
// This texture is the output of the GenerateScreenTexture shader, containing the (scaled, tiled, and antialiased) mask
|
||||
// texture in the rgb channels and the edge-of-screen mask value in the alpha channel. It is expected to have been
|
||||
// generated at our output resolution (i.e. it's 1:1 pixels with our output render target)
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_screenMaskTexture, g_screenMaskSampler);
|
||||
layout(set = 0, binding = 4) uniform sampler2D g_screenMaskTexture;
|
||||
|
||||
// This texture contains a tonemapped/blurred version of the input texture, to emulate the diffusion of the light from
|
||||
// the phosphors through the glass on the front of a CRT screen.
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_diffusionTexture, g_diffusionSampler);
|
||||
layout(set = 0, binding = 5) uniform sampler2D g_diffusionTexture;
|
||||
|
||||
// This shader is intended to render a screen of the correct shape regardless of the output render target shape,
|
||||
// effectively letterboxing or pillarboxing as needed(i.e. rendering a 4:3 screen to a 16:9 render target).
|
||||
// g_viewScale is the scale value necessary to get the resulting screen scale correct. In the event the output
|
||||
// render target is wider than the intended screen, the screen needs to be scaled down horizontally to pillarbox,
|
||||
// usually like (where screenAspectRatio is crtScreenWidth / crtScreenHeight):
|
||||
// (x: (renderTargetWidth / renderTargetHeight) * (1.0 / screenAspectRatio), y: 1.0)
|
||||
// if the output render target is taller than the intended screen, it will end up letterboxed using something like:
|
||||
// (x: 1.0, y: (renderTargetHeight / renderTargetWidth) * screenAspectRatio)
|
||||
// Note that if overscan (where the edges of the screen cover up some of the picture) is being emulated, it
|
||||
// potentially needs to be taken into account in this value too. See RGBToCRT.h for details if that's the case.
|
||||
vec2 g_viewScale = vec2(1.0);
|
||||
|
||||
// If overscan emulation is intended (where the edges of the screen cover up some of the picture), then this is the
|
||||
// amount of signal texture scaling needed to account for that. Given an overscan value "overscanAmount" that's
|
||||
// (overscanLeft + overscanRight, overscanTop + overscanBottom)
|
||||
// this value should end up being:
|
||||
// (inputImageSize.xy - overscanAmount.xy) / inputImageSize.xy
|
||||
vec2 g_overscanScale = vec2(1.0);
|
||||
|
||||
// This is the texture coordinate offset to adjust for overscan. Because the input coordinates are [-1..1] instead
|
||||
// of [0..1], this is the offset needed to recenter the value. Given an "overscanDifference" value:
|
||||
// (overscanLeft - overscanRight, overscanTop - overscanBottom)
|
||||
// this value should be:
|
||||
// overscanDifference.xy/ inputImageSize.xy * 0.5
|
||||
vec2 g_overscanOffset = vec2(0.);
|
||||
|
||||
// The amount along each axis to apply the virtual-curved screen distortion. Usually a value in [0..1]. "0" indicates
|
||||
// no curvature (a flat screen) and "1" indicates "quite curved"
|
||||
vec2 g_distortion = vec2(params.warpX, params.warpY);
|
||||
|
||||
// The RGBA color of the area around the screen.
|
||||
vec4 g_backgroundColor = vec4(0.0);
|
||||
|
||||
// How much of the previous frame's brightness to keep. 0 means "we don't use the previous frame at all" and 1 means
|
||||
// "the previous frame is at full brightness". In many CRTs, the phosphor persistence is short enough that it would
|
||||
// be effectively 0 at 50-60fps (As a CRT's phospors could potentially be completely faded out by then). However,
|
||||
// for some cases (for instance, interlaced video or for actual NES/SNES/probably other console output) it is
|
||||
// generally preferable to turn on a little bit of persistance to lessen temporal flickering on an LCD screen as it
|
||||
// can tend to look bad depending on the panel (seriously, check out https://www.youtube.com/watch?v=kA8CIY0DeS8
|
||||
// which is what my LCD panel was doing *after* the flickering interlace test truck I had had been gone for 10
|
||||
// minutes)
|
||||
float g_phosphorPersistence = global.persistence;
|
||||
|
||||
// How many scanlines there are in this field of the input (where a field is either the even or odd scanlines of an
|
||||
// interlaced frame, or the entirety of a progressive-scan frame)
|
||||
float g_scanlineCount = (params.OriginalSize.y < 400.) ? params.OriginalSize.y : params.OriginalSize.y * 0.5;
|
||||
|
||||
// The strength of the separation between scanlines. 0 means "no scanline separation at all" and 1 means "separate
|
||||
// the scanlines as much as possible" - on high-enough resolution output render target (at 4k for sure) "1" means
|
||||
// "fully black between scanlines", but to reduce aliasing that amount of separation will diminish at lower output
|
||||
// resolution.
|
||||
float g_scanlineStrength = global.scan_intens;
|
||||
|
||||
// This is the scanline-space coordinate offset to use to adjust our texture coordinate's y value based on whether
|
||||
// this is a (1-based) odd frame or an even frame. It will be 0.5 (shifting the texture up half a scanline) if it's
|
||||
// an odd frame and -0.5 (shifting the texture down half a scanline) if it's an even frame.
|
||||
float g_curEvenOddTexelOffset = 0.5;
|
||||
|
||||
// Same as above, but it's the even/odd texel offset that was relevant for the previous frame (so we can blend it in
|
||||
// at the proper spot). This should match g_curEvenOddTexelOffset for a progressive-scan signal and should be
|
||||
// "-g_curEvenOddTexelOffset" if interlaced.
|
||||
float g_prevEvenOddTexelOffset = -0.5;
|
||||
|
||||
// This is how much diffusion to apply, blending in the diffusion texture which is an emulation of the light from the
|
||||
// screen scattering in the glass on the front of the CRT - 0 means "no diffusion" and 1 means "a whole lot of
|
||||
// diffusion".
|
||||
float g_diffusionStrength = global.diffusion;
|
||||
|
||||
// How much we want to blend in the mask. 0 means "mask is not visible" and 1 means "mask is fully visible"
|
||||
float g_maskStrength = global.mask_intens;
|
||||
|
||||
// The darkness of the darkest part of the mask. 0 means the area between the "dots" is black, 0.9 means the spaces
|
||||
// between are nearly white.
|
||||
float g_maskDepth = global.mask_depth;
|
||||
|
||||
|
||||
const float pi = 3.141592653;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
// The screen texture is 1:1 with the output render target so sample it directly off of the input texture coordinates
|
||||
vec4 screenMask = texture(g_screenMaskTexture, inTexCoord);
|
||||
|
||||
// Now distort the texture coordinates to get our texture into the correct space for display.
|
||||
vec2 t = DistortCRTCoordinates((inTexCoord * 2. - 1.) * g_viewScale, g_distortion) * g_overscanScale
|
||||
+ g_overscanOffset * 2.0;
|
||||
|
||||
// Use "t" (before we do the even/odd update or the scanline-sharpening) to load our diffusion texture, which is an
|
||||
// approximation of the glass in front of the phosphors scattering light a little bit due to imperfections.
|
||||
vec3 diffusionColor = texture(g_diffusionTexture, t * 0.5 + 0.5).rgb;
|
||||
|
||||
// Offset based on whether we're an even or odd frame
|
||||
t.y += g_curEvenOddTexelOffset / g_scanlineCount;
|
||||
|
||||
// Before we adjust the y coordinate to sharpen the scanline interpolation, grab our scanline-space y coordinate.
|
||||
float scanlineSpaceY = t.y * g_scanlineCount + g_scanlineCount;
|
||||
|
||||
// Because t.y is currently in [-1, 1], this derivative multiplied by the scanline count ends up being the number of
|
||||
// total scanlines involved (including the empty ones). So this is "how much along y does one output pixel move us
|
||||
// relative to g_scanlineCount*2"
|
||||
float pixelLengthInScanlineSpace = length(ddy(t)) * g_scanlineCount;
|
||||
|
||||
// Do a little magic to sharpen up the interpolation between scanlines - a CRT (didn't really have any vertical
|
||||
// smoothing, so we want to make the centers of our texels a little more solid and do less bilinear blending
|
||||
// vertically (just a little to simulate the softness of the screen in general)
|
||||
{
|
||||
float scanlineIndex = (t.y * 0.5 + 0.5) * g_scanlineCount;
|
||||
float scanlineFrac = fract(scanlineIndex);
|
||||
scanlineIndex -= scanlineFrac;
|
||||
scanlineFrac -= 0.5;
|
||||
float signFrac = sign(scanlineFrac);
|
||||
float ySharpening = 0.1; // Any value from [0, 0.5) should work here, larger means the vertical pixels are sharper
|
||||
scanlineFrac = sign(scanlineFrac) * saturate(abs(scanlineFrac) - ySharpening) * 0.5 / (0.5 - ySharpening);
|
||||
|
||||
scanlineIndex += scanlineFrac + 0.5;
|
||||
t.y = float(scanlineIndex) / g_scanlineCount * 2. - 1.;
|
||||
}
|
||||
|
||||
// Sample the actual display texture and add in the previous frame (For phosphor persistence)
|
||||
vec3 sourceColor;
|
||||
{
|
||||
t = t * 0.5 + 0.5; // t has been in -1..1 range this whole time, scale it to 0..1 for sampling.
|
||||
sourceColor = texture(g_currentFrameTexture, t).rgb;
|
||||
|
||||
// Reduce the influence of the scanlines as we get small enough that aliasing is unavoidable (fully fading out at
|
||||
// 0.7x nyquist - early to ensure that we don't introduce any aliasing as we get too close).
|
||||
float scanlineStrength = lerp(
|
||||
g_scanlineStrength,
|
||||
0.,
|
||||
smoothstep(1.0, 1.4, length(ddy(inTexCoord)) * g_scanlineCount * 2.));
|
||||
|
||||
// $TODO: We may want to find a way to precalculate this scanline value as a texture (like the screen texture).
|
||||
// Unfortunately the screen texture is already using all 4 of its components so we'd need a new one, which is why
|
||||
// I didn't).
|
||||
float scanline;
|
||||
{
|
||||
// For the actual scanline value, we use the following equation:
|
||||
// cos(scanlineSpaceY * pi) * 0.5 + 0.5
|
||||
// That is, at scanline centers it's either 0 or 1. However, to avoid moir<69> patterns we actually want to
|
||||
// supersample it. The good news is, we can supersample over a range using numeric integration since it's a
|
||||
// sinusoid. The integration of that wave between y coordinates ya and yb ends up being:
|
||||
// (yb - ya)/2 + 1/(2pi) * (sin(pi*ya) - sin(pi*yb)))
|
||||
// but in order to turn it into an average we need to divide that result by the width of the range, which is
|
||||
// (yb - ya).
|
||||
|
||||
// As pixelLengthInScanlineSpace gets larger (i.e. effective output resolution gets smaller) we want to ramp up
|
||||
// the blurring dramatically to avoid moir<69> effects. There's no real mathematical basis for this algorithm, I
|
||||
// just eyeballed a curve until I got something that looked good at 1080p and up and introduced minimal moir<69>
|
||||
// (minimal meaning "it's not visible when the mask is also enabled").
|
||||
float scale = pow(abs(pixelLengthInScanlineSpace), 2.6) * 7;
|
||||
|
||||
float ya = scanlineSpaceY - scale;
|
||||
float yb = scanlineSpaceY + scale;
|
||||
scanline = (0.5 * (yb - ya) + 1.0 / (2. * pi) * (sin(pi * ya) - sin(pi * yb))) / (2. * scale);
|
||||
|
||||
// Now multiply in the scanline-spacing darkening according to the scanline strength.
|
||||
sourceColor *= lerp(1. - scanlineStrength, 1.0, scanline);
|
||||
}
|
||||
|
||||
vec2 prevT = t;
|
||||
float prevScanline = scanline;
|
||||
if (g_prevEvenOddTexelOffset != g_curEvenOddTexelOffset)
|
||||
{
|
||||
// We have a different scanline parity in the previous frame so we need to offset our texture coordinate (to put
|
||||
// the prev frame's scanline center at the correct spot) and then invert our scanline multiplier (to darken the
|
||||
// alternate scanlines)
|
||||
prevT.y += g_prevEvenOddTexelOffset / g_scanlineCount;
|
||||
prevScanline = 1. - prevScanline;
|
||||
}
|
||||
|
||||
// Sample the previous texture and darken the area between scanlines accordingly.
|
||||
vec3 prevSourceColor = texture(g_previousFrameTexture, prevT).rgb;
|
||||
prevSourceColor *= lerp(1. - scanlineStrength, 1.0, prevScanline);
|
||||
|
||||
// Blend our previous frame into the current one based on how much phosphor persistence we have between frames.
|
||||
sourceColor = max(prevSourceColor * g_phosphorPersistence, sourceColor);
|
||||
|
||||
// We want to adjust the brightness to somewhat compensate for the darkening due to scanlines
|
||||
sourceColor /= 1.0 - scanlineStrength * 0.5;
|
||||
}
|
||||
|
||||
// Time to put it all together: first, by applying the screen mask (i.e. the shadow mask/aperture grill, etc)...
|
||||
// $TODO: Figure out a proper scaling factor here - the 3.0 is meant to adjust for the fact that the mask cuts out
|
||||
// approximately 2/3rds of the brightness, but it's not exact, we could calculate this, I just haven't.
|
||||
screenMask.rgb = screenMask.rgb * ((2.0 + global.cat_mask_picker) - g_maskDepth) + g_maskDepth;
|
||||
vec3 result = sourceColor * lerp(vec3(1.,1.,1.), screenMask.rgb, g_maskStrength);
|
||||
|
||||
// ... then bringing in some diffusion on top (This isn't physically accurate (it should really be a lerp between res
|
||||
// and diffusionColor) but doing it this way preserves the brightness and still looks reasonable, especially when
|
||||
// displaying bright things on a dark background)
|
||||
result = max(diffusionColor * g_diffusionStrength, result);
|
||||
|
||||
// Finally, mask out everything outside of the edges to get our final output value.
|
||||
FragColor = lerp(g_backgroundColor, vec4(result, 1.), screenMask.a);
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
#include "slang_params.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader combines the current frame, the previous frame, screen mask, and diffusion into the final render.
|
||||
// It's a relatively complex shader, and certainly if there are features that aren't needed for the current render it
|
||||
// could be simplified.
|
||||
|
||||
// $TODO: May want to do something like an ubershader version that does exactly the minimum amount of work based on how
|
||||
// many features are actually in use (pulling out the distortion or, more importantly, the sampling of the previous/
|
||||
// diffusion textures when they're unneeded).
|
||||
|
||||
#include "cathode-retro-crt-distort-coordinates.inc"
|
||||
|
||||
// This is the RGB current frame texture - the output of the NTSC decode shaders if decoding was needed.
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_currentFrameTexture, g_currentFrameSampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D g_sourceTexture;
|
||||
// bypassing the signal generation temporarily just to get the CRT stuff working first
|
||||
#define g_currentFrameTexture g_sourceTexture
|
||||
|
||||
// This is the previous frame's texture (i.e. last frame's g_currentFrameTexture).
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_previousFrameTexture, g_previousFrameSampler);
|
||||
layout(set = 0, binding = 3) uniform sampler2D PassFeedback0;
|
||||
#define g_previousFrameTexture PassFeedback0
|
||||
|
||||
// This texture is the output of the GenerateScreenTexture shader, containing the (scaled, tiled, and antialiased) mask
|
||||
// texture in the rgb channels and the edge-of-screen mask value in the alpha channel. It is expected to have been
|
||||
// generated at our output resolution (i.e. it's 1:1 pixels with our output render target)
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_screenMaskTexture, g_screenMaskSampler);
|
||||
layout(set = 0, binding = 4) uniform sampler2D g_screenMaskTexture;
|
||||
|
||||
// This texture contains a tonemapped/blurred version of the input texture, to emulate the diffusion of the light from
|
||||
// the phosphors through the glass on the front of a CRT screen.
|
||||
// This sampler should be set up with linear texture sampling and should be set to clamp (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_diffusionTexture, g_diffusionSampler);
|
||||
layout(set = 0, binding = 5) uniform sampler2D g_diffusionTexture;
|
||||
|
||||
// This shader is intended to render a screen of the correct shape regardless of the output render target shape,
|
||||
// effectively letterboxing or pillarboxing as needed(i.e. rendering a 4:3 screen to a 16:9 render target).
|
||||
// g_viewScale is the scale value necessary to get the resulting screen scale correct. In the event the output
|
||||
// render target is wider than the intended screen, the screen needs to be scaled down horizontally to pillarbox,
|
||||
// usually like (where screenAspectRatio is crtScreenWidth / crtScreenHeight):
|
||||
// (x: (renderTargetWidth / renderTargetHeight) * (1.0 / screenAspectRatio), y: 1.0)
|
||||
// if the output render target is taller than the intended screen, it will end up letterboxed using something like:
|
||||
// (x: 1.0, y: (renderTargetHeight / renderTargetWidth) * screenAspectRatio)
|
||||
// Note that if overscan (where the edges of the screen cover up some of the picture) is being emulated, it
|
||||
// potentially needs to be taken into account in this value too. See RGBToCRT.h for details if that's the case.
|
||||
vec2 g_viewScale = vec2(1.0);
|
||||
|
||||
// If overscan emulation is intended (where the edges of the screen cover up some of the picture), then this is the
|
||||
// amount of signal texture scaling needed to account for that. Given an overscan value "overscanAmount" that's
|
||||
// (overscanLeft + overscanRight, overscanTop + overscanBottom)
|
||||
// this value should end up being:
|
||||
// (inputImageSize.xy - overscanAmount.xy) / inputImageSize.xy
|
||||
vec2 g_overscanScale = vec2(1.0);
|
||||
|
||||
// This is the texture coordinate offset to adjust for overscan. Because the input coordinates are [-1..1] instead
|
||||
// of [0..1], this is the offset needed to recenter the value. Given an "overscanDifference" value:
|
||||
// (overscanLeft - overscanRight, overscanTop - overscanBottom)
|
||||
// this value should be:
|
||||
// overscanDifference.xy/ inputImageSize.xy * 0.5
|
||||
vec2 g_overscanOffset = vec2(0.);
|
||||
|
||||
// The amount along each axis to apply the virtual-curved screen distortion. Usually a value in [0..1]. "0" indicates
|
||||
// no curvature (a flat screen) and "1" indicates "quite curved"
|
||||
vec2 g_distortion = vec2(params.warpX, params.warpY);
|
||||
|
||||
// The RGBA color of the area around the screen.
|
||||
vec4 g_backgroundColor = vec4(0.0);
|
||||
|
||||
// How much of the previous frame's brightness to keep. 0 means "we don't use the previous frame at all" and 1 means
|
||||
// "the previous frame is at full brightness". In many CRTs, the phosphor persistence is short enough that it would
|
||||
// be effectively 0 at 50-60fps (As a CRT's phospors could potentially be completely faded out by then). However,
|
||||
// for some cases (for instance, interlaced video or for actual NES/SNES/probably other console output) it is
|
||||
// generally preferable to turn on a little bit of persistance to lessen temporal flickering on an LCD screen as it
|
||||
// can tend to look bad depending on the panel (seriously, check out https://www.youtube.com/watch?v=kA8CIY0DeS8
|
||||
// which is what my LCD panel was doing *after* the flickering interlace test truck I had had been gone for 10
|
||||
// minutes)
|
||||
float g_phosphorPersistence = global.persistence;
|
||||
|
||||
// How many scanlines there are in this field of the input (where a field is either the even or odd scanlines of an
|
||||
// interlaced frame, or the entirety of a progressive-scan frame)
|
||||
float g_scanlineCount = (params.OriginalSize.y < 400.) ? params.OriginalSize.y : params.OriginalSize.y * 0.5;
|
||||
|
||||
// The strength of the separation between scanlines. 0 means "no scanline separation at all" and 1 means "separate
|
||||
// the scanlines as much as possible" - on high-enough resolution output render target (at 4k for sure) "1" means
|
||||
// "fully black between scanlines", but to reduce aliasing that amount of separation will diminish at lower output
|
||||
// resolution.
|
||||
float g_scanlineStrength = global.scan_intens;
|
||||
|
||||
// This is the scanline-space coordinate offset to use to adjust our texture coordinate's y value based on whether
|
||||
// this is a (1-based) odd frame or an even frame. It will be 0.5 (shifting the texture up half a scanline) if it's
|
||||
// an odd frame and -0.5 (shifting the texture down half a scanline) if it's an even frame.
|
||||
float g_curEvenOddTexelOffset = 0.5;
|
||||
|
||||
// Same as above, but it's the even/odd texel offset that was relevant for the previous frame (so we can blend it in
|
||||
// at the proper spot). This should match g_curEvenOddTexelOffset for a progressive-scan signal and should be
|
||||
// "-g_curEvenOddTexelOffset" if interlaced.
|
||||
float g_prevEvenOddTexelOffset = -0.5;
|
||||
|
||||
// This is how much diffusion to apply, blending in the diffusion texture which is an emulation of the light from the
|
||||
// screen scattering in the glass on the front of the CRT - 0 means "no diffusion" and 1 means "a whole lot of
|
||||
// diffusion".
|
||||
float g_diffusionStrength = global.diffusion;
|
||||
|
||||
// How much we want to blend in the mask. 0 means "mask is not visible" and 1 means "mask is fully visible"
|
||||
float g_maskStrength = global.mask_intens;
|
||||
|
||||
// The darkness of the darkest part of the mask. 0 means the area between the "dots" is black, 0.9 means the spaces
|
||||
// between are nearly white.
|
||||
float g_maskDepth = global.mask_depth;
|
||||
|
||||
|
||||
const float pi = 3.141592653;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
// The screen texture is 1:1 with the output render target so sample it directly off of the input texture coordinates
|
||||
vec4 screenMask = texture(g_screenMaskTexture, inTexCoord);
|
||||
|
||||
// Now distort the texture coordinates to get our texture into the correct space for display.
|
||||
vec2 t = DistortCRTCoordinates((inTexCoord * 2. - 1.) * g_viewScale, g_distortion) * g_overscanScale
|
||||
+ g_overscanOffset * 2.0;
|
||||
|
||||
// Use "t" (before we do the even/odd update or the scanline-sharpening) to load our diffusion texture, which is an
|
||||
// approximation of the glass in front of the phosphors scattering light a little bit due to imperfections.
|
||||
vec3 diffusionColor = texture(g_diffusionTexture, t * 0.5 + 0.5).rgb;
|
||||
|
||||
// Offset based on whether we're an even or odd frame
|
||||
t.y += g_curEvenOddTexelOffset / g_scanlineCount;
|
||||
|
||||
// Before we adjust the y coordinate to sharpen the scanline interpolation, grab our scanline-space y coordinate.
|
||||
float scanlineSpaceY = t.y * g_scanlineCount + g_scanlineCount;
|
||||
|
||||
// Because t.y is currently in [-1, 1], this derivative multiplied by the scanline count ends up being the number of
|
||||
// total scanlines involved (including the empty ones). So this is "how much along y does one output pixel move us
|
||||
// relative to g_scanlineCount*2"
|
||||
float pixelLengthInScanlineSpace = length(ddy(t)) * g_scanlineCount;
|
||||
|
||||
// Do a little magic to sharpen up the interpolation between scanlines - a CRT (didn't really have any vertical
|
||||
// smoothing, so we want to make the centers of our texels a little more solid and do less bilinear blending
|
||||
// vertically (just a little to simulate the softness of the screen in general)
|
||||
{
|
||||
float scanlineIndex = (t.y * 0.5 + 0.5) * g_scanlineCount;
|
||||
float scanlineFrac = fract(scanlineIndex);
|
||||
scanlineIndex -= scanlineFrac;
|
||||
scanlineFrac -= 0.5;
|
||||
float signFrac = sign(scanlineFrac);
|
||||
float ySharpening = 0.1; // Any value from [0, 0.5) should work here, larger means the vertical pixels are sharper
|
||||
scanlineFrac = sign(scanlineFrac) * saturate(abs(scanlineFrac) - ySharpening) * 0.5 / (0.5 - ySharpening);
|
||||
|
||||
scanlineIndex += scanlineFrac + 0.5;
|
||||
t.y = float(scanlineIndex) / g_scanlineCount * 2. - 1.;
|
||||
}
|
||||
|
||||
// Sample the actual display texture and add in the previous frame (For phosphor persistence)
|
||||
vec3 sourceColor;
|
||||
{
|
||||
t = t * 0.5 + 0.5; // t has been in -1..1 range this whole time, scale it to 0..1 for sampling.
|
||||
sourceColor = texture(g_currentFrameTexture, t).rgb;
|
||||
|
||||
// Reduce the influence of the scanlines as we get small enough that aliasing is unavoidable (fully fading out at
|
||||
// 0.7x nyquist - early to ensure that we don't introduce any aliasing as we get too close).
|
||||
float scanlineStrength = lerp(
|
||||
g_scanlineStrength,
|
||||
0.,
|
||||
smoothstep(1.0, 1.4, length(ddy(inTexCoord)) * g_scanlineCount * 2.));
|
||||
|
||||
// $TODO: We may want to find a way to precalculate this scanline value as a texture (like the screen texture).
|
||||
// Unfortunately the screen texture is already using all 4 of its components so we'd need a new one, which is why
|
||||
// I didn't).
|
||||
float scanline;
|
||||
{
|
||||
// For the actual scanline value, we use the following equation:
|
||||
// cos(scanlineSpaceY * pi) * 0.5 + 0.5
|
||||
// That is, at scanline centers it's either 0 or 1. However, to avoid moir<69> patterns we actually want to
|
||||
// supersample it. The good news is, we can supersample over a range using numeric integration since it's a
|
||||
// sinusoid. The integration of that wave between y coordinates ya and yb ends up being:
|
||||
// (yb - ya)/2 + 1/(2pi) * (sin(pi*ya) - sin(pi*yb)))
|
||||
// but in order to turn it into an average we need to divide that result by the width of the range, which is
|
||||
// (yb - ya).
|
||||
|
||||
// As pixelLengthInScanlineSpace gets larger (i.e. effective output resolution gets smaller) we want to ramp up
|
||||
// the blurring dramatically to avoid moir<69> effects. There's no real mathematical basis for this algorithm, I
|
||||
// just eyeballed a curve until I got something that looked good at 1080p and up and introduced minimal moir<69>
|
||||
// (minimal meaning "it's not visible when the mask is also enabled").
|
||||
float scale = pow(abs(pixelLengthInScanlineSpace), 2.6) * 7;
|
||||
|
||||
float ya = scanlineSpaceY - scale;
|
||||
float yb = scanlineSpaceY + scale;
|
||||
scanline = (0.5 * (yb - ya) + 1.0 / (2. * pi) * (sin(pi * ya) - sin(pi * yb))) / (2. * scale);
|
||||
|
||||
// Now multiply in the scanline-spacing darkening according to the scanline strength.
|
||||
sourceColor *= lerp(1. - scanlineStrength, 1.0, scanline);
|
||||
}
|
||||
|
||||
vec2 prevT = t;
|
||||
float prevScanline = scanline;
|
||||
if (g_prevEvenOddTexelOffset != g_curEvenOddTexelOffset)
|
||||
{
|
||||
// We have a different scanline parity in the previous frame so we need to offset our texture coordinate (to put
|
||||
// the prev frame's scanline center at the correct spot) and then invert our scanline multiplier (to darken the
|
||||
// alternate scanlines)
|
||||
prevT.y += g_prevEvenOddTexelOffset / g_scanlineCount;
|
||||
prevScanline = 1. - prevScanline;
|
||||
}
|
||||
|
||||
// Sample the previous texture and darken the area between scanlines accordingly.
|
||||
vec3 prevSourceColor = texture(g_previousFrameTexture, prevT).rgb;
|
||||
prevSourceColor *= lerp(1. - scanlineStrength, 1.0, prevScanline);
|
||||
|
||||
// Blend our previous frame into the current one based on how much phosphor persistence we have between frames.
|
||||
sourceColor = max(prevSourceColor * g_phosphorPersistence, sourceColor);
|
||||
|
||||
// We want to adjust the brightness to somewhat compensate for the darkening due to scanlines
|
||||
sourceColor /= 1.0 - scanlineStrength * 0.5;
|
||||
}
|
||||
|
||||
// Time to put it all together: first, by applying the screen mask (i.e. the shadow mask/aperture grill, etc)...
|
||||
// $TODO: Figure out a proper scaling factor here - the 3.0 is meant to adjust for the fact that the mask cuts out
|
||||
// approximately 2/3rds of the brightness, but it's not exact, we could calculate this, I just haven't.
|
||||
screenMask.rgb = screenMask.rgb * ((2.0 + global.cat_mask_picker) - g_maskDepth) + g_maskDepth;
|
||||
vec3 result = sourceColor * lerp(vec3(1.,1.,1.), screenMask.rgb, g_maskStrength);
|
||||
|
||||
// ... then bringing in some diffusion on top (This isn't physically accurate (it should really be a lerp between res
|
||||
// and diffusionColor) but doing it this way preserves the brightness and still looks reasonable, especially when
|
||||
// displaying bright things on a dark background)
|
||||
result = max(diffusionColor * g_diffusionStrength, result);
|
||||
|
||||
// Finally, mask out everything outside of the edges to get our final output value.
|
||||
FragColor = lerp(g_backgroundColor, vec4(result, 1.), screenMask.a);
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader takes a composite signal (like you'd get via a console's composite input) and separates out the luma
|
||||
// from the chroma so it is effectively an SVideo signal. I tried all sorts of wacky hijinks to filter these things
|
||||
// out in a solid emulation of what old CRT hardware was doing, but all of those filters were both VERY expensive and
|
||||
// also had annoying artifacts (like ringing). I tried all sorts of low pass and band stop solutions to get the luma
|
||||
// separated from the chroma.
|
||||
//
|
||||
// However, it turns out, that all you REALLY need to do (if your color cycle frequency is a nice multiple of your
|
||||
// pixel resolution, which it is), is average out that many pixels' worth of signal, and that flattens out the wave
|
||||
// frequency very nicely, leaving you with just the luma. This average (or box filter) is very effective at removing
|
||||
// exactly the frequency that we care about and leaving the rest. And it's about the simplest (and highest-
|
||||
// performance) possible way to do it.
|
||||
//
|
||||
// Basically, if you average every sample of the below sine wave, what you end up with is a value that represents the
|
||||
// y coordinate of its center (the line in the diagram).
|
||||
// _....._
|
||||
// ,=" "=.
|
||||
// ," ".
|
||||
// / ".
|
||||
// ---------------------.-------------------/
|
||||
// ". ."
|
||||
// "._ _,"
|
||||
// "-.....-"
|
||||
//
|
||||
//
|
||||
// That center line corresponds to the "luma" portion of the composite signal - the effective brightness of that
|
||||
// portion of the scanline (or, if you prefer, the Y component in the YIQ encoding).
|
||||
//
|
||||
// Once you have that, you can simply subtract this luma value from the original signal and you're left with JUST the
|
||||
// wave information (which is the chroma, or IQ portion of the YIQ encoding).
|
||||
//
|
||||
// It's also worth noting that this shader is capable of doing TWO luma/chroma demodulations at once (one to the
|
||||
// r and g output channels and one to b and a), for purposes of reducing/eliminating any jitter between two frames for
|
||||
// signals where the color phases alternate frame by frame (the NES and SNES do this, for instance).
|
||||
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-box-filter.inc"
|
||||
#define inTex vTexCoord
|
||||
|
||||
// This texture is expected to represent the visible signal part of an NTSC field (a full frame for a progressive scan
|
||||
// input, or one half of an interlaced frame for an interlaced one). If this were using the input from a true NTSC
|
||||
// signal, this would only contain information in the red channel - however, some emulated systems (the NES and SNES,
|
||||
// for instance) have different chroma phase components on alternating frames, and we can pass a variant of the
|
||||
// current frame with a different phase to average and reduce the flickering of temporal artifacts from frame to
|
||||
// frame ... this doesn't make much difference if your emulator is running at a perfectly smooth 60fps, but it can
|
||||
// make a big difference if there are frame drops).
|
||||
// The sampler should be set to linear sampling, and clamped addressing (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
// How many samples (texels along a scanline) there are per colorburst cycle (the color wave in the composite
|
||||
// signal). This shader currently assumes that it's integral (hence the int input) but it is totally possible to
|
||||
// make the shader support a floating-point value instead - it would just need to do an extra fractional addition of
|
||||
// the last texel.
|
||||
uint g_samplesPerColorburstCycle = uint(global.cb_samples);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 inputTexDim = params.OriginalSize.xy;
|
||||
|
||||
// Doing a box filter (an average) at exactly the colorburst cycle length is a *very* effective and efficient way to
|
||||
// remove the chroma information completely, leaving with just the luma.
|
||||
vec4 centerSample;
|
||||
vec2 luma = BoxFilter(
|
||||
g_sourceTexture,
|
||||
1.0 / inputTexDim,
|
||||
g_samplesPerColorburstCycle,
|
||||
inTex,
|
||||
centerSample).xy;
|
||||
|
||||
// Take our one or two computed luma channels and output them (swizzled) as r and b, then subtract those one or two
|
||||
// two lumas and subtract them from the corresponding input waves to get the separated chroma component, output as
|
||||
// g and a.
|
||||
// This way, if our render target is just a two-component texture (we're not doing any temporal aliasing reduction),
|
||||
// then r will be the luma and g will be the chroma.
|
||||
FragColor = vec4(luma, centerSample.xy - luma).rbga;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader does a horizontal three-tap blur or sharpen to each input scanline.
|
||||
|
||||
|
||||
#include "slang_params.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
|
||||
// The input RGB texture that will be filtered. It should be set up for linear filtering and clamp addressing.
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
// This is the strength of the blur - 0.0 will leave the output texture unchanged from the input, 1.0 will do a full
|
||||
// 3-texel average, and -1 will do a very extreme sharpen pass.
|
||||
float g_blurStrength = -global.blurStrength;
|
||||
|
||||
// The step between each sample, in texels. Usually scaled to be relative to the original input, which may have had a
|
||||
// different resolution than our input texture due to the NTSC generation/decode process (if there's no original
|
||||
// input, i.e. we're not using the NTSC signal generator and have gotten a signal straight from a real NTSC signal,
|
||||
// then you'd just want to pick some nice-on- average value instead)
|
||||
float g_stepSize = global.stepSize;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 inputTexelSize = vec2(ddx(inTexCoord).x, ddy(inTexCoord).y);
|
||||
|
||||
// Just a simple three-tap 1D filter.
|
||||
// $TODO: This could absolutely be done with two samples instead of 3, by taking advantage of linear filtering the
|
||||
// same way we do in the gaussian/lanczos filters.
|
||||
float blurSide = g_blurStrength / 3.0;
|
||||
float blurCenter = 1.0 - 2.0 * blurSide;
|
||||
vec4 color = texture(g_sourceTexture, (inTexCoord - vec2(g_stepSize, 0) * inputTexelSize))
|
||||
* blurSide
|
||||
+ texture(g_sourceTexture, (inTexCoord))
|
||||
* blurCenter
|
||||
+ texture(g_sourceTexture, (inTexCoord + vec2(g_stepSize, 0) * inputTexelSize))
|
||||
* blurSide;
|
||||
|
||||
FragColor = color;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This signal takes an input S-Video signal and converts it into a set of YIQ color space values.
|
||||
//
|
||||
// This process is called NTSC color demodulation, which is a type of QAM demodulation.
|
||||
//
|
||||
// An NTSC signal (either composite or S-Video) is really just encoded YIQ data, where the Y value is encoded as luma and IQ is encoded
|
||||
// as a waveform in the chroma. Since this shader takes an effective S-Video signal as input, the Y component is easy - it's just the
|
||||
// luma channel of the input.
|
||||
//
|
||||
// Just like filtering the luma from the chroma (see the comment in CompositeToSVideo.hlsl), rather than doing any sort of fancy filtering
|
||||
// here, the absolute best results I've gotten were from a super-basic average (which very nicely removes a very specific frequency and
|
||||
// all integer multiples of it), which is also exactly what we need here.
|
||||
//
|
||||
// Additionally, if we want temporal artifact reduction, we're doing two of this calculation (we have a (lumaA, chromaA, lumaB, chromaB)
|
||||
// texture representing the same frame with two different color phases), and we use the temporal artifact reduction value to blend between
|
||||
// them, which reduces or eliminates alternating-phase-related flickering that systems like the NES could generate.
|
||||
//
|
||||
// The output of this shader is a texture that contains the decoded Y, I, and Q channels in R, G, and B (plus 1.0 in alpha).
|
||||
|
||||
|
||||
#include "slang_params.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
// This is a 2- or 4-component texture that contains either a single luma, chroma sample pair or two luma, chroma pairs of S-Video-like
|
||||
// signal. It's 2 components if we have no temporal artifact reduction (we're not blending two versions of the same frame), 4 if we do.
|
||||
// This sampler should be set up for linear filtering and clamped addressing (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sourceSampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D IQ_separate;
|
||||
#define g_sourceTexture IQ_separate
|
||||
|
||||
// This is a 1- or 2-component texture that contains the colorburst phase offsets for each scanline. It's 1 component if we have no
|
||||
// temporal artifact reduction, and 2 if we do.
|
||||
// Each phase value in this texture is the phase in (fractional) multiples of the colorburst wavelength.
|
||||
// This sampler should be set up for linear filtering and clamped addressing (no wrapping).
|
||||
//DECLARE_TEXTURE2D(g_scanlinePhases, g_scanlinePhasesSampler);
|
||||
layout(set = 0, binding = 3) uniform sampler2D g_scanlinePhases;
|
||||
|
||||
|
||||
// How many samples (horizontal texels) there are per each color wave cycle.
|
||||
uint g_samplesPerColorburstCycle = uint(global.cb_samples);
|
||||
|
||||
// A value representing the tint offset (in colorburst wavelengths) from baseline that we want to use. Mostly used for fun to emulate the
|
||||
// tint dial of a CRT TV.
|
||||
float g_tint = global.tint;
|
||||
|
||||
// The width of the input signal
|
||||
uint g_inputWidth = uint(params.SourceSize.x);
|
||||
|
||||
|
||||
const float k_pi = 3.141592653;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
uint sampleXIndex = uint(floor(inTexCoord.x * g_inputWidth));
|
||||
|
||||
// Get the reference phase for our scanline
|
||||
vec2 relativePhase = texture(g_scanlinePhases, inTexCoord.yy).xy + g_tint;
|
||||
|
||||
// This is the chroma decode process, it's a QAM demodulation.
|
||||
// You multiply the chroma signal by a reference waveform and its quadrature (Basically, sin and cos at a given time) and then filter
|
||||
// out the chroma frequency (here done by a box filter (an average)). What you're left with are the approximate I and Q color space
|
||||
// values for this part of the image.
|
||||
vec4 chroma = texture(g_sourceTexture, inTexCoord).yyww;
|
||||
|
||||
// This is part of the chroma decode process, separated out into its own shader for efficiency's sake - it's the first half of a QAM
|
||||
// demodulation. Each piece of the chroma signal is multiplied by a reference waveform and its quadrature (basically, sin and cos at a
|
||||
// given time) and then filter out the chroma frequency (done in the next pass).
|
||||
vec2 s, c;
|
||||
sincos(2.0 * k_pi * (float(sampleXIndex) / g_samplesPerColorburstCycle + relativePhase), s, c);
|
||||
|
||||
// Return the chromas modulated with our sines and cosines (swizzled to line them up correctly)
|
||||
FragColor = chroma * vec4(s, -c).xzyw;
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This signal takes an input S-Video signal and converts it into a set of YIQ color space values.
|
||||
//
|
||||
// This process is called NTSC color demodulation, which is a type of QAM demodulation.
|
||||
//
|
||||
// An NTSC signal (either composite or S-Video) is really just encoded YIQ data, where the Y value is encoded as luma
|
||||
// and IQ is encoded as a waveform in the chroma. Since this shader takes an effective S-Video signal as input, the Y
|
||||
// component is easy - it's just the luma channel of the input.
|
||||
//
|
||||
// Just like filtering the luma from the chroma (see the comment in CompositeToSVideo.hlsl), rather than doing any sort
|
||||
// of fancy filtering here, the absolute best results I've gotten were from a super-basic average (which very nicely
|
||||
// removes a very specific frequency and all integer multiples of it), which is also exactly what we need here.
|
||||
//
|
||||
// Additionally, if we want temporal artifact reduction, we're doing two of this calculation (we have a
|
||||
// (lumaA, chromaA, lumaB, chromaB) texture representing the same frame with two different color phases), and we use
|
||||
// the temporal artifact reduction value to blend between them, which reduces or eliminates alternating-phase-related
|
||||
// flickering that systems like the NES could generate.
|
||||
//
|
||||
// The output of this shader is a texture that contains the decoded Y, I, and Q channels in R, G, and B
|
||||
// (plus 1.0 in alpha).
|
||||
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-box-filter.inc"
|
||||
|
||||
|
||||
// This is a 2- or 4-component texture that contains either a single luma, chroma sample pair or two luma, chroma pairs
|
||||
// of S-Video-like signal. It's 2 components if we have no temporal artifact reduction (we're not blending two
|
||||
// versions of the same frame), 4 if we do. This sampler should be set up for linear filtering and clamped addressing.
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D IQ_separate;
|
||||
#define g_sourceTexture IQ_separate
|
||||
|
||||
// This is a 2- or 4-component texture that contains the chroma portions of the signal modulated with the carrier
|
||||
// quadrature (basically, it's float4(chromaA * sin, chromaA * cos, chromaB * sin, chromaB * cos).
|
||||
// This has been modulated in a separate pass for efficiency - otherwise we'd need to do a bunch of sines and cosines
|
||||
// per pixel here.
|
||||
//DECLARE_TEXTURE2D(g_modulatedChromaTexture, g_modulatedChromaSampler);
|
||||
layout(set = 0, binding = 3) uniform sampler2D g_modulatedChromaTexture;
|
||||
|
||||
|
||||
// How many samples (horizontal texels) there are per each color wave cycle.
|
||||
uint g_samplesPerColorburstCycle = uint(global.cb_samples);
|
||||
|
||||
// This is a value representing how saturated we want the output to be. 0 basically means we'll decode as a grayscale
|
||||
// image, 1 means fully saturated color (i.e. the intended input saturation), and you could even set values greater
|
||||
// than 1 to oversaturate.
|
||||
// This corresponds to the saturation dial of a CRT TV.
|
||||
// $NOTE: This value should be pre-scaled by the g_brightness value, so that if brightness is 0, saturation is always
|
||||
// 0 - otherwise, you get weird output values where there should have been nothing visible but instead you get a
|
||||
// pure color instead.
|
||||
float g_saturation = global.cat_sat * saturate(global.cat_bright);
|
||||
|
||||
// This is a value representing the brightness of the output. a value of 0 means we'll output pure black, and 1 means
|
||||
// "the intended brightness based on the input signal". Values above 1 will over-brighten the output.
|
||||
// This corresponds to the brightness dial of a CRT TV.
|
||||
float g_brightness = global.cat_bright;
|
||||
|
||||
// This is the luma value of the input signal that represents black. For our synthetic signals it's typically 0.0,
|
||||
// but from a real NTSC signal this can be some other voltage level, since a voltage of 0 typically indicates a
|
||||
// horizontal or vertical blank instead. This is calculated from/generated with the composite or S-Video signal we
|
||||
// were given.
|
||||
float g_blackLevel = global.cat_black_lvl;
|
||||
|
||||
// This is the luma value of the input signal that represents brightest white. For our synthetic signals it's
|
||||
// typically 1.0, but from a real NTSC signal (or if we've applied some signal artifacts like ghosting) it could be
|
||||
// some other value. This is calculated from/generated with the composite or S-Video signal we were given.
|
||||
float g_whiteLevel = global.cat_white_lvl;
|
||||
|
||||
// A [0..1] value indicating how much we want to blend in an alternate version of the generated signal to adjust for
|
||||
// any artifacting between successive frames. 0 means we only have (or want to use) a single input luma/chroma pair.
|
||||
// A value > 0 means we are going to blend the results of two parallel-computed versions of our YIQ values, with a
|
||||
// value of 1.0 being a pure average of the two.
|
||||
float g_temporalArtifactReduction = global.temp_artifact_blend;
|
||||
|
||||
// The width of the input signal (including any side padding)
|
||||
uint g_inputWidth = uint(params.SourceSize.x);
|
||||
|
||||
// The width of the output RGB image (should be the width of the input signal minus the side padding)
|
||||
uint g_outputWidth = uint(params.OutputSize.x) - uint(global.sig_pad);
|
||||
|
||||
|
||||
const float k_pi = 3.141592653;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 inTexCoord = vTexCoord.xy;
|
||||
inTexCoord.x = (inTexCoord.x - 0.5) * float(g_outputWidth) / float(g_inputWidth) + 0.5;
|
||||
|
||||
vec2 inputTexelSize = vec2(ddx(inTexCoord).x, ddy(inTexCoord).y);
|
||||
|
||||
// Get the index of our x sample.
|
||||
uint sampleXIndex = uint(floor(inTexCoord.x / inputTexelSize.x));
|
||||
|
||||
// This is the chroma decode process, it's a QAM demodulation.
|
||||
// You multiply the chroma signal by a reference waveform and its quadrature (Basically, sin and cos at a given
|
||||
// time) and then filter out the chroma frequency (here done by a box filter (an average)). What you're left with
|
||||
// are the approximate I and Q color space values for this part of the image.
|
||||
vec2 Y = texture(g_sourceTexture, inTexCoord).xz;
|
||||
|
||||
|
||||
// We're going to sample chroma at double our actual colorburst cycle to eliminate some deeply rough artifacting on
|
||||
// the edges. In this case we're going to average by DOUBLE the color burst cycle - doing just a single cycle ends
|
||||
// up with a LOT of edge artifacting on things, which are too strong. 2x is much softer but not so large that you
|
||||
// can really notice that it's adding a ton of extra blurring on the color channel (it needs to be an integer
|
||||
// multiple of our cycle sample count for the filtering to work).
|
||||
// $TODO: This could actually be reduced to 1 when we're doing an decode of a signal that was not originally
|
||||
// composite, since there are no areas where luma changes will have crept into the color channel, which is typically
|
||||
// the artifacting we see.
|
||||
uint filterWidth = g_samplesPerColorburstCycle * 2U;
|
||||
|
||||
vec4 unused; // BoxFilter outputs a center sample, but we don't care about that.
|
||||
vec4 IQ = BoxFilter(
|
||||
g_modulatedChromaTexture,
|
||||
vec2(1.0 / float(g_inputWidth), 0.0),
|
||||
2U * g_samplesPerColorburstCycle,
|
||||
inTexCoord,
|
||||
unused);
|
||||
|
||||
// Adjust our components, first Y to account for the signal's black/white level (and user-chosen brightness), then IQ
|
||||
// for saturation (Which should also include the signal's brightness scale)
|
||||
Y = (Y - g_blackLevel) / (g_whiteLevel - g_blackLevel) * g_brightness;
|
||||
IQ *= vec4(g_saturation, g_saturation, g_saturation, g_saturation);
|
||||
|
||||
// we have 1 or 2 components of Y, and 2 or 4 of IQ. Blend them together based on our temporal aliasing reduction to
|
||||
// get our final decoded YIQ values.
|
||||
Y.x = lerp(Y.x, Y.y, g_temporalArtifactReduction * 0.5);
|
||||
IQ.xy = lerp(IQ.xy, IQ.zw, g_temporalArtifactReduction * 0.5);
|
||||
|
||||
// Do some gamma adjustments (values effectively based on eyeballing the results of NTSC signals from NES, SNES, and
|
||||
// Genesis consoles)
|
||||
Y.x = pow(saturate(Y.x), 2.0 / 2.2);
|
||||
float iqSat = saturate(length(IQ.xy));
|
||||
IQ.xy *= pow(iqSat, 2.0 / 2.2) / max(0.00001, iqSat);
|
||||
|
||||
// Finally, run the YIQ values through the standard (SMPTE C) YIQ to RGB conversion matrix
|
||||
// (from https://en.wikipedia.org/wiki/YIQ)
|
||||
vec3 yiq = vec3(Y.x, IQ.xy);
|
||||
FragColor = vec4(
|
||||
dot(yiq, vec3(1.0, 0.946882, 0.623557)),
|
||||
dot(yiq, vec3(1.0, -0.274788, -0.635691)),
|
||||
dot(yiq, vec3(1.0, -1.108545, 1.7090047)),
|
||||
1.0);
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader applies ghosting and noise to an SVideo or Composite signal.
|
||||
//
|
||||
// Noise is just that, some randomized signal offset per sample to simulate analog error in the signal.
|
||||
//
|
||||
// Ghosting basically is what happens when a copy of your signal "skips" its intended path through the cable and mixes
|
||||
// in with your normal signal (like an EM leak of the signal) and is basically a pre-echo of the signal. So as a very
|
||||
// rough approximation of this, we'll just blend in an offset, blurred, and scaled version of the original signal.
|
||||
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-noise.inc"
|
||||
#define inputTexCoord vTexCoord
|
||||
|
||||
// The texture to apply artifacts to. Should either be:
|
||||
// - a 1-channel composite signal
|
||||
// - a 2-channel luma/chroma S-Video signal
|
||||
// - a 2-channel doubled composite texture (two composite versions of the same frame for temporal artifact reduction)
|
||||
// - a 4-channel luma/chroma/luma/chroma doubled S-Video signal (two S-Video versions of the same frame for temporal
|
||||
// artifact reduction)
|
||||
// This sampler should be set to linear sampling, and either clamp addressing or perhaps border mode, depending.
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
// This represents how much ghosting is visible - 0.0 is "no ghosting" and 1.0 is "the ghost is as strong as the
|
||||
// original signal."
|
||||
float g_ghostVisibility = global.ghost_vis;
|
||||
|
||||
// This is the distance (in colorburst cycles) before the original signal that ghosting should appear.
|
||||
float g_ghostDistance = global.ghost_dist;
|
||||
|
||||
// How far to blur the ghost, in colorburst cycles.
|
||||
float g_ghostSpreadScale = global.ghost_spread;
|
||||
|
||||
// The strength of noise to add to the signal - 0.0 means no noise, 1.0 means, just, a lot of noise. So much.
|
||||
// Probably too much.
|
||||
float g_noiseStrength = global.noise_strength;
|
||||
|
||||
// An integer seed used to generate the per-output-texel noise. This can be a monotonically-increasing value, or
|
||||
// random every frame, just as long as it's different every frame so that the noise changes.
|
||||
uint g_noiseSeed = uint(((params.noise_seed) * float(params.FrameCount)));
|
||||
|
||||
// The width of the input signal texture
|
||||
uint g_signalTextureWidth = uint(params.SourceSize.x);
|
||||
|
||||
// The number of scanlines in this field of NTSC video
|
||||
uint g_scanlineCount = (params.OriginalSize.y < 400.) ? uint(params.OriginalSize.y) : uint(params.OriginalSize.y * 0.5);
|
||||
|
||||
// How many samples (texels along a scanline) there are per colorburst cycle (the color wave in the composite signal)
|
||||
uint g_samplesPerColorburstCycle = uint(global.cb_samples);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 signal = texture(g_sourceTexture, inputTexCoord);
|
||||
if (g_ghostVisibility != 0)
|
||||
{
|
||||
// Calculate the center sample position of our ghost based on our input params, as well as how far to spread our
|
||||
// sampling for the blur
|
||||
vec2 ghostCenterCoord = inputTexCoord
|
||||
+ vec2(g_ghostDistance * g_samplesPerColorburstCycle, 0.) / vec2(g_signalTextureWidth, g_scanlineCount);
|
||||
vec2 ghostSampleSpread = vec2(g_ghostSpreadScale * g_samplesPerColorburstCycle, 0)
|
||||
/ vec2(g_signalTextureWidth, g_scanlineCount);
|
||||
|
||||
// The following is a 9-tap gaussian, written as 5 samples using bilinear interpolation to sample two taps at once:
|
||||
// 0.00761441700, 0.0360749699, 0.109586075, 0.2134445460,
|
||||
// 0.26655998800, 0.2134445460, 0.109586075, 0.0360749699, 0.00761441700
|
||||
vec4 ghost;
|
||||
ghost = texture(g_sourceTexture, ghostCenterCoord - ghostSampleSpread * 1.174285279339)
|
||||
* 0.0436893869;
|
||||
ghost += texture(g_sourceTexture, ghostCenterCoord - ghostSampleSpread * 1.339243613069)
|
||||
* 0.323030611;
|
||||
ghost += texture(g_sourceTexture, ghostCenterCoord)
|
||||
* 0.266559988;
|
||||
ghost += texture(g_sourceTexture, ghostCenterCoord + ghostSampleSpread * 1.339243613069)
|
||||
* 0.323030611;
|
||||
ghost += texture(g_sourceTexture, ghostCenterCoord + ghostSampleSpread * 1.174285279339)
|
||||
* 0.0436893869;
|
||||
|
||||
signal += ghost * g_ghostVisibility;
|
||||
}
|
||||
|
||||
// Also add some noise for each texel. Use the noise seed and our x/y position to c
|
||||
vec2 pixelIndex = inputTexCoord
|
||||
* vec2(g_signalTextureWidth / (g_samplesPerColorburstCycle * 2.0 / 3.0), g_scanlineCount);
|
||||
float xFrac = fract(pixelIndex.x);
|
||||
pixelIndex = floor(pixelIndex);
|
||||
|
||||
// Do a little perlin-style interpolation to make the noise seem more analogue. This is one of those things that
|
||||
// isn't strictly accurate (like I would prefer all the things be), but it *looks* right to me.
|
||||
float noiseL = Noise2D(pixelIndex, float(g_noiseSeed));
|
||||
float noiseR = Noise2D(pixelIndex + vec2(1.0, 0.0), float(g_noiseSeed));
|
||||
float noise = lerp(noiseL, noiseR, xFrac) * 2.0 - 1.0;
|
||||
|
||||
// Finally we'll scale and bias the noise and return it added to our signal.
|
||||
FragColor = (signal + noise * g_noiseStrength) / (1.0 + g_ghostVisibility);
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader generates the phase of the colorburst for each scanline.
|
||||
//
|
||||
// Basically, outside of the visible portion of a color NTSC scanline, there is a colorburst which the TV uses to tell
|
||||
// which color is the reference phase (which is yellow in color). We're not generating a full NTSC signal, but we
|
||||
// still need to generate this data, and to do that we have a set of emulated timings for the virtual system that is
|
||||
// generating our output.
|
||||
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-tracking-instability.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
// This is the colorburst phase (in fractional multiples of the colorburst wavelength) for the first scanline in our
|
||||
// generated signal.
|
||||
float g_initialFrameStartPhase = global.cb_first_start;
|
||||
|
||||
// This is the colorburst phase for the first scanline of the previous frame - this is used if we're generating two
|
||||
// sets of phase information for use with temporal artifact reduction.
|
||||
float g_prevFrameStartPhase = global.cb_last_start;
|
||||
|
||||
// The amount that the phase increments every scanline, in (fractional) multiples of the colorburst wavelength.
|
||||
float g_phaseIncrementPerScanline = global.cb_phase_inc;
|
||||
|
||||
// How many samples (texels along a scanline) there are per colorburst cycle (the color wave in the composite signal)
|
||||
uint g_samplesPerColorburstCycle = uint(global.cb_samples);
|
||||
|
||||
// The scale of any picture instability (horizontal scanline-by-scanline tracking issues). This is used to ensure the
|
||||
// phase values generated line up with the offset of the texture sampling that happens in RGBToSVideoOrComposite.
|
||||
// Must match the similarly-named value in cathode-retro-generator-rgb-to-svideo-or-composite.
|
||||
float g_instabilityScale = global.horz_track_scale;
|
||||
|
||||
// A seed for the noise used to generate the scanline-by-scanline picture instability. Must match the simiarly-named
|
||||
// value in cathode-retro-generator-rgb-to-svideo-or-composite.
|
||||
uint g_noiseSeed = uint(mod(params.FrameCount, 13637));
|
||||
|
||||
// The width of the signal texture that is being generated, in texels.
|
||||
uint g_signalTextureWidth = uint(params.OriginalSize.x);
|
||||
|
||||
// The number of scanlines for this field of video.
|
||||
uint g_scanlineCount = (params.OriginalSize.y < 400.) ? uint(params.OriginalSize.y) : uint(params.OriginalSize.y * 0.5);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
uint scanlineIndex = uint(round(inTexCoord.y * g_scanlineCount - 0.5));
|
||||
|
||||
// Calculate the phase for the start of the given scanline.
|
||||
vec2 phases = vec2(g_initialFrameStartPhase, g_prevFrameStartPhase)
|
||||
+ g_phaseIncrementPerScanline * float(scanlineIndex);
|
||||
|
||||
// Offset by half a cycle so that it lines up with the *center* of the filter instead of the left edge of it.
|
||||
phases += 0.5 / float(g_samplesPerColorburstCycle);
|
||||
|
||||
// Finally, offset by the scaled instability so that if there are color artifacts, they'll have the correct phase.
|
||||
// $TODO: This is actually slightly wrong, and I'm assuming it's due to the way the texture sampling works when
|
||||
// generating the signal texture. This value is slightly too high when we're at half a colorburst phase offset (by
|
||||
// something like 0.05, found by manually tweaking the tint slider to get a close match), and I'm not entirely sure
|
||||
// why. Intuition says that because we're not oversampling the RGB texture to generate the signal texture, any
|
||||
// intentional linear-blending texture offsets (like for 1bpp CGA modes) aren't actually offset *exactly* halfway at
|
||||
// that point, and so the calculated phase on NTSC demodulation is slightly off from true.
|
||||
float instability = CalculateTrackingInstabilityOffset(
|
||||
scanlineIndex,
|
||||
g_noiseSeed,
|
||||
g_instabilityScale,
|
||||
g_signalTextureWidth);
|
||||
phases += instability * float(g_signalTextureWidth) / float(g_samplesPerColorburstCycle);
|
||||
|
||||
FragColor = vec4(fract(phases), 0, 0);
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader takes an RGB image and turns it into either an S-Video or Composite signal (Based on whether g_compositeBlend is 0 or 1).
|
||||
// We might also be generating a PAIR of these, using two different sets of phase inputs (if g_scanlinePhases is a two-component input),
|
||||
// for purposes of temporal aliasing reduction.
|
||||
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-tracking-instability.inc"
|
||||
#define signalTexCoord vTexCoord
|
||||
|
||||
// This is the RGB input texture. It is expected to be g_inputWidth x g_scanlineCount in size.
|
||||
// This sampler should be set up with linear filtering, and either clamp or border addressing.
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sourceSampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D g_sourceTexture;
|
||||
|
||||
// This is the scanline phases texture, generated by GeneratePhaseTexture. It is g_scanlineCount x 1 in size, and each texel component in
|
||||
// it represents the phase offset of the NTSC colorburst for the corresponding scanline, in multiples of the colorburst wavelength.
|
||||
// This sampler should be set up with linear filtering, and either clamp or border addressing.
|
||||
//DECLARE_TEXTURE2D(g_scanlinePhases, g_scanlinePhasesSampler);
|
||||
layout(set = 0, binding = 3) uniform sampler2D g_scanlinePhases;
|
||||
|
||||
// The number of texels that the output texture will contain for each color cycle wave (i.e. the wavelength in output samples of
|
||||
// the color carrier wave).
|
||||
uint g_outputTexelsPerColorburstCycle = uint(global.cb_samples);
|
||||
|
||||
// The width of the input texture.
|
||||
uint g_inputWidth = uint(params.OriginalSize.x);
|
||||
|
||||
// The width of the output render target.
|
||||
uint g_outputWidth = uint(params.OutputSize.x);
|
||||
|
||||
// The number of scanlines in the current field of video (the height of the input texture).
|
||||
uint g_scanlineCount = (params.OriginalSize.y < 400.) ? uint(params.OriginalSize.y) : uint(params.OriginalSize.y * 0.5);
|
||||
|
||||
// This is whether we're blending the generated luma/chroma into a single output channel or not. It is expected to be 0 or 1 (no
|
||||
// intermediate values), where "0" means "keep luma and chroma separate, like an S-Video signal" and "1" means "add the two together,
|
||||
// like a composite signal".
|
||||
float g_compositeBlend = global.composite;
|
||||
|
||||
// The scale of any picture instability (horizontal scanline-by-scanline tracking issues). This is used to offset our texture sampling
|
||||
// when generating the output so the picture tracking is imperfect. Must match the similarly-named value in GeneratePhaseTexture.
|
||||
float g_instabilityScale = global.horz_track_scale;
|
||||
|
||||
// A seed for the noise used to generate the scanline-by-scanline picture instability. Must match the simiarly-named value in
|
||||
// GeneratePhaseTexture.
|
||||
uint g_noiseSeed = uint(mod(params.FrameCount, 13637));
|
||||
|
||||
// the number of output texels to pad on either side of the signal texture (so that filtering won't have visible artifacts on the left
|
||||
// and right sides).
|
||||
uint g_sidePaddingTexelCount = uint(global.sig_pad);
|
||||
|
||||
|
||||
const float pi = 3.141592653;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 signalTexelIndex = uvec2(floor(signalTexCoord * vec2(g_outputWidth, g_scanlineCount)));
|
||||
|
||||
// The texcoord we're using to sample the input texture neesd to be adjusted slightly. The 0.25 offset ensures that our generated texture
|
||||
// is centered on the RGB texture.
|
||||
vec2 texCoord = (vec2(signalTexelIndex) * vec2(float(g_inputWidth) / float(g_outputWidth), 1.) + vec2(0.25, 0.5))
|
||||
/ vec2(g_inputWidth, g_scanlineCount);
|
||||
|
||||
// Expand our sampling a little bit to adjust for the padding we want on the sides.
|
||||
uint effectiveOutputWidth = g_outputWidth - g_sidePaddingTexelCount;
|
||||
texCoord.x = (texCoord.x - 0.5) * float(g_outputWidth) / float(effectiveOutputWidth) + 0.5;
|
||||
|
||||
// Add in the instability to our x coordinate to wiggle our generated texture around.
|
||||
float instability = CalculateTrackingInstabilityOffset(
|
||||
signalTexelIndex.y,
|
||||
g_noiseSeed,
|
||||
g_instabilityScale,
|
||||
g_outputWidth);
|
||||
texCoord.x += instability;
|
||||
|
||||
vec3 rgb = texture(g_sourceTexture, texCoord).rgb;
|
||||
|
||||
// Convert RGB to YIQ using the NTSC standard (SMPTE C) conversion (from https://en.wikipedia.org/wiki/YIQ)
|
||||
vec3 yiq;
|
||||
|
||||
yiq.r = dot(rgb, vec3(0.3000, 0.5900, 0.1100));
|
||||
yiq.g = dot(rgb, vec3(0.5990, -0.2773, -0.3217));
|
||||
yiq.b = dot(rgb, vec3(0.2130, -0.5251, 0.3121));
|
||||
|
||||
// Do some gamma adjustments to counter for the gamma that will be used at decode time.
|
||||
yiq.x = pow(saturate(yiq.x), 2.2 / 2.0);
|
||||
float iqSat = saturate(length(yiq.yz));
|
||||
yiq.yz *= pow(iqSat, 2.2 / 2.0) / max(0.00001, iqSat);
|
||||
|
||||
// Separate the YIQ channels out because YIQ.y ended up being confusing (is it Y? no it's I! but also it's y!)
|
||||
float Y = yiq.x;
|
||||
float I = yiq.y;
|
||||
float Q = yiq.z;
|
||||
|
||||
// Calculate the phase for our current x position on the current scanline.
|
||||
vec2 scanlinePhase = texture(
|
||||
g_scanlinePhases,
|
||||
(vec2(0.0, signalTexelIndex.y + 0.5) / g_scanlineCount)).xy;
|
||||
vec2 phase = scanlinePhase + signalTexelIndex.x / float(g_outputTexelsPerColorburstCycle);
|
||||
|
||||
// Now we need to encode our IQ component in the carrier wave at the correct phase. This is QAM modulation.
|
||||
vec2 s, c;
|
||||
sincos(2.0 * pi * phase, s, c);
|
||||
|
||||
vec2 luma = vec2(Y, Y);
|
||||
vec2 chroma = s * I - c * Q;
|
||||
|
||||
if (g_compositeBlend > 0.5)
|
||||
{
|
||||
// We are outputting a composite signal so combine luma and chroma and output it into our expected 1- or 2-channel texture.
|
||||
FragColor = (luma + chroma).xyxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outputting svideo, so don't combine luma and chroma, and write them out into our expected 2- or 4-channel texture.
|
||||
FragColor = vec4(luma, chroma).xzyw;
|
||||
}
|
||||
}
|
50
crt/shaders/cathode-retro/cathode-retro-util-box-filter.inc
Normal file
50
crt/shaders/cathode-retro/cathode-retro-util-box-filter.inc
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Perform a centered box filter - which means we might need to sample a half-texel off of either end of the filter, if
|
||||
// the filter width is even (and it probably is).
|
||||
|
||||
vec4 BoxFilter(
|
||||
sampler2D decal,
|
||||
vec2 invTextureSize,
|
||||
uint filterWidth,
|
||||
vec2 texCoord,
|
||||
out vec4 centerSample)
|
||||
{
|
||||
// Get the center sample (which we'll write out to the caller)
|
||||
centerSample = texture(decal, texCoord);
|
||||
|
||||
// Average starts with the center sample
|
||||
vec4 avg = centerSample;
|
||||
|
||||
// Excluding the center texel, iterate over every two samples to either side of the center sampling in the middle to
|
||||
// get a nice bilinear average of the two (getting two texel samples for free). Multiply by 2 so that we get 1.0 of
|
||||
// each of them.
|
||||
uint iterEnd = uint((filterWidth - 1U) / 2U);
|
||||
for (uint i = 2U; i < iterEnd; i += 2U)
|
||||
{
|
||||
avg += 2 * texture(decal, (texCoord + vec2(i - 0.5, 0) * invTextureSize));
|
||||
avg += 2 * texture(decal, (texCoord - vec2(i - 0.5, 0) * invTextureSize));
|
||||
}
|
||||
|
||||
// Now we either have no texels, a half texel, a full texel, or 1.5 texels remaining (or in other words, our
|
||||
// width-minus-center modulo 4 is one of 0 through 3. If it's 0, we don't have to do anything, we've averaged all
|
||||
// of the texels.
|
||||
uint remainder = (filterWidth - 1U) % 4U;
|
||||
if (remainder == 3U)
|
||||
{
|
||||
// If it's 3, we have 1.5 texels left per end. Sample 1/3rd of the way into the inner-most texel (Because of how
|
||||
// lerp works, that means we get 2/3rd of the inner texel and 1/3rd of the outermost), then multiply the result by
|
||||
// 1.5 to give us 1.0 of the innermost and 0.5 of the outermost texel (we want one half of the outer texel and
|
||||
// full weight of the inner one).
|
||||
avg += 1.5 * texture(decal, (texCoord + vec2(iterEnd + 1.0 / 3.0, 0) * invTextureSize));
|
||||
avg += 1.5 * texture(decal, (texCoord - vec2(iterEnd + 1.0 / 3.0, 0) * invTextureSize));
|
||||
}
|
||||
else if (remainder > 0U)
|
||||
{
|
||||
// If the remainder is either 1 or 2, we either have a half texel or a full texel remaining on either end. Sample
|
||||
// the center of that texel and then scale it down by 0.5 if we only need half of its weight.
|
||||
float scale = (remainder == 2U) ? 1.0 : 0.0;
|
||||
avg += scale * texture(decal, (texCoord + vec2(iterEnd + 1.0, 0) * invTextureSize));
|
||||
avg += scale * texture(decal, (texCoord - vec2(iterEnd + 1.0, 0) * invTextureSize));
|
||||
}
|
||||
|
||||
return avg / float(filterWidth);
|
||||
}
|
24
crt/shaders/cathode-retro/cathode-retro-util-copy.slang
Normal file
24
crt/shaders/cathode-retro/cathode-retro-util-copy.slang
Normal file
|
@ -0,0 +1,24 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Do a 1:1 copy of a texture
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-lanczos.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = texture(g_sourceTexture, inTexCoord);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Downsample an input image by 2x along a given axis by using a lanczos filter.
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-lanczos.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
// The direction that we're downsampling along. Should either be (1, 0) to downsample to a half-width texture or
|
||||
// (0, 1) to downsample to a half-height texture.
|
||||
vec2 g_filterDir = vec2(1., 0.);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = Lanczos2xDownsample(
|
||||
g_sourceTexture,
|
||||
inTexCoord,
|
||||
g_filterDir);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Downsample an input image by 2x along a given axis by using a lanczos filter.
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-lanczos.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
// The direction that we're downsampling along. Should either be (1, 0) to downsample to a half-width texture or
|
||||
// (0, 1) to downsample to a half-height texture.
|
||||
vec2 g_filterDir = vec2(0., 1.);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = Lanczos2xDownsample(
|
||||
g_sourceTexture,
|
||||
inTexCoord,
|
||||
g_filterDir);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader does a 1D gaussian blur using a 13-tap filter.
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-language-helpers.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
// 13-tap gaussian kernel coefficients for bilinear shading, optimized to only require 7 texture samples by taking
|
||||
// advantage of linear texture filtering. These coefficients were generated using https://drilian.com/gaussian-kernel/
|
||||
|
||||
/*
|
||||
The actual coefficients for this blur are:
|
||||
|
||||
1.107819053e-2
|
||||
2.478669128e-2
|
||||
4.790462288e-2
|
||||
7.997337680e-2
|
||||
1.153247662e-1
|
||||
1.436510723e-1
|
||||
1.545625599e-1
|
||||
1.436510723e-1
|
||||
1.153247662e-1
|
||||
7.997337680e-2
|
||||
4.790462288e-2
|
||||
2.478669128e-2
|
||||
1.107819053e-2
|
||||
*/
|
||||
|
||||
|
||||
//DECLARE_TEXTURE2D(g_sourceTex, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D g_sourceTex;
|
||||
|
||||
const int k_sampleCount = 7;
|
||||
const float k_coeffs[k_sampleCount] = float[k_sampleCount](
|
||||
3.586488181e-2,
|
||||
1.278779997e-1,
|
||||
2.589758386e-1,
|
||||
1.545625599e-1,
|
||||
2.589758386e-1,
|
||||
1.278779997e-1,
|
||||
3.586488181e-2
|
||||
);
|
||||
|
||||
const float k_offsets[k_sampleCount] = float[k_sampleCount](
|
||||
-5.308886854,
|
||||
-3.374611919,
|
||||
-1.445310910,
|
||||
0.000000000,
|
||||
1.445310910,
|
||||
3.374611919,
|
||||
5.308886854
|
||||
);
|
||||
|
||||
// Blur a texture along the blur direction (for a horizontal blur, use (1, 0) and for vertical use (0, 1)), centered at
|
||||
// "centerTexCoord" (which is in standard [0..1] texture space).
|
||||
vec4 Blur(vec2 centerTexCoord, vec2 blurDirection)
|
||||
{
|
||||
ivec2 dim = ivec2(params.SourceSize.xy);
|
||||
|
||||
vec4 v = vec4(0., 0., 0., 0.);
|
||||
for (int i = 0; i < k_sampleCount; i++)
|
||||
{
|
||||
vec2 c = centerTexCoord + blurDirection / vec2(dim) * k_offsets[i];
|
||||
v += texture(g_sourceTex, c) * k_coeffs[i];
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// The direction to blur along. Should be (1, 0) to do a horizontal blur and (0, 1) to do a vertical blur.
|
||||
vec2 g_blurDir = vec2(1., 0.);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = Blur(inTexCoord, g_blurDir);
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader does a 1D gaussian blur using a 13-tap filter.
|
||||
|
||||
#include "slang_params.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
|
||||
// 13-tap gaussian kernel coefficients for bilinear shading, optimized to only require 7 texture samples by taking
|
||||
// advantage of linear texture filtering. These coefficients were generated using https://drilian.com/gaussian-kernel/
|
||||
|
||||
/*
|
||||
The actual coefficients for this blur are:
|
||||
|
||||
1.107819053e-2
|
||||
2.478669128e-2
|
||||
4.790462288e-2
|
||||
7.997337680e-2
|
||||
1.153247662e-1
|
||||
1.436510723e-1
|
||||
1.545625599e-1
|
||||
1.436510723e-1
|
||||
1.153247662e-1
|
||||
7.997337680e-2
|
||||
4.790462288e-2
|
||||
2.478669128e-2
|
||||
1.107819053e-2
|
||||
*/
|
||||
|
||||
|
||||
//DECLARE_TEXTURE2D(g_sourceTex, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
// access the previous pass, since we're separate horz and vert
|
||||
#define g_sourceTex Source
|
||||
|
||||
const int k_sampleCount = 7;
|
||||
const float k_coeffs[k_sampleCount] = float[k_sampleCount](
|
||||
3.586488181e-2,
|
||||
1.278779997e-1,
|
||||
2.589758386e-1,
|
||||
1.545625599e-1,
|
||||
2.589758386e-1,
|
||||
1.278779997e-1,
|
||||
3.586488181e-2
|
||||
);
|
||||
|
||||
const float k_offsets[k_sampleCount] = float[k_sampleCount](
|
||||
-5.308886854,
|
||||
-3.374611919,
|
||||
-1.445310910,
|
||||
0.000000000,
|
||||
1.445310910,
|
||||
3.374611919,
|
||||
5.308886854
|
||||
);
|
||||
|
||||
// Blur a texture along the blur direction (for a horizontal blur, use (1, 0) and for vertical use (0, 1)), centered at
|
||||
// "centerTexCoord" (which is in standard [0..1] texture space).
|
||||
vec4 Blur(vec2 centerTexCoord, vec2 blurDirection)
|
||||
{
|
||||
ivec2 dim = ivec2(params.SourceSize.xy);
|
||||
|
||||
vec4 v = vec4(0., 0., 0., 0.);
|
||||
for (int i = 0; i < k_sampleCount; i++)
|
||||
{
|
||||
vec2 c = centerTexCoord + blurDirection / vec2(dim) * k_offsets[i];
|
||||
v += texture(g_sourceTex, c) * k_coeffs[i];
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// The direction to blur along. Should be (1, 0) to do a horizontal blur and (0, 1) to do a vertical blur.
|
||||
vec2 g_blurDir = vec2(0., 1.);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = Blur(inTexCoord, g_blurDir);
|
||||
}
|
40
crt/shaders/cathode-retro/cathode-retro-util-lanczos.inc
Normal file
40
crt/shaders/cathode-retro/cathode-retro-util-lanczos.inc
Normal file
|
@ -0,0 +1,40 @@
|
|||
// This is a lanczos2 kernel for a nice 2x downsample. I wish I'd documented how I generated it but I didn't so instead
|
||||
// this is what you get. Sorry about that.
|
||||
//static const float k_lanczos2[8] = {-0.009, -0.042, 0.117, 0.434, 0.434, 0.117, -0.042, -0.009};
|
||||
|
||||
// However, it's been optimized to take advantage of the linear filtering using the same technique I used for the
|
||||
// gaussian filters at https://drilian.com/gaussian-kernel/
|
||||
// That means it's only 4 texture samples for an 8-tap lanczos, which is, if I did my math correctly, twice as good!
|
||||
|
||||
const int k_sampleCount = 4;
|
||||
const float k_coeffs[k_sampleCount] = float[k_sampleCount](
|
||||
-0.051,
|
||||
0.551,
|
||||
0.551,
|
||||
-0.051
|
||||
);
|
||||
|
||||
const float k_offsets[k_sampleCount] = float[k_sampleCount](
|
||||
-2.67647052,
|
||||
-0.712341249,
|
||||
0.712341189,
|
||||
2.67647052
|
||||
);
|
||||
|
||||
|
||||
vec4 Lanczos2xDownsample(
|
||||
sampler2D decal,
|
||||
vec2 centerTexCoord,
|
||||
vec2 filterDir)
|
||||
{
|
||||
ivec2 dim = ivec2(params.SourceSize.xy);
|
||||
|
||||
vec4 v = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
for (int i = 0; i < k_sampleCount; i++)
|
||||
{
|
||||
vec2 c = centerTexCoord + filterDir / float2(dim) * k_offsets[i];
|
||||
v += texture(decal, c) * k_coeffs[i];
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This file contains all the stuff used to make these shaders compile both as GLSL and HLSL.
|
||||
//
|
||||
// Note that one of GLSL or HLSL must be defined.
|
||||
|
||||
|
||||
#ifndef NTSC_UTIL_LANG
|
||||
#define NTSC_UTIL_LANG
|
||||
|
||||
#ifndef GLSL
|
||||
#ifndef HLSL
|
||||
// Decfine this to make my syntax highlighting less angry
|
||||
#define HLSL
|
||||
#endif
|
||||
#endif
|
||||
#ifdef GLSL
|
||||
#define uint2 uvec2
|
||||
#define uint3 uvec3
|
||||
#define uint4 uvec4
|
||||
#define int2 ivec2
|
||||
#define int3 ivec3
|
||||
#define int4 ivec4
|
||||
#define float2 vec2
|
||||
#define float3 vec3
|
||||
#define float4 vec4
|
||||
#define ddx dFdx
|
||||
#define ddy dFdy
|
||||
#define frac fract
|
||||
#define lerp mix
|
||||
|
||||
precision mediump float;
|
||||
|
||||
float saturate(float a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float2 saturate(float2 a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float3 saturate(float3 a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float4 saturate(float4 a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void sincos(float angle, out float s, out float c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
void sincos(float2 angle, out float2 s, out float2 c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
void sincos(float3 angle, out float3 s, out float3 c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
void sincos(float4 angle, out float4 s, out float4 c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
#define CONST const
|
||||
#define BEGIN_CONST_ARRAY(type, name, size) const type name[size] = type[size](
|
||||
#define END_CONST_ARRAY );
|
||||
|
||||
// GLSL doesn't have separate samplers and textures, so we're using texName for everything
|
||||
#define DECLARE_TEXTURE2D(texName, samplerName) uniform sampler2D texName
|
||||
|
||||
#define DECLARE_TEXTURE2D_AND_SAMPLER_PARAM(texName, samplerName) sampler2D texName
|
||||
#define PASS_TEXTURE2D_AND_SAMPLER_PARAM(texName, samplerName) texName
|
||||
#define SAMPLE_TEXTURE(texName, samplerName, coord) texture2D(texName, vec2((coord).x, 1.0 - (coord).y))
|
||||
#define SAMPLE_TEXTURE_BIAS(texName, samplerName, coord, bias) \
|
||||
texture2D(texName, vec2((coord).x, 1.0 - (coord).y), bias)
|
||||
#define GET_TEXTURE_SIZE(tex, outVar) outVar = textureSize(tex, 0)
|
||||
|
||||
#define PS_MAIN in float2 vsOutTexCoord; out float4 psOutPos; void main() { psOutPos = Main(vsOutTexCoord); }
|
||||
|
||||
#define CBUFFER uniform
|
||||
#endif
|
||||
|
||||
#ifdef HLSL
|
||||
#define CONST static const
|
||||
|
||||
#define BEGIN_CONST_ARRAY(type, name, size) static const type name[size] = {
|
||||
#define END_CONST_ARRAY };
|
||||
|
||||
#define DECLARE_TEXTURE2D(texName, samplerName) Texture2D<float4> texName; sampler samplerName
|
||||
|
||||
#define DECLARE_TEXTURE2D_AND_SAMPLER_PARAM(texName, samplerName) Texture2D<float4> texName, sampler samplerName
|
||||
#define PASS_TEXTURE2D_AND_SAMPLER_PARAM(texName, samplerName) texName, samplerName
|
||||
#define SAMPLE_TEXTURE(texName, samplerName, coord) texName.Sample(samplerName, coord)
|
||||
#define SAMPLE_TEXTURE_BIAS(texName, samplerName, coord, bias) texName.SampleBias(samplerName, coord, bias)
|
||||
#define GET_TEXTURE_SIZE(tex, outVar) tex.GetDimensions(outVar.x, outVar.y)
|
||||
|
||||
#define PS_MAIN float4 main(float2 inTexCoord: TEX): SV_TARGET { return Main(inTexCoord); }
|
||||
|
||||
#define CBUFFER cbuffer
|
||||
#endif
|
||||
#endif
|
17
crt/shaders/cathode-retro/cathode-retro-util-noise.inc
Normal file
17
crt/shaders/cathode-retro/cathode-retro-util-noise.inc
Normal file
|
@ -0,0 +1,17 @@
|
|||
#ifndef noise_func
|
||||
#define noise_func
|
||||
|
||||
|
||||
float Noise2D(vec2 coord, float iseed)
|
||||
{
|
||||
float fseed = fract(iseed / 10000.0);
|
||||
float angle = fract(distance(coord.xy, 1000.0 * (vec2(fseed + 0.3, 0.1) + 1.0)));
|
||||
return fract(tan(angle) * distance(coord.xy, 1000.0 * (vec2(fseed + 0.1, fseed + 0.2) - 2.0)));
|
||||
}
|
||||
|
||||
|
||||
float Noise1D(float coord, float iseed)
|
||||
{
|
||||
return Noise2D(vec2(coord, 0.0), iseed);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,48 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader does a tonemap and 1D downsample of a texture, which is intended to be for the diffusion emulation in
|
||||
// the CRT side of the whole Cathode Retro process. It's worth noting that in practice we're not doing a true 2x
|
||||
// downsample, it's going to be something in that ballpark, but not exact. However, the output of this shader is going
|
||||
// to be blurred so it doesn't actually matter in practice.
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-lanczos.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
// The direction we want to apply the downsample.
|
||||
vec2 g_downsampleDir = vec2(1., 0.);
|
||||
float g_minLuminosity = 1.0 - global.minlum;
|
||||
|
||||
float g_colorPower = 2.0 - global.colorpower;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 samp = Lanczos2xDownsample(
|
||||
g_sourceTexture,
|
||||
inTexCoord,
|
||||
g_downsampleDir);
|
||||
|
||||
// Calculate the luminosity of the input.
|
||||
float inLuma = dot(samp.rgb, vec3(0.30, 0.59, 0.11));
|
||||
|
||||
// Calculate the desired output luminosity.
|
||||
float outLuma = (inLuma - g_minLuminosity) / (1.0 - g_minLuminosity);
|
||||
outLuma = pow(saturate(outLuma), g_colorPower);
|
||||
|
||||
// Apply the luminosity scaling.
|
||||
samp.rgb *= outLuma / inLuma;
|
||||
FragColor = samp;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#version 450
|
||||
|
||||
/*
|
||||
A port of DeadlyRedCube's Cathode-Retro shader to slang format
|
||||
based on a snapshot from https://github.com/DeadlyRedCube/Cathode-Retro circa Nov. 2023
|
||||
ported by hunterk
|
||||
license: MIT
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This shader does a tonemap and 1D downsample of a texture, which is intended to be for the diffusion emulation in
|
||||
// the CRT side of the whole Cathode Retro process. It's worth noting that in practice we're not doing a true 2x
|
||||
// downsample, it's going to be something in that ballpark, but not exact. However, the output of this shader is going
|
||||
// to be blurred so it doesn't actually matter in practice.
|
||||
|
||||
#include "slang_params.inc"
|
||||
#include "cathode-retro-util-lanczos.inc"
|
||||
#define inTexCoord vTexCoord
|
||||
|
||||
//DECLARE_TEXTURE2D(g_sourceTexture, g_sampler);
|
||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||
#define g_sourceTexture Source
|
||||
|
||||
// The direction we want to apply the downsample.
|
||||
vec2 g_downsampleDir = vec2(0., 1.);
|
||||
float g_minLuminosity = 1.0 - global.minlum;
|
||||
|
||||
float g_colorPower = 2.0 - global.colorpower;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 samp = Lanczos2xDownsample(
|
||||
g_sourceTexture,
|
||||
inTexCoord,
|
||||
g_downsampleDir);
|
||||
|
||||
// Calculate the luminosity of the input.
|
||||
float inLuma = dot(samp.rgb, vec3(0.30, 0.59, 0.11));
|
||||
|
||||
// Calculate the desired output luminosity.
|
||||
float outLuma = (inLuma - g_minLuminosity) / (1.0 - g_minLuminosity);
|
||||
outLuma = pow(saturate(outLuma), g_colorPower);
|
||||
|
||||
// Apply the luminosity scaling.
|
||||
samp.rgb *= outLuma / inLuma;
|
||||
FragColor = samp;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#include "cathode-retro-util-noise.inc"
|
||||
|
||||
// Calculate the instability noise per scanline
|
||||
float CalculateTrackingInstabilityOffset(uint scanlineIndex, uint noiseSeed, float scale, uint signalTextureWidth)
|
||||
{
|
||||
return (Noise1D(float(scanlineIndex), float(noiseSeed)) - 0.5) * scale / signalTextureWidth;
|
||||
}
|
103
crt/shaders/cathode-retro/signal_test.slangp
Normal file
103
crt/shaders/cathode-retro/signal_test.slangp
Normal file
|
@ -0,0 +1,103 @@
|
|||
shaders = 18
|
||||
|
||||
#mipmap_input1 = true
|
||||
#mipmap_input2 = true
|
||||
#mipmap_input3 = true
|
||||
#mipmap_input4 = true
|
||||
#mipmap_input5 = true
|
||||
#mipmap_input6 = true
|
||||
#mipmap_input7 = true
|
||||
#mipmap_input8 = true
|
||||
#mipmap_input9 = true
|
||||
#mipmap_input10 = true
|
||||
#mipmap_input11 = true
|
||||
#mipmap_input12 = true
|
||||
#mipmap_input13 = true
|
||||
#mipmap_input14 = true
|
||||
#mipmap_input15 = true
|
||||
#mipmap_input16 = true
|
||||
#mipmap_input17 = true
|
||||
|
||||
shader0 = cathode-retro-util-copy.slang
|
||||
alias0 = g_sourceTexture
|
||||
filter_linear0 = true
|
||||
|
||||
shader1 = cathode-retro-util-downsample-2x-horz.slang
|
||||
filter_linear1 = true
|
||||
scale_x1 = 0.5
|
||||
|
||||
shader2 = cathode-retro-util-downsample-2x-vert.slang
|
||||
filter_linear2 = true
|
||||
scale_y2 = 0.5
|
||||
|
||||
shader3 = cathode-retro-util-tonemap-and-downsample-horz.slang
|
||||
filter_linear3 = true
|
||||
scale_x3 = 0.5
|
||||
|
||||
shader4 = cathode-retro-util-tonemap-and-downsample-vert.slang
|
||||
filter_linear4 = true
|
||||
scale_y4 = 0.5
|
||||
alias4 = g_sourceTex
|
||||
|
||||
shader5 = cathode-retro-util-gaussian-blur-horz.slang
|
||||
filter_linear5 = true
|
||||
|
||||
shader6 = cathode-retro-util-gaussian-blur-vert.slang
|
||||
filter_linear6 = true
|
||||
alias6 = g_diffusionTexture
|
||||
|
||||
shader7 = cathode-retro-generator-gen-phase.slang
|
||||
float_framebuffer7 = true
|
||||
alias7 = g_scanlinePhases
|
||||
scale_type7 = absolute
|
||||
scale_y7 = 1.0
|
||||
scale_x7 = 224.0
|
||||
filter_linear7 = true
|
||||
|
||||
shader8 = ../../../stock.slang
|
||||
scale_type_y8 = absolute
|
||||
scale_y8 = 224.0
|
||||
|
||||
shader9 = cathode-retro-generator-rgb-to-svideo-or-composite.slang
|
||||
float_framebuffer9 = true
|
||||
alias9 = rgb2yiq
|
||||
|
||||
shader10 = cathode-retro-generator-apply-artifacts.slang
|
||||
filter_linear10 = true
|
||||
float_framebuffer10 = true
|
||||
alias10 = YIQ_tex
|
||||
|
||||
shader11 = cathode-retro-decoder-composite-to-svideo.slang
|
||||
filter_linear11 = true
|
||||
float_framebuffer11 = true
|
||||
alias11 = IQ_separate
|
||||
|
||||
shader12 = cathode-retro-decoder-svideo-to-modulated-chroma.slang
|
||||
float_framebuffer12 = true
|
||||
alias12 = g_modulatedChromaTexture
|
||||
|
||||
shader13 = cathode-retro-decoder-svideo-to-rgb.slang
|
||||
float_framebuffer13 = true
|
||||
alias13 = yiq2rgb
|
||||
|
||||
shader14 = cathode-retro-decoder-filter-rgb.slang
|
||||
float_framebuffer14 = true
|
||||
alias14 = g_currentFrameTexture
|
||||
|
||||
shader15 = cathode-retro-crt-generate-masks.slang
|
||||
alias15 = g_maskTexture
|
||||
#mipmap_input15 = false
|
||||
wrap_mode15 = repeat
|
||||
scale_type15 = viewport
|
||||
float_framebuffer15 = true
|
||||
|
||||
shader16 = cathode-retro-crt-generate-screen-texture.slang
|
||||
alias16 = g_screenMaskTexture
|
||||
#mipmap_input16 = false
|
||||
filter_linear16 = true
|
||||
wrap_mode16 = clamp_to_edge
|
||||
float_framebuffer16 = true
|
||||
|
||||
shader17 = cathode-retro-crt-rgb-to-crt.slang
|
||||
#mipmap_input17 = false
|
||||
filter_linear17 = true
|
132
crt/shaders/cathode-retro/slang_params.inc
Normal file
132
crt/shaders/cathode-retro/slang_params.inc
Normal file
|
@ -0,0 +1,132 @@
|
|||
layout(push_constant) uniform Push
|
||||
{
|
||||
vec4 SourceSize;
|
||||
vec4 OriginalSize;
|
||||
vec4 OutputSize;
|
||||
uint FrameCount;
|
||||
vec4 FinalViewportSize;
|
||||
float warpX, warpY, corner, noise_seed;
|
||||
} params;
|
||||
|
||||
#pragma parameter div0 "--------Screen Settings--------" 0.0 0.0 0.0 0.0
|
||||
#pragma parameter scan_intens "Scanline Intensity" 0.4 0.0 1.0 0.01
|
||||
#pragma parameter cat_mask_picker "Mask (0=none, 1=aperture, 2=slot, 3=shadow)" 1.0 0.0 3.0 1.0
|
||||
#pragma parameter mask_intens "Mask Strength" 0.4 0.0 1.0 0.01
|
||||
#pragma parameter mask_scale "Mask Scale (2 or 3 for 4K)" 1.0 1.0 200.0 1.0
|
||||
#pragma parameter mask_depth "Mask Background Darkness" 0.3 0.0 1.0 0.01
|
||||
#pragma parameter warpX "Barrel Distortion X" 0.2 0.0 1.0 0.01
|
||||
#pragma parameter warpY "Barrel Distortion Y" 0.1 0.0 1.0 0.01
|
||||
#pragma parameter anim_noise "Animate Anti-Moire Noise" 0.0 0.0 1.0 1.0
|
||||
#pragma parameter corner "Rounded Corner Size" 0.03 0.0 1.0 0.01
|
||||
#pragma parameter persistence "Phosphor Persistence" 0.25 0.0 1.0 0.01
|
||||
#pragma parameter diffusion "Diffusion Strength" 0.5 0.0 1.0 0.01
|
||||
#pragma parameter div1 "---------TV Knob Settings---------" 0.0 0.0 0.0 0.0
|
||||
#pragma parameter cat_sat "Saturation" 1.0 0.0 2.0 0.01
|
||||
#pragma parameter cat_bright "Brightness" 1.0 0.0 2.0 0.01
|
||||
#pragma parameter cat_white_lvl "White Level" 1.0 0.0 2.0 0.01
|
||||
#pragma parameter cat_black_lvl "Black Level" 0.0 0.0 2.0 0.01
|
||||
#pragma parameter tint "Tint Knob Adjustment" 0.0 -1.0 1.0 0.01
|
||||
#pragma parameter blurStrength "Sharpness" -0.15 -1.0 1.0 0.01
|
||||
#pragma parameter div2 "---------Signal Parameters---------" 0.0 0.0 0.0 0.0
|
||||
#pragma parameter composite "Blend Chrome/Luma (aka Composite)" 1.0 0.0 1.0 1.0
|
||||
#pragma parameter sig_pad "Signal Padding at Edges" 0.0 0.0 10.0 1.0
|
||||
#pragma parameter minlum "Minimum Luminance" 1.0 0.0 1.0 0.01
|
||||
#pragma parameter colorpower "Color Power" 1.0 0.0 2.0 0.01
|
||||
#pragma parameter noise_seed "Noise Seed" 247.0 179.0 313.0 1.0
|
||||
#pragma parameter cb_samples "Samples Per Color Burst Cyle" 2.0 1.0 100.0 1.0
|
||||
#pragma parameter cb_first_start "Color Burst Phase First Scanline" 0.0 0.0 100.0 1.0
|
||||
#pragma parameter cb_last_start "CB Phase Prev Frame First Scanline" 1.0 0.0 100.0 1.0
|
||||
#pragma parameter cb_phase_inc "Color Burst Phase Increment" 1.66666 0.0 3.0 0.01
|
||||
#pragma parameter stepSize "Texels Between Each Sample" 1.0 1.0 100.0 1.0
|
||||
#pragma parameter div3 "-------Artifact Settings-------" 0.0 0.0 0.0 0.0
|
||||
#pragma parameter horz_track_scale "Horizontal Tracking Instability Scale" 1.0 0.0 3.0 0.05
|
||||
#pragma parameter ghost_vis "Ghost Visibility" 0.15 0.0 1.0 0.01
|
||||
#pragma parameter ghost_dist "Ghost Delay Cycles" 1.0 0.0 100.0 1.0
|
||||
#pragma parameter ghost_spread "Ghost Spread Cycles" 1.0 0.0 100.0 1.0
|
||||
#pragma parameter noise_strength "Artifact Noise Strength" 0.15 0.0 1.0 0.01
|
||||
#pragma parameter temp_artifact_blend "Temporal Artifact Blending (Toggle)" 0.0 0.0 1.0 1.0
|
||||
|
||||
#define uint2 uvec2
|
||||
#define uint3 uvec3
|
||||
#define uint4 uvec4
|
||||
#define int2 ivec2
|
||||
#define int3 ivec3
|
||||
#define int4 ivec4
|
||||
#define float2 vec2
|
||||
#define float3 vec3
|
||||
#define float4 vec4
|
||||
#define ddx dFdx
|
||||
#define ddy dFdy
|
||||
#define frac fract
|
||||
#define lerp mix
|
||||
|
||||
float saturate(float a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec2 saturate(vec2 a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 saturate(vec3 a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec4 saturate(vec4 a)
|
||||
{
|
||||
return clamp(a, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void sincos(float angle, out float s, out float c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
void sincos(vec2 angle, out vec2 s, out vec2 c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
void sincos(vec3 angle, out vec3 s, out vec3 c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
void sincos(vec4 angle, out vec4 s, out vec4 c)
|
||||
{
|
||||
s = sin(angle);
|
||||
c = cos(angle);
|
||||
}
|
||||
|
||||
layout(std140, set = 0, binding = 0) uniform UBO
|
||||
{
|
||||
mat4 MVP;
|
||||
vec4 OriginalSize;
|
||||
float cb_first_start, cb_last_start, cb_phase_inc, cb_samples, horz_track_scale,
|
||||
minlum, colorpower, composite, sig_pad, ghost_vis, ghost_dist, ghost_spread,
|
||||
noise_strength, tint, cat_sat, cat_bright, cat_white_lvl, cat_black_lvl,
|
||||
temp_artifact_blend, blurStrength, stepSize, persistence, scan_intens,
|
||||
diffusion, mask_intens, mask_depth, cat_mask_picker, mask_scale, anim_noise,
|
||||
div0, div1, div2, div3;
|
||||
} global;
|
||||
|
||||
#pragma stage vertex
|
||||
layout(location = 0) in vec4 Position;
|
||||
layout(location = 1) in vec2 TexCoord;
|
||||
layout(location = 0) out vec2 vTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = global.MVP * Position;//vec4(Position.x * 2.0 - 1.0, Position.y * 2.0 - 1.0, 0.0, 1.0);//
|
||||
vTexCoord = vec2(TexCoord.x, TexCoord.y) * 1.0001;//vec2(Position.x, 1.0 - Position.y);//
|
||||
}
|
||||
|
||||
#pragma stage fragment
|
||||
layout(location = 0) in vec2 vTexCoord;
|
||||
layout(location = 0) out vec4 FragColor;
|
|
@ -1,25 +1,37 @@
|
|||
shaders = 4
|
||||
shaders = 6
|
||||
|
||||
shader0 = ../stock.slang
|
||||
alias0 = vt220_refpass
|
||||
|
||||
shader1 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang
|
||||
scale_type1 = source
|
||||
scale_x1 = 4.0
|
||||
filter_linear1 = false
|
||||
scale_y1 = 1.0
|
||||
float_framebuffer1 = true
|
||||
shader1 = ../stock.slang
|
||||
alias1 = PrePass0
|
||||
|
||||
shader2 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang
|
||||
scale_type2 = source
|
||||
scale_x2 = 0.5
|
||||
shader2 = shaders/guest/advanced/ntsc/ntsc-pass1.slang
|
||||
alias2 = NPass1
|
||||
scale_type_x2 = source
|
||||
scale_type_y2 = source
|
||||
scale_x2 = 4.0
|
||||
scale_y2 = 1.0
|
||||
float_framebuffer2 = true
|
||||
filter_linear2 = false
|
||||
|
||||
shader3 = shaders/vt220/vt220.slang
|
||||
mipmap_input3 = true
|
||||
wrap_mode3 = mirrored_repeat
|
||||
scale_type3 = viewport
|
||||
shader3 = shaders/guest/advanced/ntsc/ntsc-pass2.slang
|
||||
filter_linear3 = true
|
||||
float_framebuffer3 = true
|
||||
scale_type3 = source
|
||||
scale_x3 = 0.5
|
||||
scale_y3 = 1.0
|
||||
|
||||
shader4 = shaders/guest/advanced/ntsc/ntsc-pass3.slang
|
||||
filter_linear4 = true
|
||||
scale_type4 = source
|
||||
scale_x4 = 1.0
|
||||
scale_y4 = 1.0
|
||||
|
||||
shader5 = shaders/vt220/vt220.slang
|
||||
mipmap_input5 = true
|
||||
wrap_mode5 = mirrored_repeat
|
||||
scale_type5 = viewport
|
||||
|
||||
parameters = "quality"
|
||||
quality = 1.0
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
shaders = 2
|
||||
shaders = 4
|
||||
|
||||
shader0 = shaders/ntsc-adaptive/ntsc-pass1.slang
|
||||
scale_type0 = source
|
||||
scale_x0 = 4.0
|
||||
filter_linear0 = false
|
||||
scale_y0 = 1.0
|
||||
float_framebuffer0 = true
|
||||
shader0 = ../stock.slang
|
||||
alias0 = PrePass0
|
||||
|
||||
shader1 = shaders/ntsc-adaptive/ntsc-pass2.slang
|
||||
scale_type1 = source
|
||||
scale_x1 = 0.5
|
||||
shader1 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass1.slang
|
||||
alias1 = NPass1
|
||||
scale_type_x1 = source
|
||||
scale_type_y1 = source
|
||||
scale_x1 = 4.0
|
||||
scale_y1 = 1.0
|
||||
filter_linear1 = true
|
||||
float_framebuffer1 = true
|
||||
filter_linear1 = false
|
||||
|
||||
shader2 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang
|
||||
filter_linear2 = true
|
||||
float_framebuffer2 = true
|
||||
scale_type2 = source
|
||||
scale_x2 = 0.5
|
||||
scale_y2 = 1.0
|
||||
|
||||
shader3 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass3.slang
|
||||
filter_linear3 = true
|
||||
scale_type3 = source
|
||||
scale_x3 = 1.0
|
||||
scale_y3 = 1.0
|
||||
|
|
|
@ -1,35 +1,47 @@
|
|||
shaders = 7
|
||||
shaders = 9
|
||||
|
||||
shader0 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang
|
||||
scale_type0 = source
|
||||
scale_x0 = 4.0
|
||||
filter_linear0 = false
|
||||
scale_y0 = 1.0
|
||||
float_framebuffer0 = true
|
||||
shader0 = ../stock.slang
|
||||
alias0 = PrePass0
|
||||
|
||||
shader1 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang
|
||||
scale_type1 = source
|
||||
scale_x1 = 0.5
|
||||
shader1 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass1.slang
|
||||
alias1 = NPass1
|
||||
scale_type_x1 = source
|
||||
scale_type_y1 = source
|
||||
scale_x1 = 4.0
|
||||
scale_y1 = 1.0
|
||||
filter_linear1 = true
|
||||
float_framebuffer1 = true
|
||||
filter_linear1 = false
|
||||
|
||||
shader2 = ../crt/shaders/geom-deluxe/phosphor_apply.slang
|
||||
alias2 = internal1
|
||||
shader2 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang
|
||||
filter_linear2 = true
|
||||
float_framebuffer2 = true
|
||||
scale_type2 = source
|
||||
scale_x2 = 0.5
|
||||
scale_y2 = 1.0
|
||||
|
||||
shader3 = ../crt/shaders/geom-deluxe/phosphor_update.slang
|
||||
alias3 = phosphor
|
||||
shader3 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass3.slang
|
||||
filter_linear3 = true
|
||||
scale_type3 = source
|
||||
scale_x3 = 1.0
|
||||
scale_y3 = 1.0
|
||||
|
||||
shader4 = ../crt/shaders/geom-deluxe/gaussx.slang
|
||||
filter_linear4 = true
|
||||
alias4 = internal2
|
||||
shader4 = ../crt/shaders/geom-deluxe/phosphor_apply.slang
|
||||
alias4 = internal1
|
||||
|
||||
shader5 = ../crt/shaders/geom-deluxe/gaussy.slang
|
||||
filter_linear5 = true
|
||||
alias5 = blur_texture
|
||||
shader5 = ../crt/shaders/geom-deluxe/phosphor_update.slang
|
||||
alias5 = phosphor
|
||||
|
||||
shader6 = ../crt/shaders/geom-deluxe/crt-geom-deluxe.slang
|
||||
shader6 = ../crt/shaders/geom-deluxe/gaussx.slang
|
||||
filter_linear6 = true
|
||||
mipmap_input6 = true
|
||||
alias6 = internal2
|
||||
|
||||
shader7 = ../crt/shaders/geom-deluxe/gaussy.slang
|
||||
filter_linear7 = true
|
||||
alias7 = blur_texture
|
||||
|
||||
shader8 = ../crt/shaders/geom-deluxe/crt-geom-deluxe.slang
|
||||
filter_linear8 = true
|
||||
mipmap_input8 = true
|
||||
|
||||
# ntsc-adaptive
|
||||
# These settings are meant to get correct looking transparencies and
|
||||
|
|
|
@ -13,25 +13,36 @@
|
|||
# 1.) bloom_approx_scale_x = scale_x2
|
||||
# 2.) mask_resize_viewport_scale = float2(scale_x6, scale_y5)
|
||||
# Finally, shader passes need to know the value of geom_max_aspect_ratio used to
|
||||
# calculate scale_y5 (among other values):
|
||||
# calculate scale_y7 (among other values):
|
||||
# 1.) geom_max_aspect_ratio = (geom_max_aspect_ratio used to calculate scale_y5)
|
||||
|
||||
shaders = "14"
|
||||
shaders = "16"
|
||||
|
||||
# NTSC Shader Passes
|
||||
shader0 = "../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang"
|
||||
shader1 = "../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang"
|
||||
shader0 = ../stock.slang
|
||||
alias0 = PrePass0
|
||||
|
||||
scale_type0 = source
|
||||
scale_x0 = 4.0
|
||||
filter_linear0 = false
|
||||
scale_y0 = 1.0
|
||||
float_framebuffer0 = true
|
||||
|
||||
scale_type1 = source
|
||||
scale_x1 = 0.5
|
||||
shader1 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass1.slang
|
||||
alias1 = NPass1
|
||||
scale_type_x1 = source
|
||||
scale_type_y1 = source
|
||||
scale_x1 = 4.0
|
||||
scale_y1 = 1.0
|
||||
filter_linear1 = true
|
||||
float_framebuffer1 = true
|
||||
filter_linear1 = false
|
||||
|
||||
shader2 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang
|
||||
filter_linear2 = true
|
||||
float_framebuffer2 = true
|
||||
scale_type2 = source
|
||||
scale_x2 = 0.5
|
||||
scale_y2 = 1.0
|
||||
|
||||
shader3 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass3.slang
|
||||
filter_linear3 = true
|
||||
scale_type3 = source
|
||||
scale_x3 = 1.0
|
||||
scale_y3 = 1.0
|
||||
|
||||
# Set an identifier, filename, and sampling traits for the phosphor mask texture.
|
||||
# Load an aperture grille, slot mask, and an EDP shadow mask, and load a small
|
||||
|
@ -64,32 +75,32 @@ mask_shadow_texture_small_mipmap = "false" # Mipmapping causes artifacts with m
|
|||
mask_shadow_texture_large_mipmap = "true" # Essential for hardware-resized masks
|
||||
|
||||
|
||||
# Pass2: Linearize the input based on CRT gamma and bob interlaced fields.
|
||||
# Pass 4: Linearize the input based on CRT gamma and bob interlaced fields.
|
||||
# (Bobbing ensures we can immediately blur without getting artifacts.)
|
||||
shader2 = "../crt/shaders/crt-royale/src/crt-royale-first-pass-linearize-crt-gamma-bob-fields.slang"
|
||||
alias2 = "ORIG_LINEARIZED"
|
||||
filter_linear2 = "false"
|
||||
scale_type2 = "source"
|
||||
scale2 = "1.0"
|
||||
srgb_framebuffer2 = "true"
|
||||
shader4 = "../crt/shaders/crt-royale/src/crt-royale-first-pass-linearize-crt-gamma-bob-fields.slang"
|
||||
alias4 = "ORIG_LINEARIZED"
|
||||
filter_linear4 = "false"
|
||||
scale_type4 = "source"
|
||||
scale4 = "1.0"
|
||||
srgb_framebuffer4 = "true"
|
||||
|
||||
# Pass3: Resample interlaced (and misconverged) scanlines vertically.
|
||||
# Pass 5: Resample interlaced (and misconverged) scanlines vertically.
|
||||
# Separating vertical/horizontal scanline sampling is faster: It lets us
|
||||
# consider more scanlines while calculating weights for fewer pixels, and
|
||||
# it reduces our samples from vertical*horizontal to vertical+horizontal.
|
||||
# This has to come right after ORIG_LINEARIZED, because there's no
|
||||
# "original_source" scale_type we can use later.
|
||||
shader3 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-vertical-interlacing.slang"
|
||||
alias3 = "VERTICAL_SCANLINES"
|
||||
filter_linear3 = "true"
|
||||
scale_type_x3 = "source"
|
||||
scale_x3 = "1.0"
|
||||
scale_type_y3 = "viewport"
|
||||
scale_y3 = "1.0"
|
||||
#float_framebuffer3 = "true"
|
||||
srgb_framebuffer3 = "true"
|
||||
shader5 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-vertical-interlacing.slang"
|
||||
alias5 = "VERTICAL_SCANLINES"
|
||||
filter_linear5 = "true"
|
||||
scale_type_x5 = "source"
|
||||
scale_x5 = "1.0"
|
||||
scale_type_y5 = "viewport"
|
||||
scale_y5 = "1.0"
|
||||
#float_framebuffer5 = "true"
|
||||
srgb_framebuffer5 = "true"
|
||||
|
||||
# Pass4: Do a small resize blur of ORIG_LINEARIZED at an absolute size, and
|
||||
# Pass 6: Do a small resize blur of ORIG_LINEARIZED at an absolute size, and
|
||||
# account for convergence offsets. We want to blur a predictable portion of the
|
||||
# screen to match the phosphor bloom, and absolute scale works best for
|
||||
# reliable results with a fixed-size bloom. Picking a scale is tricky:
|
||||
|
@ -99,44 +110,44 @@ srgb_framebuffer3 = "true"
|
|||
# b.) 320x240 works well for the "real bloom" version: It's 1-1.5% faster, and
|
||||
# the only noticeable visual difference is a larger halation spread (which
|
||||
# may be a good thing for people who like to crank it up).
|
||||
# Note the 4:3 aspect ratio assumes the input has cropped geom_overscan (so it's
|
||||
# *intended* for an ~4:3 aspect ratio).
|
||||
shader4 = "../crt/shaders/crt-royale/src/crt-royale-bloom-approx.slang"
|
||||
alias4 = "BLOOM_APPROX"
|
||||
filter_linear4 = "true"
|
||||
scale_type4 = "absolute"
|
||||
scale_x4 = "320"
|
||||
scale_y4 = "240"
|
||||
srgb_framebuffer4 = "true"
|
||||
# Note the 4:5 aspect ratio assumes the input has cropped geom_overscan (so it's
|
||||
# *intended* for an ~4:5 aspect ratio).
|
||||
shader6 = "../crt/shaders/crt-royale/src/crt-royale-bloom-approx.slang"
|
||||
alias6 = "BLOOM_APPROX"
|
||||
filter_linear6 = "true"
|
||||
scale_type6 = "absolute"
|
||||
scale_x6 = "320"
|
||||
scale_y6 = "240"
|
||||
srgb_framebuffer6 = "true"
|
||||
|
||||
# Pass5: Vertically blur the input for halation and refractive diffusion.
|
||||
# Pass 7: Vertically blur the input for halation and refractive diffusion.
|
||||
# Base this on BLOOM_APPROX: This blur should be small and fast, and blurring
|
||||
# a constant portion of the screen is probably physically correct if the
|
||||
# viewport resolution is proportional to the simulated CRT size.
|
||||
shader5 = "../blurs/shaders/royale/blur9fast-vertical.slang"
|
||||
filter_linear5 = "true"
|
||||
scale_type5 = "source"
|
||||
scale5 = "1.0"
|
||||
srgb_framebuffer5 = "true"
|
||||
shader7 = "../blurs/shaders/royale/blur9fast-vertical.slang"
|
||||
filter_linear7 = "true"
|
||||
scale_type7 = "source"
|
||||
scale7 = "1.0"
|
||||
srgb_framebuffer7 = "true"
|
||||
|
||||
# Pass6: Horizontally blur the input for halation and refractive diffusion.
|
||||
# Pass 8: Horizontally blur the input for halation and refractive diffusion.
|
||||
# Note: Using a one-pass 9x9 blur is about 1% slower.
|
||||
shader6 = "../blurs/shaders/royale/blur9fast-horizontal.slang"
|
||||
alias6 = "HALATION_BLUR"
|
||||
filter_linear6 = "true"
|
||||
scale_type6 = "source"
|
||||
scale6 = "1.0"
|
||||
srgb_framebuffer6 = "true"
|
||||
shader8 = "../blurs/shaders/royale/blur9fast-horizontal.slang"
|
||||
alias8 = "HALATION_BLUR"
|
||||
filter_linear8 = "true"
|
||||
scale_type8 = "source"
|
||||
scale8 = "1.0"
|
||||
srgb_framebuffer8 = "true"
|
||||
|
||||
# Pass7: Lanczos-resize the phosphor mask vertically. Set the absolute
|
||||
# scale_x7 == mask_texture_small_size.x (see IMPORTANT above). Larger scales
|
||||
# Pass 9: Lanczos-resize the phosphor mask vertically. Set the absolute
|
||||
# scale_x9 == mask_texture_small_size.x (see IMPORTANT above). Larger scales
|
||||
# will blur, and smaller scales could get nasty. The vertical size must be
|
||||
# based on the viewport size and calculated carefully to avoid artifacts later.
|
||||
# First calculate the minimum number of mask tiles we need to draw.
|
||||
# Since curvature is computed after the scanline masking pass:
|
||||
# num_resized_mask_tiles = 2.0;
|
||||
# If curvature were computed in the scanline masking pass (it's not):
|
||||
# max_mask_texel_border = ~3.0 * (1/3.0 + 4.0*sqrt(2.0) + 0.5 + 1.0);
|
||||
# max_mask_texel_border = ~3.0 * (1/3.0 + 4.0*sqrt(2.0) + 0.7 + 1.0);
|
||||
# max_mask_tile_border = max_mask_texel_border/
|
||||
# (min_resized_phosphor_triad_size * mask_triads_per_tile);
|
||||
# num_resized_mask_tiles = max(2.0, 1.0 + max_mask_tile_border * 2.0);
|
||||
|
@ -146,7 +157,7 @@ srgb_framebuffer6 = "true"
|
|||
# to relate them to vertical resolution. The widest we expect is:
|
||||
# geom_max_aspect_ratio = 4.0/3.0 # Note: Shader passes need to know this!
|
||||
# The fewer triads we tile across the screen, the larger each triad will be as a
|
||||
# fraction of the viewport size, and the larger scale_y5 must be to draw a full
|
||||
# fraction of the viewport size, and the larger scale_y7 must be to draw a full
|
||||
# num_resized_mask_tiles. Therefore, we must decide the smallest number of
|
||||
# triads we'll guarantee can be displayed on screen. We'll set this according
|
||||
# to 3-pixel triads at 768p resolution (the lowest anyone's likely to use):
|
||||
|
@ -154,70 +165,70 @@ srgb_framebuffer6 = "true"
|
|||
# Now calculate the viewport scale that ensures we can draw resized_mask_tiles:
|
||||
# min_scale_x = resized_mask_tiles * mask_triads_per_tile /
|
||||
# min_allowed_viewport_triads
|
||||
# scale_y7 = geom_max_aspect_ratio * min_scale_x
|
||||
# scale_y9 = geom_max_aspect_ratio * min_scale_x
|
||||
# # Some code might depend on equal scales:
|
||||
# scale_x8 = scale_y7
|
||||
# scale_x10 = scale_y7
|
||||
# Given our default geom_max_aspect_ratio and min_allowed_viewport_triads:
|
||||
# scale_y7 = 4.0/3.0 * 2.0/(341.33333 / 8.0) = 0.0625
|
||||
# scale_y9 = 4.0/3.0 * 2.0/(341.33335 / 8.0) = 0.0625
|
||||
# IMPORTANT: The scales MUST be calculated in this way. If you wish to change
|
||||
# geom_max_aspect_ratio, update that constant in user-cgp-constants.h!
|
||||
shader7 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-vertical.slang"
|
||||
filter_linear7 = "true"
|
||||
scale_type_x7 = "absolute"
|
||||
scale_x7 = "64"
|
||||
scale_type_y7 = "viewport"
|
||||
scale_y7 = "0.0625" # Safe for >= 341.333 horizontal triads at viewport size
|
||||
#srgb_framebuffer7 = "false" # mask_texture is already assumed linear
|
||||
shader9 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-vertical.slang"
|
||||
filter_linear9 = "true"
|
||||
scale_type_x9 = "absolute"
|
||||
scale_x9 = "64"
|
||||
scale_type_y9 = "viewport"
|
||||
scale_y9 = "0.0625" # Safe for >= 341.333 horizontal triads at viewport size
|
||||
#srgb_framebuffer9 = "false" # mask_texture is already assumed linear
|
||||
|
||||
# Pass8: Lanczos-resize the phosphor mask horizontally. scale_x8 = scale_y7.
|
||||
# Pass 10: Lanczos-resize the phosphor mask horizontally. scale_x10 = scale_y7.
|
||||
# TODO: Check again if the shaders actually require equal scales.
|
||||
shader8 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-horizontal.slang"
|
||||
alias8 = "MASK_RESIZE"
|
||||
filter_linear8 = "false"
|
||||
scale_type_x8 = "viewport"
|
||||
scale_x8 = "0.0625"
|
||||
scale_type_y8 = "source"
|
||||
scale_y8 = "1.0"
|
||||
#srgb_framebuffer8 = "false" # mask_texture is already assumed linear
|
||||
shader10 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-horizontal.slang"
|
||||
alias10 = "MASK_RESIZE"
|
||||
filter_linear10 = "false"
|
||||
scale_type_x10 = "viewport"
|
||||
scale_x10 = "0.0625"
|
||||
scale_type_y10 = "source"
|
||||
scale_y10 = "1.0"
|
||||
#srgb_framebuffer10 = "false" # mask_texture is already assumed linear
|
||||
|
||||
# Pass9: Resample (misconverged) scanlines horizontally, apply halation, and
|
||||
# Pass 11: Resample (misconverged) scanlines horizontally, apply halation, and
|
||||
# apply the phosphor mask.
|
||||
shader9 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang"
|
||||
alias9 = "MASKED_SCANLINES"
|
||||
filter_linear9 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type9 = "viewport"
|
||||
scale9 = "1.0"
|
||||
#float_framebuffer9 = "true"
|
||||
srgb_framebuffer9 = "true"
|
||||
|
||||
# Pass 10: Compute a brightpass. This will require reading the final mask.
|
||||
shader10 = "../crt/shaders/crt-royale/src/crt-royale-brightpass.slang"
|
||||
alias10 = "BRIGHTPASS"
|
||||
filter_linear10 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type10 = "viewport"
|
||||
scale10 = "1.0"
|
||||
srgb_framebuffer10 = "true"
|
||||
|
||||
# Pass 11: Blur the brightpass vertically
|
||||
shader11 = "../crt/shaders/crt-royale/src/crt-royale-bloom-vertical.slang"
|
||||
shader11 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang"
|
||||
alias11 = "MASKED_SCANLINES"
|
||||
filter_linear11 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type11 = "source"
|
||||
scale_type11 = "viewport"
|
||||
scale11 = "1.0"
|
||||
#float_framebuffer11 = "true"
|
||||
srgb_framebuffer11 = "true"
|
||||
|
||||
# Pass 12: Blur the brightpass horizontally and combine it with the dimpass:
|
||||
shader12 = "../crt/shaders/crt-royale/src/crt-royale-bloom-horizontal-reconstitute.slang"
|
||||
filter_linear12 = "true"
|
||||
scale_type12 = "source"
|
||||
# Pass 12: Compute a brightpass. This will require reading the final mask.
|
||||
shader12 = "../crt/shaders/crt-royale/src/crt-royale-brightpass.slang"
|
||||
alias12 = "BRIGHTPASS"
|
||||
filter_linear12 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type12 = "viewport"
|
||||
scale12 = "1.0"
|
||||
srgb_framebuffer12 = "true"
|
||||
|
||||
# Pass 13: Compute curvature/AA:
|
||||
shader13 = "../crt/shaders/crt-royale/src/crt-royale-geometry-aa-last-pass.slang"
|
||||
filter_linear13 = "true"
|
||||
scale_type13 = "viewport"
|
||||
mipmap_input13 = "true"
|
||||
texture_wrap_mode13 = "clamp_to_edge"
|
||||
# Pass 13: Blur the brightpass vertically
|
||||
shader13 = "../crt/shaders/crt-royale/src/crt-royale-bloom-vertical.slang"
|
||||
filter_linear13 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type13 = "source"
|
||||
scale13 = "1.0"
|
||||
srgb_framebuffer13 = "true"
|
||||
|
||||
# Pass 14: Blur the brightpass horizontally and combine it with the dimpass:
|
||||
shader14 = "../crt/shaders/crt-royale/src/crt-royale-bloom-horizontal-reconstitute.slang"
|
||||
filter_linear14 = "true"
|
||||
scale_type14 = "source"
|
||||
scale14 = "1.0"
|
||||
srgb_framebuffer14 = "true"
|
||||
|
||||
# Pass 15: Compute curvature/AA:
|
||||
shader15 = "../crt/shaders/crt-royale/src/crt-royale-geometry-aa-last-pass.slang"
|
||||
filter_linear15 = "true"
|
||||
scale_type15 = "viewport"
|
||||
mipmap_input15 = "true"
|
||||
texture_wrap_mode15 = "clamp_to_edge"
|
||||
|
||||
parameters = "quality"
|
||||
quality = 1.0
|
||||
|
|
|
@ -13,25 +13,36 @@
|
|||
# 1.) bloom_approx_scale_x = scale_x2
|
||||
# 2.) mask_resize_viewport_scale = float2(scale_x6, scale_y5)
|
||||
# Finally, shader passes need to know the value of geom_max_aspect_ratio used to
|
||||
# calculate scale_y5 (among other values):
|
||||
# calculate scale_y7 (among other values):
|
||||
# 1.) geom_max_aspect_ratio = (geom_max_aspect_ratio used to calculate scale_y5)
|
||||
|
||||
shaders = "14"
|
||||
shaders = "16"
|
||||
|
||||
# NTSC Shader Passes
|
||||
shader0 = "../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang"
|
||||
shader1 = "../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang"
|
||||
shader0 = ../stock.slang
|
||||
alias0 = PrePass0
|
||||
|
||||
scale_type0 = source
|
||||
scale_x0 = 4.0
|
||||
filter_linear0 = false
|
||||
scale_y0 = 1.0
|
||||
float_framebuffer0 = true
|
||||
|
||||
scale_type1 = source
|
||||
scale_x1 = 0.5
|
||||
shader1 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass1.slang
|
||||
alias1 = NPass1
|
||||
scale_type_x1 = source
|
||||
scale_type_y1 = source
|
||||
scale_x1 = 4.0
|
||||
scale_y1 = 1.0
|
||||
filter_linear1 = true
|
||||
float_framebuffer1 = true
|
||||
filter_linear1 = false
|
||||
|
||||
shader2 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang
|
||||
filter_linear2 = true
|
||||
float_framebuffer2 = true
|
||||
scale_type2 = source
|
||||
scale_x2 = 0.5
|
||||
scale_y2 = 1.0
|
||||
|
||||
shader3 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass3.slang
|
||||
filter_linear3 = true
|
||||
scale_type3 = source
|
||||
scale_x3 = 1.0
|
||||
scale_y3 = 1.0
|
||||
|
||||
# Set an identifier, filename, and sampling traits for the phosphor mask texture.
|
||||
# Load an aperture grille, slot mask, and an EDP shadow mask, and load a small
|
||||
|
@ -64,32 +75,32 @@ mask_shadow_texture_small_mipmap = "false" # Mipmapping causes artifacts with m
|
|||
mask_shadow_texture_large_mipmap = "true" # Essential for hardware-resized masks
|
||||
|
||||
|
||||
# Pass2: Linearize the input based on CRT gamma and bob interlaced fields.
|
||||
# Pass 4: Linearize the input based on CRT gamma and bob interlaced fields.
|
||||
# (Bobbing ensures we can immediately blur without getting artifacts.)
|
||||
shader2 = "../crt/shaders/crt-royale/src/crt-royale-first-pass-linearize-crt-gamma-bob-fields.slang"
|
||||
alias2 = "ORIG_LINEARIZED"
|
||||
filter_linear2 = "false"
|
||||
scale_type2 = "source"
|
||||
scale2 = "1.0"
|
||||
srgb_framebuffer2 = "true"
|
||||
shader4 = "../crt/shaders/crt-royale/src/crt-royale-first-pass-linearize-crt-gamma-bob-fields.slang"
|
||||
alias4 = "ORIG_LINEARIZED"
|
||||
filter_linear4 = "false"
|
||||
scale_type4 = "source"
|
||||
scale4 = "1.0"
|
||||
srgb_framebuffer4 = "true"
|
||||
|
||||
# Pass3: Resample interlaced (and misconverged) scanlines vertically.
|
||||
# Pass 5: Resample interlaced (and misconverged) scanlines vertically.
|
||||
# Separating vertical/horizontal scanline sampling is faster: It lets us
|
||||
# consider more scanlines while calculating weights for fewer pixels, and
|
||||
# it reduces our samples from vertical*horizontal to vertical+horizontal.
|
||||
# This has to come right after ORIG_LINEARIZED, because there's no
|
||||
# "original_source" scale_type we can use later.
|
||||
shader3 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-vertical-interlacing.slang"
|
||||
alias3 = "VERTICAL_SCANLINES"
|
||||
filter_linear3 = "true"
|
||||
scale_type_x3 = "source"
|
||||
scale_x3 = "1.0"
|
||||
scale_type_y3 = "viewport"
|
||||
scale_y3 = "1.0"
|
||||
#float_framebuffer3 = "true"
|
||||
srgb_framebuffer3 = "true"
|
||||
shader5 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-vertical-interlacing.slang"
|
||||
alias5 = "VERTICAL_SCANLINES"
|
||||
filter_linear5 = "true"
|
||||
scale_type_x5 = "source"
|
||||
scale_x5 = "1.0"
|
||||
scale_type_y5 = "viewport"
|
||||
scale_y5 = "1.0"
|
||||
#float_framebuffer5 = "true"
|
||||
srgb_framebuffer5 = "true"
|
||||
|
||||
# Pass4: Do a small resize blur of ORIG_LINEARIZED at an absolute size, and
|
||||
# Pass 6: Do a small resize blur of ORIG_LINEARIZED at an absolute size, and
|
||||
# account for convergence offsets. We want to blur a predictable portion of the
|
||||
# screen to match the phosphor bloom, and absolute scale works best for
|
||||
# reliable results with a fixed-size bloom. Picking a scale is tricky:
|
||||
|
@ -99,44 +110,44 @@ srgb_framebuffer3 = "true"
|
|||
# b.) 320x240 works well for the "real bloom" version: It's 1-1.5% faster, and
|
||||
# the only noticeable visual difference is a larger halation spread (which
|
||||
# may be a good thing for people who like to crank it up).
|
||||
# Note the 4:3 aspect ratio assumes the input has cropped geom_overscan (so it's
|
||||
# *intended* for an ~4:3 aspect ratio).
|
||||
shader4 = "../crt/shaders/crt-royale/src/crt-royale-bloom-approx.slang"
|
||||
alias4 = "BLOOM_APPROX"
|
||||
filter_linear4 = "true"
|
||||
scale_type4 = "absolute"
|
||||
scale_x4 = "320"
|
||||
scale_y4 = "240"
|
||||
srgb_framebuffer4 = "true"
|
||||
# Note the 4:5 aspect ratio assumes the input has cropped geom_overscan (so it's
|
||||
# *intended* for an ~4:5 aspect ratio).
|
||||
shader6 = "../crt/shaders/crt-royale/src/crt-royale-bloom-approx.slang"
|
||||
alias6 = "BLOOM_APPROX"
|
||||
filter_linear6 = "true"
|
||||
scale_type6 = "absolute"
|
||||
scale_x6 = "320"
|
||||
scale_y6 = "240"
|
||||
srgb_framebuffer6 = "true"
|
||||
|
||||
# Pass5: Vertically blur the input for halation and refractive diffusion.
|
||||
# Pass 7: Vertically blur the input for halation and refractive diffusion.
|
||||
# Base this on BLOOM_APPROX: This blur should be small and fast, and blurring
|
||||
# a constant portion of the screen is probably physically correct if the
|
||||
# viewport resolution is proportional to the simulated CRT size.
|
||||
shader5 = "../blurs/shaders/royale/blur9fast-vertical.slang"
|
||||
filter_linear5 = "true"
|
||||
scale_type5 = "source"
|
||||
scale5 = "1.0"
|
||||
srgb_framebuffer5 = "true"
|
||||
shader7 = "../blurs/shaders/royale/blur9fast-vertical.slang"
|
||||
filter_linear7 = "true"
|
||||
scale_type7 = "source"
|
||||
scale7 = "1.0"
|
||||
srgb_framebuffer7 = "true"
|
||||
|
||||
# Pass6: Horizontally blur the input for halation and refractive diffusion.
|
||||
# Pass 8: Horizontally blur the input for halation and refractive diffusion.
|
||||
# Note: Using a one-pass 9x9 blur is about 1% slower.
|
||||
shader6 = "../blurs/shaders/royale/blur9fast-horizontal.slang"
|
||||
alias6 = "HALATION_BLUR"
|
||||
filter_linear6 = "true"
|
||||
scale_type6 = "source"
|
||||
scale6 = "1.0"
|
||||
srgb_framebuffer6 = "true"
|
||||
shader8 = "../blurs/shaders/royale/blur9fast-horizontal.slang"
|
||||
alias8 = "HALATION_BLUR"
|
||||
filter_linear8 = "true"
|
||||
scale_type8 = "source"
|
||||
scale8 = "1.0"
|
||||
srgb_framebuffer8 = "true"
|
||||
|
||||
# Pass7: Lanczos-resize the phosphor mask vertically. Set the absolute
|
||||
# scale_x7 == mask_texture_small_size.x (see IMPORTANT above). Larger scales
|
||||
# Pass 9: Lanczos-resize the phosphor mask vertically. Set the absolute
|
||||
# scale_x9 == mask_texture_small_size.x (see IMPORTANT above). Larger scales
|
||||
# will blur, and smaller scales could get nasty. The vertical size must be
|
||||
# based on the viewport size and calculated carefully to avoid artifacts later.
|
||||
# First calculate the minimum number of mask tiles we need to draw.
|
||||
# Since curvature is computed after the scanline masking pass:
|
||||
# num_resized_mask_tiles = 2.0;
|
||||
# If curvature were computed in the scanline masking pass (it's not):
|
||||
# max_mask_texel_border = ~3.0 * (1/3.0 + 4.0*sqrt(2.0) + 0.5 + 1.0);
|
||||
# max_mask_texel_border = ~3.0 * (1/3.0 + 4.0*sqrt(2.0) + 0.7 + 1.0);
|
||||
# max_mask_tile_border = max_mask_texel_border/
|
||||
# (min_resized_phosphor_triad_size * mask_triads_per_tile);
|
||||
# num_resized_mask_tiles = max(2.0, 1.0 + max_mask_tile_border * 2.0);
|
||||
|
@ -146,7 +157,7 @@ srgb_framebuffer6 = "true"
|
|||
# to relate them to vertical resolution. The widest we expect is:
|
||||
# geom_max_aspect_ratio = 4.0/3.0 # Note: Shader passes need to know this!
|
||||
# The fewer triads we tile across the screen, the larger each triad will be as a
|
||||
# fraction of the viewport size, and the larger scale_y5 must be to draw a full
|
||||
# fraction of the viewport size, and the larger scale_y7 must be to draw a full
|
||||
# num_resized_mask_tiles. Therefore, we must decide the smallest number of
|
||||
# triads we'll guarantee can be displayed on screen. We'll set this according
|
||||
# to 3-pixel triads at 768p resolution (the lowest anyone's likely to use):
|
||||
|
@ -154,70 +165,70 @@ srgb_framebuffer6 = "true"
|
|||
# Now calculate the viewport scale that ensures we can draw resized_mask_tiles:
|
||||
# min_scale_x = resized_mask_tiles * mask_triads_per_tile /
|
||||
# min_allowed_viewport_triads
|
||||
# scale_y7 = geom_max_aspect_ratio * min_scale_x
|
||||
# scale_y9 = geom_max_aspect_ratio * min_scale_x
|
||||
# # Some code might depend on equal scales:
|
||||
# scale_x8 = scale_y7
|
||||
# scale_x10 = scale_y7
|
||||
# Given our default geom_max_aspect_ratio and min_allowed_viewport_triads:
|
||||
# scale_y7 = 4.0/3.0 * 2.0/(341.33333 / 8.0) = 0.0625
|
||||
# scale_y9 = 4.0/3.0 * 2.0/(341.33335 / 8.0) = 0.0625
|
||||
# IMPORTANT: The scales MUST be calculated in this way. If you wish to change
|
||||
# geom_max_aspect_ratio, update that constant in user-cgp-constants.h!
|
||||
shader7 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-vertical.slang"
|
||||
filter_linear7 = "true"
|
||||
scale_type_x7 = "absolute"
|
||||
scale_x7 = "64"
|
||||
scale_type_y7 = "viewport"
|
||||
scale_y7 = "0.0625" # Safe for >= 341.333 horizontal triads at viewport size
|
||||
#srgb_framebuffer7 = "false" # mask_texture is already assumed linear
|
||||
shader9 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-vertical.slang"
|
||||
filter_linear9 = "true"
|
||||
scale_type_x9 = "absolute"
|
||||
scale_x9 = "64"
|
||||
scale_type_y9 = "viewport"
|
||||
scale_y9 = "0.0625" # Safe for >= 341.333 horizontal triads at viewport size
|
||||
#srgb_framebuffer9 = "false" # mask_texture is already assumed linear
|
||||
|
||||
# Pass8: Lanczos-resize the phosphor mask horizontally. scale_x8 = scale_y7.
|
||||
# Pass 10: Lanczos-resize the phosphor mask horizontally. scale_x10 = scale_y7.
|
||||
# TODO: Check again if the shaders actually require equal scales.
|
||||
shader8 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-horizontal.slang"
|
||||
alias8 = "MASK_RESIZE"
|
||||
filter_linear8 = "false"
|
||||
scale_type_x8 = "viewport"
|
||||
scale_x8 = "0.0625"
|
||||
scale_type_y8 = "source"
|
||||
scale_y8 = "1.0"
|
||||
#srgb_framebuffer8 = "false" # mask_texture is already assumed linear
|
||||
shader10 = "../crt/shaders/crt-royale/src/crt-royale-mask-resize-horizontal.slang"
|
||||
alias10 = "MASK_RESIZE"
|
||||
filter_linear10 = "false"
|
||||
scale_type_x10 = "viewport"
|
||||
scale_x10 = "0.0625"
|
||||
scale_type_y10 = "source"
|
||||
scale_y10 = "1.0"
|
||||
#srgb_framebuffer10 = "false" # mask_texture is already assumed linear
|
||||
|
||||
# Pass9: Resample (misconverged) scanlines horizontally, apply halation, and
|
||||
# Pass 11: Resample (misconverged) scanlines horizontally, apply halation, and
|
||||
# apply the phosphor mask.
|
||||
shader9 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang"
|
||||
alias9 = "MASKED_SCANLINES"
|
||||
filter_linear9 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type9 = "viewport"
|
||||
scale9 = "1.0"
|
||||
#float_framebuffer9 = "true"
|
||||
srgb_framebuffer9 = "true"
|
||||
|
||||
# Pass 10: Compute a brightpass. This will require reading the final mask.
|
||||
shader10 = "../crt/shaders/crt-royale/src/crt-royale-brightpass.slang"
|
||||
alias10 = "BRIGHTPASS"
|
||||
filter_linear10 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type10 = "viewport"
|
||||
scale10 = "1.0"
|
||||
srgb_framebuffer10 = "true"
|
||||
|
||||
# Pass 11: Blur the brightpass vertically
|
||||
shader11 = "../crt/shaders/crt-royale/src/crt-royale-bloom-vertical.slang"
|
||||
shader11 = "../crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang"
|
||||
alias11 = "MASKED_SCANLINES"
|
||||
filter_linear11 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type11 = "source"
|
||||
scale_type11 = "viewport"
|
||||
scale11 = "1.0"
|
||||
#float_framebuffer11 = "true"
|
||||
srgb_framebuffer11 = "true"
|
||||
|
||||
# Pass 12: Blur the brightpass horizontally and combine it with the dimpass:
|
||||
shader12 = "../crt/shaders/crt-royale/src/crt-royale-bloom-horizontal-reconstitute.slang"
|
||||
filter_linear12 = "true"
|
||||
scale_type12 = "source"
|
||||
# Pass 12: Compute a brightpass. This will require reading the final mask.
|
||||
shader12 = "../crt/shaders/crt-royale/src/crt-royale-brightpass.slang"
|
||||
alias12 = "BRIGHTPASS"
|
||||
filter_linear12 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type12 = "viewport"
|
||||
scale12 = "1.0"
|
||||
srgb_framebuffer12 = "true"
|
||||
|
||||
# Pass 13: Compute curvature/AA:
|
||||
shader13 = "../crt/shaders/crt-royale/src/crt-royale-geometry-aa-last-pass.slang"
|
||||
filter_linear13 = "true"
|
||||
scale_type13 = "viewport"
|
||||
mipmap_input13 = "true"
|
||||
texture_wrap_mode13 = "clamp_to_edge"
|
||||
# Pass 13: Blur the brightpass vertically
|
||||
shader13 = "../crt/shaders/crt-royale/src/crt-royale-bloom-vertical.slang"
|
||||
filter_linear13 = "true" # This could just as easily be nearest neighbor.
|
||||
scale_type13 = "source"
|
||||
scale13 = "1.0"
|
||||
srgb_framebuffer13 = "true"
|
||||
|
||||
# Pass 14: Blur the brightpass horizontally and combine it with the dimpass:
|
||||
shader14 = "../crt/shaders/crt-royale/src/crt-royale-bloom-horizontal-reconstitute.slang"
|
||||
filter_linear14 = "true"
|
||||
scale_type14 = "source"
|
||||
scale14 = "1.0"
|
||||
srgb_framebuffer14 = "true"
|
||||
|
||||
# Pass 15: Compute curvature/AA:
|
||||
shader15 = "../crt/shaders/crt-royale/src/crt-royale-geometry-aa-last-pass.slang"
|
||||
filter_linear15 = "true"
|
||||
scale_type15 = "viewport"
|
||||
mipmap_input15 = "true"
|
||||
texture_wrap_mode15 = "clamp_to_edge"
|
||||
|
||||
parameters = "quality"
|
||||
quality = 0.0
|
||||
|
|
|
@ -1,50 +1,61 @@
|
|||
shaders = 7
|
||||
shaders = 9
|
||||
|
||||
shader0 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass1.slang
|
||||
scale_type0 = source
|
||||
filter_linear0 = false
|
||||
scale_x0 = 4.0
|
||||
scale_y0 = 1.0
|
||||
frame_count_mod0 = 2
|
||||
float_framebuffer0 = true
|
||||
shader0 = ../stock.slang
|
||||
alias0 = PrePass0
|
||||
|
||||
shader1 = ../ntsc/shaders/ntsc-adaptive/ntsc-pass2.slang
|
||||
filter_linear1 = false
|
||||
scale_type1 = source
|
||||
scale_x1 = 0.5
|
||||
shader1 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass1.slang
|
||||
alias1 = NPass1
|
||||
scale_type_x1 = source
|
||||
scale_type_y1 = source
|
||||
scale_x1 = 4.0
|
||||
scale_y1 = 1.0
|
||||
float_framebuffer1 = true
|
||||
filter_linear1 = false
|
||||
|
||||
shader2 = ../crt/shaders/phosphorlut/scanlines-interlace-linearize.slang
|
||||
alias2 = firstPass
|
||||
scale2 = 2.0
|
||||
shader2 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang
|
||||
filter_linear2 = true
|
||||
float_framebuffer2 = true
|
||||
scale_type2 = source
|
||||
srgb_framebuffer2 = true
|
||||
filter_linear2 = false
|
||||
scale_x2 = 0.5
|
||||
scale_y2 = 1.0
|
||||
|
||||
shader3 = ../blurs/shaders/royale/blur5fast-vertical.slang
|
||||
scale_type3 = source
|
||||
scale3 = 1.0
|
||||
srgb_framebuffer3 = true
|
||||
shader3 = ../crt/shaders/guest/advanced/ntsc/ntsc-pass3.slang
|
||||
filter_linear3 = true
|
||||
alias3 = blurPassV
|
||||
scale_type3 = source
|
||||
scale_x3 = 1.0
|
||||
scale_y3 = 1.0
|
||||
|
||||
shader4 = ../blurs/shaders/royale/blur5fast-horizontal.slang
|
||||
alias4 = blurPass
|
||||
filter_linear4 = true
|
||||
scale4 = 1.0
|
||||
shader4 = ../crt/shaders/phosphorlut/scanlines-interlace-linearize.slang
|
||||
alias4 = firstPass
|
||||
scale4 = 2.0
|
||||
scale_type4 = source
|
||||
srgb_framebuffer4 = true
|
||||
filter_linear4 = false
|
||||
|
||||
shader5 = ../crt/shaders/phosphorlut/phosphorlut-pass0.slang
|
||||
alias5 = phosphorPass
|
||||
filter_linear5 = true
|
||||
shader5 = ../blurs/shaders/royale/blur5fast-vertical.slang
|
||||
scale_type5 = source
|
||||
scale_x5 = 4.0
|
||||
scale_y5 = 2.0
|
||||
scale5 = 1.0
|
||||
srgb_framebuffer5 = true
|
||||
filter_linear5 = true
|
||||
alias5 = blurPassV
|
||||
|
||||
shader6 = ../crt/shaders/phosphorlut/phosphorlut-pass1.slang
|
||||
shader6 = ../blurs/shaders/royale/blur5fast-horizontal.slang
|
||||
alias6 = blurPass
|
||||
filter_linear6 = true
|
||||
scale6 = 1.0
|
||||
scale_type6 = source
|
||||
srgb_framebuffer6 = true
|
||||
|
||||
shader7 = ../crt/shaders/phosphorlut/phosphorlut-pass0.slang
|
||||
alias7 = phosphorPass
|
||||
filter_linear7 = true
|
||||
scale_type7 = source
|
||||
scale_x7 = 4.0
|
||||
scale_y7 = 2.0
|
||||
srgb_framebuffer7 = true
|
||||
|
||||
shader8 = ../crt/shaders/phosphorlut/phosphorlut-pass1.slang
|
||||
filter_linear8 = true
|
||||
|
||||
parameters = "diffusion;PHOSPHOR_SCALE_X"
|
||||
diffusion = 0.6
|
||||
|
|
Loading…
Reference in a new issue