├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── assets ├── sat-32x32.png ├── sat-8x8-upscaled-to-256x256.png └── sat-8x8.png ├── glsl-sat-demo.js ├── glsl-sat.js ├── package.json └── scripts └── 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 | Sq3mj3BFGc090A+I1nBpvMHC4up6jXQF1Tz22IBeTAPdygVrOWl9YDOjG0+jEloRrX/GYvXhluAgb8b4RHmvs+ZlYRykn6Y4HFBcnT88T04tzdBj+huPjOYo3gvw7u8o9Wh/4LfIgOOk9js3TKtqxh7h8GAqsU++5UIP54GPlrLYvC+wJ5FslYElWmIDhCrXBZERGjEGNRweaVeVorE1vNiyqvB5NYYF728fQuiwQKyOGQYQAlyYan80lPiQEzWVQWPblUocZqgawYC/MgQSU0xs5R+p+PcQyV/Px60A/jZjgNFhFwdJea/GzrVD9xPbMU+YNOM5NDKSLDvSUMABhodbDdc+rlOgC5Qj6H2FdZM+I7CP8a1w+fPNxJ9q3FCV4wp7j5RimbA9U0JckPcv2HOx1UV2Yx0/pTfRJiHNDgqUDft69ZjcaghEvmTuC+uC99N/YudUWDJad+KPf3ETuUHubqrqOzJl0tTkNYnyJ5b/kVVBFdlAp+AH1uZCEP6BRfAZX1z1E7wMXNS4pQJFgPb8EXJtg5CFf5UTeLZuWuEEmRgw0YhH7DQTg0YP51xYh6AAIHneqbvxaTnUPVztIH1YDWgUKc4q46AshRy5SD5r/OUZw0G6ms5qOfsQ4CB5T36qgljLcKUvKVf4oxzX1ulyqZ40c6CnHfsg3X1uMZY= 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-sat 3 | --- 4 | 5 | 6 | ####Description 7 | 8 | glsl-sat is a shader generator for WebGL, to generate a summed-area-table texture of an input texture. 9 | 10 | Based on [**Summed-Area Tables Area Tables** And Their Application to Dynamic And Their Application to 11 | Dynamic Glossy Environment Reflections](http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/GDC2005_SATEnvironmentReflections.pdf) 12 | 13 | 14 | See `glsl-sat-demo.js` for usage. 15 | 16 | ####Dependencies 17 | 18 | * nodejs 19 | * browserify 20 | * [glsl-quad](https://github.com/realazthat/glsl-quad) 21 | * [regl](https://github.com/mikolalysenko/regl) 22 | * [glsl-numerify](https://github.com/realazthat/glsl-numerify) (for demo) 23 | * [resl](https://github.com/mikolalysenko/resl) (for demo) 24 | * budo (for quick demo as an alternative to running browserify) 25 | 26 | 27 | ####Demo 28 | 29 | To run the demo, run: 30 | 31 | ``` 32 | cd ./glsl-sat 33 | 34 | #install npm dependencies 35 | npm install 36 | 37 | #browser should open with the demo 38 | budo glsl-sat-demo.js --open 39 | 40 | 41 | ``` 42 | 43 | **Live:** 44 | 45 | branch | demo 46 | -------|------- 47 | master | [glsl-sat-demo](https://realazthat.github.io/glsl-sat/master/www/glsl-sat-demo/index.html) 48 | develop | [glsl-sat-demo](https://realazthat.github.io/glsl-sat/develop/www/glsl-sat-demo/index.html) 49 | 50 | 51 | Source Upscaled | Source Red Numerified | SAT Result Upscaled | SAT Result Red Numerified 52 | -----------------|-----------------------|---------------------|---------------------------- 53 | Source Upscaled | Source Red Numerified | SAT Result Upscaled | SAT Result Red Numerified 54 | 55 | 56 | 57 | ####Docs 58 | 59 | ``` 60 | const sat = require('./glsl-sat.js'); 61 | ``` 62 | 63 | ##### `sat.computeNumPasses ({textureSize, sampleSize})` 64 | 65 | * Computes the number of passes that will be required for a texture of this size, for a single direction. 66 | Actual number of passes will be double what this returns. 67 | * `textureSize` - the size of the texture in pixels. This should be the largest side. 68 | * `sampleSize` - Sample size is stuck at 16 right now, so use 16. 69 | 70 | ##### `sat.computeNumBitsRequired ({width, height, channelBitDepth})` 71 | 72 | * Computes the number of bits of precision required to process and hold the resulting SAT texture, in the 73 | intermediary and result FBOs. Note that this is theoretical; a few bits might be lost with 32 bit floats, 74 | experimentation required. 75 | * `width` the input texture width. 76 | * `height` the input texture height. 77 | * `channelBitDepth` the input texture bits per channel. 78 | 79 | ##### `sat.computeSat ({regl, texture, fbos, currentFboIndex = 0, outFbo = null, components = 'rgba', type = 'vec4', clipY = 1}) 80 | 81 | * Does all the heavy lifting and computes the summed area table. 82 | * `regl` - a regl context. 83 | * `texture` - the regl input texture. should prolly be in opengl form; where the origin uv is the lower left of the texture. 84 | * `fbos` - an array with at least 2 regl FBOs, used for ping-ponging during processing; should prolly have 85 | a type of float (32-bit) for each channel. 86 | * `currentFboIndex` the regl FBO index in `fbos` array to begin at for ping-ponging. The function will begin by incrementing this 87 | value and using the next FBO in the array. The function will return a value in the form of 88 | `{currentFboIndex}` with the position of the last-used FBO. Defaults to `0`. 89 | * `outFbo` - destination regl FBO. Can be null, in which case the SAT will be left inside the `fbos` array 90 | on the last ping-pong; the return value with be of the form `{currentFboIndex}` so that you 91 | can retrieve it. 92 | * `components` - a string indicating which components need to be processed and summed; defaults to `'rgba'`. 93 | * `type` - a glsl type in string format indicating the type that can hold the compnents that need to be processed; defaults to `'vec4'`. 94 | * `clipY` - a value that represents the clipspace y multiple; a default value of `1` indicates opengl-style lower-left-corner-as-origin; 95 | a value of `-1` would mean a upper-left-corner-as-origin. 96 | * returns a value in the form of `{currentFboIndex}` with the position of the last-used FBO. 97 | 98 | ####Usage 99 | 100 | See `glsl-sat-demo.js` for a full demo using [regl](https://github.com/mikolalysenko/regl) 101 | and [resl](https://github.com/mikolalysenko/resl). 102 | 103 | An excerpt: 104 | 105 | ``` 106 | 107 | computeSat({texture: texture, fbos: fbos, outFbo: outFbo, regl}); 108 | 109 | 110 | 111 | ``` 112 | 113 | 114 | -------------------------------------------------------------------------------- /assets/sat-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-sat/6383851d201e4a3548d62d3c8853a862fce3efd2/assets/sat-32x32.png -------------------------------------------------------------------------------- /assets/sat-8x8-upscaled-to-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-sat/6383851d201e4a3548d62d3c8853a862fce3efd2/assets/sat-8x8-upscaled-to-256x256.png -------------------------------------------------------------------------------- /assets/sat-8x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realazthat/glsl-sat/6383851d201e4a3548d62d3c8853a862fce3efd2/assets/sat-8x8.png -------------------------------------------------------------------------------- /glsl-sat-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 computeSat = require('./glsl-sat.js').computeSat; 12 | 13 | const numerify = require('glsl-numerify'); 14 | const quad = require('glsl-quad'); 15 | 16 | // command to copy a texture to an FBO, assumes the texture is in opengl-order 17 | // where the origin is the lower left of the texture. 18 | const drawTextureToFbo = regl({ 19 | frag: quad.shader.frag, 20 | vert: quad.shader.vert, 21 | attributes: { 22 | a_position: quad.verts, 23 | a_uv: quad.uvs 24 | }, 25 | elements: quad.indices, 26 | uniforms: { 27 | u_tex: regl.prop('texture'), 28 | u_clip_y: 1 29 | }, 30 | framebuffer: regl.prop('fbo') 31 | }); 32 | 33 | // command to copy a texture to an FBO, but flipping the Y axis so that the uvs begin 34 | // at the upper right corner, so that it can be drawn to canvas etc. 35 | const drawToCanvasFBO = regl({ 36 | frag: quad.shader.frag, 37 | vert: quad.shader.vert, 38 | attributes: { 39 | a_position: quad.verts, 40 | a_uv: quad.uvs 41 | }, 42 | elements: quad.indices, 43 | uniforms: { 44 | u_tex: regl.prop('texture'), 45 | u_clip_y: -1 46 | }, 47 | framebuffer: regl.prop('fbo') 48 | }); 49 | 50 | function dataURIFromFBO ({fbo, width, height, regl}) { 51 | let canvasFBO = regl.framebuffer({ 52 | color: regl.texture({ 53 | width: width, 54 | height: height, 55 | stencil: false, 56 | format: 'rgba', 57 | type: 'uint8', 58 | depth: false, 59 | wrap: 'clamp', 60 | mag: 'nearest', 61 | min: 'nearest' 62 | }) 63 | }); 64 | 65 | let data = []; 66 | try { 67 | drawToCanvasFBO({texture: fbo.color[0], fbo: canvasFBO}); 68 | 69 | let bindFbo = regl({framebuffer: canvasFBO}); 70 | bindFbo(function () { 71 | data = regl.read(); 72 | }); 73 | } finally { 74 | canvasFBO.destroy(); 75 | } 76 | 77 | var canvas = document.createElement('canvas'); 78 | canvas.width = width; 79 | canvas.height = height; 80 | var context = canvas.getContext('2d'); 81 | 82 | // Copy the pixels to a 2D canvas 83 | var imageData = context.createImageData(width, height); 84 | imageData.data.set(data); 85 | context.putImageData(imageData, 0, 0); 86 | 87 | return canvas.toDataURL(); 88 | } 89 | 90 | const sat8x8pnguri = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYA 91 | 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`.replace(/\s*/g, ''); 141 | 142 | resl({ 143 | manifest: { 144 | texture: { 145 | type: 'image', 146 | src: sat8x8pnguri, 147 | parser: (data) => regl.texture({ 148 | data: data, 149 | mag: 'nearest', 150 | min: 'nearest', 151 | flipY: true 152 | }) 153 | }, digitsTexture: { 154 | type: 'image', 155 | src: numerify.digits.uri, 156 | parser: (data) => regl.texture({ 157 | data: data, 158 | mag: 'nearest', 159 | min: 'nearest', 160 | flipY: true 161 | }) 162 | } 163 | }, 164 | onDone: ({texture, digitsTexture}) => { 165 | // make a bunch of fbos for ping-ponging intermediate computations, and the output buffer etc. 166 | let fbos = [null, null, null, null].map(function () { 167 | return regl.framebuffer({ 168 | color: regl.texture({ 169 | width: texture.width, 170 | height: texture.height, 171 | stencil: false, 172 | format: 'rgba', 173 | type: 'float', 174 | depth: false, 175 | wrap: 'clamp', 176 | mag: 'nearest', 177 | min: 'nearest' 178 | }), 179 | stencil: false, 180 | depth: false, 181 | depthStencil: false, 182 | wrap: 'clamp', 183 | mag: 'nearest', 184 | min: 'nearest' 185 | }); 186 | }); 187 | 188 | // use one FBO for the output. 189 | let outFbo = fbos.pop(); 190 | 191 | // and another for the input, for later use. 192 | let inFbo = fbos.pop(); 193 | 194 | computeSat({texture: texture, fbos: fbos, outFbo: outFbo, regl, components: 'rgb', type: 'vec3'}); 195 | 196 | let upscaledCellWidth = 32; 197 | let upscaledCellHeight = 32; 198 | let upscaledWidth = texture.width * Math.max(upscaledCellWidth, upscaledCellHeight); 199 | let upscaledHeight = texture.height * Math.max(upscaledCellWidth, upscaledCellHeight); 200 | 201 | // a command to take an input texture and "numerify" it and place it in a destination FBO. 202 | const drawNumbersToFbo = regl({ 203 | frag: numerify.makeFrag({ multiplier: 256.0, 204 | sourceSize: `vec2(${texture.width}, ${texture.height})`, 205 | destinationCellSize: `vec2(${upscaledCellWidth - 1}, ${upscaledCellHeight - 1})`, 206 | destinationSize: `vec2(${upscaledWidth}, ${upscaledHeight})`, 207 | component: 'r'}), 208 | vert: numerify.makeVert(), 209 | attributes: { 210 | a_position: quad.verts, 211 | a_uv: quad.uvs 212 | }, 213 | elements: quad.indices, 214 | uniforms: { 215 | digits_texture: digitsTexture, 216 | source_texture: regl.prop('texture'), 217 | u_clip_y: 1 218 | }, 219 | framebuffer: regl.prop('fbo') 220 | }); 221 | 222 | // allocate two FBOS to store the numerified textures. 223 | let numbersFBOs = [null, null].map(function () { 224 | return regl.framebuffer({ 225 | color: regl.texture({ 226 | width: upscaledWidth, 227 | height: upscaledHeight, 228 | stencil: false, 229 | format: 'rgba', 230 | type: 'uint8', 231 | depth: false, 232 | wrap: 'clamp', 233 | mag: 'nearest', 234 | min: 'nearest' 235 | }) 236 | }); 237 | }); 238 | 239 | // one FBO to store the input texture as a numerified texture. 240 | let inNumbersFBO = numbersFBOs[0]; 241 | // and a second FBO to store the SAT result texture as a numerified texture. 242 | let outNumbersFBO = numbersFBOs[1]; 243 | 244 | // copy the input texture to the `inFbo`. 245 | drawTextureToFbo({texture, fbo: inFbo}); 246 | // "numerify" the input texture. 247 | drawNumbersToFbo({texture: inFbo.color[0], fbo: inNumbersFBO}); 248 | // "numerify" the SAT result texture. 249 | drawNumbersToFbo({texture: outFbo.color[0], fbo: outNumbersFBO}); 250 | 251 | // draw the stuff to img tags, and put everything into the DOM for display. 252 | 253 | let $srcDiv = $('
').css('text-align', 'center').appendTo('body'); 254 | $('

').appendTo($srcDiv).css('text-align', 'center').text('Source image (upscaled)'); 255 | 256 | let $resultDiv = $('
').css('text-align', 'center').appendTo('body'); 257 | $('

').appendTo($resultDiv).css('text-align', 'center').text('Result image (upscaled)'); 258 | 259 | function figureTemplate ({src, captionHtml = '', alt = ''}) { 260 | return ` 261 |
262 | ${alt} 263 |
${captionHtml}
264 |
265 | `; 266 | } 267 | 268 | let $srcImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: inFbo, width: upscaledWidth, height: upscaledHeight, regl}), 269 | alt: 'Source image (Rescaled)', 270 | captionHtml: 'Source image (Rescaled)'})); 271 | let $srcNumbersImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: inNumbersFBO, width: upscaledWidth, height: upscaledHeight, regl}), 272 | alt: 'Source image numerified red', 273 | captionHtml: 'Source image, numerified red'})); 274 | let $satImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: outFbo, width: upscaledWidth, height: upscaledHeight, regl}), 275 | alt: 'Result SAT image (Rescaled)', 276 | captionHtml: 'Result SAT image (Rescaled)'})); 277 | let $satNumbersImg = $.parseHTML(figureTemplate({src: dataURIFromFBO({fbo: outNumbersFBO, width: upscaledWidth, height: upscaledHeight, regl}), 278 | alt: 'Result SAT image numerified red', 279 | captionHtml: 'Result SAT image, numerified red'})); 280 | 281 | $($srcImg).css('display', 'inline-block').appendTo($srcDiv); 282 | $($srcNumbersImg).css('display', 'inline-block').appendTo($srcDiv); 283 | $($satImg).css('display', 'inline-block').appendTo($resultDiv); 284 | $($satNumbersImg).css('display', 'inline-block').appendTo($resultDiv); 285 | } 286 | }); 287 | -------------------------------------------------------------------------------- /glsl-sat.js: -------------------------------------------------------------------------------- 1 | 2 | const quad = require('glsl-quad'); 3 | 4 | const makeVert = function makeVert ({passIndex, textureSize, direction}) { 5 | direction = direction.toLowerCase(); 6 | 7 | if (direction !== 'v' && direction !== 'h') { 8 | // console.error('direction is not "V" or "H" ... direction: ', direction); 9 | throw new Error('direction is not "V" or "H" ... direction: ' + direction); 10 | } 11 | 12 | let pixelSize = 1.0 / textureSize; 13 | let passOffset = Math.pow(16.0, passIndex) * pixelSize; 14 | 15 | let wzOffset = `vec2(${passOffset}, 0)`; 16 | let xyIOffset = `vec2((2.0 * float(i)) * float(${passOffset}), 0)`; 17 | let wzIOffset = `vec2((2.0 * float(i) + 1.0) * float(${passOffset}), 0)`; 18 | 19 | // console.log('direction:', direction); 20 | if (direction === 'v') { 21 | wzOffset = `vec2(0, ${passOffset})`; 22 | xyIOffset = `vec2(0, (2.0 * float(i)) * float(${passOffset}))`; 23 | wzIOffset = `vec2(0, (2.0 * float(i) + 1.0) * float(${passOffset}))`; 24 | } 25 | 26 | 27 | return ` 28 | precision highp float; 29 | attribute vec2 a_position; 30 | attribute vec2 a_uv; 31 | uniform float u_clip_y; 32 | varying vec4 v_sample_uvs[8]; 33 | 34 | 35 | void main() { 36 | gl_Position = vec4(a_position*vec2(1,u_clip_y), 0, 1); 37 | 38 | v_sample_uvs[0].xy = a_uv; 39 | v_sample_uvs[0].wz = v_sample_uvs[0].xy - ${wzOffset}; 40 | for (int i=1; i<8; i++) { 41 | v_sample_uvs[i].xy = v_sample_uvs[0].xy - ${xyIOffset}; 42 | v_sample_uvs[i].wz = v_sample_uvs[0].xy - ${wzIOffset}; 43 | } 44 | }`; 45 | }; 46 | 47 | const makeFrag = function makeFrag ({type = 'vec4', components = 'rgba'}) { 48 | return ` 49 | 50 | precision highp float; 51 | varying vec4 v_sample_uvs[8]; 52 | uniform highp sampler2D u_tex; 53 | void main () { 54 | 55 | highp ${type} t[8]; 56 | // add 16 texture samples with pyramidal scheme 57 | // to maintain precision 58 | for (int i=0; i<8; i++) { 59 | highp ${type} lhs, rhs; 60 | 61 | if (any(lessThan(v_sample_uvs[i].xy, vec2(0))) || any(greaterThan(v_sample_uvs[i].xy, vec2(1)))) 62 | lhs = ${type}(0.0); 63 | else 64 | lhs = texture2D(u_tex, v_sample_uvs[i].xy).${components}; 65 | 66 | if (any(lessThan(v_sample_uvs[i].wz, vec2(0))) || any(greaterThan(v_sample_uvs[i].wz, vec2(1)))) 67 | rhs = ${type}(0.0); 68 | else 69 | rhs = texture2D(u_tex, v_sample_uvs[i].wz).${components}; 70 | 71 | t[i] = lhs + rhs; 72 | } 73 | t[0] += t[1]; t[2] += t[3]; 74 | t[4] += t[5]; t[6] += t[7]; 75 | t[0] += t[2]; t[4] += t[6]; 76 | 77 | highp ${type} result = (t[0] + t[4]); 78 | 79 | gl_FragColor = vec4(1); 80 | // gl_FragColor = vec4(v_sample_uvs[0].y,0,0,1); 81 | gl_FragColor.${components} = result.${components}; 82 | // gl_FragColor = texture2D(u_tex, v_sample_uvs[0].xy); 83 | 84 | } 85 | `; 86 | }; 87 | 88 | function logtobase ({value, base}) { 89 | return Math.log(value) / Math.log(base); 90 | } 91 | 92 | function computeNumPasses ({textureSize, sampleSize}) { 93 | return Math.ceil(logtobase({value: textureSize, base: sampleSize})); 94 | } 95 | function computeNumBitsRequired ({width, height, channelBitDepth}) { 96 | return Math.ceil(Math.log2(width)) + Math.ceil(Math.log2(height)) + channelBitDepth; 97 | } 98 | 99 | function runPasses ({regl, inputTexture, textureSize, direction, passes, currentFboIndex, fbos, type = 'vec4', components = 'rgba', clipY = 1, outFbo = null}) { 100 | for (let passIndex = 0; passIndex < passes; ++passIndex) { 101 | let passInTtexture = passIndex === 0 ? inputTexture : fbos[currentFboIndex].color[0]; 102 | 103 | let vert = makeVert({ passIndex, textureSize: textureSize, direction: direction }); 104 | let frag = makeFrag({type, components}); 105 | 106 | const draw = regl({ 107 | vert: vert, 108 | frag: frag, 109 | attributes: { 110 | a_position: quad.verts, 111 | a_uv: quad.uvs 112 | }, 113 | elements: quad.indices, 114 | uniforms: { 115 | u_clip_y: clipY, 116 | u_tex: regl.prop('texture') 117 | }, 118 | framebuffer: regl.prop('fbo') 119 | }); 120 | 121 | if (outFbo !== null && outFbo !== undefined && passIndex === passes - 1) { 122 | // if runPasses was passed an outFbo, we want to write to that. 123 | draw({texture: passInTtexture, fbo: outFbo}); 124 | } else { 125 | // otherwise ping-pong to the next intermediary fbo 126 | currentFboIndex += 1; 127 | currentFboIndex %= fbos.length; 128 | // console.log('writing to currentFboIndex: ',currentFboIndex) 129 | draw({texture: passInTtexture, fbo: fbos[currentFboIndex]}); 130 | } 131 | } 132 | 133 | return {currentFboIndex}; 134 | } 135 | 136 | function computeSat ({regl, texture, fbos, currentFboIndex = 0, outFbo = null, components = 'rgba', type = 'vec4', clipY = 1}) { 137 | // http://developer.amd.com/wordpress/media/2012/10/GDC2005_SATEnvironmentReflections.pdf 138 | 139 | if (fbos.length < 2) { 140 | throw new Error('fbos.length must be >= 2'); 141 | } 142 | 143 | let sampleSize = 16; 144 | let textureSize = Math.max(texture.width, texture.height); 145 | 146 | let passes = computeNumPasses({textureSize, sampleSize}); 147 | 148 | ({currentFboIndex} = runPasses({inputTexture: texture, 149 | textureSize, 150 | direction: 'V', 151 | passes, 152 | currentFboIndex, 153 | fbos, 154 | type, 155 | components, 156 | clipY, 157 | regl, 158 | outFbo: null})); 159 | ({currentFboIndex} = runPasses({inputTexture: fbos[currentFboIndex].color[0], 160 | textureSize, 161 | direction: 'H', 162 | passes, 163 | currentFboIndex, 164 | fbos, 165 | type, 166 | components, 167 | clipY, 168 | regl, 169 | outFbo: outFbo})); 170 | 171 | return {currentFboIndex}; 172 | } 173 | 174 | module.exports = {computeSat, makeFrag, makeVert, computeNumPasses, computeNumBitsRequired, runPasses}; 175 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glsl-sat", 3 | "version": "0.1.0", 4 | "description": "glsl-sat is a shader generator for WebGL, to generate a summed-area-table texture of an input texture", 5 | "main": "glsl-sat.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/realazthat/glsl-sat.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/realazthat/glsl-sat/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 | "regl": "^0.10.0" 24 | }, 25 | "devDependencies": { 26 | "disc": "^1.3.2", 27 | "glsl-numerify": "1.0.0", 28 | "indexhtmlify": "^1.3.0", 29 | "jquery-browserify": "^1.8.1", 30 | "resl": "^1.0.1", 31 | "semistandard": "~8.0.0", 32 | "snazzy": "^4.0.0" 33 | }, 34 | "keywords": [ 35 | "webgl", 36 | "gl", 37 | "graphics", 38 | "computer graphics", 39 | "opengl", 40 | "glsl", 41 | "data", 42 | "shader", 43 | "image processing", 44 | "dsp", 45 | "convolution", 46 | "kernel", 47 | "filter", 48 | "blur", 49 | "summed area table" 50 | ], 51 | "scripts": { 52 | "mytest": "semistandard | snazzy", 53 | "build": "npm run build-script && npm run build-demo", 54 | "build-script": "mkdir -p ./dist && browserify glsl-sat.js --standalone glsl-sat > ./dist/glsl-sat.js", 55 | "build-demo": "mkdir -p ./www/glsl-sat-demo/ && browserify ./glsl-sat-demo.js | indexhtmlify > ./www/glsl-sat-demo/index.html" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /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-sat" ] && [ "$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-sat 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 | --------------------------------------------------------------------------------