Skip to content

Instantly share code, notes, and snippets.

@pbeshai
Last active March 8, 2024 17:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pbeshai/28c7f3acdde4ca5a13854f06c5d7e334 to your computer and use it in GitHub Desktop.
Save pbeshai/28c7f3acdde4ca5a13854f06c5d7e334 to your computer and use it in GitHub Desktop.
Draw 100,000 points with regl
license: mit
height: 720
border: no
!function(n){function t(c){if(g[c])return g[c].exports;var l=g[c]={i:c,l:!1,exports:{}};return n[c].call(l.exports,l,l.exports,t),l.l=!0,l.exports}var g={};t.m=n,t.c=g,t.i=function(n){return n},t.d=function(n,g,c){t.o(n,g)||Object.defineProperty(n,g,{configurable:!1,enumerable:!0,get:c})},t.n=function(n){var g=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(g,"a",g),g},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=0)}([function(module,exports,__webpack_require__){"use strict";eval("\n\nfunction main(err, regl) {\n var numPoints = 100000;\n var pointWidth = 4;\n var width = window.innerWidth;\n var height = window.innerHeight;\n\n // create initial set of points\n var rng = d3.randomNormal(0, 0.15);\n var points = d3.range(numPoints).map(function (i) {\n return {\n x: rng() * width + width / 2,\n y: rng() * height + height / 2,\n color: [0, Math.random(), 0]\n };\n });\n\n // function to compile a draw points regl func\n var drawPoints = regl({\n frag: '\\n\\t\\t// set the precision of floating point numbers\\n\\t precision highp float;\\n\\n\\t // this value is populated by the vertex shader\\n\\t\\tvarying vec3 fragColor;\\n\\n\\t\\tvoid main() {\\n\\t\\t\\t// gl_FragColor is a special variable that holds the color of a pixel\\n\\t\\t\\tgl_FragColor = vec4(fragColor, 1);\\n\\t\\t}\\n\\t\\t',\n\n vert: '\\n\\t\\t// per vertex attributes\\n\\t\\tattribute vec2 position;\\n\\t\\tattribute vec3 color;\\n\\n\\t\\t// variables to send to the fragment shader\\n\\t\\tvarying vec3 fragColor;\\n\\n\\t\\t// values that are the same for all vertices\\n\\t\\tuniform float pointWidth;\\n\\t\\tuniform float stageWidth;\\n\\t\\tuniform float stageHeight;\\n\\n\\t\\t// helper function to transform from pixel space to normalized device coordinates (NDC)\\n\\t\\t// in NDC (0,0) is the middle, (-1, 1) is the top left and (1, -1) is the bottom right.\\n\\t\\tvec2 normalizeCoords(vec2 position) {\\n\\t\\t\\t// read in the positions into x and y vars\\n float x = position[0];\\n float y = position[1];\\n\\n\\t\\t\\treturn vec2(\\n\\t 2.0 * ((x / stageWidth) - 0.5),\\n\\t // invert y since we think [0,0] is bottom left in pixel space\\n\\t -(2.0 * ((y / stageHeight) - 0.5)));\\n\\t\\t}\\n\\n\\t\\tvoid main() {\\n\\t\\t\\t// update the size of a point based on the prop pointWidth\\n\\t\\t\\tgl_PointSize = pointWidth;\\n\\n // send color to the fragment shader\\n fragColor = color;\\n\\n\\t\\t\\t// scale to normalized device coordinates\\n\\t\\t\\t// gl_Position is a special variable that holds the position of a vertex\\n gl_Position = vec4(normalizeCoords(position), 0.0, 1.0);\\n\\t\\t}\\n\\t\\t',\n\n attributes: {\n // each of these gets mapped to a single entry for each of the points.\n // this means the vertex shader will receive just the relevant value for a given point.\n position: points.map(function (d) {\n return [d.x, d.y];\n }),\n color: points.map(function (d) {\n return d.color;\n })\n },\n\n uniforms: {\n // by using `regl.prop` to pass these in, we can specify them as arguments\n // to our drawPoints function\n pointWidth: regl.prop('pointWidth'),\n\n // regl actually provides these as viewportWidth and viewportHeight but I\n // am using these outside and I want to ensure they are the same numbers,\n // so I am explicitly passing them in.\n stageWidth: regl.prop('stageWidth'),\n stageHeight: regl.prop('stageHeight')\n },\n\n // specify the number of points to draw\n count: points.length,\n\n // specify that each vertex is a point (not part of a mesh)\n primitive: 'points'\n });\n\n // start an animation loop\n var frameLoop = regl.frame(function () {\n // clear the buffer\n regl.clear({\n // background color (black)\n color: [0, 0, 0, 1],\n depth: 1\n });\n\n // draw the points using our created regl func\n // note that the arguments are available via `regl.prop`.\n drawPoints({\n pointWidth: pointWidth,\n stageWidth: width,\n stageHeight: height\n });\n\n // since we are only drawing once right now, let's just cancel the loop immediately\n if (frameLoop) {\n frameLoop.cancel();\n }\n });\n}\n\n// initialize regl\ncreateREGL({\n // callback when regl is initialized\n onDone: main\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zY3JpcHQuanM/OWE5NSJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBtYWluKGVyciwgcmVnbCkge1xuICBjb25zdCBudW1Qb2ludHMgPSAxMDAwMDA7XG4gIGNvbnN0IHBvaW50V2lkdGggPSA0O1xuICBjb25zdCB3aWR0aCA9IHdpbmRvdy5pbm5lcldpZHRoO1xuICBjb25zdCBoZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQ7XG5cbiAgLy8gY3JlYXRlIGluaXRpYWwgc2V0IG9mIHBvaW50c1xuICBjb25zdCBybmcgPSBkMy5yYW5kb21Ob3JtYWwoMCwgMC4xNSk7XG4gIGNvbnN0IHBvaW50cyA9IGQzLnJhbmdlKG51bVBvaW50cykubWFwKGkgPT4gKHtcbiAgICB4OiBybmcoKSAqIHdpZHRoICsgd2lkdGggLyAyLFxuICAgIHk6IHJuZygpICogaGVpZ2h0ICsgaGVpZ2h0IC8gMixcbiAgICBjb2xvcjogWzAsIE1hdGgucmFuZG9tKCksIDBdLFxuICB9KSk7XG5cbiAgLy8gZnVuY3Rpb24gdG8gY29tcGlsZSBhIGRyYXcgcG9pbnRzIHJlZ2wgZnVuY1xuICBjb25zdCBkcmF3UG9pbnRzID0gcmVnbCh7XG4gICAgZnJhZzogYFxuXHRcdC8vIHNldCB0aGUgcHJlY2lzaW9uIG9mIGZsb2F0aW5nIHBvaW50IG51bWJlcnNcblx0ICBwcmVjaXNpb24gaGlnaHAgZmxvYXQ7XG5cblx0ICAvLyB0aGlzIHZhbHVlIGlzIHBvcHVsYXRlZCBieSB0aGUgdmVydGV4IHNoYWRlclxuXHRcdHZhcnlpbmcgdmVjMyBmcmFnQ29sb3I7XG5cblx0XHR2b2lkIG1haW4oKSB7XG5cdFx0XHQvLyBnbF9GcmFnQ29sb3IgaXMgYSBzcGVjaWFsIHZhcmlhYmxlIHRoYXQgaG9sZHMgdGhlIGNvbG9yIG9mIGEgcGl4ZWxcblx0XHRcdGdsX0ZyYWdDb2xvciA9IHZlYzQoZnJhZ0NvbG9yLCAxKTtcblx0XHR9XG5cdFx0YCxcblxuICAgIHZlcnQ6IGBcblx0XHQvLyBwZXIgdmVydGV4IGF0dHJpYnV0ZXNcblx0XHRhdHRyaWJ1dGUgdmVjMiBwb3NpdGlvbjtcblx0XHRhdHRyaWJ1dGUgdmVjMyBjb2xvcjtcblxuXHRcdC8vIHZhcmlhYmxlcyB0byBzZW5kIHRvIHRoZSBmcmFnbWVudCBzaGFkZXJcblx0XHR2YXJ5aW5nIHZlYzMgZnJhZ0NvbG9yO1xuXG5cdFx0Ly8gdmFsdWVzIHRoYXQgYXJlIHRoZSBzYW1lIGZvciBhbGwgdmVydGljZXNcblx0XHR1bmlmb3JtIGZsb2F0IHBvaW50V2lkdGg7XG5cdFx0dW5pZm9ybSBmbG9hdCBzdGFnZVdpZHRoO1xuXHRcdHVuaWZvcm0gZmxvYXQgc3RhZ2VIZWlnaHQ7XG5cblx0XHQvLyBoZWxwZXIgZnVuY3Rpb24gdG8gdHJhbnNmb3JtIGZyb20gcGl4ZWwgc3BhY2UgdG8gbm9ybWFsaXplZCBkZXZpY2UgY29vcmRpbmF0ZXMgKE5EQylcblx0XHQvLyBpbiBOREMgKDAsMCkgaXMgdGhlIG1pZGRsZSwgKC0xLCAxKSBpcyB0aGUgdG9wIGxlZnQgYW5kICgxLCAtMSkgaXMgdGhlIGJvdHRvbSByaWdodC5cblx0XHR2ZWMyIG5vcm1hbGl6ZUNvb3Jkcyh2ZWMyIHBvc2l0aW9uKSB7XG5cdFx0XHQvLyByZWFkIGluIHRoZSBwb3NpdGlvbnMgaW50byB4IGFuZCB5IHZhcnNcbiAgICAgIGZsb2F0IHggPSBwb3NpdGlvblswXTtcbiAgICAgIGZsb2F0IHkgPSBwb3NpdGlvblsxXTtcblxuXHRcdFx0cmV0dXJuIHZlYzIoXG5cdCAgICAgIDIuMCAqICgoeCAvIHN0YWdlV2lkdGgpIC0gMC41KSxcblx0ICAgICAgLy8gaW52ZXJ0IHkgc2luY2Ugd2UgdGhpbmsgWzAsMF0gaXMgYm90dG9tIGxlZnQgaW4gcGl4ZWwgc3BhY2Vcblx0ICAgICAgLSgyLjAgKiAoKHkgLyBzdGFnZUhlaWdodCkgLSAwLjUpKSk7XG5cdFx0fVxuXG5cdFx0dm9pZCBtYWluKCkge1xuXHRcdFx0Ly8gdXBkYXRlIHRoZSBzaXplIG9mIGEgcG9pbnQgYmFzZWQgb24gdGhlIHByb3AgcG9pbnRXaWR0aFxuXHRcdFx0Z2xfUG9pbnRTaXplID0gcG9pbnRXaWR0aDtcblxuICAgICAgLy8gc2VuZCBjb2xvciB0byB0aGUgZnJhZ21lbnQgc2hhZGVyXG4gICAgICBmcmFnQ29sb3IgPSBjb2xvcjtcblxuXHRcdFx0Ly8gc2NhbGUgdG8gbm9ybWFsaXplZCBkZXZpY2UgY29vcmRpbmF0ZXNcblx0XHRcdC8vIGdsX1Bvc2l0aW9uIGlzIGEgc3BlY2lhbCB2YXJpYWJsZSB0aGF0IGhvbGRzIHRoZSBwb3NpdGlvbiBvZiBhIHZlcnRleFxuICAgICAgZ2xfUG9zaXRpb24gPSB2ZWM0KG5vcm1hbGl6ZUNvb3Jkcyhwb3NpdGlvbiksIDAuMCwgMS4wKTtcblx0XHR9XG5cdFx0YCxcblxuICAgIGF0dHJpYnV0ZXM6IHtcbiAgICAgIC8vIGVhY2ggb2YgdGhlc2UgZ2V0cyBtYXBwZWQgdG8gYSBzaW5nbGUgZW50cnkgZm9yIGVhY2ggb2YgdGhlIHBvaW50cy5cbiAgICAgIC8vIHRoaXMgbWVhbnMgdGhlIHZlcnRleCBzaGFkZXIgd2lsbCByZWNlaXZlIGp1c3QgdGhlIHJlbGV2YW50IHZhbHVlIGZvciBhIGdpdmVuIHBvaW50LlxuICAgICAgcG9zaXRpb246IHBvaW50cy5tYXAoZCA9PiBbZC54LCBkLnldKSxcbiAgICAgIGNvbG9yOiBwb2ludHMubWFwKGQgPT4gZC5jb2xvciksXG4gICAgfSxcblxuICAgIHVuaWZvcm1zOiB7XG4gICAgICAvLyBieSB1c2luZyBgcmVnbC5wcm9wYCB0byBwYXNzIHRoZXNlIGluLCB3ZSBjYW4gc3BlY2lmeSB0aGVtIGFzIGFyZ3VtZW50c1xuICAgICAgLy8gdG8gb3VyIGRyYXdQb2ludHMgZnVuY3Rpb25cbiAgICAgIHBvaW50V2lkdGg6IHJlZ2wucHJvcCgncG9pbnRXaWR0aCcpLFxuXG4gICAgICAvLyByZWdsIGFjdHVhbGx5IHByb3ZpZGVzIHRoZXNlIGFzIHZpZXdwb3J0V2lkdGggYW5kIHZpZXdwb3J0SGVpZ2h0IGJ1dCBJXG4gICAgICAvLyBhbSB1c2luZyB0aGVzZSBvdXRzaWRlIGFuZCBJIHdhbnQgdG8gZW5zdXJlIHRoZXkgYXJlIHRoZSBzYW1lIG51bWJlcnMsXG4gICAgICAvLyBzbyBJIGFtIGV4cGxpY2l0bHkgcGFzc2luZyB0aGVtIGluLlxuICAgICAgc3RhZ2VXaWR0aDogcmVnbC5wcm9wKCdzdGFnZVdpZHRoJyksXG4gICAgICBzdGFnZUhlaWdodDogcmVnbC5wcm9wKCdzdGFnZUhlaWdodCcpLFxuICAgIH0sXG5cbiAgICAvLyBzcGVjaWZ5IHRoZSBudW1iZXIgb2YgcG9pbnRzIHRvIGRyYXdcbiAgICBjb3VudDogcG9pbnRzLmxlbmd0aCxcblxuICAgIC8vIHNwZWNpZnkgdGhhdCBlYWNoIHZlcnRleCBpcyBhIHBvaW50IChub3QgcGFydCBvZiBhIG1lc2gpXG4gICAgcHJpbWl0aXZlOiAncG9pbnRzJyxcbiAgfSk7XG5cbiAgLy8gc3RhcnQgYW4gYW5pbWF0aW9uIGxvb3BcbiAgbGV0IGZyYW1lTG9vcCA9IHJlZ2wuZnJhbWUoKCkgPT4ge1xuICAgIC8vIGNsZWFyIHRoZSBidWZmZXJcbiAgICByZWdsLmNsZWFyKHtcbiAgICAgIC8vIGJhY2tncm91bmQgY29sb3IgKGJsYWNrKVxuICAgICAgY29sb3I6IFswLCAwLCAwLCAxXSxcbiAgICAgIGRlcHRoOiAxLFxuICAgIH0pO1xuXG4gICAgLy8gZHJhdyB0aGUgcG9pbnRzIHVzaW5nIG91ciBjcmVhdGVkIHJlZ2wgZnVuY1xuICAgIC8vIG5vdGUgdGhhdCB0aGUgYXJndW1lbnRzIGFyZSBhdmFpbGFibGUgdmlhIGByZWdsLnByb3BgLlxuICAgIGRyYXdQb2ludHMoe1xuICAgICAgcG9pbnRXaWR0aCxcbiAgICAgIHN0YWdlV2lkdGg6IHdpZHRoLFxuICAgICAgc3RhZ2VIZWlnaHQ6IGhlaWdodCxcbiAgICB9KTtcblxuICAgIC8vIHNpbmNlIHdlIGFyZSBvbmx5IGRyYXdpbmcgb25jZSByaWdodCBub3csIGxldCdzIGp1c3QgY2FuY2VsIHRoZSBsb29wIGltbWVkaWF0ZWx5XG4gICAgaWYgKGZyYW1lTG9vcCkge1xuICAgICAgZnJhbWVMb29wLmNhbmNlbCgpO1xuICAgIH1cbiAgfSk7XG59XG5cbi8vIGluaXRpYWxpemUgcmVnbFxuY3JlYXRlUkVHTCh7XG4gIC8vIGNhbGxiYWNrIHdoZW4gcmVnbCBpcyBpbml0aWFsaXplZFxuICBvbkRvbmU6IG1haW4sXG59KTtcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyBzY3JpcHQuanMiXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFIQTtBQUFBO0FBQ0E7QUFLQTtBQUNBO0FBQ0E7QUFDQTtBQVlBO0FBQ0E7QUFzQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQ0E7QUFBQTtBQUFBO0FBSkE7QUFDQTtBQU1BO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBVEE7QUFDQTtBQVdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUE1RUE7QUFDQTtBQThFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUhBO0FBQ0E7QUFLQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFIQTtBQUNBO0FBS0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUZBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///0\n")}]);
<!DOCTYPE html>
<title>Draw 100,000 points with regl</title>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/regl/1.3.11/regl.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="dist.js"></script>
</body>
function main(err, regl) {
const numPoints = 100000;
const pointWidth = 4;
const width = window.innerWidth;
const height = window.innerHeight;
// create initial set of points
const rng = d3.randomNormal(0, 0.15);
const points = d3.range(numPoints).map(i => ({
x: rng() * width + width / 2,
y: rng() * height + height / 2,
color: [0, Math.random(), 0],
}));
// function to compile a draw points regl func
const drawPoints = regl({
frag: `
// set the precision of floating point numbers
precision highp float;
// this value is populated by the vertex shader
varying vec3 fragColor;
void main() {
// gl_FragColor is a special variable that holds the color of a pixel
gl_FragColor = vec4(fragColor, 1);
}
`,
vert: `
// per vertex attributes
attribute vec2 position;
attribute vec3 color;
// variables to send to the fragment shader
varying vec3 fragColor;
// values that are the same for all vertices
uniform float pointWidth;
uniform float stageWidth;
uniform float stageHeight;
// helper function to transform from pixel space to normalized device coordinates (NDC)
// in NDC (0,0) is the middle, (-1, 1) is the top left and (1, -1) is the bottom right.
vec2 normalizeCoords(vec2 position) {
// read in the positions into x and y vars
float x = position[0];
float y = position[1];
return vec2(
2.0 * ((x / stageWidth) - 0.5),
// invert y since we think [0,0] is bottom left in pixel space
-(2.0 * ((y / stageHeight) - 0.5)));
}
void main() {
// update the size of a point based on the prop pointWidth
gl_PointSize = pointWidth;
// send color to the fragment shader
fragColor = color;
// scale to normalized device coordinates
// gl_Position is a special variable that holds the position of a vertex
gl_Position = vec4(normalizeCoords(position), 0.0, 1.0);
}
`,
attributes: {
// each of these gets mapped to a single entry for each of the points.
// this means the vertex shader will receive just the relevant value for a given point.
position: points.map(d => [d.x, d.y]),
color: points.map(d => d.color),
},
uniforms: {
// by using `regl.prop` to pass these in, we can specify them as arguments
// to our drawPoints function
pointWidth: regl.prop('pointWidth'),
// regl actually provides these as viewportWidth and viewportHeight but I
// am using these outside and I want to ensure they are the same numbers,
// so I am explicitly passing them in.
stageWidth: regl.prop('stageWidth'),
stageHeight: regl.prop('stageHeight'),
},
// specify the number of points to draw
count: points.length,
// specify that each vertex is a point (not part of a mesh)
primitive: 'points',
});
// start an animation loop
let frameLoop = regl.frame(() => {
// clear the buffer
regl.clear({
// background color (black)
color: [0, 0, 0, 1],
depth: 1,
});
// draw the points using our created regl func
// note that the arguments are available via `regl.prop`.
drawPoints({
pointWidth,
stageWidth: width,
stageHeight: height,
});
// since we are only drawing once right now, let's just cancel the loop immediately
if (frameLoop) {
frameLoop.cancel();
}
});
}
// initialize regl
createREGL({
// callback when regl is initialized
onDone: main,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment