├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── assets ├── ASSETS.md ├── Storm Cell Over the Southern Appalachian Mountains-dsc_2303_0-256x256.png ├── result-256x256x-r1.png ├── result-256x256x-r16.png ├── result-256x256x-r2.png ├── result-256x256x-r4.png └── result-256x256x-r8.png ├── glsl-gaussian-demo.js ├── glsl-gaussian-live-demo.js ├── glsl-gaussian.js ├── package.json └── scripts ├── build-a-demo.js ├── build-demo.js ├── build-live-demo.js └── publish-static-demos.sh /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules/* 3 | *.DS_Store 4 | coverage/* 5 | bench/*.html 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bench/* 2 | example/* 3 | test/* 4 | www/* 5 | bin/* 6 | dist/* 7 | coverage/* 8 | images/* 9 | compare/* 10 | CNAME 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - '6.3' 5 | env: 6 | global: 7 | - CXX=g++-4.8 8 | #GH_TOKEN 9 | - secure: >- 10 | QxB0SfVV0ODqdhNSSkLwxX3ZKry9lTWHaxG3Q7kNeLwq8pMhc11USzq7WYOpHWpwuTyv//XUs53jAl77jaAgizuUE8O3oSemaoH+x2ycRiOFx7Vto46OiTm2vrqqy0GCblk8FNsny3Pkc+Otm9sNiOjDWdJYrZe9t4s4Rm2HMAvvoilpTU92K+0ANPIqfwyZYJbvx0Rn8vLAe32wOH1KD9ZwvJAfDcTyiF0iouVH6raXwa8t2nWVg0VY9dpnyraoRmchmpbsc1KmFnr+O9iGcZXPzMMG9cO+riKEDL29l/6p+QaKfipbpkBQlWecwxtrHpPKpWBBofSMGuPdmOzbE5i4WIAajgtOCBgctOsRmH894ExQ4A40ZSL6wguK/1Vl2bi9EvR6eQ2OwXSuYwABbhXSWepNOGJGodxXNw3IAlEszS/VlVoiPmaUxfaDa0esXN42YTtXgPXqdchnDQQOrphxQmCzuYUzoXywq+KOytdy8fbk1POYdRR92Pg4iVQn4bvxyBqraBaFGmtSufLFaOi4l/Sfu+VZMBrVog3WjslgWiUrDVmWFKbtDQbup8G/AsHmp4DLiRNrUuMDuz2NH0q7t7WkZLr4+N+BBbmqUWUVSx1ZX/UrV8bOkTpu10YOANihXeK7B3XjXG5a3NVxtDkFb1oSaj1MrkODut4rcJI= 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - g++-4.8 17 | install: 18 | - npm install --global browserify 19 | - npm install 20 | script: 21 | - npm run mytest 22 | - npm run build 23 | - bash scripts/publish-static-demos.sh 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Azriel Fasten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | glsl-gaussian 3 | --- 4 | 5 | 6 | ####Description 7 | 8 | glsl-gaussian is a shader generator for WebGL, to generate a gaussian blur of an input texture. 9 | 10 | 11 | See `glsl-gaussian-live-demo.js`, `glsl-gaussian-demo.js` for usage. 12 | 13 | ####Dependencies 14 | 15 | * nodejs 16 | * browserify 17 | * [glsl-quad](https://github.com/realazthat/glsl-quad) 18 | * [glsl-sat](https://github.com/realazthat/glsl-sat) 19 | * [regl](https://github.com/mikolalysenko/regl) 20 | * [glsl-numerify](https://github.com/realazthat/glsl-numerify) (for demo) 21 | * [resl](https://github.com/mikolalysenko/resl) (for demo) 22 | * budo (for quick demo as an alternative to running browserify) 23 | 24 | 25 | ####Demo 26 | 27 | To run the demo, run: 28 | 29 | ``` 30 | cd ./glsl-gaussian 31 | 32 | #install npm dependencies 33 | npm install 34 | 35 | #browser should open with the demo 36 | budo glsl-gaussian-demo.js --open 37 | 38 | #browser should open with the demo 39 | budo glsl-gaussian-live-demo.js --open 40 | 41 | ``` 42 | 43 | **Live:** 44 | 45 | branch | demo 46 | --------|------- 47 | master | [glsl-gaussian-demo](https://realazthat.github.io/glsl-gaussian/master/www/glsl-gaussian-demo/index.html) 48 | | [glsl-gaussian-live-demo](https://realazthat.github.io/glsl-gaussian/master/www/glsl-gaussian-live-demo/index.html) 49 | develop | [glsl-gaussian-demo](https://realazthat.github.io/glsl-gaussian/develop/www/glsl-gaussian-demo/index.html) 50 | | [glsl-gaussian-live-demo](https://realazthat.github.io/glsl-gaussian/develop/www/glsl-gaussian-live-demo/index.html) 51 | 52 | **Results:** 53 | 54 | (Image credit: [Storm Cell Over the Southern Appalachian Mountains](http://www.nasa.gov/content/storm-cell-over-the-southern-appalachian-mountains), 55 | *NASA / Stu Broce*, public domain by virtue of being created by NASA) 56 | 57 | Source Image | 58 | --------------| 59 | 60 | 61 | Gaussian Blur with radius 1 | Gaussian Blur with radius 2 | Gaussian Blur with radius 4 | 62 | ------------------------------|-----------------------------|-----------------------------| 63 | ||| 64 | 65 | 66 | ####Docs 67 | 68 | ``` 69 | const gaussian = require('./glsl-gaussian.js'); 70 | ``` 71 | 72 | ##### `gaussian.blur.gaussian.compute ({regl, texture, radius, fbos, currentFboIndex = 0, boxPasses = 3, outFbo = null, components = 'rgba', type = 'vec4', clipY = 1})` 73 | 74 | * Computes the guassian blur of the given texture. 75 | * `regl` - a regl context. 76 | * `radius` - The radius of the gaussian blur; that is to say, the kernel window around the pixel will be of size `(2*radius+1)X(2*radius+1)`. 77 | * `fbos` - an array with at least 2 regl FBOs, used for ping-ponging during processing; should prolly have 78 | a type of float (32-bit) for each channel. 79 | * `currentFboIndex` the regl FBO index in `fbos` array to begin at for ping-ponging. The function will begin by incrementing this 80 | value and using the next FBO in the array. The function will return a value in the form of 81 | `{currentFboIndex}` with the position of the last-used FBO. Defaults to `0`. 82 | * `outFbo` - destination regl FBO. Can be null, in which case the result will be left inside the `fbos` array 83 | on the last ping-pong; the return value with be of the form `{currentFboIndex}` so that you 84 | can retrieve it. 85 | * `components` - a string indicating which components need to be processed and blurred; defaults to `'rgba'`. 86 | * `type` - a glsl type in string format indicating the type that can hold the components that need to be processed; defaults to `'vec4'`. 87 | * `clipY` - a value that represents the clipspace y multiple; a default value of `1` indicates opengl-style lower-left-corner-as-origin; 88 | a value of `-1` would mean a upper-left-corner-as-origin. 89 | 90 | 91 | 92 | ##### `gaussian.blur.box.shader.vert ({textureWidth, textureHeight, radius, components = 'rgba', type = 'vec4'})` 93 | 94 | * Generate a vertex shader that computes the box blur from a Summed Area Table texture. 95 | Returns the vertex shader as a string. 96 | * `textureWidth` - The width of the inputtexture. 97 | * `textureHeight` - The height of the input texture. 98 | * `radius` - The radius of the box blur; that is to say, the box around the pixel will be of size `(2*radius+1)X(2*radius+1)`. 99 | * `components` - a string indicating which components need to be processed and blurred; defaults to `'rgba'`. 100 | * `type` - a glsl type in string format indicating the type that can hold the components that need to be processed; defaults to `'vec4'`. 101 | 102 | 103 | 104 | ##### `gaussian.blur.box.shader.frag ({textureWidth, textureHeight, radius, components = 'rgba', type = 'vec4'})` 105 | 106 | * Generate a fragment shader that computes the box blur from a Summed Area Table texture. 107 | Returns the fragment shader as a string. 108 | * See `gaussian.box.shader.vert()` for params. 109 | 110 | ##### `gaussian.blur.box.compute ({regl, src, radius, outFbo = null, components = 'rgba', type = 'vec4', clipY = 1})` 111 | 112 | * Given an input texture, will compute the box blur. 113 | * `regl` - a regl context. 114 | * `src` - A dictionary of the form `{satTexture}` OR `{texture, fbos, currentFboIndex}`. 115 | * In the first form, the Summed Area Table 116 | is provided by you, the user. No FBOs are needed, just be sure to specify the `outFbo` argument. 117 | * In the second form, you provide the input texture yourself (via the `texture` argument), and FBOs 118 | for pingponging during computation of SAT. The FBOs should be an array of at least 2. The FBOs 119 | should prolly be of a high precision type (such as float 32 bit). `currentFboIndex` will be 120 | returned in the form `{currentFboIndex}` representing the last-used FBO. If `outFbo` is not 121 | specified, then this FBO slot will hold the result of the blur. 122 | * `radius` - The radius of the box blur; that is to say, the box around the pixel will be of size (2*radius+1). 123 | * `outFbo` - Destination regl FBO. Can be null, in which case `src.fbos` is expected to exist; the result of 124 | of the computation will be left inside the `src.fbos` array on the last ping-pong; the return 125 | value with be of the form `{currentFboIndex}` so that you can retrieve it. 126 | * `components` - a string indicating which components need to be processed and blurred; defaults to `'rgba'`. 127 | * `type` - a glsl type in string format indicating the type that can hold the components that need to be processed; defaults to `'vec4'`. 128 | * `clipY` - a value that represents the clipspace y multiple; a default value of `1` indicates opengl-style lower-left-corner-as-origin; 129 | a value of `-1` would mean a upper-left-corner-as-origin. 130 | 131 | 132 | ####Usage 133 | 134 | See `glsl-gaussian-demo.js` for a full demo using [regl](https://github.com/mikolalysenko/regl) 135 | and [resl](https://github.com/mikolalysenko/resl). 136 | 137 | An excerpt: 138 | 139 | ``` 140 | 141 | gaussian.blur.gaussian.compute({regl, texture, radius, fbos, outFbo, components: 'rgb', type: 'vec3'}); 142 | 143 | 144 | ``` 145 | 146 | 147 | -------------------------------------------------------------------------------- /assets/ASSETS.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | See [wikimedia commons on policy of accepting public domain from NASA](https://commons.wikimedia.org/wiki/Template:PD-USGov-NASA). 4 | 5 | * `Storm Cell Over the Southern Appalachian Mountains-dsc_2303_0-256x256.jpg` 6 | Public Domain, from NASA: [Storm Cell Over the Southern Appalachian Mountains](http://www.nasa.gov/content/storm-cell-over-the-southern-appalachian-mountains) 7 | -------------------------------------------------------------------------------- /assets/Storm Cell Over the Southern Appalachian Mountains-dsc_2303_0-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-gaussian/dbc39183d41f9e69d4211d623251ac5956c24a64/assets/Storm Cell Over the Southern Appalachian Mountains-dsc_2303_0-256x256.png -------------------------------------------------------------------------------- /assets/result-256x256x-r1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-gaussian/dbc39183d41f9e69d4211d623251ac5956c24a64/assets/result-256x256x-r1.png -------------------------------------------------------------------------------- /assets/result-256x256x-r16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-gaussian/dbc39183d41f9e69d4211d623251ac5956c24a64/assets/result-256x256x-r16.png -------------------------------------------------------------------------------- /assets/result-256x256x-r2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-gaussian/dbc39183d41f9e69d4211d623251ac5956c24a64/assets/result-256x256x-r2.png -------------------------------------------------------------------------------- /assets/result-256x256x-r4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-gaussian/dbc39183d41f9e69d4211d623251ac5956c24a64/assets/result-256x256x-r4.png -------------------------------------------------------------------------------- /assets/result-256x256x-r8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-gaussian/dbc39183d41f9e69d4211d623251ac5956c24a64/assets/result-256x256x-r8.png -------------------------------------------------------------------------------- /glsl-gaussian-demo.js: -------------------------------------------------------------------------------- 1 | 2 | const $ = require('jquery-browserify'); 3 | const resl = require('resl'); 4 | const regl = require('regl')({ 5 | extensions: ['OES_texture_float'], 6 | // TODO: FIXME: dunno why we need this here, we do not read non-uint8 data from screen, 7 | // but it fails without this on gh-pages for some reason. 8 | attributes: {preserveDrawingBuffer: true} 9 | }); 10 | 11 | const gaussian = require('./glsl-gaussian.js'); 12 | const quad = require('glsl-quad'); 13 | 14 | // command to copy a texture to an FBO, assumes the texture is in opengl-order 15 | // where the origin is the lower left of the texture. 16 | const drawTextureToFbo = regl({ 17 | frag: quad.shader.frag, 18 | vert: quad.shader.vert, 19 | attributes: { 20 | a_position: quad.verts, 21 | a_uv: quad.uvs 22 | }, 23 | elements: quad.indices, 24 | uniforms: { 25 | u_tex: regl.prop('texture'), 26 | u_clip_y: 1 27 | }, 28 | framebuffer: regl.prop('fbo') 29 | }); 30 | 31 | // command to copy a texture to an FBO, but flipping the Y axis so that the uvs begin 32 | // at the upper right corner, so that it can be drawn to canvas etc. 33 | const drawToCanvasFBO = regl({ 34 | frag: quad.shader.frag, 35 | vert: quad.shader.vert, 36 | attributes: { 37 | a_position: quad.verts, 38 | a_uv: quad.uvs 39 | }, 40 | elements: quad.indices, 41 | uniforms: { 42 | u_tex: regl.prop('texture'), 43 | u_clip_y: -1 44 | }, 45 | framebuffer: regl.prop('fbo') 46 | }); 47 | 48 | function dataURIFromFBO ({fbo, width, height, regl}) { 49 | let canvasFBO = regl.framebuffer({ 50 | color: regl.texture({ 51 | width: width, 52 | height: height, 53 | stencil: false, 54 | format: 'rgba', 55 | type: 'uint8', 56 | depth: false, 57 | wrap: 'clamp', 58 | mag: 'nearest', 59 | min: 'nearest' 60 | }) 61 | }); 62 | 63 | let data = []; 64 | try { 65 | drawToCanvasFBO({texture: fbo.color[0], fbo: canvasFBO}); 66 | 67 | let bindFbo = regl({framebuffer: canvasFBO}); 68 | bindFbo(function () { 69 | data = regl.read(); 70 | }); 71 | } finally { 72 | canvasFBO.destroy(); 73 | } 74 | 75 | var canvas = document.createElement('canvas'); 76 | canvas.width = width; 77 | canvas.height = height; 78 | var context = canvas.getContext('2d'); 79 | 80 | // Copy the pixels to a 2D canvas 81 | var imageData = context.createImageData(width, height); 82 | imageData.data.set(data); 83 | context.putImageData(imageData, 0, 0); 84 | 85 | return canvas.toDataURL(); 86 | } 87 | 88 | resl({ 89 | manifest: { 90 | texture: { 91 | type: 'image', 92 | src: './assets/Storm Cell Over the Southern Appalachian Mountains-dsc_2303_0-256x256.png', 93 | parser: (data) => regl.texture({ 94 | data: data, 95 | mag: 'nearest', 96 | min: 'nearest', 97 | flipY: true 98 | }) 99 | } 100 | }, 101 | onDone: ({texture, digitsTexture}) => { 102 | // make a bunch of fbos for ping-ponging intermediate computations, and the output buffer etc. 103 | let fbos = [null, null, null, null].map(function () { 104 | return regl.framebuffer({ 105 | color: regl.texture({ 106 | width: texture.width, 107 | height: texture.height, 108 | stencil: false, 109 | format: 'rgba', 110 | type: 'float', 111 | depth: false, 112 | wrap: 'clamp', 113 | mag: 'nearest', 114 | min: 'nearest' 115 | }), 116 | stencil: false, 117 | depth: false, 118 | depthStencil: false, 119 | wrap: 'clamp', 120 | mag: 'nearest', 121 | min: 'nearest' 122 | }); 123 | }); 124 | 125 | // use one FBO for the output. 126 | let outFbo = fbos.pop(); 127 | 128 | // and another for the input, for later use. 129 | let inFbo = fbos.pop(); 130 | 131 | let radius = 1; 132 | gaussian.blur.gaussian.compute({regl, texture, radius, fbos, outFbo: outFbo, components: 'rgb', type: 'vec3'}); 133 | 134 | let upscaledCellWidth = 16; 135 | let upscaledCellHeight = 16; 136 | let upscaledWidth = texture.width * Math.max(upscaledCellWidth, upscaledCellHeight); 137 | let upscaledHeight = texture.height * Math.max(upscaledCellWidth, upscaledCellHeight); 138 | 139 | // copy the input texture to the `inFbo`. 140 | drawTextureToFbo({texture, fbo: inFbo}); 141 | 142 | // draw the stuff to img tags, and put everything into the DOM for display. 143 | 144 | let $srcDiv = $('
').css('text-align', 'center').appendTo('body'); 145 | $('

').appendTo($srcDiv).css('text-align', 'center').text('Source image'); 146 | 147 | let $resultDiv = $('
').css('text-align', 'center').appendTo('body'); 148 | $('

').appendTo($resultDiv).css('text-align', 'center').text('Result image'); 149 | 150 | function figureTemplate ({src, captionHtml = '', alt = ''}) { 151 | return ` 152 |
153 | ${alt} 154 |
${captionHtml}
155 |
156 | `; 157 | } 158 | 159 | upscaledWidth = texture.width; 160 | upscaledHeight = texture.height; 161 | 162 | let $srcImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: inFbo, width: upscaledWidth, height: upscaledHeight, regl}), 163 | alt: 'Source image', 164 | captionHtml: 'Source image'})); 165 | // let $srcNumbersImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: inNumbersFBO, width: upscaledWidth, height: upscaledHeight, regl}), 166 | // alt: 'Source image numerified red', 167 | // captionHtml: 'Source image, numerified red'})); 168 | let $resultImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: outFbo, width: upscaledWidth, height: upscaledHeight, regl}), 169 | alt: 'Result blurred image', 170 | captionHtml: `Result blurred image (kernel radius of ${radius})`})); 171 | // let $resultNumbersImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: outNumbersFBO, width: upscaledWidth, height: upscaledHeight, regl}), 172 | // alt: 'Result blurred image numerified red', 173 | // captionHtml: 'Result blurred image, numerified red'})); 174 | 175 | $($srcImg).css('display', 'inline-block').appendTo($srcDiv); 176 | // $($srcNumbersImg).css('display', 'inline-block').appendTo($srcDiv); 177 | $($resultImg).css('display', 'inline-block').appendTo($resultDiv); 178 | // $($resultNumbersImg).css('display', 'inline-block').appendTo($resultDiv); 179 | } 180 | }); 181 | -------------------------------------------------------------------------------- /glsl-gaussian-live-demo.js: -------------------------------------------------------------------------------- 1 | 2 | const nunjucks = require('nunjucks'); 3 | const $ = require('jquery-browserify'); 4 | const resl = require('resl'); 5 | const regl = require('regl')({ 6 | extensions: ['OES_texture_float'], 7 | // TODO: FIXME: dunno why we need this here, we do not read non-uint8 data from screen, 8 | // but it fails without this on gh-pages for some reason. 9 | attributes: {preserveDrawingBuffer: true}, 10 | profile: true 11 | }); 12 | 13 | const gaussian = require('./glsl-gaussian.js'); 14 | const quad = require('glsl-quad'); 15 | var μs = require('microseconds'); 16 | 17 | resl({ 18 | manifest: { 19 | texture: { 20 | type: 'image', 21 | src: 'assets/Storm Cell Over the Southern Appalachian Mountains-dsc_2303_0-256x256.png', 22 | parser: (data) => regl.texture({ 23 | data: data, 24 | mag: 'nearest', 25 | min: 'nearest', 26 | flipY: true 27 | }) 28 | } 29 | }, 30 | onDone: ({texture}) => { 31 | // make a bunch of fbos for ping-ponging intermediate computations, and the output buffer etc. 32 | let fbos = [null, null, null, null].map(function () { 33 | return regl.framebuffer({ 34 | color: regl.texture({ 35 | width: texture.width, 36 | height: texture.height, 37 | stencil: false, 38 | format: 'rgba', 39 | type: 'float', 40 | depth: false, 41 | wrap: 'clamp', 42 | mag: 'nearest', 43 | min: 'nearest' 44 | }), 45 | stencil: false, 46 | depth: false, 47 | depthStencil: false, 48 | wrap: 'clamp', 49 | mag: 'nearest', 50 | min: 'nearest' 51 | }); 52 | }); 53 | 54 | // use one FBO for the output. 55 | let outFbo = fbos.pop(); 56 | 57 | let draw = regl({ 58 | frag: quad.shader.frag, 59 | vert: quad.shader.vert, 60 | attributes: { 61 | a_position: quad.verts, 62 | a_uv: quad.uvs 63 | }, 64 | elements: quad.indices, 65 | uniforms: { 66 | u_tex: regl.prop('texture'), 67 | u_clip_y: +1 68 | }, 69 | viewport: { 70 | x: regl.prop('x'), 71 | y: regl.prop('y'), 72 | width: texture.width, 73 | height: texture.height 74 | } 75 | }); 76 | 77 | let maxRadius = Math.max(texture.width, texture.height); 78 | 79 | $('canvas').css('z-index', '-10'); 80 | let $page = $('
') 81 | .css('z-index', '10') 82 | .css('background-color', '#49A259') 83 | .css('color', '#CBCE92') 84 | .appendTo($('body')); 85 | 86 | let $controlsDiv = $('
').appendTo($page); 87 | 88 | let template = ` 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |
Radius
FPS (approximate rolling average)
103 | `; 104 | 105 | $controlsDiv.html(nunjucks.renderString(template)); 106 | 107 | // ------------------------------------------------------------------------- 108 | 109 | let $radiusControl = $('#radius-control'); 110 | let $radiusView = $('#radius-view'); 111 | $radiusView.text($radiusControl.val()); 112 | 113 | // ------------------------------------------------------------------------- 114 | 115 | let $FPSView = $('#fps-view'); 116 | $FPSView.text('0'); 117 | // ------------------------------------------------------------------------- 118 | 119 | function approxRollingAverage (avg, newSample, N) { 120 | // from http://stackoverflow.com/a/16757630/586784 121 | avg -= avg / N; 122 | avg += newSample / N; 123 | 124 | return avg; 125 | } 126 | 127 | let rollingFPSAvg = 0; 128 | let rollingFPSAvgN = 20; 129 | 130 | regl.frame(function ({viewportWidth, viewportHeight}) { 131 | let radius = parseInt($radiusControl.val()); 132 | 133 | let tt0 = μs.now(); 134 | gaussian.blur.gaussian.compute({regl, texture, radius, fbos, outFbo, components: 'rgb', type: 'vec3'}); 135 | 136 | let deltaSeconds = μs.since(tt0) / 1000000; 137 | 138 | rollingFPSAvg = approxRollingAverage(rollingFPSAvg, 1 / deltaSeconds, rollingFPSAvgN); 139 | 140 | regl.clear({ 141 | color: [0, 0, 0, 1], 142 | depth: 1, 143 | stencil: 0 144 | }); 145 | 146 | let x = viewportWidth / 2.0 - texture.width; 147 | let y = viewportHeight / 2.0 - texture.height / 2.0; 148 | draw({texture: texture, x, y}); 149 | 150 | x += texture.width; 151 | draw({texture: outFbo.color[0], x, y}); 152 | 153 | // ----------------------------------------------------------------------- 154 | $radiusView.text('' + radius); 155 | $FPSView.text('' + rollingFPSAvg.toFixed(2)); 156 | }); 157 | } 158 | }); 159 | -------------------------------------------------------------------------------- /glsl-gaussian.js: -------------------------------------------------------------------------------- 1 | 2 | const quad = require('glsl-quad'); 3 | const sat = require('glsl-sat'); 4 | 5 | function makeBoxBlurVShader ({textureWidth, textureHeight, radius, components = 'rgba', type = 'vec4'}) { 6 | const hradius = (1 / textureWidth) * radius; 7 | const vradius = (1 / textureHeight) * radius; 8 | 9 | const upperOffset = `vec2( ${hradius}, ${vradius})`; 10 | const lowerOffset = `vec2(-${hradius + 1.0 / textureWidth}, -${vradius + 1.0 / textureHeight})`; 11 | 12 | const vert = ` 13 | precision highp float; 14 | attribute vec2 a_position; 15 | attribute vec2 a_uv; 16 | varying vec2 v_upper; 17 | varying vec2 v_lower; 18 | varying vec2 v_UL; 19 | varying vec2 v_LR; 20 | const vec2 upper_offset = ${upperOffset}; 21 | const vec2 lower_offset = ${lowerOffset}; 22 | uniform float u_clip_y; 23 | 24 | void main() { 25 | v_upper = a_uv + upper_offset; 26 | v_lower = a_uv + lower_offset; 27 | v_UL = a_uv + vec2(lower_offset.x, upper_offset.y); 28 | v_LR = a_uv + vec2(upper_offset.x, lower_offset.y); 29 | gl_Position = vec4(a_position.xy * vec2(1, u_clip_y), 0, 1); 30 | } 31 | `; 32 | return vert; 33 | } 34 | 35 | function makeBoxBlurFShader ({textureWidth, textureHeight, radius, components = 'rgba', type = 'vec4'}) { 36 | const pixelDelta = `1.0/vec2(${textureWidth}, ${textureHeight})`; 37 | const area = (2 * radius + 1) * (2 * radius + 1); 38 | const frag = ` 39 | precision highp float; 40 | 41 | const highp float kernel_area = float(${area}); 42 | const highp vec2 pixel_delta = ${pixelDelta}; 43 | const highp vec2 min_lower = vec2(0) - (pixel_delta / 2.0); 44 | const highp vec2 max_upper = vec2(1) - (pixel_delta / 2.0); 45 | varying vec2 v_upper; 46 | varying vec2 v_lower; 47 | varying vec2 v_UL; 48 | varying vec2 v_LR; 49 | uniform sampler2D u_sat_texture; 50 | void main () { 51 | highp ${type} result = ${type}(0); 52 | result += texture2D(u_sat_texture, v_upper).${components}; 53 | result -= any(lessThan(v_UL, vec2(0))) ? ${type}(0.0) : texture2D(u_sat_texture, v_UL).${components}; 54 | result -= any(lessThan(v_LR, vec2(0))) ? ${type}(0.0) : texture2D(u_sat_texture, v_LR).${components}; 55 | result += any(lessThan(v_lower, vec2(0))) ? ${type}(0.0) : texture2D(u_sat_texture, v_lower).${components}; 56 | 57 | vec2 actual_lower = max(v_lower, min_lower); 58 | vec2 actual_upper = min(v_upper, max_upper); 59 | 60 | vec2 actual_kernel_surface = (actual_upper - actual_lower)/pixel_delta; 61 | float actual_kernel_area = actual_kernel_surface.x * actual_kernel_surface.y; 62 | result /= actual_kernel_area; 63 | 64 | gl_FragColor = vec4(1); 65 | gl_FragColor.${components} = result; 66 | } 67 | `; 68 | return frag; 69 | } 70 | 71 | function computeBoxBlur ({regl, src, radius, outFbo = null, components = 'rgba', type = 'vec4', clipY = 1}) { 72 | if ((outFbo === null || outFbo === undefined) && (src.fbos === null || src.fbos === undefined)) { 73 | throw new Error('`outFbo` is null and `src.fbos` is null; nowhere to put output'); 74 | } 75 | 76 | let satTexture = src.satTexture; 77 | let currentFboIndex = src.currentFboIndex; 78 | 79 | if (satTexture === undefined || satTexture === null) { 80 | ({currentFboIndex} = sat.computeSat({regl, texture: src.texture, fbos: src.fbos, currentFboIndex: src.currentFboIndex, components, type, clipY})); 81 | src.currentFboIndex = currentFboIndex; 82 | satTexture = src.fbos[src.currentFboIndex].color[0]; 83 | } 84 | 85 | const textureWidth = satTexture.width; 86 | const textureHeight = satTexture.height; 87 | 88 | const vert = makeBoxBlurVShader({textureWidth, textureHeight, radius, components, type}); 89 | const frag = makeBoxBlurFShader({textureWidth, textureHeight, radius, components, type}); 90 | 91 | const draw = regl({ 92 | vert: vert, 93 | frag: frag, 94 | attributes: { 95 | a_position: quad.verts, 96 | a_uv: quad.uvs 97 | }, 98 | elements: quad.indices, 99 | uniforms: { 100 | u_sat_texture: regl.prop('satTexture'), 101 | u_clip_y: clipY 102 | }, 103 | framebuffer: regl.prop('fbo') 104 | }); 105 | 106 | if (outFbo !== undefined && outFbo !== null) { 107 | draw({satTexture: satTexture, fbo: outFbo}); 108 | } else { 109 | src.currentFboIndex = (src.currentFboIndex + 1) % src.fbos.length; 110 | draw({satTexture: satTexture, fbo: src.fbos[src.currentFboIndex]}); 111 | } 112 | return {currentFboIndex}; 113 | } 114 | 115 | function computeGaussian ({ regl, texture, radius, fbos, currentFboIndex = 0 116 | , boxPasses = 3, outFbo = null, components = 'rgba', type = 'vec4', clipY = 1}) { 117 | if (fbos.length < 2) { 118 | throw new Error('fbos.length must be at least 2'); 119 | } 120 | 121 | let lastBoxBlurPass = boxPasses - 1; 122 | 123 | for (let boxBlurPass = 0; boxBlurPass < boxPasses; ++boxBlurPass) { 124 | let passInTexture = fbos[currentFboIndex].color[0]; 125 | if (boxBlurPass === 0) { 126 | passInTexture = texture; 127 | } 128 | 129 | ({currentFboIndex} = sat.computeSat({regl, texture: passInTexture, fbos: fbos, currentFboIndex, components, type, clipY})); 130 | 131 | let satFbo = fbos[currentFboIndex]; 132 | 133 | if (boxBlurPass === lastBoxBlurPass && (outFbo !== null && outFbo !== undefined)) { 134 | computeBoxBlur({regl, radius, src: {satTexture: satFbo.color[0]}, outFbo: outFbo, components, type, clipY}); 135 | } else { 136 | currentFboIndex = (currentFboIndex + 1) % fbos.length; 137 | let blurredFbo = fbos[currentFboIndex]; 138 | computeBoxBlur({regl, radius, src: {satTexture: satFbo.color[0]}, outFbo: blurredFbo, components, type, clipY}); 139 | } 140 | } 141 | 142 | return {currentFboIndex}; 143 | } 144 | 145 | // function makeSubsampleVShader () { 146 | // return ` 147 | // precision medium float; 148 | // attribute vec2 a_position; 149 | // attribute vec2 a_uv; 150 | // uniform float u_clip_y; 151 | 152 | // varying vec2 v_uv; 153 | 154 | // void main() { 155 | // v_uv = a_uv; 156 | 157 | // gl_Position = vec4(a_position.xy * vec2(1, u_clip_y), 0, 1); 158 | // } 159 | // `; 160 | // } 161 | 162 | // function makeSubsampleFShader (sourceSize, destinationSize, components = 'rgba', type = 'vec4') { 163 | // return ` 164 | // precision highp float; 165 | 166 | // varying vec2 v_uv; 167 | // uniform sampler2D u_texture; 168 | 169 | // void main () { 170 | 171 | // gl_FragColor.${components} = result; 172 | // } 173 | // `; 174 | // } 175 | 176 | // function subSample ({texture, outFbo}) { 177 | 178 | // const draw = regl({ 179 | // frag: frag, 180 | // vert: vert, 181 | // attributes: { 182 | // a_position: quad.verts, 183 | // a_uv: quad.uvs 184 | // }, 185 | // elements: quad.indices, 186 | // uniforms: { 187 | // u_texture: regl.prop('texture'), 188 | // u_clip_y: clipY 189 | // }, 190 | // framebuffer: regl.prop('fbo') 191 | // }); 192 | // } 193 | 194 | const blur = { 195 | box: { 196 | shader: { 197 | vert: makeBoxBlurVShader, 198 | frag: makeBoxBlurFShader 199 | }, 200 | compute: computeBoxBlur 201 | }, 202 | gaussian: { 203 | compute: computeGaussian 204 | } 205 | }; 206 | 207 | module.exports = {blur}; 208 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glsl-gaussian", 3 | "version": "0.1.0", 4 | "description": "glsl-gaussian is a shader generator for WebGL, to generate a gaussian blur of an input texture", 5 | "main": "glsl-gaussian.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/realazthat/glsl-gaussian.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/realazthat/glsl-gaussian/issues" 12 | }, 13 | "author": "Azriel Fasten", 14 | "license": "MIT", 15 | "semistandard": { 16 | "ignore": [ 17 | "dist/*", 18 | "www/*" 19 | ] 20 | }, 21 | "dependencies": { 22 | "glsl-quad": "1.0.0", 23 | "glsl-sat": "^0.1.0", 24 | "regl": "git://github.com/mikolalysenko/regl#gh-pages" 25 | }, 26 | "devDependencies": { 27 | "browserify": "^13.1.0", 28 | "disc": "^1.3.2", 29 | "glsl-numerify": "1.0.0", 30 | "indexhtmlify": "^1.3.0", 31 | "jquery-browserify": "^1.8.1", 32 | "microseconds": "^0.1.0", 33 | "mkdirp": "^0.5.1", 34 | "ncp": "^2.0.0", 35 | "nunjucks": "~1.3.4", 36 | "resl": "^1.0.1", 37 | "semistandard": "~8.0.0", 38 | "snazzy": "^4.0.0" 39 | }, 40 | "keywords": [ 41 | "webgl", 42 | "gl", 43 | "graphics", 44 | "computer graphics", 45 | "opengl", 46 | "glsl", 47 | "data", 48 | "shader", 49 | "image processing", 50 | "dsp", 51 | "convolution", 52 | "kernel", 53 | "filter", 54 | "blur", 55 | "summed area table", 56 | "box blur", 57 | "gaussian", 58 | "downsample", 59 | "downsampling", 60 | "subsample", 61 | "subsampling", 62 | "scaling", 63 | "mipmap" 64 | ], 65 | "scripts": { 66 | "mytest": "semistandard | snazzy", 67 | "build": "npm run build-script && npm run build-demo && npm run build-live-demo", 68 | "build-script": "mkdir -p ./dist && browserify glsl-gaussian.js --standalone glsl-gaussian > ./dist/glsl-gaussian.js", 69 | "build-demo": "node scripts/build-demo.js", 70 | "build-live-demo": "node scripts/build-live-demo.js" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /scripts/build-a-demo.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs'); 3 | const mkdirp = require('mkdirp'); 4 | const ncp = require('ncp'); 5 | const browserify = require('browserify'); 6 | 7 | function buildADemo ({BUILDDIR, MAINJSFILE, MAINHTMLFILE, TITLE}) { 8 | const HTMLCONTENT = ` 9 | 10 | 11 | 12 | 13 | ${TITLE} 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | `; 24 | 25 | mkdirp(BUILDDIR, function (err) { 26 | if (err) { 27 | throw err; 28 | } 29 | ncp('assets', `${BUILDDIR}/assets`, function (err) { 30 | if (err) { 31 | throw err; 32 | } 33 | }); 34 | 35 | let b = browserify({debug: true}); 36 | b.add(MAINJSFILE); 37 | b.bundle(function (err, bundle) { 38 | if (err) { 39 | throw err; 40 | } 41 | console.log('bundled', MAINJSFILE); 42 | 43 | fs.writeFile(`${BUILDDIR}/${MAINJSFILE}`, bundle, function (err) { 44 | if (err) { 45 | throw err; 46 | } 47 | }); 48 | }); 49 | 50 | fs.writeFile(`${BUILDDIR}/${MAINHTMLFILE}`, HTMLCONTENT, function (err) { 51 | if (err) { 52 | throw err; 53 | } 54 | }); 55 | }); 56 | } 57 | 58 | module.exports = {buildADemo}; 59 | -------------------------------------------------------------------------------- /scripts/build-demo.js: -------------------------------------------------------------------------------- 1 | 2 | var builder = require('./build-a-demo.js'); 3 | 4 | const BUILDDIR = './www/glsl-gaussian-demo/'; 5 | const MAINJSFILE = 'glsl-gaussian-demo.js'; 6 | const MAINHTMLFILE = 'index.html'; 7 | const TITLE = 'glsl-gaussian Static Demo'; 8 | 9 | builder.buildADemo({BUILDDIR, MAINJSFILE, MAINHTMLFILE, TITLE}); 10 | -------------------------------------------------------------------------------- /scripts/build-live-demo.js: -------------------------------------------------------------------------------- 1 | 2 | var builder = require('./build-a-demo.js'); 3 | 4 | const BUILDDIR = './www/glsl-gaussian-live-demo/'; 5 | const MAINJSFILE = 'glsl-gaussian-live-demo.js'; 6 | const MAINHTMLFILE = 'index.html'; 7 | const TITLE = 'glsl-gaussian Live Demo'; 8 | 9 | builder.buildADemo({BUILDDIR, MAINJSFILE, MAINHTMLFILE, TITLE}); 10 | -------------------------------------------------------------------------------- /scripts/publish-static-demos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -exv 4 | 5 | PROJECT_PATH="$PWD" 6 | 7 | 8 | PUBLISH_BRANCHES="master develop" 9 | if [ "$TRAVIS_REPO_SLUG" == "realazthat/glsl-gaussian" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [[ $PUBLISH_BRANCHES =~ "$TRAVIS_BRANCH" ]]; then 10 | 11 | echo -e "Publishing generated static data...\n" 12 | 13 | 14 | cd $HOME 15 | 16 | rm -rf gh-pages 17 | git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/realazthat/glsl-gaussian gh-pages > /dev/null 18 | 19 | cd gh-pages 20 | touch .nojekyll 21 | 22 | git rm -rf --ignore-unmatch "./$TRAVIS_BRANCH/www" 23 | git rm -rf --ignore-unmatch "./$TRAVIS_BRANCH/dist" 24 | mkdir -p "./$TRAVIS_BRANCH/." 25 | echo $TRAVIS_BUILD_NUMBER > "./$TRAVIS_BRANCH/travis_build_number" 26 | cp -Rf "$PROJECT_PATH/www/" "./$TRAVIS_BRANCH/." 27 | cp -Rf "$PROJECT_PATH/dist/" "./$TRAVIS_BRANCH/." 28 | git add -f . 29 | git -c user.email="travis@travis-ci.org" -c user.name="travis-ci" \ 30 | commit -m "Latest 'generated static data' on successful travis build $TRAVIS_BUILD_NUMBER auto-pushed to gh-pages/$TRAVIS_BRANCH" 31 | git push -fq origin gh-pages > /dev/null 32 | 33 | echo -e "Published generated static data to gh-pages.\n" 34 | 35 | fi 36 | --------------------------------------------------------------------------------