50,000 circles in fixed positions, with their radius and color animated through 52 datapoints.
Last active
May 7, 2017 13:12
-
-
Save robinhouston/4b736b08f369a94c419098a72e11c47b to your computer and use it in GitHub Desktop.
Circles animated with WebGL
This file contains 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Animated circles</title> | |
<style> | |
body { margin: 0; } | |
canvas { position: absolute; top: 0; left: 0; } | |
</style> | |
<script id="shader-vs" type="x-webgl-shader/x-vertex"> | |
uniform mat3 uTransformToClipSpace; | |
uniform float uAnimationPosition; | |
attribute vec2 aPosition; | |
attribute float aFromRadius, aToRadius; | |
attribute vec3 aFromColor, aToColor; | |
varying vec3 vColor; | |
void main(void) { | |
vec2 p = aPosition; | |
vec2 pos = (uTransformToClipSpace * vec3(aPosition, 1.0)).xy; | |
float radius = mix(aFromRadius, aToRadius, uAnimationPosition); | |
vColor = mix(aFromColor, aToColor, uAnimationPosition); | |
gl_Position = vec4(pos, 0.0, 1.0); | |
gl_PointSize = radius * 2.0; | |
} | |
</script> | |
<script id="shader-fs" type="x-webgl-shader/x-fragment"> | |
varying mediump vec3 vColor; | |
void main(void) { | |
const lowp float ALPHA = 0.75; | |
lowp vec2 pos = gl_PointCoord - vec2(0.5, 0.5); | |
lowp float dist_squared = dot(pos, pos); | |
lowp float alpha; | |
if (dist_squared < 0.25) { | |
alpha = ALPHA; | |
} else { | |
alpha = 0.0; | |
} | |
gl_FragColor = vec4(vColor, alpha); | |
} | |
</script> | |
</head> | |
<body> | |
<canvas id="canvas"></canvas> | |
<script> | |
// Animation speed | |
var MS_PER_DATAPOINT = 2000; | |
// Number of circles and datapoints | |
var num_circles = 50000, num_datapoints = 52; | |
var w = window.innerWidth, h = window.innerHeight; | |
var canvas = document.getElementById("canvas"); | |
var gl = canvas.getContext("webgl"); | |
var prog, // WebGL program | |
uAnimationPosition; // Locations of uniforms | |
function initCanvas() { | |
// Set the canvas dimensions and the WebGL viewport | |
canvas.width = w; | |
canvas.height = h; | |
gl.viewport(0, 0, canvas.width, canvas.height); | |
} | |
function glInit() { | |
// Load and compile the shaders | |
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); | |
gl.shaderSource(fragmentShader, document.getElementById("shader-fs").textContent); | |
gl.compileShader(fragmentShader); | |
var vertexShader = gl.createShader(gl.VERTEX_SHADER) | |
gl.shaderSource(vertexShader, document.getElementById("shader-vs").textContent); | |
gl.compileShader(vertexShader); | |
// Create the WebGL program | |
prog = gl.createProgram(); | |
gl.attachShader(prog, vertexShader); | |
gl.attachShader(prog, fragmentShader); | |
gl.linkProgram(prog); | |
gl.useProgram(prog); | |
// Global WebGL configuration | |
gl.enable(gl.BLEND); | |
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); | |
// Initialise uTransformToClipSpace | |
gl.uniformMatrix3fv(gl.getUniformLocation(prog, "uTransformToClipSpace"), false, [ | |
2 / w, 0, 0, | |
0, - 2 / h, 0, | |
-1, 1, 1 | |
]); | |
// Get the location of the uAnimationPosition uniform | |
uAnimationPosition = gl.getUniformLocation(prog, "uAnimationPosition"); | |
// Enable the attributes | |
gl.enableVertexAttribArray(gl.getAttribLocation(prog, "aPosition")); | |
gl.enableVertexAttribArray(gl.getAttribLocation(prog, "aFromRadius")); | |
gl.enableVertexAttribArray(gl.getAttribLocation(prog, "aToRadius")); | |
gl.enableVertexAttribArray(gl.getAttribLocation(prog, "aFromColor")); | |
gl.enableVertexAttribArray(gl.getAttribLocation(prog, "aToColor")); | |
return prog; | |
} | |
function drawScene(animation_position) { | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
gl.uniform1f(uAnimationPosition, animation_position); | |
gl.drawArrays(gl.POINTS, 0, num_circles); | |
} | |
var animation_frame, first_timestamp, datapoint = 0; | |
function beginAnimation() { | |
animation_frame = requestAnimationFrame(animationFrame); | |
} | |
function animationFrame(timestamp) { | |
if (typeof first_timestamp == "undefined") { | |
first_timestamp = timestamp; | |
} | |
var dt = timestamp - first_timestamp, | |
p = dt / MS_PER_DATAPOINT; | |
if (p >= num_datapoints - 1) return; | |
if (Math.floor(p) != datapoint) { | |
selectDatapoint(datapoint = Math.floor(p)); | |
} | |
drawScene(p % 1.0); | |
animation_frame = requestAnimationFrame(animationFrame); | |
} | |
function generateRandomCircles() { | |
var circles = []; | |
for (var i = 0; i < num_circles; i++) { | |
var circle = { | |
x: Math.random() * w, | |
y: Math.random() * h, | |
radius: [], color: [] | |
}; | |
for (var j = 0; j < num_datapoints; j++) { | |
circle.radius.push(Math.random() * 5); | |
circle.color.push([ Math.random(), Math.random(), Math.random() ]); | |
} | |
circles.push(circle); | |
} | |
return circles; | |
} | |
function loadCircles(circles) { | |
var floats_per_datapoint = 4, | |
floats_per_circle = 2 + num_datapoints * floats_per_datapoint; | |
// Pack the circle data into an array of floats | |
var data = new Float32Array(num_circles * floats_per_circle); | |
for (var i = 0; i < num_circles; i++) { | |
var circle = circles[i]; | |
data[i*2] = circle.x; | |
data[i*2 + 1] = circle.y; | |
for (var j = 0; j < num_datapoints; j++) { | |
data[num_circles * (2 + j) + i] = circle.radius[j]; | |
data[num_circles * (2 + num_datapoints + 3*j) + i*3] = circle.color[j][0]; | |
data[num_circles * (2 + num_datapoints + 3*j) + i*3 + 1] = circle.color[j][1]; | |
data[num_circles * (2 + num_datapoints + 3*j) + i*3 + 2] = circle.color[j][2]; | |
} | |
} | |
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); | |
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); | |
attrib("aPosition", 2, 0); | |
selectDatapoint(datapoint); | |
} | |
function selectDatapoint(i) { | |
console.log("selectDatapoint", i); | |
attrib("aFromRadius", 1, (2 + i) * num_circles); | |
attrib("aToRadius", 1, (2 + i+1) * num_circles); | |
attrib("aFromColor", 3, (2 + num_datapoints + 3*i) * num_circles); | |
attrib("aToColor", 3, (2 + num_datapoints + 3*(i+1)) * num_circles); | |
} | |
function attrib(attrib_name, size, offset) { | |
gl.vertexAttribPointer(gl.getAttribLocation(prog, attrib_name), | |
size, gl.FLOAT, false, size * 4, offset * 4); | |
} | |
initCanvas(); | |
glInit(); | |
loadCircles(generateRandomCircles()); | |
beginAnimation(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment