Last active
September 9, 2017 03:00
-
-
Save thomaswilburn/6b62632e79aee4c3e02a64be5c8defef to your computer and use it in GitHub Desktop.
Voronoi diagram in 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>Voronoi</title> | |
</head> | |
<body> | |
<canvas class="voronoi"></canvas> | |
<style> | |
canvas { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
} | |
</style> | |
<script> | |
var canvas = document.querySelector(".voronoi"); | |
var gl = canvas.getContext("webgl"); | |
/* | |
All the interesting stuff in this page is done | |
through the following fragment shader. A | |
fragment shader is run once for every pixel | |
contained in a polygon, or in our case, in the | |
two triangles we use to fill the canvas completely. | |
*/ | |
var fragment = gl.createShader(gl.FRAGMENT_SHADER); | |
gl.shaderSource(fragment, ` | |
precision highp float; | |
uniform vec2 u_resolution; | |
uniform float u_time; | |
// function that plots a circle using a distance field | |
// no longer used, but helpful when debugging. | |
float circle(vec2 pixel, vec2 position, float r) { | |
return step(distance(pixel, position), r); | |
} | |
// generates pseudo-random noise from an x/y value | |
// Not really random--calling with the same input | |
// returns the same output | |
float random(vec2 seed) { | |
return fract(sin(dot(seed, vec2(12.9898,78.233))) * 43758.5453123); | |
} | |
// given a "cell" onscreen, where is its associated voronoi point? | |
vec2 cellPoint(vec2 cell) { | |
float r = random(cell); | |
return vec2( | |
(sin(u_time * .0005 * r) + 1.0) / 2.0, | |
(cos(u_time * .002 * r) + 1.0) / 2.0 | |
) * .5; | |
} | |
// given a cell, what color should we assign? | |
vec3 cellColor(vec2 cell) { | |
return vec3( | |
random(cell) * .3, | |
random(cell) * .8, | |
random(cell) * .6 | |
); | |
} | |
void main() { | |
float cell_count = 10.0; | |
vec3 low = vec3(0.0, 0.4, 0.2); | |
vec3 high = vec3(0.0, 0.8, 0.6); | |
vec2 coord = gl_FragCoord.xy / u_resolution; | |
// here we split the screen into cell_count rows and columns | |
vec2 cell_coord = coord * cell_count; | |
vec2 cell = floor(cell_coord); | |
vec2 inner = fract(cell_coord); | |
vec2 point = cellPoint(cell); | |
// find the two closest points | |
// all pixels are "won" by the closest point | |
// then we'll use that to apply a color | |
// using the next-closest, we can create boundary effects | |
float closest = 3.0; | |
float next = 3.0; | |
vec2 winner = cell; | |
for (float i = -1.0; i <= 1.0; i += 1.0) { | |
for (float j = -1.0; j <= 1.0; j += 1.0) { | |
float d = distance(inner, cellPoint(cell + vec2(i, j)) + vec2(i, j)); | |
if (d < closest) { | |
next = closest; | |
winner = cell + vec2(i, j); | |
closest = d; | |
} | |
} | |
} | |
// normalize the distance so that the boundary of the poly is always 0.5 | |
float shade = closest / (closest + next); | |
// apply a sharp step if we want a boundary | |
float wall = 1.0 - step(shade, .48); | |
vec3 pigment = cellColor(winner); | |
gl_FragColor = vec4(mix(pigment, vec3(0.0), wall), 1.0); | |
// flat-shaded variant: | |
// gl_FragColor = vec4(pigment, 1.0); | |
} | |
`); | |
gl.compileShader(fragment); | |
var log = gl.getShaderInfoLog(fragment); | |
if (log) console.log(log); | |
/* | |
Everything under this point is boilerplate GL code. | |
We basically just throw two triangles together to | |
cover the screen and given the fragment shader | |
somewhere to work. It's not fancy. | |
The render function is responsible for passing in | |
the two uniforms that the fragment shader uses: | |
u_time, which contains a timestamp and is used for | |
dynamic animation, and u_resolution, which we can | |
use to normalize gl_FragCoord from screen pixels | |
into a 0.0 - 1.0 range. | |
*/ | |
var vertex = gl.createShader(gl.VERTEX_SHADER); | |
gl.shaderSource(vertex, ` | |
attribute vec2 coord; | |
void main() { | |
gl_Position = vec4(coord, 0.0, 1.0); | |
} | |
`); | |
gl.compileShader(vertex); | |
var program = gl.createProgram(); | |
gl.attachShader(program, vertex); | |
gl.attachShader(program, fragment); | |
gl.linkProgram(program); | |
gl.useProgram(program); | |
var u_resolution = gl.getUniformLocation(program, "u_resolution"); | |
var u_time = gl.getUniformLocation(program, "u_time"); | |
var polys = [ | |
-1, 1, | |
1, 1, | |
1, -1, | |
-1, 1, | |
1, -1, | |
-1, -1 | |
]; | |
var coords = gl.getAttribLocation(program, "coord"); | |
gl.enableVertexAttribArray(coords); | |
var buffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(polys), gl.STATIC_DRAW); | |
gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0); | |
var render = function(t) { | |
gl.uniform1f(u_time, t + 12581372.5324); | |
canvas.width = canvas.clientWidth; | |
canvas.height = canvas.clientHeight; | |
gl.viewport(0, 0, canvas.width, canvas.height); | |
gl.uniform2f(u_resolution, canvas.width, canvas.height); | |
gl.clearColor(0, 1, 1, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
gl.drawArrays(gl.TRIANGLES, 0, polys.length / 2); | |
requestAnimationFrame(render); | |
}; | |
render(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment