From c1976f4b53f00dbaf2acacd13264fa91fc61d367 Mon Sep 17 00:00:00 2001 From: Hyllian Date: Wed, 10 Jul 2024 02:12:47 -0300 Subject: [PATCH] Add ntsc-adaptive-lite.fx shader (#3248) - A shader that apply NTSC effects. Can be used in a stack with crt shaders. --- .../Shaders/ntsc/ntsc-adaptive-lite.fx | 437 ++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 data/resources/shaders/reshade/Shaders/ntsc/ntsc-adaptive-lite.fx diff --git a/data/resources/shaders/reshade/Shaders/ntsc/ntsc-adaptive-lite.fx b/data/resources/shaders/reshade/Shaders/ntsc/ntsc-adaptive-lite.fx new file mode 100644 index 000000000..08738c5ca --- /dev/null +++ b/data/resources/shaders/reshade/Shaders/ntsc/ntsc-adaptive-lite.fx @@ -0,0 +1,437 @@ +#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; + } +}