Created
July 5, 2015 18:03
-
-
Save andymason/c4c16e1c3a21d6a33744 to your computer and use it in GitHub Desktop.
NTSC shader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a port of the NTSC encode/decode shader pair in MAME and MESS, modified to use only | |
// one pass rather than an encode pass and a decode pass. It accurately emulates the sort of | |
// signal decimation one would see when viewing a composite signal, though it could benefit from a | |
// pre-pass to re-size the input content to more accurately reflect the actual size that would | |
// be incoming from a composite signal source. | |
// | |
// To encode the composite signal, I convert the RGB value to YIQ, then subsequently evaluate | |
// the standard NTSC composite equation. Four composite samples per RGB pixel are generated from | |
// the incoming linearly-interpolated texels. | |
// | |
// The decode pass implements a Fixed Impulse Response (FIR) filter designed by MAME/MESS contributor | |
// "austere" in matlab (if memory serves correctly) to mimic the behavior of a standard television set | |
// as closely as possible. The filter window is 83 composite samples wide, and there is an additional | |
// notch filter pass on the luminance (Y) values in order to strip the color signal from the luminance | |
// signal prior to processing. | |
// | |
// - UltraMoogleMan [8/2/2013] | |
// Useful Constants | |
const vec4 Zero = vec4(0.0); | |
const vec4 Half = vec4(0.5); | |
const vec4 One = vec4(1.0); | |
const vec4 Two = vec4(2.0); | |
const float Pi = 3.1415926535; | |
const float Pi2 = 6.283185307; | |
// NTSC Constants | |
const vec4 A = vec4(0.5); | |
const vec4 B = vec4(0.5); | |
const float P = 1.0; | |
const float CCFrequency = 3.59754545; | |
const float YFrequency = 6.0; | |
const float IFrequency = 1.2; | |
const float QFrequency = 0.6; | |
const float NotchHalfWidth = 2.0; | |
const float ScanTime = 52.6; | |
const float MaxC = 2.1183; | |
const vec4 MinC = vec4(-1.1183); | |
const vec4 CRange = vec4(3.2366); | |
// Noise | |
const float noise = 0.45; | |
//Credit: http://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl | |
float rand(vec2 co){ | |
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); | |
} | |
vec4 CompositeSample(vec2 UV) { | |
vec2 InverseRes = 1.0 / iResolution.xy; | |
vec2 InverseP = vec2(P, 0.0) * InverseRes; | |
// UVs for four linearly-interpolated samples spaced 0.25 texels apart | |
vec2 C0 = UV; | |
vec2 C1 = UV + InverseP * 0.25; | |
vec2 C2 = UV + InverseP * 0.50; | |
vec2 C3 = UV + InverseP * 0.75; | |
vec4 Cx = vec4(C0.x, C1.x, C2.x, C3.x); | |
vec4 Cy = vec4(C0.y, C1.y, C2.y, C3.y); | |
vec3 Texel0 = texture2D(iChannel0, C0).rgb; | |
vec3 Texel1 = texture2D(iChannel0, C1).rgb; | |
vec3 Texel2 = texture2D(iChannel0, C2).rgb; | |
vec3 Texel3 = texture2D(iChannel0, C3).rgb; | |
// Noise | |
Texel0 *= vec3( 1.0 ) - noise*vec3( rand( UV+0.001*iGlobalTime), rand( UV+0.0001*iGlobalTime + 0.3 ), rand( UV+0.0001*iGlobalTime+ 0.5 ) ); | |
// Calculated the expected time of the sample. | |
vec4 T = A * Cy * vec4(iResolution.x) * Two + B + Cx; | |
const vec3 YTransform = vec3(0.299, 0.587, 0.114); | |
const vec3 ITransform = vec3(0.595716, -0.274453, -0.321263); | |
const vec3 QTransform = vec3(0.211456, -0.522591, 0.311135); | |
float Y0 = dot(Texel0, YTransform); | |
float Y1 = dot(Texel1, YTransform); | |
float Y2 = dot(Texel2, YTransform); | |
float Y3 = dot(Texel3, YTransform); | |
vec4 Y = vec4(Y0, Y1, Y2, Y3); | |
float I0 = dot(Texel0, ITransform); | |
float I1 = dot(Texel1, ITransform); | |
float I2 = dot(Texel2, ITransform); | |
float I3 = dot(Texel3, ITransform); | |
vec4 I = vec4(I0, I1, I2, I3); | |
float Q0 = dot(Texel0, QTransform); | |
float Q1 = dot(Texel1, QTransform); | |
float Q2 = dot(Texel2, QTransform); | |
float Q3 = dot(Texel3, QTransform); | |
vec4 Q = vec4(Q0, Q1, Q2, Q3); | |
vec4 W = vec4(Pi2 * CCFrequency * ScanTime); | |
vec4 Encoded = Y + I * cos(T * W) + Q * sin(T * W); | |
return (Encoded - MinC) / CRange; | |
} | |
vec3 NTSCCodec(vec2 UV) | |
{ | |
vec2 InverseRes = 1.0 / iResolution.xy; | |
vec4 YAccum = Zero; | |
vec4 IAccum = Zero; | |
vec4 QAccum = Zero; | |
float QuadXSize = iResolution.x * 4.0; | |
float TimePerSample = ScanTime / QuadXSize; | |
// Frequency cutoffs for the individual portions of the signal that we extract. | |
// Y1 and Y2 are the positive and negative frequency limits of the notch filter on Y. | |
// | |
float Fc_y1 = (CCFrequency - NotchHalfWidth) * TimePerSample; | |
float Fc_y2 = (CCFrequency + NotchHalfWidth) * TimePerSample; | |
float Fc_y3 = YFrequency * TimePerSample; | |
float Fc_i = IFrequency * TimePerSample; | |
float Fc_q = QFrequency * TimePerSample; | |
float Pi2Length = Pi2 / 82.0; | |
vec4 NotchOffset = vec4(0.0, 1.0, 2.0, 3.0); | |
vec4 W = vec4(Pi2 * CCFrequency * ScanTime); | |
for(float n = -41.0; n < 42.0; n += 4.0) | |
{ | |
vec4 n4 = n + NotchOffset; | |
vec4 CoordX = UV.x + InverseRes.x * n4 * 0.25; | |
vec4 CoordY = vec4(UV.y); | |
vec2 TexCoord = vec2(CoordX.r, CoordY.r); | |
vec4 C = CompositeSample(TexCoord) * CRange + MinC; | |
vec4 WT = W * (CoordX + A * CoordY * Two * iResolution.x + B); | |
vec4 SincYIn1 = Pi2 * Fc_y1 * n4; | |
vec4 SincYIn2 = Pi2 * Fc_y2 * n4; | |
vec4 SincYIn3 = Pi2 * Fc_y3 * n4; | |
bvec4 notEqual = notEqual(SincYIn1, Zero); | |
vec4 SincY1 = sin(SincYIn1) / SincYIn1; | |
vec4 SincY2 = sin(SincYIn2) / SincYIn2; | |
vec4 SincY3 = sin(SincYIn3) / SincYIn3; | |
if(SincYIn1.x == 0.0) SincY1.x = 1.0; | |
if(SincYIn1.y == 0.0) SincY1.y = 1.0; | |
if(SincYIn1.z == 0.0) SincY1.z = 1.0; | |
if(SincYIn1.w == 0.0) SincY1.w = 1.0; | |
if(SincYIn2.x == 0.0) SincY2.x = 1.0; | |
if(SincYIn2.y == 0.0) SincY2.y = 1.0; | |
if(SincYIn2.z == 0.0) SincY2.z = 1.0; | |
if(SincYIn2.w == 0.0) SincY2.w = 1.0; | |
if(SincYIn3.x == 0.0) SincY3.x = 1.0; | |
if(SincYIn3.y == 0.0) SincY3.y = 1.0; | |
if(SincYIn3.z == 0.0) SincY3.z = 1.0; | |
if(SincYIn3.w == 0.0) SincY3.w = 1.0; | |
//vec4 IdealY = (2.0 * Fc_y1 * SincY1 - 2.0 * Fc_y2 * SincY2) + 2.0 * Fc_y3 * SincY3; | |
vec4 IdealY = (2.0 * Fc_y1 * SincY1 - 2.0 * Fc_y2 * SincY2) + 2.0 * Fc_y3 * SincY3; | |
vec4 FilterY = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealY; | |
vec4 SincIIn = Pi2 * Fc_i * n4; | |
vec4 SincI = sin(SincIIn) / SincIIn; | |
if (SincIIn.x == 0.0) SincI.x = 1.0; | |
if (SincIIn.y == 0.0) SincI.y = 1.0; | |
if (SincIIn.z == 0.0) SincI.z = 1.0; | |
if (SincIIn.w == 0.0) SincI.w = 1.0; | |
vec4 IdealI = 2.0 * Fc_i * SincI; | |
vec4 FilterI = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealI; | |
vec4 SincQIn = Pi2 * Fc_q * n4; | |
vec4 SincQ = sin(SincQIn) / SincQIn; | |
if (SincQIn.x == 0.0) SincQ.x = 1.0; | |
if (SincQIn.y == 0.0) SincQ.y = 1.0; | |
if (SincQIn.z == 0.0) SincQ.z = 1.0; | |
if (SincQIn.w == 0.0) SincQ.w = 1.0; | |
vec4 IdealQ = 2.0 * Fc_q * SincQ; | |
vec4 FilterQ = (0.54 + 0.46 * cos(Pi2Length * n4)) * IdealQ; | |
YAccum = YAccum + C * FilterY; | |
IAccum = IAccum + C * cos(WT) * FilterI; | |
QAccum = QAccum + C * sin(WT) * FilterQ; | |
} | |
float Y = YAccum.r + YAccum.g + YAccum.b + YAccum.a; | |
float I = (IAccum.r + IAccum.g + IAccum.b + IAccum.a) * 2.0; | |
float Q = (QAccum.r + QAccum.g + QAccum.b + QAccum.a) * 2.0; | |
vec3 YIQ = vec3(Y, I, Q); | |
vec3 OutRGB = vec3(dot(YIQ, vec3(1.0, 0.956, 0.621)), dot(YIQ, vec3(1.0, -0.272, -0.647)), dot(YIQ, vec3(1.0, -1.106, 1.703))); | |
return OutRGB; | |
} | |
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { | |
vec2 InverseRes = 1.0 / iResolution.xy; | |
vec2 uv = fragCoord.xy * InverseRes; | |
vec3 col = NTSCCodec(uv); | |
// vignette | |
float vig = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y)); | |
vig = pow(vig,0.1); | |
col *= vec3(vig); | |
float scans = clamp( 0.355+0.05*sin(1.5+uv.y*iResolution.y*1.6), 0.0, 1.0); | |
float s = pow(scans,0.3); | |
col = col * vec3(s) ; | |
// Brightness | |
col = pow(col, vec3(0.75)); | |
fragColor = vec4(col, 1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment