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' }); }));