Back to shaders
Shader test bench
Comet Tails.fs
runnable fragment
Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.
Code
uniform sampler2D inputImage;
uniform float absorptionRate;
uniform float dischargeRate;
precision mediump float;
/*{
"CATEGORIES": [
"Feedback",
"Glitch"
],
"CREDIT": "VIDVOX",
"DESCRIPTION": "This does a feedback motion blur based on the brightness of pixels to create an analog recording comet trails effect",
"INPUTS": [
{
"NAME": "inputImage",
"TYPE": "image"
},
{
"DEFAULT": 0.5,
"MAX": 1,
"MIN": 0,
"NAME": "absorptionRate",
"TYPE": "float"
},
{
"DEFAULT": 0.02,
"MAX": 1,
"MIN": 0,
"NAME": "dischargeRate",
"TYPE": "float"
}
],
"ISFVSN": "2",
"PASSES": [
{
"FLOAT": true,
"PERSISTENT": true,
"TARGET": "feedbackBuffer"
}
]
}
*/
// Comet Tails is a specific type of Image Lag
// From https://bavc.github.io/avaa/artifacts/image_lag.html
/*
Image lag occurs in video recorded or displayed using certain types of pick-up devices and cameras, including the Vidicon picture tube, among others.
This type of camera tube captures light radiating from a scene through a lens and projects it onto a photoconductive target, creating a charge-density pattern which is scanned using low-velocity electrons.
The resulting image can be amplified and recorded to tape or output to a video monitor.
The electrical charge remains present on the target until it is re-scanned or the charge dissipates.
*/
const vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
const vec4 kRGBToI = vec4 (0.596, -0.275, -0.321, 0.0);
const vec4 kRGBToQ = vec4 (0.212, -0.523, 0.311, 0.0);
const vec4 kYIQToR = vec4 (1.0, 0.956, 0.621, 0.0);
const vec4 kYIQToG = vec4 (1.0, -0.272, -0.647, 0.0);
const vec4 kYIQToB = vec4 (1.0, -1.107, 1.704, 0.0);
vec3 rgb2yiq(vec3 c) {
float YPrime = dot (c, kRGBToYPrime.rgb);
float I = dot (c, kRGBToI.rgb);
float Q = dot (c, kRGBToQ.rgb);
return vec3(YPrime,I,Q);
}
vec3 yiq2rgb(vec3 c) {
vec3 yIQ = vec3 (c.r, c.g, c.b);
return vec3(dot (yIQ, kYIQToR.rgb),dot (yIQ, kYIQToG.rgb),dot (yIQ, kYIQToB.rgb));
}
void main()
{
vec4 freshPixel = texture2D(inputImage, (gl_FragCoord.xy) / u_resolution.xy);
vec4 stalePixel = texture2D(feedbackBuffer, (gl_FragCoord.xy) / u_resolution.xy);
// the input gamma range is 0.0-1.0 (normalized). the actual gamma range i want to use is 0.0 - 5.0.
// however, actual gamma 0.0-1.0 is just as interesting as actual gamma 1.0-5.0, so we scale the normalized input to match...
float realGamma = (absorptionRate<=0.5) ? (absorptionRate * 2.0) : (((absorptionRate-0.5) * 2.0 * 4.0) + 1.0);
vec4 tmpColorA = stalePixel;
vec4 tmpColorB;
tmpColorB.rgb = pow(tmpColorA.rgb, vec3(1.0/realGamma));
tmpColorB.a = tmpColorA.a;
float feedbackLevel = rgb2yiq(tmpColorB.rgb).r;
// if the new pixel is brighter, it immediately fills up the buffer to its level
if (rgb2yiq(freshPixel.rgb).r > feedbackLevel) {
feedbackLevel = 0.0;
}
// otherwise, discharge
else {
feedbackLevel *= (1.0 - dischargeRate);
}
gl_FragColor = mix(freshPixel,stalePixel,feedbackLevel);
}