mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-12-12 15:35:38 +00:00
c1976f4b53
- A shader that apply NTSC effects. Can be used in a stack with crt shaders.
438 lines
15 KiB
HLSL
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;
|
|
}
|
|
}
|