├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── bundle.js ├── example ├── bunny.frag ├── bunny.vert ├── index.js └── post.frag ├── index.html ├── index.js ├── package.json ├── post.vert └── test ├── bunny.frag ├── bunny.vert ├── index.js ├── smokestack.js └── test.frag /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | bundle.js 5 | test 6 | test.js 7 | example 8 | -------------------------------------------------------------------------------- /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-post 2 | ![](http://img.shields.io/badge/stability-experimental-orange.svg?style=flat) 3 | ![](http://img.shields.io/npm/v/gl-post.svg?style=flat) 4 | ![](http://img.shields.io/npm/dm/gl-post.svg?style=flat) 5 | ![](http://img.shields.io/npm/l/gl-post.svg?style=flat) 6 | 7 | Simple WebGL post-processing using some pieces from [stackgl](http://stack.gl/). 8 | 9 | **[check out the demo](http://stack.gl/gl-post)**. 10 | 11 | ## Usage 12 | 13 | [![NPM](https://nodei.co/npm/gl-post.png)](https://nodei.co/npm/gl-post/) 14 | 15 | See the [example](example) code for a full usage example. 16 | 17 | ### `post = glPost(gl, shader, opts={})` 18 | 19 | Creates a new post-processing instance, where `gl` is a `WebGLContext` instance 20 | and `shader` is a shader instance from either 21 | [gl-shader-core](http://github.com/mikolalysenko/gl-shader-core) or 22 | [glslify](http://github.com/stackgl/glslify). 23 | 24 | The vertex shader is supplied for you, and available at `gl-post/post.vert`. 25 | The shader you pass in may also be a function that takes a `WebGLContext` and 26 | returns a shader instance too, so the following is valid: 27 | 28 | ``` javascript 29 | var glslify = require('glslify') 30 | var glPost = require('gl-post') 31 | 32 | post = glPost(gl, glslify({ 33 | vert: 'gl-post' 34 | , frag: './src/my-shader.frag' 35 | })) 36 | ``` 37 | 38 | There are also a few options you can include too: 39 | 40 | * `minFilter`: the texture minification filter to use. Defaults to `gl.LINEAR`. 41 | * `magFilter`: the texture magnification filter to use. Defaults to `gl.LINEAR`. 42 | * `colorBufferName`: the name of your color buffer uniform to use in your 43 | shader. Defaults to `colorBuffer`. 44 | 45 | In simple cases, you'll want to do something like this: 46 | 47 | ``` javascript 48 | var glslify = require('glslify') 49 | var glPost = require('gl-post') 50 | 51 | post = glPost(gl, glslify({ 52 | vert: 'gl-post' 53 | , frag: './src/my-shader.frag' 54 | })) 55 | 56 | function render() { 57 | post.bind() 58 | 59 | // Note that it's important you clear your 60 | // depth/color buffers for this to work properly :) 61 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 62 | gl.viewport(0, 0, canvas.width, canvas.height) 63 | 64 | // ...draw your scene to the framebuffer here... 65 | 66 | post.draw() 67 | } 68 | ``` 69 | 70 | ### `post.shader` 71 | 72 | The shader you gave `gl-post` will also be exposed here, for quickly changing 73 | uniform variable values. 74 | 75 | ### `post.bind()` 76 | 77 | Starts drawing to the post-processing buffer. Anything you do now will not be 78 | immediately drawn to the screen, but instead drawn to an off-screen 79 | [framebuffer](http://github.com/stackgl/gl-fbo) for you to draw later using 80 | the post-processing shader. 81 | 82 | You should this when you're ready to start drawing your scene. 83 | 84 | ### `post.draw()` 85 | 86 | Draws the framebuffer to the screen using your shader, returning your drawing 87 | power to the screen in the process. 88 | 89 | ### `post.unbind()` 90 | 91 | Call this if you want to explicitly disable rendering to the framebuffer before 92 | drawing to the screen. 93 | 94 | ## License 95 | 96 | MIT. See [LICENSE.md](http://github.com/stackgl/gl-post/blob/master/LICENSE.md) for details. 97 | -------------------------------------------------------------------------------- /example/bunny.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | // Sets the color of the current fragment (pixel) 4 | // to display the normal at the current position. 5 | // Using `abs()` to prevent negative values, which 6 | // would just end up being black. 7 | 8 | varying vec3 vNormal; 9 | 10 | void main() { 11 | gl_FragColor = vec4(abs(vNormal), 1.0); 12 | } 13 | -------------------------------------------------------------------------------- /example/bunny.vert: -------------------------------------------------------------------------------- 1 | // Our vertex shader is run once for each of these 2 | // vectors, to determine the final position of the vertex 3 | // on the screen and pass data off to the fragment shader. 4 | 5 | precision mediump float; 6 | 7 | // Our attributes, i.e. the arrays of vectors in the bunny mesh. 8 | attribute vec3 aPosition; 9 | attribute vec3 aNormal; 10 | 11 | // This is passed from here to be used in `bunny.frag`. 12 | varying vec3 vNormal; 13 | 14 | uniform mat4 uProjection; 15 | uniform mat4 uModel; 16 | uniform mat4 uView; 17 | 18 | void main() { 19 | vNormal = aNormal; 20 | 21 | // - `uProjection` will apply our perspective matrix, and 22 | // - `uView` will apply our camera transforms. 23 | // - `uModel` is unused here, but is traditionally used to 24 | // move the object around the scene. 25 | gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0); 26 | } 27 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var Geometry = require('gl-geometry') 2 | var mat4 = require('gl-mat4') 3 | var normals = require('normals') 4 | var glslify = require('glslify') 5 | var bunny = require('bunny') 6 | var Post = require('../') 7 | 8 | var canvas = document.body.appendChild(document.createElement('canvas')) 9 | var gl = require('gl-context')(canvas, render) 10 | var camera = require('canvas-orbit-camera')(canvas) 11 | var start = Date.now() 12 | 13 | var post = Post(gl, glslify({ 14 | vert: '../post.vert' 15 | , frag: './post.frag' 16 | })) 17 | 18 | window.addEventListener('resize' 19 | , require('canvas-fit')(canvas) 20 | , false 21 | ) 22 | 23 | var geometry = Geometry(gl) 24 | 25 | geometry.attr('aPosition', bunny.positions) 26 | geometry.attr('aNormal', normals.vertexNormals( 27 | bunny.cells 28 | , bunny.positions 29 | )) 30 | 31 | geometry.faces(bunny.cells) 32 | 33 | var projection = mat4.create() 34 | var model = mat4.create() 35 | var view = mat4.create() 36 | var height 37 | var width 38 | 39 | var shader = glslify({ 40 | vert: './bunny.vert' 41 | , frag: './bunny.frag' 42 | })(gl) 43 | 44 | function update() { 45 | width = gl.drawingBufferWidth 46 | height = gl.drawingBufferHeight 47 | 48 | camera.view(view) 49 | camera.tick() 50 | 51 | mat4.perspective(projection 52 | , Math.PI / 4 53 | , gl.drawingBufferWidth / gl.drawingBufferHeight 54 | , 0.01 55 | , 100 56 | ) 57 | } 58 | 59 | function render() { 60 | update() 61 | 62 | post.bind() 63 | 64 | gl.viewport(0, 0, width, height) 65 | gl.clearColor(0, 0, 0, 1) 66 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 67 | gl.enable(gl.DEPTH_TEST) 68 | gl.enable(gl.CULL_FACE) 69 | 70 | geometry.bind(shader) 71 | 72 | shader.uniforms.uProjection = projection 73 | shader.uniforms.uView = view 74 | shader.uniforms.uModel = model 75 | 76 | geometry.draw(gl.TRIANGLES) 77 | 78 | post.shader.bind() 79 | post.shader.uniforms.t = (Date.now() - start) * 0.001 80 | post.draw() 81 | } 82 | -------------------------------------------------------------------------------- /example/post.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D colorBuffer; 4 | uniform float t; 5 | 6 | varying vec2 screenPosition; 7 | 8 | void main() { 9 | // Here we're changing the texture sample coordinates 10 | // to get a distorted image. 11 | vec2 samplePosition = screenPosition; 12 | 13 | samplePosition = (sin(screenPosition * 15.0) + 1.0) * 0.5; 14 | samplePosition = samplePosition + (cos(t) + 1.0) * 0.25; 15 | 16 | vec4 sampleColor = texture2D(colorBuffer, samplePosition); 17 | 18 | gl_FragColor = vec4(sampleColor.rgb, 1); 19 | } 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gl-post 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var triangle = require('a-big-triangle') 2 | var FBO = require('gl-fbo') 3 | 4 | module.exports = Post 5 | 6 | function Post(gl, shader, opts) { 7 | if (!(this instanceof Post)) return new Post(gl, shader, opts) 8 | 9 | opts = opts || {} 10 | shader = typeof shader === 'function' 11 | ? shader(gl) 12 | : shader 13 | 14 | this.gl = gl 15 | this.canvas = gl.canvas 16 | this.shader = shader 17 | this.shader.attributes.position.location = 0 18 | 19 | this._colorBufferName = opts.colorBufferName || 'colorBuffers' 20 | 21 | this.fbo = FBO(gl, [gl.drawingBufferWidth, gl.drawingBufferHeight]) 22 | this.fbo.color[0].minFilter = opts.minFilter || gl.LINEAR 23 | this.fbo.color[0].magFilter = opts.magFilter || gl.LINEAR 24 | } 25 | 26 | var dims = [0, 0] 27 | 28 | Post.prototype.bind = function() { 29 | var gl = this.gl 30 | 31 | dims[0] = gl.drawingBufferWidth 32 | dims[1] = gl.drawingBufferHeight 33 | 34 | this.fbo.bind() 35 | this.fbo.shape = dims 36 | } 37 | 38 | Post.prototype.unbind = function() { 39 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null) 40 | } 41 | 42 | Post.prototype.draw = function() { 43 | var gl = this.gl 44 | 45 | this.unbind() 46 | 47 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight) 48 | gl.disable(gl.DEPTH_TEST) 49 | gl.disable(gl.CULL_FACE) 50 | 51 | this.shader.bind() 52 | this.shader.uniforms[this._colorBufferName] = this.fbo.color[0].bind(0) 53 | 54 | triangle(gl) 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl-post", 3 | "version": "1.0.1", 4 | "description": "Simple WebGL post-processing", 5 | "main": "index.js", 6 | "glslify": "post.vert", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "beefy example/index.js:bundle.js --open -- -t glslify", 10 | "bundle": "browserify -t glslify example/index.js -o bundle.js", 11 | "test": "npm run test:chrome && npm run test:firefox", 12 | "test:chrome": "browserify -t glslify test/smokestack.js | smokestack -b chrome | tap-spec", 13 | "test:firefox": "browserify -t glslify test/smokestack.js | smokestack -b firefox | tap-spec" 14 | }, 15 | "author": { 16 | "name": "Hugh Kennedy", 17 | "email": "hughskennedy@gmail.com", 18 | "url": "http://hughsk.io/" 19 | }, 20 | "dependencies": { 21 | "a-big-triangle": "^1.0.0", 22 | "gl-fbo": "^2.0.3" 23 | }, 24 | "devDependencies": { 25 | "bunny": "^1.0.1", 26 | "canvas-fit": "^1.2.0", 27 | "canvas-orbit-camera": "^1.0.1", 28 | "canvas-pixels": "0.0.0", 29 | "gl-context": "^0.1.1", 30 | "gl-geometry": "^1.0.0", 31 | "gl-mat4": "^1.0.1", 32 | "glslify": "^1.6.0", 33 | "normals": "^1.0.1", 34 | "smokestack": "^2.0.0", 35 | "tap-spec": "^2.1.0", 36 | "tape": "^3.0.3" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git://github.com/stackgl/gl-post.git" 41 | }, 42 | "keywords": [ 43 | "post-processing", 44 | "postprocessing", 45 | "distort", 46 | "recolor", 47 | "webgl", 48 | "stackgl" 49 | ], 50 | "homepage": "https://github.com/stackgl/gl-post", 51 | "bugs": { 52 | "url": "https://github.com/stackgl/gl-post/issues" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /post.vert: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | attribute vec2 position; 4 | varying vec2 screenPosition; 5 | 6 | void main() { 7 | screenPosition = (position + 1.0) * 0.5; 8 | gl_Position = vec4(position, 1.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /test/bunny.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | void main() { 4 | gl_FragColor = vec4(1); 5 | } 6 | -------------------------------------------------------------------------------- /test/bunny.vert: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | attribute vec3 position; 3 | uniform mat4 proj; 4 | uniform mat4 view; 5 | void main() { 6 | gl_Position = proj * view * vec4(position, 1.0); 7 | } 8 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var pixels = require('canvas-pixels') 2 | var Geom = require('gl-geometry') 3 | var mat4 = require('gl-mat4') 4 | var glslify = require('glslify') 5 | var glPost = require('../') 6 | var bunny = require('bunny') 7 | var test = require('tape') 8 | 9 | test('gl-post', function(t) { 10 | var canvas = document.body.appendChild(document.createElement('canvas')) 11 | var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') 12 | 13 | var camera = require('canvas-orbit-camera')(canvas) 14 | var width = canvas.width = 300 15 | var height = canvas.height = 300 16 | var post = glPost(gl, glslify({ 17 | vert: '../post.vert' 18 | , frag: './test.frag' 19 | })) 20 | 21 | var shader = glslify({ 22 | vert: './bunny.vert' 23 | , frag: './bunny.frag' 24 | })(gl) 25 | 26 | var proj = mat4.create() 27 | var view = mat4.create() 28 | var geom = Geom(gl) 29 | geom.attr('position', bunny.positions) 30 | geom.faces(bunny.cells) 31 | 32 | function drawScene() { 33 | camera.distance = 17 34 | camera.center = [0, 4, 0] 35 | camera.view(view) 36 | mat4.perspective(proj, Math.PI / 4, width / height, 0.01, 100) 37 | 38 | gl.viewport(0, 0, width, height) 39 | gl.clearColor(0, 0, 0, 1) 40 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 41 | 42 | geom.bind(shader) 43 | shader.uniforms.proj = proj 44 | shader.uniforms.view = view 45 | geom.draw() 46 | } 47 | 48 | drawScene() 49 | var plain = pixels(gl) 50 | 51 | post.bind() 52 | drawScene() 53 | post.draw() 54 | 55 | var postplain = pixels(gl) 56 | var count = 0 57 | 58 | for (var i = 0; i < plain.length; i++) { 59 | if (plain[i] !== postplain[i]) count++ 60 | } 61 | 62 | var error = count / plain.length 63 | var percent = (100-error*100).toFixed(2) 64 | 65 | t.ok(error <= 0.01, 'images matched! ' + percent + '% equivalent') 66 | t.end() 67 | }) 68 | -------------------------------------------------------------------------------- /test/smokestack.js: -------------------------------------------------------------------------------- 1 | require('./index') 2 | require('tape')('shutdown', function(t) { 3 | t.end() 4 | setTimeout(function(){window.close()}) 5 | }) 6 | -------------------------------------------------------------------------------- /test/test.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D colorBuffer; 4 | uniform float t; 5 | 6 | varying vec2 screenPosition; 7 | 8 | void main() { 9 | gl_FragColor = texture2D(colorBuffer, screenPosition); 10 | } 11 | --------------------------------------------------------------------------------