Duckstation/data/resources/shaders/reshade/Shaders/ntsc/ntsc-adaptive-lite.fx
Hyllian c1976f4b53
Add ntsc-adaptive-lite.fx shader (#3248)
- A shader that apply NTSC effects. Can be used in a stack with crt shaders.
2024-07-10 15:12:47 +10:00

438 lines
15 KiB
HLSL

#include "ReShade.fxh"
// NTSC-Adaptive-Lite - Faster for 2-Phase games (only 15 taps!)
// based on Themaister's NTSC shader
uniform int quality <
ui_type = "combo";
ui_items = "Custom\0Svideo\0Composite\0RF\0";
ui_label = "NTSC Preset";
> = 2;
uniform bool ntsc_fields <
ui_type = "radio";
ui_label = "NTSC Merge Fields";
> = false;
uniform int ntsc_phase <
ui_type = "combo";
ui_items = "Auto\0(2-Phase)\0(3-Phase)\0";
ui_label = "NTSC Phase";
> = 0;
uniform float ntsc_scale <
ui_type = "drag";
ui_min = 0.20;
ui_max = 3.0;
ui_step = 0.05;
ui_label = "NTSC Resolution Scaling";
> = 1.0;
uniform float ntsc_sat <
ui_type = "drag";
ui_min = 0.0;
ui_max = 2.0;
ui_step = 0.01;
ui_label = "NTSC Color Saturation";
> = 1.0;
uniform float ntsc_bright <
ui_type = "drag";
ui_min = 0.0;
ui_max = 1.5;
ui_step = 0.01;
ui_label = "NTSC Brightness";
> = 1.0;
uniform float cust_fringing <
ui_type = "drag";
ui_min = 0.0;
ui_max = 5.0;
ui_step = 0.1;
ui_label = "NTSC Custom Fringing Value";
> = 0.0;
uniform float cust_artifacting <
ui_type = "drag";
ui_min = 0.0;
ui_max = 5.0;
ui_step = 0.1;
ui_label = "NTSC Custom Artifacting Value";
> = 0.0;
uniform float chroma_scale <
ui_type = "drag";
ui_min = 0.2;
ui_max = 4.0;
ui_step = 0.1;
ui_label = "NTSC Chroma Scaling";
> = 1.0;
uniform float ntsc_artifacting_rainbow <
ui_type = "drag";
ui_min = -1.0;
ui_max = 1.0;
ui_step = 0.1;
ui_label = "NTSC Artifacting Rainbow Effect";
> = 0.0;
uniform bool linearize <
ui_type = "radio";
ui_label = "NTSC Linearize Output Gamma";
> = false;
uniform float FrameCount < source = "framecount"; >;
uniform float2 NormalizedNativePixelSize < source = "normalized_native_pixel_size"; >;
uniform float BufferWidth < source = "bufferwidth"; >;
uniform float BufferHeight < source = "bufferheight"; >;
// RGB16f is the same as float_framebuffer.
texture2D tNTSC_P0 < pooled = false; > {Width=BUFFER_WIDTH;Height=BUFFER_HEIGHT;Format=RGBA16f;};
sampler2D sNTSC_P0{Texture=tNTSC_P0;AddressU=CLAMP;AddressV=CLAMP;AddressW=CLAMP;MagFilter=LINEAR;MinFilter=LINEAR;};
#define PI 3.14159265
#define OutputSize float2(BufferWidth,BufferHeight)
struct ST_VertexOut
{
float2 pix_no : TEXCOORD1;
float phase : TEXCOORD2;
float BRIGHTNESS : TEXCOORD3;
float SATURATION : TEXCOORD4;
float FRINGING : TEXCOORD5;
float ARTIFACTING : TEXCOORD6;
float CHROMA_MOD_FREQ : TEXCOORD7;
float MERGE : TEXCOORD8;
};
// Vertex shader generating a triangle covering the entire screen
void VS_NTSC_ADAPTIVE_P0(in uint id : SV_VertexID, out float4 position : SV_Position, out float2 TexCoord : TEXCOORD, out ST_VertexOut vVARS)
{
TexCoord.x = (id == 2) ? 2.0 : 0.0;
TexCoord.y = (id == 1) ? 2.0 : 0.0;
position = float4(TexCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
float res = ntsc_scale;
float OriginalSize = 1.0/NormalizedNativePixelSize.x;
float2 SourceSize = 1.0/NormalizedNativePixelSize;
if (res < 1.0) vVARS.pix_no = TexCoord * SourceSize.xy * (res * OutputSize.xy / SourceSize.xy); else
vVARS.pix_no = TexCoord * SourceSize.xy * ( OutputSize.xy / SourceSize.xy);
vVARS.phase = (ntsc_phase < 1) ? ((OriginalSize > 300.0) ? 2.0 : 3.0) : ((ntsc_phase > 2) ? 3.0 : 2.0);
float Quality = float(quality-1);
res = max(res, 1.0);
vVARS.CHROMA_MOD_FREQ = (vVARS.phase < 2.5) ? (4.0 * PI / 15.0) : (PI / 3.0);
vVARS.ARTIFACTING = (Quality > -0.5) ? Quality * 0.5*(res+1.0) : cust_artifacting;
vVARS.FRINGING = (Quality > -0.5) ? Quality : cust_fringing;
vVARS.SATURATION = ntsc_sat;
vVARS.BRIGHTNESS = ntsc_bright;
vVARS.pix_no.x = vVARS.pix_no.x * res;
vVARS.MERGE = (Quality == 2.0 || vVARS.phase < 2.5) ? 0.0 : 1.0;
vVARS.MERGE = (Quality == -1.0) ? float(ntsc_fields == true) : vVARS.MERGE;
}
#define mix_mat float3x3(vVARS.BRIGHTNESS, vVARS.FRINGING, vVARS.FRINGING, vVARS.ARTIFACTING, 2.0 * vVARS.SATURATION, 0.0, vVARS.ARTIFACTING, 0.0, 2.0 * vVARS.SATURATION)
static const float3x3 yiq2rgb_mat = float3x3(
1.0, 0.956, 0.6210,
1.0, -0.2720, -0.6474,
1.0, -1.1060, 1.7046);
float3 yiq2rgb(float3 yiq)
{
return mul(yiq2rgb_mat, yiq);
}
static const float3x3 yiq_mat = float3x3(
0.2989, 0.5870, 0.1140,
0.5959, -0.2744, -0.3216,
0.2115, -0.5229, 0.3114
);
float3 rgb2yiq(float3 col)
{
return mul(yiq_mat, col);
}
static const float3 Y = float3( 0.299, 0.587, 0.114);
float df3(float3 a, float3 b, float3 c)
{
return dot(smoothstep(0.0, 0.56, 3.0*(b - a) * (b - c)), Y);
}
float4 PS_NTSC_ADAPTIVE_P0(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD, in ST_VertexOut vVARS) : SV_Target
{
float3 col = tex2D(ReShade::BackBuffer, vTexCoord).rgb;
float3 yiq = rgb2yiq(col);
float3 yiq2 = yiq;
float4 SourceSize = float4(1.0/NormalizedNativePixelSize, NormalizedNativePixelSize);
float mod1 = 2.0;
float mod2 = 3.0;
float2 dx = float2(1.0, 0.0)*SourceSize.zw;
float2 dy = float2(0.0, 1.0)*SourceSize.zw;
float3 C = tex2D(ReShade::BackBuffer, vTexCoord ).xyz;
float3 L = tex2D(ReShade::BackBuffer, vTexCoord -dx).xyz;
float3 R = tex2D(ReShade::BackBuffer, vTexCoord +dx).xyz;
float3 U = tex2D(ReShade::BackBuffer, vTexCoord -dy).xyz;
float3 D = tex2D(ReShade::BackBuffer, vTexCoord +dy).xyz;
float3 UL = tex2D(ReShade::BackBuffer, vTexCoord -dx -dy).xyz;
float3 UR = tex2D(ReShade::BackBuffer, vTexCoord +dx -dy).xyz;
float3 DL = tex2D(ReShade::BackBuffer, vTexCoord -dx +dy).xyz;
float3 DR = tex2D(ReShade::BackBuffer, vTexCoord +dx +dy).xyz;
float hori = step(0.01,(df3(L, C, R) * df3(UL, U, UR) * df3(DL, D, DR)));
float vert = 1.0 - step(0.01,(df3(U, C, D) * df3(UL, L, DL) * df3(UR, R, DR)));
float blend = hori * vert * ntsc_artifacting_rainbow;
if (vVARS.MERGE > 0.5)
{
float chroma_phase2 = (vVARS.phase < 2.5) ? PI * ((vVARS.pix_no.y % mod1) + ((FrameCount+1.) % 2.)) : 0.6667 * PI * ((vVARS.pix_no.y % mod2) + ((FrameCount+1.) % 2.));
float mod_phase2 = (blend + 1.0) * chroma_phase2 + vVARS.pix_no.x * vVARS.CHROMA_MOD_FREQ;
float i_mod2 = cos(mod_phase2);
float q_mod2 = sin(mod_phase2);
yiq2.yz *= float2(i_mod2, q_mod2); // Modulate.
yiq2 = mul(mix_mat, yiq2); // Cross-talk.
yiq2.yz *= float2(i_mod2, q_mod2); // Demodulate.
}
float chroma_phase = (vVARS.phase < 2.5) ? PI * ((vVARS.pix_no.y % mod1) + ((FrameCount+1.) % 2.)) : 0.6667 * PI * ((vVARS.pix_no.y % mod2) + ((FrameCount+1.) % 2.));
float mod_phase = (blend + 1.0) * chroma_phase + vVARS.pix_no.x * vVARS.CHROMA_MOD_FREQ;
float i_mod = cos(mod_phase);
float q_mod = sin(mod_phase);
yiq.yz *= float2(i_mod, q_mod); // Modulate.
yiq = mul(mix_mat, yiq); // Cross-talk.
yiq.yz *= float2(i_mod, q_mod); // Demodulate.
yiq = (vVARS.MERGE < 0.5) ? yiq : 0.5*(yiq+yiq2);
return float4(yiq, 1.0);
}
// Vertex shader generating a triangle covering the entire screen
void VS_NTSC_ADAPTIVE_P1(in uint id : SV_VertexID, out float4 position : SV_Position, out float2 TexCoord : TEXCOORD)
{
TexCoord.x = (id == 2) ? 2.0 : 0.0;
TexCoord.y = (id == 1) ? 2.0 : 0.0;
position = float4(TexCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float3 fetch_offset(sampler2D Source, float2 tex, float offset, float2 one_x)
{
/* Insert chroma scaling. Thanks to guest.r ideas. */
float3 yiq;
yiq.x = tex2D(Source, tex + float2((offset) * (one_x.x), 0.0)).x;
yiq.yz = tex2D(Source, tex + float2((offset) * (one_x.y), 0.0)).yz;
return yiq;
/* Old code
return texture(Source, vTexCoord + float2((offset) * (one_x), 0.0)).xyz;
*/
}
/* These are accurate and normalized coeffs. */
static const int TAPS_3_phase = 24;
static const float luma_filter_3_phase[25] = {
-0.0000120203033684164,
-0.0000221465589348544,
-0.0000131553320142694,
-0.0000120203033684164,
-0.0000499802614018372,
-0.000113942875690297,
-0.000122153082899506,
-5.61214E-06,
0.000170520303591422,
0.000237204986579451,
0.000169644281482376,
0.000285695210375719,
0.000984598849305758,
0.0020187339488074,
0.00200232553469184,
-0.000909904964181485,
-0.00704925890919635,
-0.0132231937269633,
-0.0126072491817548,
0.00246092210875218,
0.0358691302651096,
0.0840185734607569,
0.135566921437963,
0.175265691355518,
0.190181351796957};
/* These are accurate and normalized coeffs. */
static const float chroma_filter_3_phase[25] = {
-0.000135741056915795,
-0.000568115749081878,
-0.00130605691082327,
-0.00231369942971182,
-0.00350569685928248,
-0.00474731062446688,
-0.00585980203774502,
-0.00663114046295865,
-0.00683148404964774,
-0.00623234997205773,
-0.00462792764511295,
-0.00185665431957684,
0.00217899013894782,
0.00749647783836479,
0.0140227874371299,
0.021590863169257,
0.0299437436530477,
0.0387464461271303,
0.0476049759842373,
0.0560911497485196,
0.0637713405314321,
0.0702368383153846,
0.0751333078160781,
0.0781868487834974,
0.0792244191487085};
/* These are accurate and normalized coeffs. Though they don't produce ideal smooth vertical lines transparency. */
static const int TAPS_2_phase = 15;
static const float luma_filter_2_phase[16] = {
0.00134372867555492,
0.00294231678339247,
0.00399617683765551,
0.00303632635732925,
-0.00110556727614119,
-0.00839970341605087,
-0.0169515379999301,
-0.0229874881474188,
-0.0217113019865528,
-0.00889151239892142,
0.0173269874254282,
0.0550969075027442,
0.098655909675851,
0.139487291941771,
0.168591277052964,
0.17914037794465};
/* These are accurate and normalized coeffs. */
static const float chroma_filter_2_phase[16] = {
0.00406084767413046,
0.00578573638571078,
0.00804447474387669,
0.0109152541019797,
0.0144533032717188,
0.0186765858322351,
0.0235518468184291,
0.0289834149989225,
0.034807373222651,
0.0407934139180355,
0.0466558344725586,
0.0520737649339226,
0.0567190701585739,
0.0602887575746322,
0.0625375226221969,
0.0633055985408521};
float4 PS_NTSC_ADAPTIVE_P1(float4 vpos: SV_Position, float2 vTexCoord : TEXCOORD) : SV_Target
{
float4 SourceSize = float4(BufferWidth, 1.0/NormalizedNativePixelSize.y, 1.0/BufferWidth, NormalizedNativePixelSize.y);
float res = ntsc_scale;
float OriginalSize = 1.0/NormalizedNativePixelSize.x;
float3 signal = float3(0.0, 0.0, 0.0);
float phase = (ntsc_phase < 1) ? ((OriginalSize > 300.0) ? 2.0 : 3.0) : ((ntsc_phase > 1) ? 3.0 : 2.0);
float chroma_scale = phase > 2.5 ? min(chroma_scale, 2.2) : chroma_scale/2.0;
float2 one_x = (SourceSize.z / res) * float2(1.0, 1.0 / chroma_scale);
float2 tex = vTexCoord;
if(phase < 2.5)
{
float3 sums = fetch_offset(sNTSC_P0, tex, 0.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 0.0, one_x);
signal += sums * float3(luma_filter_2_phase[0], chroma_filter_2_phase[0], chroma_filter_2_phase[0]);
sums = fetch_offset(sNTSC_P0, tex, 1.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 1.0, one_x);
signal += sums * float3(luma_filter_2_phase[1], chroma_filter_2_phase[1], chroma_filter_2_phase[1]);
sums = fetch_offset(sNTSC_P0, tex, 2.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 2.0, one_x);
signal += sums * float3(luma_filter_2_phase[2], chroma_filter_2_phase[2], chroma_filter_2_phase[2]);
sums = fetch_offset(sNTSC_P0, tex, 3.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 3.0, one_x);
signal += sums * float3(luma_filter_2_phase[3], chroma_filter_2_phase[3], chroma_filter_2_phase[3]);
sums = fetch_offset(sNTSC_P0, tex, 4.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 4.0, one_x);
signal += sums * float3(luma_filter_2_phase[4], chroma_filter_2_phase[4], chroma_filter_2_phase[4]);
sums = fetch_offset(sNTSC_P0, tex, 5.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 5.0, one_x);
signal += sums * float3(luma_filter_2_phase[5], chroma_filter_2_phase[5], chroma_filter_2_phase[5]);
sums = fetch_offset(sNTSC_P0, tex, 6.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 6.0, one_x);
signal += sums * float3(luma_filter_2_phase[6], chroma_filter_2_phase[6], chroma_filter_2_phase[6]);
sums = fetch_offset(sNTSC_P0, tex, 7.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 7.0, one_x);
signal += sums * float3(luma_filter_2_phase[7], chroma_filter_2_phase[7], chroma_filter_2_phase[7]);
sums = fetch_offset(sNTSC_P0, tex, 8.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 8.0, one_x);
signal += sums * float3(luma_filter_2_phase[8], chroma_filter_2_phase[8], chroma_filter_2_phase[8]);
sums = fetch_offset(sNTSC_P0, tex, 9.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 9.0, one_x);
signal += sums * float3(luma_filter_2_phase[9], chroma_filter_2_phase[9], chroma_filter_2_phase[9]);
sums = fetch_offset(sNTSC_P0, tex, 10.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 10.0, one_x);
signal += sums * float3(luma_filter_2_phase[10], chroma_filter_2_phase[10], chroma_filter_2_phase[10]);
sums = fetch_offset(sNTSC_P0, tex, 11.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 11.0, one_x);
signal += sums * float3(luma_filter_2_phase[11], chroma_filter_2_phase[11], chroma_filter_2_phase[11]);
sums = fetch_offset(sNTSC_P0, tex, 12.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 12.0, one_x);
signal += sums * float3(luma_filter_2_phase[12], chroma_filter_2_phase[12], chroma_filter_2_phase[12]);
sums = fetch_offset(sNTSC_P0, tex, 13.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 13.0, one_x);
signal += sums * float3(luma_filter_2_phase[13], chroma_filter_2_phase[13], chroma_filter_2_phase[13]);
sums = fetch_offset(sNTSC_P0, tex, 14.0 - 15.0, one_x) + fetch_offset(sNTSC_P0, tex, 15.0 - 14.0, one_x);
signal += sums * float3(luma_filter_2_phase[14], chroma_filter_2_phase[14], chroma_filter_2_phase[14]);
signal += tex2D(sNTSC_P0, vTexCoord).xyz *
float3(luma_filter_2_phase[TAPS_2_phase], chroma_filter_2_phase[TAPS_2_phase], chroma_filter_2_phase[TAPS_2_phase]);
}
else if(phase > 2.5)
{
for (int i = 0; i < TAPS_3_phase; i++)
{
float offset = float(i);
float3 sums = fetch_offset(sNTSC_P0, tex, offset - float(TAPS_3_phase), one_x) +
fetch_offset(sNTSC_P0, tex, float(TAPS_3_phase) - offset, one_x);
signal += sums * float3(luma_filter_3_phase[i], chroma_filter_3_phase[i], chroma_filter_3_phase[i]);
}
signal += tex2D(sNTSC_P0, vTexCoord).xyz *
float3(luma_filter_3_phase[TAPS_3_phase], chroma_filter_3_phase[TAPS_3_phase], chroma_filter_3_phase[TAPS_3_phase]);
}
float3 rgb = yiq2rgb(signal);
if(linearize == false) return float4(rgb, 1.0);
else return pow(float4(rgb, 1.0), float4(2.2, 2.2, 2.2, 2.2));
}
technique NTSC_ADAPTIVE
{
pass
{
VertexShader = VS_NTSC_ADAPTIVE_P0;
PixelShader = PS_NTSC_ADAPTIVE_P0;
RenderTarget = tNTSC_P0;
}
pass
{
VertexShader = PostProcessVS;
PixelShader = PS_NTSC_ADAPTIVE_P1;
}
}