//////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //// //// VERTEX SHADER : STOLEN FROM NEIGHBOURING SHADERS //// //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// #ifdef VERTEX uniform mat4 MVPMatrix; in vec4 VertexCoord; in vec2 TexCoord; out vec2 vTexcoord; void main() { gl_Position = MVPMatrix * VertexCoord; vTexcoord = TexCoord; } #endif //VERTEX #ifdef FRAGMENT uniform struct { vec2 video_size; vec2 texture_size; vec2 output_size; } IN; uniform float Time; uniform sampler2D s_p; in vec2 vTexcoord; out vec4 FragColor; float saturate(float x) { return max(0, min(1, x)); } vec2 saturate(vec2 x) { return max(vec2(0.0,0.0), min(vec2(1.0,1.0), x)); } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //// EFFECT CONSTANTS : TWEAK THEM! //// // Size of the border effect const vec2 OverscanMaskHardness = vec2(12.0f ,12.0f ); // Attenuation of the border effect const float OverscanMaskPower = 4.0f; // Intensity of the border effect const float OverscanIntensity = 0.96f; // Intensity of the TV Corners (round-ness) deformation const float TVDeformInstensity = 0.02f; // How much R, G and B are offset : default is -0.333 pixels in fake-pixel-space const float ColorFringeIntensity = -0.666; // How much luminosity is output by a fake-pixel const float FakePixelMaskGain = 0.75f; // How much luminosity is output between fake-pixels (adds to the fake-pixel value) const float FakePixelMaskOffset = 0.55f; // How sharp will appear the pixels (Horizontal Sharpness, Vertical Sharpness A.K.A Scanlines) const vec2 FakePixelMaskPower = vec2(0.150f ,2.0f ); // Scanline Off Sync (Slides one line out of two) const float ScanlineOffSync = 0.25; // Base Brightness const float BaseBrightness = 0.55f; // How much the Fake-Pixel effect is Active (0.0 = normal image, 1.0 = full FakePixel Effect) const float FakePixelEffectBlend = 0.95f; // Ghost Sampling : enable define to activate #define GHOST_SAMPLING; const float GhostLatencyIntensity = 0.03f; // Number of samples (higer is slower) const int GhostNumSamples = 32; // Latency of the RGB Signal (per-signal, in screen width percentage) const vec3 SignalLatencyRGB = vec3(0.184f,0.08f,0.0624f); // Attenuation of the ghosting latency const float SignalLatencyAttenuation = 1.0f; // Bloom : enable define to activate #define BLOOM; const float BloomIntensity = 0.75f; const float BloomExponent = 1.00f; const float BloomWeights[25] = float[] ( 0.003765, 0.015019, 0.023792, 0.015019, 0.003765, 0.015019, 0.059912, 0.094907, 0.059912, 0.015019, 0.023792, 0.094907, 0.150342, 0.094907, 0.023792, 0.015019, 0.059912, 0.094907, 0.059912, 0.015019, 0.003765, 0.015019, 0.023792, 0.015019, 0.003765 ); const float BloomPositions[5] = float[] ( -2, -1, 0 , 1 , 2 ); //// //// //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// float expow(float value, float exponent) { return mix(1.0f,pow(value,max(exponent,1.0f)),saturate(exponent)); } //the code that calls expow() carefully builds vec2 for some reason and calls this only to have it implicitly thrown away (which is a warning) //so this was added to get rid of the warning float expow(vec2 value, vec2 exponent) { return mix(1.0f,pow(value.x,max(exponent.x,1.0f)),saturate(exponent.x)); } float expow(vec2 value, float exponent) { return mix(1.0f,pow(value.x,max(exponent,1.0f)),saturate(exponent)); } // MultiSampling for ghosting effect vec3 GhostSample(vec2 t, float latency) { vec3 Out = texture(s_p,t).rgb; float Weight = 1.0f; vec2 Direction = vec2(-latency,0.0f); for(int i=1; i < GhostNumSamples; i++) { float curweight = pow(1.0f-(float(i)/GhostNumSamples),1.0f/SignalLatencyAttenuation); Out += GhostLatencyIntensity * curweight * texture(s_p,saturate(t+(1.0f-curweight)*Direction)).xyz; Weight += GhostLatencyIntensity * curweight; } return Out/Weight; } // MultiSampling for ghosting effect vec3 Bloom(vec2 t, vec2 r) { vec3 Out = vec3(0,0,0); for(int j = 0; j < 5; j++) for(int i = 0; i < 5; i++) { vec2 offset = vec2(BloomPositions[i],BloomPositions[j]) / r; Out += texture(s_p, t + offset).rgb * BloomWeights[i*5+j]; } return pow(Out, vec3(BloomExponent,BloomExponent,BloomExponent)) * BloomIntensity; } // Compositing of the TV Emulation vec3 TVEffect(vec2 in_Position, vec2 FakeResolution, float Time) { // TV Deformation vec2 ScreenPos = in_Position + dot(in_Position-0.5f,in_Position-0.5f)*(in_Position-0.5f)* TVDeformInstensity; // Apply Off-Sync ScreenPos += (ScanlineOffSync/FakeResolution.x) * vec2(sin((Time*30*3.1415926)+(ScreenPos.y*3.1415926*FakeResolution.y)),0); // Sampling 3 Images biased to simulate TV RGB Offset #ifdef GHOST_SAMPLING vec3 latencyweight = vec3(0.0f,0.0f,0.0f); for(int i=1; i < GhostNumSamples; i++) { latencyweight += texture(s_p, ScreenPos + vec2(1.0f/FakeResolution.x,0.0f)).xyz; } vec3 LatencyRGB = SignalLatencyRGB * (1.0-(latencyweight/GhostNumSamples)); vec3 SMP_Red = GhostSample((ScreenPos),LatencyRGB.x).xyz; vec3 SMP_Green = GhostSample((ScreenPos) + ((vec2(ColorFringeIntensity,0.0f))/FakeResolution),LatencyRGB.y).xyz; vec3 SMP_Blue = GhostSample((ScreenPos) + ((vec2(ColorFringeIntensity*2.0f,0.0f))/FakeResolution),LatencyRGB.z).xyz; #else vec3 SMP_Red = texture(s_p, (ScreenPos)).xyz; vec3 SMP_Green = texture(s_p, (ScreenPos) + ((vec2(ColorFringeIntensity,0.0f))/FakeResolution)).xyz; vec3 SMP_Blue = texture(s_p, (ScreenPos) + ((vec2(ColorFringeIntensity*2.0f,0.0f))/FakeResolution)).xyz; #endif #ifdef BLOOM vec3 bloom = Bloom(ScreenPos, FakeResolution); SMP_Red += bloom.r; SMP_Green += bloom.g; SMP_Blue += bloom.b; #endif // Apply base Brightness SMP_Red *= BaseBrightness; SMP_Green *= BaseBrightness; SMP_Blue *= BaseBrightness; // Overscan Darkening Mask float ScreenMaskPower = 1.0f/OverscanMaskPower; vec2 ScreenMask = pow(saturate(ScreenPos*(1.0f-ScreenPos)*OverscanMaskHardness),vec2(ScreenMaskPower,ScreenMaskPower)); float mask = mix(1.0, ScreenMask.x * ScreenMask.y, OverscanIntensity); // CRT Cell Masks (HorizontalRGB+Scanline) float PixelMaskR = expow(saturate(4*fract(ScreenPos.x*FakeResolution.x)*(1.0f-fract(ScreenPos.x*FakeResolution.x))),FakePixelMaskPower.x); float PixelMaskG = expow(saturate(4*fract(ScreenPos.x*FakeResolution.x+vec2(ColorFringeIntensity,0.0f))*(1.0f-fract(ScreenPos.x*FakeResolution.x+vec2(ColorFringeIntensity,0.0f)))),FakePixelMaskPower.x); float PixelMaskB = expow(saturate(4*fract(ScreenPos.x*FakeResolution.x+vec2(ColorFringeIntensity*2.0f,0.0f))*(1.0f-fract(ScreenPos.x*FakeResolution.x+vec2(ColorFringeIntensity*2.0f,0.0f)))),FakePixelMaskPower.x); float PixelMaskScanline = pow(saturate(4*fract(ScreenPos.y*FakeResolution.y)*(1.0f-fract(ScreenPos.y*FakeResolution.y))),FakePixelMaskPower.y); vec3 PixelRGB = vec3 ( ((PixelMaskR*PixelMaskScanline * FakePixelMaskGain)+FakePixelMaskOffset) * SMP_Red.x , ((PixelMaskG*PixelMaskScanline * FakePixelMaskGain)+FakePixelMaskOffset) * SMP_Green.y , ((PixelMaskB*PixelMaskScanline * FakePixelMaskGain)+FakePixelMaskOffset) * SMP_Blue.z ); // Non-Pixelated Image vec3 ImageRGB = texture(s_p, ScreenPos).xyz; return mix(ImageRGB, PixelRGB, FakePixelEffectBlend) * mask; } void main() { vec4 color = vec4(1.0f,1.0f,1.0f,1.0f); color.xyz = TVEffect(vTexcoord, IN.texture_size, Time); FragColor = color; } #endif