effects/seriously.tvglitch.js (258 lines of code) (raw):
/* global define, require */
(function (root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['seriously'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
factory(require('seriously'));
} else {
if (!root.Seriously) {
root.Seriously = { plugin: function (name, opt) { this[name] = opt; } };
}
factory(root.Seriously);
}
}(this, function (Seriously) {
'use strict';
//particle parameters
var minVelocity = 0.2,
maxVelocity = 0.8,
minSize = 0.02,
maxSize = 0.3,
particleCount = 20;
Seriously.plugin('tvglitch', function () {
var lastHeight,
lastTime,
particleBuffer,
particleShader,
particleFrameBuffer,
gl;
return {
initialize: function (parent) {
var i,
sizeRange,
velocityRange,
particleVertex,
particleFragment,
particles;
gl = this.gl;
lastHeight = this.height;
//initialize particles
particles = [];
sizeRange = maxSize - minSize;
velocityRange = maxVelocity - minVelocity;
for (i = 0; i < particleCount; i++) {
particles.push(Math.random() * 2 - 1); //position
particles.push(Math.random() * velocityRange + minVelocity); //velocity
particles.push(Math.random() * sizeRange + minSize); //size
particles.push(Math.random() * 0.2); //intensity
}
particleBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, particleBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(particles), gl.STATIC_DRAW);
particleBuffer.itemSize = 4;
particleBuffer.numItems = particleCount;
particleVertex = [
'precision mediump float;',
'attribute vec4 particle;',
'uniform float time;',
'uniform float height;',
'varying float intensity;',
'void main(void) {',
' float y = particle.x + time * particle.y;',
' y = fract((y + 1.0) / 2.0) * 4.0 - 2.0;',
' intensity = particle.w;',
' gl_Position = vec4(0.0, -y , 1.0, 2.0);',
//' gl_Position = vec4(0.0, 1.0 , 1.0, 1.0);',
' gl_PointSize = height * particle.z;',
'}'
].join('\n');
particleFragment = [
'precision mediump float;',
'varying float intensity;',
'void main(void) {',
' gl_FragColor = vec4(1.0);',
' gl_FragColor.a = 2.0 * intensity * (1.0 - abs(gl_PointCoord.y - 0.5));',
'}'
].join('\n');
particleShader = new Seriously.util.ShaderProgram(gl, particleVertex, particleFragment);
particleFrameBuffer = new Seriously.util.FrameBuffer(gl, 1, Math.max(1, this.height / 2));
parent();
},
commonShader: true,
shader: function (inputs, shaderSource) {
shaderSource.fragment = [
'precision mediump float;',
'#define HardLight(top, bottom) (1.0 - 2.0 * (1.0 - top) * (1.0 - bottom))',
'varying vec2 vTexCoord;',
'uniform sampler2D source;',
'uniform sampler2D particles;',
'uniform float time;',
'uniform float scanlines;',
'uniform float lineSync;',
'uniform float lineHeight;', //for scanlines and distortion
'uniform float distortion;',
'uniform float vsync;',
'uniform float bars;',
'uniform float frameSharpness;',
'uniform float frameShape;',
'uniform float frameLimit;',
'uniform vec4 frameColor;',
//todo: need much better pseudo-random number generator
Seriously.util.shader.noiseHelpers +
Seriously.util.shader.snoise2d +
'void main(void) {',
' vec2 texCoord = vTexCoord;',
//distortion
' float drandom = snoise(vec2(time * 50.0, texCoord.y /lineHeight));',
' float distortAmount = distortion * (drandom - 0.25) * 0.5;',
//line sync
' vec4 particleOffset = texture2D(particles, vec2(0.0, texCoord.y));',
' distortAmount -= lineSync * (2.0 * particleOffset.a - 0.5);',
' texCoord.x -= distortAmount;',
' texCoord.x = mod(texCoord.x, 1.0);',
//vertical sync
' float roll;',
' if (vsync != 0.0) {',
' roll = fract(time / vsync);',
' texCoord.y = mod(texCoord.y - roll, 1.0);',
' }',
' vec4 pixel = texture2D(source, texCoord);',
//horizontal bars
' float barsAmount = particleOffset.r;',
' if (barsAmount > 0.0) {',
' pixel = vec4(pixel.r + bars * barsAmount,' +
'pixel.g + bars * barsAmount,' +
'pixel.b + bars * barsAmount,' +
'pixel.a);',
' }',
' if (mod(texCoord.y / lineHeight, 2.0) < 1.0 ) {',
' pixel.rgb *= (1.0 - scanlines);',
' }',
' float f = (1.0 - gl_FragCoord.x * gl_FragCoord.x) * (1.0 - gl_FragCoord.y * gl_FragCoord.y);',
' float frame = clamp( frameSharpness * (pow(f, frameShape) - frameLimit), 0.0, 1.0);',
' gl_FragColor = mix(frameColor, pixel, frame);',
'}'
].join('\n');
return shaderSource;
},
resize: function () {
if (particleFrameBuffer) {
particleFrameBuffer.resize(1, Math.max(1, this.height / 2));
}
},
draw: function (shader, model, uniforms, frameBuffer, parent) {
var doParticles = (lastTime !== this.inputs.time),
vsyncPeriod;
if (lastHeight !== this.height) {
lastHeight = this.height;
doParticles = true;
}
//todo: make this configurable?
uniforms.lineHeight = 1 / this.height;
if (this.inputs.verticalSync) {
vsyncPeriod = 0.2 / this.inputs.verticalSync;
uniforms.vsync = vsyncPeriod;
} else {
vsyncPeriod = 1;
uniforms.vsync = 0;
}
uniforms.time = (this.inputs.time % (1000 * vsyncPeriod));
uniforms.distortion = Math.random() * this.inputs.distortion;
//render particle canvas and attach uniform
//todo: this is a good spot for parallel processing. ParallelArray maybe?
if (doParticles && (this.inputs.lineSync || this.inputs.bars)) {
particleShader.use();
gl.viewport(0, 0, 1, this.height / 2);
gl.bindFramebuffer(gl.FRAMEBUFFER, particleFrameBuffer.frameBuffer);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enableVertexAttribArray(particleShader.location.particle);
gl.bindBuffer(gl.ARRAY_BUFFER, particleBuffer);
gl.vertexAttribPointer(particleShader.location.particle, particleBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
particleShader.time.set(uniforms.time);
particleShader.height.set(this.height);
gl.drawArrays(gl.POINTS, 0, particleCount);
lastTime = this.inputs.time;
}
uniforms.particles = particleFrameBuffer.texture;
parent(shader, model, uniforms, frameBuffer);
},
destroy: function () {
particleBuffer = null;
if (particleFrameBuffer) {
particleFrameBuffer.destroy();
particleFrameBuffer = null;
}
}
};
},
{
inPlace: false,
inputs: {
source: {
type: 'image',
uniform: 'source',
shaderDirty: false
},
time: {
type: 'number',
defaultValue: 0
},
distortion: {
type: 'number',
defaultValue: 0.1,
min: 0,
max: 1
},
verticalSync: {
type: 'number',
defaultValue: 0.1,
min: 0,
max: 1
},
lineSync: {
type: 'number',
uniform: 'lineSync',
defaultValue: 0.2,
min: 0,
max: 1
},
scanlines: {
type: 'number',
uniform: 'scanlines',
defaultValue: 0.3,
min: 0,
max: 1
},
bars: {
type: 'number',
uniform: 'bars',
defaultValue: 0,
min: 0,
max: 1
},
frameShape: {
type: 'number',
uniform: 'frameShape',
min: 0,
max: 2,
defaultValue: 0.27
},
frameLimit: {
type: 'number',
uniform: 'frameLimit',
min: -1,
max: 1,
defaultValue: 0.34
},
frameSharpness: {
type: 'number',
uniform: 'frameSharpness',
min: 0,
max: 40,
defaultValue: 8.4
},
frameColor: {
type: 'color',
uniform: 'frameColor',
defaultValue: [0, 0, 0, 1]
}
},
title: 'TV Glitch'
});
}));