A ripple effect on video, achieved through gaussian blur. Caveats: ( - it crashes your machine after a while
Created
June 8, 2017 15:34
-
-
Save jakelear/d7707cea025113dcaa940cc0870670bf to your computer and use it in GitHub Desktop.
WebGL video through 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
<script id="shader-fs" type="x-shader/x-fragment"> | |
' | |
varying highp vec2 vTextureCoord; | |
varying highp vec3 vLighting; | |
#ifdef GL_ES | |
precision highp float; | |
#endif | |
uniform highp vec2 resolution; | |
uniform highp float time; | |
uniform sampler2D uSampler; | |
void main(void) { | |
highp vec2 cPos = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy; | |
float cLength = length(cPos); | |
highp vec2 uv = gl_FragCoord.xy/resolution.xy+(cPos/cLength)*cos(cLength*12.0-time*4.0)*0.03; | |
/* | |
highp vec2 uv = vec2(vTextureCoord.s, vTextureCoord.t); | |
*/ | |
highp vec4 texelColor = texture2D(uSampler, uv); | |
//gl_FragColor = vec4(texelColor.rgb * vLighting, texelColor.a); | |
gl_FragColor = texelColor; | |
} | |
</script> | |
<script id="shader-vs" type="x-shader/x-vertex"> | |
' | |
attribute highp vec3 aVertexNormal; | |
attribute highp vec3 aVertexPosition; | |
attribute highp vec2 aTextureCoord; | |
uniform highp mat4 uNormalMatrix; | |
uniform highp mat4 uMVMatrix; | |
uniform highp mat4 uPMatrix; | |
varying highp vec2 vTextureCoord; | |
varying highp vec3 vLighting; | |
void main(void) { | |
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); | |
vTextureCoord = aTextureCoord; | |
// Apply lighting effect | |
highp vec3 ambientLight = vec3(0.6, 0.6, 0.6); | |
highp vec3 directionalLightColor = vec3(0.5, 0.5, 0.75); | |
highp vec3 directionalVector = vec3(0.85, 0.8, 0.75); | |
highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0); | |
highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0); | |
vLighting = ambientLight + (directionalLightColor * directional); | |
} | |
</script> | |
body | |
.original | |
h2 Original | |
video#video(controls autoplay muted loop) | |
source src="http://images.jakebot.com/zDIO5XD0.mp4" type="video/mp4" | |
.processed | |
h2 Processed | |
canvas#glcanvas width=640 height=640*9/16 |
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
// WebGL video | |
// developed by working through https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Animating_textures_in_WebGL | |
// with fixes from http://stackoverflow.com/questions/13572304/video-texture-in-webgl-not-working#13580005 | |
// CORS needs to be disabled (to load the video into texture) | |
// launch with | |
// open -a Google Chrome --args --disable-web-security | |
var canvas; | |
var gl; | |
var intervalID; | |
var time = 0; | |
var cubeVerticesBuffer; | |
var cubeVerticesNormalBuffer; | |
var cubeVerticesTextureCoordBuffer; | |
var cubeVerticesIndexBuffer; | |
var cubeVerticesIndexBuffer; | |
var cubeTexture; | |
var mvMatrix; | |
var shaderProgram; | |
var vertexPositionAttribute; | |
var vertexNormalAttribute; | |
var textureCoordAttribute; | |
var projectionMatrix; | |
var videoElement; | |
$(start); | |
// | |
// start | |
// | |
// Called when the canvas is created to get the ball rolling. | |
// | |
function start() { | |
canvas = document.getElementById("glcanvas"); | |
videoElement = document.getElementById("video"); | |
initWebGL(canvas); // Initialize the GL context | |
// Only continue if WebGL is available and working | |
if (gl) { | |
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque | |
gl.clearDepth(1.0); // Clear everything | |
gl.enable(gl.DEPTH_TEST); // Enable depth testing | |
gl.depthFunc(gl.LEQUAL); // Near things obscure far things | |
// Initialize the shaders; this is where all the lighting for the | |
// vertices and so forth is established. | |
initShaders(); | |
// Here's where we call the routine that builds all the objects | |
// we'll be drawing. | |
initBuffers(); | |
// Next, load and set up the textures we'll be using. | |
initTextures(); | |
// Start listening for the canplaythrough event, so we don't | |
// start playing the video until we can do so without stuttering | |
videoElement.addEventListener("canplaythrough", startVideo, true); | |
// Start listening for the ended event, so we can stop the | |
// animation when the video is finished playing. | |
videoElement.addEventListener("ended", videoDone, true); | |
} | |
} | |
// | |
// initWebGL | |
// | |
// Initialize WebGL, returning the GL context or null if | |
// WebGL isn't available or could not be initialized. | |
// | |
function initWebGL() { | |
gl = null; | |
try { | |
gl = canvas.getContext("webgl"); | |
} | |
catch(e) { | |
} | |
// If we don't have a GL context, give up now | |
if (!gl) { | |
alert("Unable to initialize WebGL. Your browser may not support it."); | |
} | |
} | |
// | |
// initBuffers | |
// | |
// Initialize the buffers we'll need. For this demo, we just have | |
// one object -- a simple two-dimensional cube. | |
// | |
function initBuffers() { | |
// Create a buffer for the cube's vertices. | |
cubeVerticesBuffer = gl.createBuffer(); | |
// Select the cubeVerticesBuffer as the one to apply vertex | |
// operations to from here out. | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer); | |
// Now create an array of vertices for the cube. | |
var vertices = [ | |
// Front face | |
-1.0, -1.0, 1.0, | |
1.0, -1.0, 1.0, | |
1.0, 1.0, 1.0, | |
-1.0, 1.0, 1.0, | |
]; | |
// Now pass the list of vertices into WebGL to build the shape. We | |
// do this by creating a Float32Array from the JavaScript array, | |
// then use it to fill the current vertex buffer. | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); | |
// Set up the normals for the vertices, so that we can compute lighting. | |
cubeVerticesNormalBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); | |
var vertexNormals = [ | |
// Front | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals), | |
gl.STATIC_DRAW); | |
// Map the texture onto the cube's faces. | |
cubeVerticesTextureCoordBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer); | |
var textureCoordinates = [ | |
// Front | |
0.0, 0.0, | |
1.0, 0.0, | |
1.0, 1.0, | |
0.0, 1.0, | |
]; | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), | |
gl.STATIC_DRAW); | |
// Build the element array buffer; this specifies the indices | |
// into the vertex array for each face's vertices. | |
cubeVerticesIndexBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer); | |
// This array defines each face as two triangles, using the | |
// indices into the vertex array to specify each triangle's | |
// position. | |
var cubeVertexIndices = [ | |
0, 1, 2, 0, 2, 3, // front | |
] | |
// Now send the element array to GL | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, | |
new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW); | |
} | |
// | |
// initTextures | |
// | |
// Initialize the textures we'll be using, then initiate a load of | |
// the texture images. The handleTextureLoaded() callback will finish | |
// the job; it gets called each time a texture finishes loading. | |
// | |
function initTextures() { | |
cubeTexture = gl.createTexture(); | |
} | |
// | |
// updateTexture | |
// | |
// Update the texture to contain the latest frame from | |
// our video. | |
// | |
function updateTexture() { | |
gl.bindTexture(gl.TEXTURE_2D, cubeTexture); | |
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, | |
gl.UNSIGNED_BYTE, videoElement); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.bindTexture(gl.TEXTURE_2D, null); | |
} | |
// | |
// startVideo | |
// | |
// Starts playing the video, so that it will start being used | |
// as our texture. | |
// | |
function startVideo() { | |
videoElement.play(); | |
intervalID = setInterval(drawScene, 15); | |
} | |
// | |
// videoDone | |
// | |
// Called when the video is done playing; this will terminate | |
// the animation. | |
// | |
function videoDone() { | |
clearInterval(intervalID); | |
} | |
// | |
// drawScene | |
// | |
// Draw the scene. | |
// | |
function drawScene() { | |
updateTexture(); | |
// Clear the canvas before we start drawing on it. | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
projectionMatrix = makeOrtho(-1, 1, -1, 1, 0.1, 100.0); | |
// Set the drawing position to the "identity" point, which is | |
// the center of the scene. | |
loadIdentity(); | |
// Now move the drawing position a bit to where we want to start | |
// drawing the cube. | |
mvTranslate([0.0, 0.0, -3]); | |
// update the ripple effect uniforms | |
time = (time + 5) % 5000; | |
gl.uniform1f(gl.getUniformLocation(shaderProgram, "time"), time / 1000); | |
gl.uniform2f(gl.getUniformLocation(shaderProgram, "resolution"), 640, 480); | |
// Draw the square by binding the array buffer to the cube's vertic | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer); | |
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); | |
// Set the texture coordinates attribute for the vertices. | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer); | |
gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0); | |
// Bind the normals buffer to the shader attribute. | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); | |
gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0); | |
// Specify the texture to map onto the faces. | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, cubeTexture); | |
gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0); | |
// Draw the cube. | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer); | |
setMatrixUniforms(); | |
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); | |
} | |
// | |
// initShaders | |
// | |
// Initialize the shaders, so WebGL knows how to light our scene. | |
// | |
function initShaders() { | |
var fragmentShader = getShader(gl, "shader-fs"); | |
var vertexShader = getShader(gl, "shader-vs"); | |
// Create the shader program | |
shaderProgram = gl.createProgram(); | |
gl.attachShader(shaderProgram, vertexShader); | |
gl.attachShader(shaderProgram, fragmentShader); | |
gl.linkProgram(shaderProgram); | |
// If creating the shader program failed, alert | |
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { | |
console.log("Unable to initialize the shader program."); | |
} | |
gl.useProgram(shaderProgram); | |
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); | |
gl.enableVertexAttribArray(vertexPositionAttribute); | |
textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); | |
gl.enableVertexAttribArray(textureCoordAttribute); | |
vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal"); | |
gl.enableVertexAttribArray(vertexNormalAttribute); | |
} | |
// | |
// getShader | |
// | |
// Loads a shader program by scouring the current document, | |
// looking for a script with the specified ID. | |
// | |
function getShader(gl, id) { | |
var shaderScript = document.getElementById(id); | |
// Didn't find an element with the specified ID; abort. | |
if (!shaderScript) { | |
return null; | |
} | |
var theSource = $(`#${id}`).text(); | |
// Now figure out what type of shader script we have, | |
// based on its MIME type. | |
var shader; | |
if (shaderScript.type == "x-shader/x-fragment") { | |
shader = gl.createShader(gl.FRAGMENT_SHADER); | |
} else if (shaderScript.type == "x-shader/x-vertex") { | |
shader = gl.createShader(gl.VERTEX_SHADER); | |
} else { | |
return null; // Unknown shader type | |
} | |
// Send the source to the shader object | |
gl.shaderSource(shader, theSource); | |
// Compile the shader program | |
gl.compileShader(shader); | |
// See if it compiled successfully | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
console.log("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader)); | |
return null; | |
} | |
return shader; | |
} | |
// | |
// Matrix utility functions | |
// | |
function loadIdentity() { | |
mvMatrix = Matrix.I(4); | |
} | |
function multMatrix(m) { | |
mvMatrix = mvMatrix.x(m); | |
} | |
function mvTranslate(v) { | |
multMatrix(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4()); | |
} | |
function setMatrixUniforms() { | |
var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); | |
gl.uniformMatrix4fv(pUniform, false, new Float32Array(projectionMatrix.flatten())); | |
var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); | |
gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten())); | |
var normalMatrix = mvMatrix.inverse(); | |
normalMatrix = normalMatrix.transpose(); | |
var nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix"); | |
gl.uniformMatrix4fv(nUniform, false, new Float32Array(normalMatrix.flatten())); | |
} |
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
<script src="//cdnjs.cloudflare.com/ajax/libs/zepto/1.1.4/zepto.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/sylvester/0.1.3/sylvester.js"></script> | |
<script src="https://developer.mozilla.org/samples/webgl/sample8/glUtils.js"></script> |
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
body { | |
font-family: sans-serif; | |
} | |
.original, .processed { | |
display: inline-block; | |
width: 100%; | |
video, canvas { | |
width: 100%; | |
height: auto; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment