├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── demo.js ├── index.html ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | bundle.js 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | bundle.js 5 | test 6 | test.js 7 | demo 8 | example 9 | .npmignore -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2014 [stackgl](http://github.com/stackgl/) contributors 5 | 6 | *stackgl contributors listed at * 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gl-particles 2 | 3 | [![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) 4 | 5 | Convenience module for FBO-driven particle simulations. 6 | 7 | **[view demo](http://stack.gl/gl-particles/)** 8 | 9 | ## Usage 10 | 11 | [![NPM](https://nodei.co/npm/gl-particles.png)](https://nodei.co/npm/gl-particles/) 12 | 13 | See [demo.js](demo.js) for a full example. 14 | 15 | ### `particles = Particles(gl, options)` 16 | 17 | Creates a new particle simulation given a WebGLContext `gl` and set 18 | of `options`: 19 | 20 | * `logic`: the logic shader for simulating the particles, as a string. Required. 21 | * `vert`: the vertex shader responsible for determining the rendered particles' 22 | position and size. Required. 23 | * `frag`: the fragment shader responsible for determining the color/texture of 24 | each particle. Required. 25 | * `shape`: a `[width, height]` array for the dimensions of the particle texture. 26 | This determines the total amount of particles, which should be `width * height`. 27 | Defaults to `[64, 64]`. 28 | 29 | Your logic shader will automatically be assigned the following uniforms: 30 | 31 | * `sampler2D data`: the particle data texture. 32 | * `vec2 resolution`: the width/height of the data texture. 33 | 34 | And your fragment/vertex shaders will be assigned the following: 35 | 36 | * `sampler2D data`: the particle data texture. 37 | 38 | ### `particles.populate((u, v, vec4) =>)` 39 | 40 | Populates the data for each particle in your FBO textures individually. 41 | 42 | * `u` is the horizontal index of the particle in pixels. 43 | * `v` is the vertical index of the particle in pixels. 44 | * `vec4` is a 4-element array which you should modify in-place to update 45 | the current particle's values. 46 | 47 | For example, if you have 2D positions for your particles you would set them 48 | randomly like so: 49 | 50 | ``` javascript 51 | particles.populate(function(u, v, vec4) { 52 | vec4[0] = Math.random() * 2 - 1 53 | vec4[1] = Math.random() * 2 - 1 54 | }) 55 | ``` 56 | 57 | ### `particles.step((uniforms) =>)` 58 | 59 | Runs one step of the `logic` shader – should generally be done once per 60 | frame. 61 | 62 | You may optionally pass in a function to update the shader's uniforms, e.g.: 63 | 64 | ``` javascript 65 | var start = Date.now() 66 | 67 | particles.step(function(uniforms) { 68 | uniforms.time = (Date.now() - start) / 1000 69 | }) 70 | ``` 71 | 72 | *Note that this will modify your WebGL state. Specifically, it will reset 73 | your current framebuffer, viewport and shader.* 74 | 75 | ### `particles.draw((uniforms) =>)` 76 | 77 | Draws your particles to the screen using the `vert` and `frag` shaders. 78 | 79 | As with `particles.step`, you may pass in an optional function for updating 80 | the shader's uniforms. 81 | 82 | ### `particles.setLogicShader(logicShaderSource)` 83 | 84 | Change the logic shader to `logicShaderSource`. 85 | 86 | ## Contributing 87 | 88 | See [stackgl/contributing](https://github.com/stackgl/contributing) for details. 89 | 90 | ## License 91 | 92 | MIT. See [LICENSE.md](http://github.com/stackgl/gl-particles/blob/master/LICENSE.md) for details. 93 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | const canvas = document.body.appendChild(document.createElement('canvas')) 2 | const gl = require('gl-context')(canvas, render) 3 | const fit = require('canvas-fit') 4 | const glslify = require('glslify') 5 | const Particles = require('./') 6 | 7 | const logics = [ 8 | glslify(` 9 | precision mediump float; 10 | 11 | #define PI 3.14159265359 12 | 13 | uniform sampler2D data; 14 | uniform float time; 15 | uniform vec2 resolution; 16 | 17 | #pragma glslify: noise = require('glsl-noise/simplex/3d') 18 | 19 | void main() { 20 | vec2 uv = gl_FragCoord.xy / resolution; 21 | vec4 tData = texture2D(data, uv); 22 | vec2 position = tData.xy; 23 | vec2 speed = tData.zw; 24 | 25 | speed.x += noise(vec3(position * 2.125, uv.x + time)) * 0.000225; 26 | speed.y += noise(vec3(position * 2.125, uv.y + time + 1000.0)) * 0.000225; 27 | 28 | float r = length(position); 29 | float a; 30 | 31 | if (r > 0.001) { 32 | a = atan(position.y, position.x); 33 | } else { 34 | a = 0.0; 35 | } 36 | 37 | position.x += cos(a + PI * 0.5) * 0.005; 38 | position.y += sin(a + PI * 0.5) * 0.005; 39 | 40 | position += speed; 41 | speed *= 0.975; 42 | position *= 0.995; 43 | 44 | gl_FragColor = vec4(position, speed); 45 | gl_FragColor = vec4(position, speed); 46 | } 47 | `, { inline: true }), 48 | glslify(` 49 | precision mediump float; 50 | 51 | uniform sampler2D data; 52 | uniform float time; 53 | uniform vec2 resolution; 54 | 55 | #pragma glslify: noise = require('glsl-noise/simplex/3d') 56 | 57 | void main() { 58 | vec2 uv = gl_FragCoord.xy / resolution; 59 | vec4 tData = texture2D(data, uv); 60 | vec2 position = tData.xy; 61 | vec2 speed = tData.zw; 62 | 63 | speed.x += noise(vec3(position * 2.125, uv.x + time)) * 0.0005; 64 | speed.y += noise(vec3(position * 2.125, uv.y + time + 1000.0)) * 0.0005; 65 | 66 | position += speed; 67 | speed *= 0.975; 68 | position *= 0.995; 69 | 70 | gl_FragColor = vec4(position, speed); 71 | gl_FragColor = vec4(position, speed); 72 | } 73 | `, { inline: true }) 74 | ] 75 | 76 | const particles = Particles(gl, { 77 | shape: [64, 64], 78 | logic: logics[0], 79 | vert: ` 80 | precision mediump float; 81 | 82 | uniform sampler2D data; 83 | uniform vec2 resolution; 84 | attribute vec2 uv; 85 | 86 | void main() { 87 | vec4 tData = texture2D(data, uv); 88 | vec2 position = tData.xy; 89 | 90 | position.x *= resolution.y / resolution.x; 91 | 92 | gl_PointSize = 4.0; 93 | gl_Position = vec4(position, 1, 1); 94 | } 95 | `, 96 | frag: ` 97 | precision mediump float; 98 | 99 | void main() { 100 | vec2 p = (gl_PointCoord.xy - 0.5) * 2.0; 101 | float d = 1.0 - dot(p, p); 102 | 103 | gl_FragColor = vec4(d * vec3(0.15, 0.2, 0.25), 1); 104 | } 105 | ` 106 | }) 107 | 108 | particles.populate(function(u, v, data) { 109 | var a = Math.random() * Math.PI * 2 110 | var l = Math.random() * 0.04 111 | data[0] = 0 112 | data[1] = 0 113 | data[2] = Math.cos(a) * l 114 | data[3] = Math.sin(a) * l 115 | }) 116 | 117 | const start = Date.now() 118 | 119 | function render() { 120 | const width = gl.drawingBufferWidth 121 | const height = gl.drawingBufferHeight 122 | 123 | // Disabling blending here is important – if it's still 124 | // enabled your simulation will behave differently 125 | // to what you'd expect. 126 | gl.disable(gl.BLEND) 127 | particles.step(function(uniforms) { 128 | uniforms.time = (Date.now() - start) / 1000 129 | }) 130 | 131 | gl.enable(gl.BLEND) 132 | gl.blendFunc(gl.ONE, gl.ONE) 133 | gl.clearColor(0.045, 0.02, 0.095, 1) 134 | gl.clear(gl.COLOR_BUFFER_BIT) 135 | gl.viewport(0, 0, width, height) 136 | 137 | particles.draw(function(uniforms) { 138 | uniforms.resolution = [width, height] 139 | }) 140 | } 141 | 142 | window.addEventListener('resize', fit(canvas), false) 143 | 144 | logics.forEach(function(source, i) { 145 | var el = document.body.appendChild(document.createElement('div')) 146 | 147 | el.innerHTML = i + 1 148 | el.style.position = 'absolute' 149 | el.style.cursor = 'pointer' 150 | el.style.color = '#66c4ff' 151 | el.style.zIndex = 9999 152 | el.style.left = (1.25 + i * 1.25) + 'em' 153 | el.style.top = '1.25em' 154 | el.style.fontFamily = '"Ubuntu Mono", monospace' 155 | 156 | el.addEventListener('click', function(e) { 157 | particles.setLogicShader(logics[i]) 158 | }, false) 159 | }) 160 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gl-particles 5 | 6 | 52 | 53 | 54 |
55 |

gl-particles

56 |

57 | Convenience module for FBO-driven particle simulations. 58 |

59 |

60 | 61 | Check it out on GitHub 62 | 63 |

64 |
65 | 66 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var triangle = require('a-big-triangle') 2 | var Geom = require('gl-geometry') 3 | var Shader = require('gl-shader') 4 | var ndarray = require('ndarray') 5 | var FBO = require('gl-fbo') 6 | 7 | module.exports = Particles 8 | 9 | var logicVert = [ 10 | 'precision mediump float;', 11 | 'attribute vec2 position;', 12 | 'void main() {', 13 | ' gl_Position = vec4(position, 1, 1);', 14 | '}' 15 | ].join('\n') 16 | 17 | function Particles(gl, options) { 18 | if (!(this instanceof Particles)) 19 | return new Particles(gl, options) 20 | 21 | options = options || {} 22 | 23 | this.gl = gl 24 | 25 | if (!options.logic) throw new Error('Please pass in the "logic" shader option') 26 | if (!options.vert) throw new Error('Please pass in the "vert" shader option') 27 | if (!options.frag) throw new Error('Please pass in the "frag" shader option') 28 | 29 | this.shape = options.shape || [64, 64] 30 | this.logic = Shader(gl, logicVert, options.logic) 31 | this.render = Shader(gl, options.vert, options.frag) 32 | 33 | this.logicVertSource = logicVert 34 | 35 | this.geom = Geom(gl) 36 | this.geom.attr('uv', generateLUT(this.shape), { 37 | size: 2 38 | }) 39 | 40 | this.prev = FBO(gl, [this.shape[0], this.shape[1]], { float: true }) 41 | this.curr = FBO(gl, [this.shape[0], this.shape[1]], { float: true }) 42 | } 43 | 44 | Particles.prototype.step = function(update) { 45 | this.curr.bind() 46 | this.gl.viewport(0, 0, this.shape[0], this.shape[1]) 47 | 48 | this.logic.bind() 49 | this.logic.uniforms.resolution = this.shape 50 | this.logic.uniforms.data = this.prev.color[0].bind(0) 51 | 52 | if (update) update(this.logic.uniforms) 53 | 54 | triangle(this.gl) 55 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null) 56 | 57 | var prev = this.prev 58 | this.prev = this.curr 59 | this.curr = prev 60 | } 61 | 62 | Particles.prototype.draw = function(update) { 63 | this.geom.bind(this.render) 64 | this.render.uniforms.data = this.prev.color[0].bind(0) 65 | 66 | if (update) update(this.render.uniforms) 67 | this.geom.draw(this.gl.POINTS) 68 | } 69 | 70 | Particles.prototype.populate = function(map) { 71 | var data = new Float32Array(this.shape[0] * this.shape[1] * 4) 72 | var vec4 = new Float32Array(4) 73 | var i = 0 74 | 75 | for (var x = 0; x < this.shape[0]; x++) 76 | for (var y = 0; y < this.shape[1]; y++) { 77 | map(x, y, vec4) 78 | 79 | data[i++] = vec4[0] 80 | data[i++] = vec4[1] 81 | data[i++] = vec4[2] 82 | data[i++] = vec4[3] 83 | 84 | vec4[0] = 0 85 | vec4[1] = 0 86 | vec4[2] = 0 87 | vec4[3] = 0 88 | } 89 | 90 | var pixels = ndarray(data, [this.shape[0], this.shape[1], 4]) 91 | 92 | this.prev.color[0].setPixels(pixels) 93 | this.curr.color[0].setPixels(pixels) 94 | } 95 | 96 | Particles.prototype.setLogicShader = function(logicFrag) { 97 | this.logic.update(logicVert, logicFrag) 98 | } 99 | 100 | function generateLUT(shape) { 101 | var size = shape[0] * shape[1] * 2 102 | var data = new Float32Array(size) 103 | var k = 0 104 | 105 | for (var i = 0; i < shape[0]; i++) 106 | for (var j = 0; j < shape[1]; j++) { 107 | var u = i / (shape[0] - 1) 108 | var v = j / (shape[1] - 1) 109 | 110 | data[k++] = u 111 | data[k++] = v 112 | } 113 | 114 | return data 115 | } 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl-particles", 3 | "version": "1.1.0", 4 | "description": "Convenience module for FBO-driven particle simulations", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "budo demo.js -o bundle.js -t babelify -t glslify | garnish", 9 | "bundle": "browserify demo.js -o bundle.js -t babelify -t glslify" 10 | }, 11 | "author": { 12 | "name": "Hugh Kennedy", 13 | "email": "hughskennedy@gmail.com", 14 | "url": "http://hughsk.io/" 15 | }, 16 | "dependencies": { 17 | "a-big-triangle": "^1.0.0", 18 | "gl-fbo": "^2.0.3", 19 | "gl-geometry": "^1.0.3", 20 | "gl-shader": "^4.0.1", 21 | "ndarray": "^1.0.16" 22 | }, 23 | "devDependencies": { 24 | "babelify": "^5.0.4", 25 | "browserify": "^9.0.3", 26 | "budo": "^2.1.2", 27 | "canvas-fit": "^1.2.0", 28 | "garnish": "^2.0.1", 29 | "gl-context": "^0.1.1", 30 | "glsl-noise": "0.0.0", 31 | "glslify": "^2.0.1", 32 | "watchify": "^3.0.0" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/stackgl/gl-particles.git" 37 | }, 38 | "keywords": [ 39 | "ecosystem:stackgl" 40 | ], 41 | "homepage": "https://github.com/stackgl/gl-particles", 42 | "bugs": { 43 | "url": "https://github.com/stackgl/gl-particles/issues" 44 | } 45 | } 46 | --------------------------------------------------------------------------------