├── LICENSE ├── README.md ├── shaderboy.js ├── shaderboy.min.js └── test ├── img ├── car.jpg ├── carMask.png └── smoke.jpg ├── index.html └── shader.glsl /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Christian Engel 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Shaderboy 2 | ========= 3 | 4 | This library makes it easy to use GLSL shaders on your websites to create really fast, pixel-based animations for images. 5 | 6 | Simply place an `` tag somewhere, write a GLSL shader for it and connect them with shaderboy. 7 | 8 | Here`s a demo: https://wearekiss.com/preview/shaderboy/test.html 9 | 10 | ## Changelog 11 | 12 | - __Version 1.1__: Now passing mouse-related data to the shaders 13 | 14 | ## Basic usage 15 | 16 | First, download `shaderboy.js` or `shaderboy.min.js` and load it in your html document. 17 | 18 | ````javascript 19 | 20 | ```` 21 | 22 | You need to write a GLSL shader to manipulate the pixeldata of your images. You can either store your shader(s) in 23 | separate files on your server, or embed them in script tags into your document - its totally up to you. 24 | 25 | To advise shaderboy which images it should manipulate and how to do that, you need to apply some `data-*` attributes 26 | to your `` elements. 27 | 28 | The easiest way to attach a GLSL shader to an `` element is this: 29 | 30 | ````html 31 | My cool animation 32 | ```` 33 | 34 | Shaderboy will try to load the file `./myshader.glsl` via AJAX and will start to animate the image as soon as it has been loaded. 35 | 36 | If you have embedded your shader directly into the HTML document, you need to apply an id to the script tag and tell 37 | shaderboy to use the shader with the given id: 38 | 39 | ````html 40 | 43 | My cool animation 44 | ```` 45 | 46 | The `#` symbol tells shaderboy that `data-shader` should be treated as an id reference rather than a url. 47 | 48 | ## Whats happening next 49 | 50 | Shaderboy will replace the `` element with a `` element. If you have placed 51 | any css classnames on the image, they will be copied over to the canvas element. 52 | 53 | After the image has been replaced, the WebGL context will be created by shaderboy, any additional 54 | textures are being loaded and the animation is started. 55 | 56 | ## Using multiple textures 57 | 58 | A modern shader can utilize multiple textures to be used as masks or data sources to compose 59 | complicated effects. Shaderboy allows you as well to load multiple textures to be used to 60 | animate _one_ image tag. Define them like so: 61 | 62 | ````html 63 | My cool animation 70 | ```` 71 | 72 | Currently, shaderboy allows you to load up to 6 additional texures (together with your original images data) 73 | into your shaders. Simply place the attributes `data-texture0` to `data-texture5`. 74 | 75 | All loaded image data - the information of your original `` tag, as well as any 76 | additionally loaded textures are made available inside your GLSL shaders. 77 | 78 | Read more about how to access them, below. 79 | 80 | ## GLSL variables 81 | 82 | Shaderboy provides a couple of variables you may access inside your GLSL shaders: 83 | 84 | ````glsl 85 | uniform sampler2D image; 86 | uniform sampler2D texture0; 87 | uniform sampler2D texture1; 88 | # ... up to texture5 89 | uniform float time; 90 | varying vec2 pixelCoords; 91 | 92 | uniform vec2 mouseCoords; 93 | uniform int mouseDownLeft; 94 | uniform int mouseDownRight; 95 | ```` 96 | 97 | The original images data is provided as a texture named `image`, all additionally loaded texures 98 | can be accessed through `texture0` to `texture5`. 99 | 100 | Together with the image data, shaderboy will pass a `time` variable to enable you to change your shaders appearance 101 | over time, as well as a vector named `pixelCoords` that contains the coordinates of the currently rendered pixel. 102 | 103 | Mouse data has been added in version 1.1 - you can grab a vec2 with `mouseCoords` that 104 | are already mapped to the WebGL coordinates space (x from 0 to 1, y from 1 to 0). 105 | 106 | The uniforms `mouseDownLeft` and `mouseDownRight` are either 0 or 1 - depending on if one of those 107 | mouse buttons have been pressed. 108 | 109 | ## How the hell do I write GLSL shaders? 110 | 111 | Well, there are a lot of books out there that help you learn "GLSL es" - the version of GLSL that is used 112 | by WebGL. 113 | 114 | If you want to have a free introduction into the topic, I recommend [The book of shaders](https://thebookofshaders.com/) - I also recommend you to donate something to the author, 115 | because he had a lot of work creating this great book! 116 | 117 | Have fun with shaderboy - and don't forget to share your creations with me :) 118 | 119 | -------------------------------------------------------------------------------- /shaderboy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shaderboy 3 | * ========= 4 | * 5 | * @author Christian Engel 6 | * @url https://github.com/Paratron/shaderboy 7 | * @license MIT 8 | * @version 1.1 9 | */ 10 | (function () { 11 | 'use strict'; 12 | 13 | var shaderboy; 14 | 15 | window['shaderboy'] = shaderboy = {}; 16 | 17 | shaderboy['canvases'] = []; 18 | shaderboy['fragmentShaders'] = {}; 19 | shaderboy['defaultVertexShader'] = 'attribute vec2 position;varying vec2 pixelCoords;\nvoid main() {\ngl_Position = vec4(vec2(position * vec2(2,2)) - vec2(1,1), 0., 1.);\npixelCoords=position;}'; 20 | shaderboy['defaultVertex'] = [1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0]; 21 | shaderboy['onerror'] = function () { 22 | }; 23 | 24 | var gl, 25 | loaders = {}, 26 | store = {}, 27 | resizeTimeout = null; 28 | 29 | /** 30 | * Will attempt to load all required shaders and/or textures for the given domElement. 31 | * @param domElement 32 | */ 33 | function collectAssets(domElement, callback) { 34 | var shaderIdentifier = domElement.getAttribute('data-shader'), 35 | scriptElement, 36 | waitingFor = 0, 37 | outShaders = {fragment: shaderIdentifier, vertex: null}, 38 | outTextures = []; 39 | 40 | function startedLoading() { 41 | waitingFor++; 42 | } 43 | 44 | function finishedLoading() { 45 | waitingFor--; 46 | 47 | if (waitingFor <= 0) { 48 | callback(outShaders, outTextures); 49 | } 50 | } 51 | 52 | startedLoading(); 53 | 54 | //Shader loader has not been cached? 55 | if (!loaders[shaderIdentifier]) { 56 | if (shaderIdentifier.substr(0, 1) === '#') { 57 | scriptElement = document.getElementById(shaderIdentifier.substr(1)); 58 | if (scriptElement === null) { 59 | console.error('Unable to locate the script tag with id ' + shaderIdentifier); 60 | shaderboy.onerror(); 61 | return; 62 | } 63 | loaders[shaderIdentifier] = scriptElement; 64 | storeFragmentShader(shaderIdentifier, scriptElement.innerHTML); 65 | } else { 66 | scriptElement = new XMLHttpRequest(); 67 | 68 | scriptElement.onload = function (e) { 69 | loaders[shaderIdentifier].innerHTML = e.target.responseText; 70 | storeFragmentShader(shaderIdentifier, e.target.responseText); 71 | finishedLoading(); 72 | }; 73 | startedLoading(); 74 | 75 | scriptElement.open('GET', shaderIdentifier); 76 | scriptElement.send(); 77 | loaders[shaderIdentifier] = document.createElement('script'); 78 | } 79 | 80 | } else { 81 | //Shader loader (script tag) has been created before. Use that. 82 | storeFragmentShader(shaderIdentifier, loaders[scriptIdentifier].innerHTML); 83 | } 84 | 85 | var textures = [], 86 | i, t; 87 | 88 | textures.push(domElement.getAttribute('src')); 89 | outTextures.push(null); 90 | 91 | for (i = 0; i <= 5; i++) { 92 | if (!domElement.hasAttribute('data-texture' + i)) { 93 | break; 94 | } 95 | 96 | textures.push(domElement.getAttribute('data-texture' + i)); 97 | outTextures.push(null); 98 | } 99 | 100 | for (i = 0; i < textures.length; i++) { 101 | t = textures[i]; 102 | 103 | if (!loaders[t]) { 104 | if (t.substr(0, 1) === '#') { 105 | outTextures[i] = document.getElementById(t.substr(1)); 106 | if (outTextures[i] === null) { 107 | console.error('Unable to locate image tag with id ' + t); 108 | shaderboy.onerror(); 109 | return; 110 | } 111 | loaders[t] = outTextures[i]; 112 | } else { 113 | outTextures[i] = loaders[t] = new Image(); 114 | outTextures[i].onload = function () { 115 | finishedLoading(); 116 | }; 117 | startedLoading(); 118 | outTextures[i].src = t; 119 | } 120 | } else { 121 | outTextures[i] = loaders[t]; 122 | } 123 | } 124 | 125 | finishedLoading(); 126 | } 127 | 128 | /** 129 | * Stores the source of a fragment shader. 130 | * @param key 131 | * @param source 132 | */ 133 | function storeFragmentShader(key, source) { 134 | if (shaderboy.fragmentShaders[key]) { 135 | console.log('Shader source with the id "' + key + '" already exists and has been replaced.'); 136 | } 137 | shaderboy.fragmentShaders[key] = source; 138 | } 139 | 140 | /** 141 | * Returns the default vertex shader thats used for 99% of all images. 142 | * @param gl 143 | * @returns {*} 144 | */ 145 | function getDefaultVertexShader(gl) { 146 | return createShader(gl, shaderboy.defaultVertexShader, gl.VERTEX_SHADER); 147 | } 148 | 149 | function createShader(gl, source, type) { 150 | var shader = gl.createShader(type); 151 | gl.shaderSource(shader, source); 152 | gl.compileShader(shader); 153 | 154 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 155 | console.log('Shader compilation failed'); 156 | var error = gl.getShaderInfoLog(shader); 157 | console.error(error); 158 | shaderboy.onerror(); 159 | return null; 160 | } 161 | 162 | return shader; 163 | } 164 | 165 | function getOffsetLeft(elm) { 166 | var offsetLeft = 0; 167 | do { 168 | if (!isNaN(elm.offsetLeft)) { 169 | offsetLeft += elm.offsetLeft; 170 | } 171 | } while (elm = elm.offsetParent); 172 | return offsetLeft; 173 | } 174 | 175 | function getOffsetTop(elm) { 176 | var offsetTop = 0; 177 | do { 178 | if (!isNaN(elm.offsetTop)) { 179 | offsetTop += elm.offsetTop; 180 | } 181 | } while (elm = elm.offsetParent); 182 | return offsetTop; 183 | } 184 | 185 | /** 186 | * This creates a webgl program and compiles and links all the needed shaders. 187 | * @param gl 188 | * @param vertexShader 189 | * @param fragmentShader 190 | */ 191 | function compileShaders(gl, vertexShaderName, fragmentShaderName) { 192 | var program = gl.createProgram(), 193 | vertexShader, 194 | fragmentShader; 195 | 196 | if (!vertexShaderName) { 197 | vertexShader = getDefaultVertexShader(gl); 198 | } 199 | 200 | fragmentShader = createShader(gl, shaderboy.fragmentShaders[fragmentShaderName], gl.FRAGMENT_SHADER); 201 | 202 | gl.attachShader(program, vertexShader); 203 | gl.attachShader(program, fragmentShader); 204 | 205 | gl.deleteShader(vertexShader); 206 | gl.deleteShader(fragmentShader); 207 | 208 | gl.linkProgram(program); 209 | 210 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 211 | console.error(gl.getProgramInfoLog(program)); 212 | return; 213 | } 214 | 215 | gl.useProgram(program); 216 | 217 | return program; 218 | } 219 | 220 | function webGLStart(inCanvasElement, domElement, fragmentShaderId, textures) { 221 | var gl, 222 | sbId = Math.random().toString().split('.')[1]; 223 | 224 | inCanvasElement.sbId = sbId; 225 | 226 | gl = inCanvasElement.getContext('experimental-webgl', {antialias: true}); 227 | 228 | if (!gl) { 229 | gl = inCanvasElement.getContext('webgl'); 230 | } 231 | 232 | gl.viewportWidth = inCanvasElement.width; 233 | gl.viewportHeight = inCanvasElement.height; 234 | 235 | var program = compileShaders(gl, null, fragmentShaderId); 236 | 237 | if (!program) { 238 | return; 239 | } 240 | 241 | var vertexPosition = gl.getAttribLocation(program, 'position'); 242 | 243 | store[sbId] = { 244 | canvas: inCanvasElement, 245 | gl: gl, 246 | program: program, 247 | renderStart: Date.now(), 248 | textures: [], 249 | mouseX: -1, 250 | mouseY: -1, 251 | mouseL: 0, 252 | mouseR: 0, 253 | uniforms: { 254 | viewport: gl.getUniformLocation(program, 'viewport'), 255 | mouseCoords: gl.getUniformLocation(program, 'mouseCoords'), 256 | mouseDownLeft: gl.getUniformLocation(program, 'mouseDownLeft'), 257 | mouseDownRight: gl.getUniformLocation(program, 'mouseDownRight'), 258 | time: gl.getUniformLocation(program, 'time'), 259 | image: gl.getUniformLocation(program, 'image'), 260 | texture0: gl.getUniformLocation(program, 'texture0'), 261 | texture1: gl.getUniformLocation(program, 'texture1'), 262 | texture2: gl.getUniformLocation(program, 'texture2'), 263 | texture3: gl.getUniformLocation(program, 'texture3'), 264 | texture4: gl.getUniformLocation(program, 'texture4'), 265 | texture5: gl.getUniformLocation(program, 'texture5') 266 | }, 267 | attributes: { 268 | position: vertexPosition, 269 | vertexes: initBuffers(gl) 270 | } 271 | }; 272 | 273 | inCanvasElement.addEventListener('mousedown', function (e) { 274 | store[sbId].mouseL = e.button === 0 ? 1 : 0; 275 | store[sbId].mouseR = e.button === 2 ? 1 : 0; 276 | }); 277 | 278 | inCanvasElement.addEventListener('mouseup', function (e) { 279 | if(e.button === 0){ 280 | store[sbId].mouseL = 0; 281 | } 282 | if(e.button === 2){ 283 | store[sbId].mouseR = 0; 284 | } 285 | }); 286 | 287 | inCanvasElement.addEventListener('mousemove', function (e) { 288 | store[sbId].mouseX = e.clientX - getOffsetLeft(e.target) + document.body.scrollLeft; 289 | store[sbId].mouseY = e.clientY - getOffsetTop(e.target) + document.body.scrollTop; 290 | }); 291 | 292 | inCanvasElement.addEventListener('mouseout', function (e) { 293 | store[sbId].mouseX = store[sbId].mouseY = -1; 294 | store[sbId].mouseL = store[sbId].mouseR = 0; 295 | }); 296 | 297 | gl.enableVertexAttribArray(vertexPosition); 298 | 299 | //Add all previously collected textures. 300 | for (var i = 0; i < textures.length; i++) { 301 | store[sbId].textures.push(true); 302 | linkTexture(gl, textures[i], i); 303 | } 304 | 305 | gl.clearColor(0, 0, 0, 0); 306 | gl.disable(gl.DEPTH_TEST); 307 | } 308 | 309 | function initBuffers(gl) { 310 | var squareVertexPositionBuffer = gl.createBuffer(); 311 | 312 | gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); 313 | 314 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(shaderboy.defaultVertex), gl.STATIC_DRAW); 315 | squareVertexPositionBuffer.itemSize = 3; 316 | squareVertexPositionBuffer.numItems = 4; 317 | 318 | return squareVertexPositionBuffer; 319 | } 320 | 321 | function linkTexture(gl, img, index) { 322 | var texture = gl.createTexture(); 323 | gl.activeTexture(gl.TEXTURE0 + index); 324 | gl.bindTexture(gl.TEXTURE_2D, texture); 325 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 326 | 327 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); 328 | 329 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 330 | // Prevents s-coordinate wrapping (repeating). 331 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 332 | // Prevents t-coordinate wrapping (repeating). 333 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 334 | } 335 | 336 | 337 | function drawScene(inStore) { 338 | var gl = inStore.gl, 339 | canvas = inStore.canvas, 340 | squareVertexPositionBuffer = inStore.attributes.vertexes, 341 | vertexPosition = inStore.attributes.position; 342 | 343 | gl.viewport(0, 0, canvas.width, canvas.height); 344 | gl.clear(gl.COLOR_BUFFER_BIT); 345 | //if (!currentProgram) 346 | // return; 347 | gl.uniform2i(inStore.uniforms.viewport, canvas.width, canvas.height); 348 | if (inStore.mouseX !== -1) { 349 | gl.uniform2f(inStore.uniforms.mouseCoords, inStore.mouseX / canvas.width, 1 - (inStore.mouseY / canvas.height)); 350 | } else { 351 | gl.uniform2f(inStore.uniforms.mouseCoords, -1, -1); 352 | } 353 | gl.uniform1i(inStore.uniforms.mouseDownLeft, inStore.mouseL); 354 | gl.uniform1i(inStore.uniforms.mouseDownRight, inStore.mouseR); 355 | gl.uniform1i(inStore.uniforms.image, 0); 356 | gl.uniform1f(inStore.uniforms.time, (Date.now() - inStore.renderStart) / 1000); 357 | 358 | for (var i = 0; i < inStore.textures.length; i++) { 359 | if (inStore.textures[i]) { 360 | gl.uniform1i(inStore.uniforms['texture' + i], i + 1); 361 | } 362 | } 363 | 364 | /*gl.uniform1f(currentProgram.uniformsCache['uTime'], time); 365 | gl.uniform1i(currentProgram.uniformsCache['uSampler0'], 0); 366 | gl.uniform1i(currentProgram.uniformsCache['uSampler1'], 1); 367 | gl.uniform1i(currentProgram.uniformsCache['uSampler2'], 2); 368 | gl.uniform1i(currentProgram.uniformsCache['uSampler3'], 3); 369 | gl.uniform1i(currentProgram.uniformsCache['uSampler4'], 4); 370 | gl.uniform1i(currentProgram.uniformsCache['uSampler5'], 5); 371 | gl.uniform1f(currentProgram.uniformsCache['mousex'], mousex); 372 | gl.uniform1f(currentProgram.uniformsCache['mousey'], mousey);*/ 373 | 374 | gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); 375 | gl.vertexAttribPointer(vertexPosition, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); 376 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems); 377 | } 378 | 379 | function tick() { 380 | window.requestAnimationFrame(tick); 381 | 382 | for (var key in store) { 383 | drawScene(store[key]); 384 | } 385 | } 386 | 387 | /** 388 | * Replaces a DOM element with a webGL enabled canvas. 389 | * @param domElement 390 | */ 391 | function replaceElement(domElement, keepOriginal, callback) { 392 | var canvas = document.createElement('canvas'); 393 | 394 | shaderboy['canvases'].push(canvas); 395 | 396 | canvas.className = domElement.className + ' shaderboy'; 397 | 398 | setSize(canvas, domElement); 399 | 400 | collectAssets(domElement, function (shaders, textures) { 401 | 402 | if (keepOriginal) { 403 | domElement.className += ' shaderboy-replaced'; 404 | domElement.parentNode.insertBefore(canvas, domElement); 405 | } else { 406 | domElement.parentNode.replaceChild(canvas, domElement); 407 | } 408 | 409 | webGLStart( 410 | canvas, 411 | domElement, 412 | shaders.fragment, 413 | textures 414 | ); 415 | 416 | setTimeout(function () { 417 | tick(canvas); 418 | }, 1); 419 | 420 | if (callback) { 421 | callback(canvas); 422 | } 423 | }); 424 | } 425 | 426 | function setSize(toCanvas, fromElement) { 427 | var w = fromElement.offsetWidth, h = fromElement.offsetHeight; 428 | toCanvas.width = w; 429 | toCanvas.height = h; 430 | } 431 | 432 | // ==================================================================================================== 433 | 434 | shaderboy.webglSupported = false; 435 | 436 | (function () { 437 | var test = document.createElement('canvas'), 438 | gl; 439 | 440 | try { 441 | gl = test.getContext('experimental-webgl'); 442 | 443 | if (!gl) { 444 | gl = test.getContext('webgl'); 445 | } 446 | 447 | if (!gl) { 448 | console.log('Webgl is not supported on this platform.'); 449 | return; 450 | } 451 | } 452 | catch (e) { 453 | console.log('Webgl is not supported on this platform.'); 454 | return; 455 | } 456 | 457 | shaderboy.webglSupported = true; 458 | })(); 459 | 460 | /** 461 | * Initializes the library. 462 | * This will scan the DOM for elements that have a "data-shader" attribute defined and replaces them 463 | * with a WebGL enabled canvas element. 464 | */ 465 | shaderboy['autoReplace'] = function () { 466 | if (!shaderboy.webglSupported) { 467 | return; 468 | } 469 | 470 | var elements = document.querySelectorAll('[data-shader]'); 471 | 472 | for (var i = 0; i < elements.length; i++) { 473 | replaceElement(elements[i]); 474 | } 475 | }; 476 | 477 | shaderboy['replace'] = function (domElement, keepOriginal, callback) { 478 | if (!shaderboy.webglSupported) { 479 | return; 480 | } 481 | 482 | replaceElement(domElement, keepOriginal, callback); 483 | }; 484 | 485 | window.addEventListener('resize', function () { 486 | clearTimeout(resizeTimeout); 487 | resizeTimeout = setTimeout(function () { 488 | var canvases = shaderboy['canvases']; 489 | for (var i = 0; i < canvases.length; i++) { 490 | setSize(canvases[i], canvases[i]); 491 | } 492 | }, 100); 493 | }); 494 | })(); -------------------------------------------------------------------------------- /shaderboy.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shaderboy 3 | * ========= 4 | * 5 | * @author Christian Engel 6 | * @url https://github.com/Paratron/shaderboy 7 | * @license MIT 8 | * @version 1.1 9 | */ 10 | (function(){function v(a,b){function c(){g--;0>=g&&b(w,k)}var d=a.getAttribute("data-shader"),e,g=0,w={fragment:d,vertex:null},k=[];g++;if(l[d])n(d,l[scriptIdentifier].innerHTML);else if("#"===d.substr(0,1)){e=document.getElementById(d.substr(1));if(null===e){console.error("Unable to locate the script tag with id "+d);f.onerror();return}l[d]=e;n(d,e.innerHTML)}else e=new XMLHttpRequest,e.onload=function(a){l[d].innerHTML=a.target.responseText;n(d,a.target.responseText);c()},g++,e.open("GET",d),e.send(), 11 | l[d]=document.createElement("script");e=[];var h,m;e.push(a.getAttribute("src"));k.push(null);for(h=0;5>=h&&a.hasAttribute("data-texture"+h);h++)e.push(a.getAttribute("data-texture"+h)),k.push(null);for(h=0;h 3 | 4 | 5 | 6 | 9 | Shaderboy Test 10 | 29 | 30 | 31 |

Shaderboy Test

32 | 33 |

Simply place an <img> tag somewhere, write a GLSL shader for it and poof:

34 | 35 | 42 | 43 | 44 | 45 | 48 | 49 | -------------------------------------------------------------------------------- /test/shader.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D image; 4 | uniform sampler2D texture0; 5 | uniform sampler2D texture1; 6 | uniform float time; 7 | varying vec2 pixelCoords; 8 | 9 | mat4 rotationMatrix(vec3 axis, float angle) 10 | { 11 | axis = normalize(axis); 12 | float s = sin(angle); 13 | float c = cos(angle); 14 | float oc = 1.0 - c; 15 | 16 | return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, 17 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, 18 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 19 | 0.0, 0.0, 0.0, 1.0); 20 | } 21 | 22 | 23 | void main() { 24 | mat4 rot = rotationMatrix(vec3(1.0,1.0,1.0), 1.0472); 25 | vec4 uv = vec4(pixelCoords.xy, 1.0, 1.0); 26 | vec4 uvRot = uv * rot; 27 | 28 | vec4 map = texture2D(texture0, pixelCoords); 29 | float distortion = (sin(uvRot.x*50.0+time)*0.005); 30 | float distortion2 = (sin(uvRot.y*42.0+time)*0.005); 31 | 32 | vec4 dist = vec4(distortion, distortion2, 1.0, 1.0); 33 | 34 | float xPos = (uv.x+dist.y); 35 | float yPos = (uv.y+dist.x); 36 | 37 | gl_FragColor = texture2D( texture1, vec2(xPos,yPos)); 38 | if(map.r < 0.2){ 39 | gl_FragColor = texture2D(image, pixelCoords); 40 | } 41 | } --------------------------------------------------------------------------------