1 2// A very lightweight layer on top of the WebGL API to make coding WebGL faster and much more fun. 3// 4// This isn't a 3D engine, but could be used as the foundation for one. 5 6/*! 7 @copyright 2016 Justin Windle 8 @license http://opensource.org/licenses/MIT 9 @author Justin Windle <[email protected]> 10 @see https://github.com/soulwire/gl.js 11 @see http://soulwire.co.uk 12 */ 13 14var GL = (function() { 15 16 'use strict'; 17 18 var GL = { 19 20 // 21 // Create 22 // -------------------------------------------------- 23 // 24 // Creates a WebGL drawing context and augments it 25 // to add the functionality of the __gl.js__ library 26 // 27 // Parameters: 28 // - `canvas` _(optional)_ The canvas element to use 29 // 30 31 create: function( canvas ) { 32 33 var gl; 34 35 if ( !canvas ) canvas = document.createElement( 'canvas' ); 36 37 if ( !( gl = ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ) ) 38 39 throw 'Error creating WebGL context'; 40 41 var shader, bindings = {}; 42 43 return extend( gl, { 44 45 bindings: bindings, 46 47 // 48 // Shader 49 // -------------------------------------------------- 50 // 51 // Creates a shader object from a given type and source code 52 // 53 // Parameters: 54 // - `source` Source string for the shader 55 // - `type` The shader type (`VERTEX_SHADER` or `FRAGMENT_SHADER`) 56 // 57 58 shader: function( source, type ) { 59 60 source = PRECISION + '\n' + source; 61 62 gl.shaderSource( shader = gl.createShader( type ), source ); 63 gl.compileShader( shader ); 64 65 if ( !gl.getShaderParameter( shader, COMPILE_STATUS ) ) 66 67 logShaderError( gl.getShaderInfoLog( shader ), source ); 68 69 return shader; 70 }, 71 72 // 73 // Program 74 // -------------------------------------------------- 75 // 76 // Creates a shader program from _vertex_ and _fragment_ 77 // shader source code 78 // 79 // Parameters: 80 // - `vs` Source string for the vertex shader 81 // - `fs` Source string for the fragment shader 82 // 83 84 program: function( vs, fs ) { 85 86 var attribute, program, uniform, source, name, type, size, span, 87 88 program = gl.createProgram(); 89 program.state = {}; 90 91 gl.attachShader( program, gl.shader( vs, VERTEX_SHADER ) ); 92 gl.attachShader( program, gl.shader( fs, FRAGMENT_SHADER ) ); 93 gl.linkProgram( program ); 94 95 if ( !gl.getProgramParameter( program, LINK_STATUS ) ) 96 97 throw gl.getProgramInfoLog( program ); 98 99 /* 100 101 Concatenates vertex and fragment shader source code 102 and strips out comments, ready to be parsed for 103 attributes and uniforms 104 105 */ 106 107 source = [ vs, fs ].join( '\n' ).replace( COMMENT, '' ); 108 109 /* 110 111 Parse attributes in the shader code, create `attribute` 112 objects for each and store them in the program 113 114 */ 115 116 while ( attribute = ATTRIBUTE.exec( source ) ) { 117 118 type = attribute[1]; 119 name = attribute[2]; 120 121 if ( !program[ name ] ) 122 123 program[ name ] = gl.attribute( program, name, type ); 124 } 125 126 /* 127 128 Parse uniforms in the shader code, create `uniform` 129 objects for each and store them in the program 130 131 */ 132 133 while ( uniform = UNIFORM.exec( source ) ) { 134 135 type = uniform[1]; 136 name = uniform[2]; 137 span = uniform[5]; 138 139 if ( !program[ name ] ) 140 141 program[ name ] = gl.uniform( program, name, type, span ); 142 } 143 144 /* Extends the core program functionalty */ 145 146 return extend( program, { 147 148 // ##### on 149 // Tells WebGL to start using this program 150 151 on: function() { 152 153 if ( gl.usedProgram !== program ) 154 155 gl.useProgram( gl.usedProgram = program ); 156 }, 157 158 // ##### draw 159 // Draw a `mesh` object using this program 160 // 161 // Parameters 162 // - `mesh` The `mesh` object to draw 163 // - `mode` _(optional)_ The drawing mode to use (if not `mesh.mode`)
164 // - `first` _(optional)_ The index to start drawing from 165 // - `count` _(optional)_ How many items to draw 166 167 draw: function( mesh, mode, first, count ) { 168 169 program.on(); 170 171 var key, val, obj, unit = 0; 172 173 /* Attributes */ 174 175 for ( key in obj = mesh.attributes ) 176 177 if ( program[ key ] ) 178 179 obj[ key ].on( program[ key ] ); 180 181 /* Uniforms */ 182 183 for ( key in obj = mesh.uniforms ) 184 185 if ( program[ key ] ) 186 187 program[ key ].set( obj[ key ] ); 188 189 /* Textures */ 190 191 for ( key in obj = mesh.textures ) 192 193 if ( program[ key ] ) 194 195 obj[ key ].on( unit ), program[ key ].set( unit++ ); 196 197 /* Vertices */ 198 199 for ( key in obj = mesh.vertices ) 200 201 ( val = obj[ key ] ).on( program[ key ] ); 202 203 /* Draw */ 204 205 if ( mesh.indices ) { 206 207 mesh.indices.on(); 208 gl.drawElements( TRIANGLES, mesh.indices.length, UNSIGNED_SHORT, 0 ); 209 210 } else { 211 212 gl.drawArrays( 213 mode || mesh.mode, 214 first || 0, 215 count || ( val.length / program[ key ].size ) 216 ); 217 } 218 } 219 }); 220 }, 221 222 // 223 // Attribute 224 // -------------------------------------------------- 225 // 226 // Creates an object that encapsulates a vertex shader 227 // attribute and provides methods for using it 228 // 229 // Parameters: 230 // - `program` The `program` object the attribute belongs to 231 // - `name` The name of the attribute (usually prefixed with _a_) 232 // - `type` The type of object used for this attribute (e.g. _vec3_) 233 // 234 235 attribute: function( program, name, type ) { 236 237 var location = gl.getAttribLocation( program, name ); 238 var dataType = getType( type ); 239 var size = getSize( type ); 240 var on = false; 241 242 return { 243 244 name: name, 245 type: type, 246 size: size, 247 dataType: dataType, 248 location: location, 249 250 // ##### on 251 // Enable the attribute and setup pointers 252 253 on: function() { 254 255 if ( !on ) gl.enableVertexAttribArray( location ); 256 gl.vertexAttribPointer( location, size, dataType, false, 0, 0 ); 257 on = true; 258 }, 259 260 // ##### off 261 // Disable the attribute 262 263 off: function() { 264 265 gl.disableVertexAttribArray( location ); 266 } 267 }; 268 }, 269 270 // 271 // Uniform 272 // -------------------------------------------------- 273 // 274 // Creates an object that encapsulates a program 275 // uniform and provides methods for using it 276 // 277 // Parameters: 278 // - `program` The `program` object the uniform belongs to 279 // - `name` The name of the uniform (usually prefixed with _u_) 280 // - `type` The type of object used for this uniform (e.g. _vec3_) 281 // - `span` _(optional)_ The length of this uniform if it's an array 282 // 283 284 uniform: function( program, name, type, span ) { 285 286 var location = gl.getUniformLocation( program, name ); 287 var setter = gl[ uniformMethod( type, span ) ]; 288 var state = program.state; 289 290 return { 291 292 name: name, 293 type: type, 294 span: span || 1, 295 location: location, 296 setter: setter, 297 298 // ##### get
299 // Returns the current value of the uniform 300 301 get: function() { 302 303 return gl.getUniform( program, location ); 304 }, 305 306 // ##### set 307 // Sets the current value of the uniform 308 309 set: function( value ) { 310 311 if ( state[ name ] !== value ) { 312 313 program.on(); 314 setter.apply( gl, [ location ].concat( state[ name ] = value ) ); 315 } 316 } 317 }; 318 }, 319 320 // 321 // Buffer 322 // -------------------------------------------------- 323 // 324 // Creates a buffer object to use for attributes or indices 325 // 326 // Parameters: 327 // - `type` The type of buffer (`ARRAY_BUFFER` or `ELEMENT_ARRAY_BUFFER`) 328 // - `data` _(optional)_ The initial data to fill the buffer with 329 // 330 331 buffer: function( type, data ) { 332 333 var ArrayType = type === ARRAY_BUFFER ? Float32Array : Uint16Array; 334 var buffer = gl.createBuffer(); 335 336 extend( buffer, { 337 338 // ##### length 339 // The current length of the buffer 340 341 length: 0, 342 343 // ##### on 344 // Binds the buffer to the appropriate bind point and 345 // enables the attribute for the buffer 346 // 347 // Parameters 348 // - `attribute` The attribute that this buffer is for 349 350 on: function( attribute ) { 351 352 if ( bindings[ type ] !== buffer ) { 353 354 gl.bindBuffer( type, bindings[ type ] = buffer ); 355 if ( attribute ) attribute.on(); 356 } 357 }, 358 359 // ##### fill 360 // Fills the buffer with data 361 // 362 // Parameters 363 // - `data` The data to fill the buffer with 364 // - `usage` _(optional)_ How the buffer will be used (defaults to `STATIC_DRAW`) 365 366 fill: function( data, usage ) { 367 368 if ( toType( data ) == 'array' ) data = new ArrayType( data ); 369 370 gl.bindBuffer( type, buffer ); 371 gl.bufferData( type, data, usage || STATIC_DRAW ); 372 373 buffer.length = data.length; 374 }, 375 376 // ##### push 377 // Pushes data into the buffer at a given offset 378 // 379 // Parameters 380 // - `data` The data to push into the buffer 381 // - `offset` The point at which to start pushing in data 382 383 push: function( data, offset ) { 384 385 if ( isArray( data ) ) data = new ArrayType( data ); 386 387 gl.bindBuffer( type, buffer ); 388 gl.bufferSubData( type, offset, data ); 389 } 390 }); 391 392 if ( data ) buffer.fill( data ); 393 394 return buffer; 395 }, 396 397 // 398 // VBO 399 // -------------------------------------------------- 400 // 401 // Creates a vertex buffer for sending attributes to a vertex shader 402 // (internally this calls `gl.buffer` with type `ARRAY_BUFFER`) 403 // 404 // Parameters: 405 // - `data` _(optional)_ The initial data to fill the buffer with 406 // 407 408 vbo: function( data ) { 409 410 return gl.buffer( ARRAY_BUFFER, data ); 411 }, 412 413 // 414 // IBO 415 // -------------------------------------------------- 416 // 417 // Creates a buffer for specifying vertex indices (internally 418 // this calls `gl.buffer` with type `ELEMENT_ARRAY_BUFFER`) 419 // 420 // Parameters: 421 // - `data` _(optional)_ The initial data to fill the buffer with 422 // 423 424 ibo: function( data ) { 425 426 return gl.buffer( ELEMENT_ARRAY_BUFFER, data ); 427 }, 428 429 // 430 // FBO 431 // -------------------------------------------------- 432 // 433 // Creates a frame buffer object that can be used as an 434 // alternative rendering target 435 // 436 // Parameters: 437 // - `width` _(optional)_ The initial width of the frame buffer 438 // - `height` _(optional)_ The initial height of the frame buffer 439 // - `data` _(optional)_ The initial texture data 440 // 441 // TODO: 442 // - Add support for `renderBuffers` 443 // 444 445 fbo: function( width, height, data, options ) { 446 447 var fbo = gl.createFramebuffer(); 448 var tex = gl.texture(); 449 450 extend( fbo, { 451 452 // ##### texture 453 // The texture object that the frame buffer renders to 454 455 texture: tex, 456 457 // ##### set 458 // Sets up the frame buffer with optional data and dimensions 459 // 460 // Parameters: 461 // - `data` _(optional)_ Image data for the frame buffer 462 // - `width` _(optional)_ The initial width of the frame buffer 463 // - `height` _(optional)_ The initial height of the frame buffer 464 465 set: function( data, width, height, options ) { 466 467 fbo.on(); 468 tex.set( data, fbo.width = width, fbo.height = height, options ); 469 gl.framebufferTexture2D( FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, tex, 0 ); 470 fbo.off(); 471 }, 472 473 // ##### on 474 // Binds the frame buffer to use as an off screen rendering target 475 476 on: function() { 477 478 gl.bindFramebuffer( FRAMEBUFFER, fbo ); 479 }, 480 481 // ##### off 482 // Unbinds the frame buffer, re-enabling the default render target 483 484 off: function() { 485 486 gl.bindFramebuffer( FRAMEBUFFER, null ); 487 } 488 }); 489 490 fbo.set( data, width || 1, height || 1, options ); 491 492 return fbo; 493 }, 494 495 // 496 // Texture 497 // -------------------------------------------------- 498 // 499 // Creates a texture object 500 // 501 // Parameters: 502 // - `source` _(optional)_ The initial source data for the texture. 503 // Possible values are: 504 // - `string` (path to an image file) 505 // - `ArrayBufferView` _(pixels)_ 506 // - `ImageData` _(pixels)_ 507 // - `HTMLImageElement` _(May throw DOMException)_ 508 // - `HTMLCanvasElement` _(May throw DOMException)_ 509 // - `HTMLVideoElement` _(May throw DOMException)_ 510 // - `width` _(optional)_ Width of the texture, if not one of the above types 511 // - `height` _(optional)_ Height of the texture, if not one of the above types 512 // - `options` _(optional)_ Options to use during `setup` 513 // 514 // TODO: 515 // - Auto convert arrays to typed 516 // 517 518 texture: function( source, width, height, options ) { 519 520 var texture = gl.createTexture(); 521 var content; 522 var pot; 523 524 if ( typeof width == 'object' ) 525 526 options = width; 527 528 extend( texture, { 529 530 // ##### param 531 // Sets a parameter for this texture (for example `TEXTURE_MIN_FILTER`) 532 // 533 // Parameters: 534 // - `name` The name of the parameter to set (e.g. `TEXTURE_MIN_FILTER`) 535 // - `value` The value for the parameter (e.g. `LINEAR`) 536 537 param: function( name, value ) { 538 539 gl.texParameteri( TEXTURE_2D, name, value ); 540 }, 541 542 // ##### setup 543 // Sets up the texture by configuring several parameters and generates 544 // mipmaps if appropriate 545 // 546 // Parameters: 547 // - `options` _(optional)_ An object c
547ontaining parameter names and their values 548 // Possible keys are: 549 // - `min` How to resample when shrinking 550 // - `mag` How to resample when stretching 551 // - `s` Whether to clamp or repeat on the _s_ (_x_) axis 552 // - `t` Whether to clamp or repeat on the _t_ (_y_) axis 553 554 setup: function( options ) { 555 556 options = extend( options || {}, { 557 min: LINEAR, 558 mag: LINEAR, 559 s: CLAMP_TO_EDGE, 560 t: CLAMP_TO_EDGE 561 }); 562 563 texture.on(); 564 565 texture.param( TEXTURE_MAG_FILTER, options.mag ); 566 texture.param( TEXTURE_MIN_FILTER, options.min ); 567 texture.param( TEXTURE_WRAP_S, options.s ); 568 texture.param( TEXTURE_WRAP_T, options.t ); 569 570 if ( options.mipmap ) gl.generateMipmap( TEXTURE_2D ); 571 572 texture.off(); 573 }, 574 575 // ##### on 576 // Binds the texture and if `unit` is specified, tells WebGL to 577 // use this texture as the texture at the given unit, which will 578 // correspond to a shader uniform 579 // 580 // Parameters: 581 // - `unit` _(optional)_ The unit to activate this texture for 582 583 on: function( unit ) { 584 585 if ( exists( unit ) ) gl.activeTexture( TEXTURE0 + unit ); 586 gl.bindTexture( TEXTURE_2D, texture ); 587 }, 588 589 // ##### off 590 // Unbinds this texture 591 592 off: function() { 593 594 gl.bindTexture( TEXTURE_2D, null ); 595 }, 596 597 // ##### set 598 // Populates this texture object with data of one of the supported 599 // types (see the constructor's `source` parameter above) 600 // 601 // Parameters: 602 // - `data` _(optional)_ The data to populate the texture with 603 // - `width` _(optional)_ The width of the texture 604 // - `height` _(optional)_ The height of the texture 605 606 set: function( data, width, height, options ) { 607 608 options = extend( options || {}, { 609 mipmap: pot = width > 1 && height > 1 && isPowerOf2( width ) && isPowerOf2( height ), 610 format: RGBA, 611 min: pot ? LINEAR_MIPMAP_LINEAR : LINEAR, 612 flip: true 613 }); 614 615 var path = typeof data === 'string'; 616 617 if ( path || !data && !width && !height ) { 618 619 texture.set( EMPTY_PIXEL, 1, 1 ); 620 621 if ( path ) 622 623 loadImage( data, texture.set ); 624 625 return; 626 } 627 628 var type = /^float/i.test( toType( data ) ) ? FLOAT : UNSIGNED_BYTE; 629 630 texture.on(); 631 632 gl.pixelStorei( UNPACK_FLIP_Y_WEBGL, options.flip ); 633 634 if ( width && height ) 635 636 gl.texImage2D( TEXTURE_2D, 0, options.format, width, height, 0, options.format, type, data ); 637 638 else 639 640 gl.texImage2D( TEXTURE_2D, 0, options.format, options.format, UNSIGNED_BYTE, data ); 641 642 if ( data !== content ) 643 644 texture.setup( options ); 645 646 content = data; 647 648 texture.off(); 649 } 650 }); 651 652 texture.set( source, width, height, options ); 653 654 return texture; 655 }, 656 657 // 658 // Size 659 // -------------------------------------------------- 660 // 661 // Resizes the canvas and viewport 662 // 663 // Parameters: 664 // - `width` The desired width 665 // - `height` The desired height 666 // 667 668 size: function( width, height ) { 669 670 gl.viewport( 0, 0, width, height ); 671 gl.height = canvas.height = height; 672 gl.width = canvas.width = width; 673 }, 674 675 // 676 // Mesh 677 // -------------------------------------------------- 678 // 679 // Creates a mesh object that encapsulates the properties 680 // needed to render a mesh and can be given to a `program` 681 // to draw 682 // 683 // Parameters: 684 // - `options` The properties to initialise the mesh with 685 // - `mode` The default drawing mode to use 686 // - `attributes` Names attribute keys and buffer their values 687 // - `uniforms` A hash of uniforms and their initial values 688 // - `textures` Texture uniform keys and corresponding textures 689 // - `textures` Texture uniform keys and corresponding textures 690 // - `textures` Texture uniform keys and corresponding textures 691 // 692 // Example: 693 // 694 // var quad = gl.mesh({ 695 // uniforms: { 696 // uTime: 0 697 // }, 698 // textures: { 699 // uTexture: gl.texture( 'some-image.png' ) 700 // }, 701 // vertices: { 702 // aPosition: gl.vbo([ -1, 1, 1, 1, -1, -1, 1, -1 ]) 703 // }, 704 // attributes: { 705 // aTexCoord: gl.vbo([ 0, 1, 1, 1, 0, 0, 1, 0 ]) 706 // }, 707 // indices: gl.ibo([ 0, 1, 3, 3, 1, 2 ]) 708 // }); 709 // 710 // program.draw( quad ); 711 // 712 713 mesh: function( options ) { 714 715 var mesh = extend( options || {}, { 716 717 mode: TRIANGLE_STRIP, 718 attributes: {}, 719 uniforms: {}, 720 textures: {}, 721 indices: null, 722 vertices: { /* programAttributeName: VBO */ } 723 }); 724 725 return mesh;
726 } 727 }); 728 } 729 }; 730 731 // 732 // Internal 733 // -------------------------------------------------- 734 // 735 // Constants and helper methods, not exposed via the API 736 // 737 738 var EMPTY_PIXEL = new Uint8Array([ 0, 0, 0, 0 ]); 739 var PRECISION = '#ifdef GL_ES\nprecision mediump float;\n#endif'; 740 var ATTRIBUTE = /attribute\s+(\w+)\s+([\w_-]+)/gi; 741 var UNIFORM = /uniform\s+(\w+)\s+([\w_-]+)(\[(\s+)?([\w_-]+))?/gi; 742 var COMMENT = /(\/\/.+|\/\*[^(\*\/)]+\*\/)/i; 743 744 /* 745 746 Enumeration 747 748 */ 749 750 var ELEMENT_ARRAY_BUFFER = 34963; 751 var LINEAR_MIPMAP_LINEAR = 9987; 752 var UNPACK_FLIP_Y_WEBGL = 37440; 753 var TEXTURE_MAG_FILTER = 10240; 754 var TEXTURE_MIN_FILTER = 10241; 755 var COLOR_ATTACHMENT0 = 36064; 756 var FRAGMENT_SHADER = 35632; 757 var COMPILE_STATUS = 35713; 758 var UNSIGNED_SHORT = 5123; 759 var TEXTURE_WRAP_S = 10242; 760 var TEXTURE_WRAP_T = 10243; 761 var TRIANGLE_STRIP = 5; 762 var CLAMP_TO_EDGE = 33071; 763 var UNSIGNED_BYTE = 5121; 764 var VERTEX_SHADER = 35633; 765 var ARRAY_BUFFER = 34962; 766 var FRAMEBUFFER = 36160; 767 var STATIC_DRAW = 35044; 768 var LINK_STATUS = 35714; 769 var TEXTURE_2D = 3553; 770 var TRIANGLES = 4; 771 var TEXTURE0 = 33984; 772 var NEAREST = 9728; 773 var LINEAR = 9729; 774 var FLOAT = 5126; 775 var RGBA = 6408; 776 var INT = 5124; 777 778 /* 779 780 Utils 781 782 */ 783 784 var define = Object.defineProperty; 785 786 /* 787 788 Merge properties of `source` into `target` 789 790 */ 791 792 function extend( target, source, overwrite ) { 793 794 for ( var key in source ) 795 796 if ( overwrite || ( !( key in target ) || !exists( target[ key ] ) ) ) 797 798 target[ key ] = source[ key ]; 799 800 return target; 801 } 802 803 /* 804 805 Checks whether an object has been defined 806 807 */ 808 809 function exists( object ) { 810 811 return object != null; 812 } 813 814 /* 815 816 Checks whether a number is power of two 817 818 */ 819 820 function isPowerOf2( value ) { 821 822 return ( value & ( value - 1 ) ) === 0; 823 } 824 825 /* 826 827 Load an image from a given path and fire a callback when ready 828 829 */ 830 831 function loadImage( path, callback ) { 832 833 var image = new Image(); 834 image.onload = function() { callback( image ); }; 835 image.src = path; 836 } 837 838 /* 839 840 Returns the name of the uniform setter method for a uniform type, 841 for example a type of `vec2` would yield `uniform2f` 842 843 */ 844 845 function uniformMethod( type, span ) { 846 847 return 'uniform{m}{d}{t}{a}' 848 849 .replace( '{m}', /mat/i.test( type ) ? 'Matrix' : '' ) 850 .replace( '{d}', /\d$/.test( type ) ? type.match( /\d$/ )[0] : 1 ) 851 .replace( '{t}', /^[bis]/i.test( type ) ? 'i' : 'f' ) 852 .replace( '{a}', span > 0 ? 'v' : '' ); 853 } 854 855 /* 856 857 Returns the size (number of dimensions) of an object type 858 859 */ 860 861 function getSize( type ) { 862 863 return /\d/.test( type ) ? parseInt( type.match( /\d$/ )[0], 10 ) : 1; 864 } 865 866 /* 867 868 Returns the WebGL datatype of an object type (e.g vec3 -> gl.FLOAT) 869 870 */ 871 872 function getType( type ) { 873 874 return /^[bi]/i.test( type ) ? INT : FLOAT; 875 } 876 877 /* 878 879 Returns a string representing the type of object provided 880 @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ 881 882 */ 883 884 function toType( obj ) { 885 886 return ({}).toString.call( obj ).match( /\s([a-zA-Z0-9]+)/ )[1].toLowerCase(); 887 } 888 889 /* 890 891 Highlights GLSL code and the location of an error therein 892 893 */ 894 895 function logShaderError( error, source ) { 896 897 var line = /\d+:(\d+)/g.exec( error ); 898 899 if ( line ) { 900 901 line = parseInt( line[1], 10 ) - 1; 902 source = source.split( '\n' ); 903 904 console.log( 905 906 '%c' + source.splice( 0, line ).join( '\n' ) + 907 '%c' + '\n' + source[ 0 ] + '\n' + 908 '%c' + source.splice( 1 ).join( '\n' ), 909 910 'color: #ccc', 911 'color: red', 912 'color: #ccc' 913 ); 914 } 915 916 throw error; 917 } 918 919 // ##### Finally, return the API 920 921 return GL; 922 923})(); 924 925 926 927var Shaders = Shaders || {}; 928Shaders.Glitch = {}; 929
930Shaders.Glitch.VertexShader = [ 931 'attribute vec2 aPosition;', 932 'attribute vec2 aTexCoord;', 933 'varying vec2 vTexCoord;', 934 'void main() {', 935 'gl_Position = vec4( aPosition, 0, 1 );', 936 'vTexCoord = aTexCoord;', 937 '}' 938].join('\n'); 939 940Shaders.Glitch.FragmentShader = [ 941 'uniform sampler2D uTexture;', 942 'uniform sampler2D uOffsets;', 943 'varying vec2 vTexCoord;', 944 945 'uniform int uSkip;', 946 'uniform float uAmount;', 947 'uniform float uAngle;', 948 'uniform float uSeed;', 949 'uniform float uSeedX;', 950 'uniform float uSeedY;', 951 'uniform float uDistortionX;', 952 'uniform float uDistortionY;', 953 'uniform float uColS;', 954 955 'float rand(vec2 co){', 956 'return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);', 957 '}', 958 959 'void main() {', 960 'vec4 texel = texture2D( uTexture, vTexCoord );', 961 'gl_FragColor = texel;', 962 963 'if (uSkip < 1) {', 964 'vec2 p = vTexCoord;', 965 'float xs = floor(gl_FragCoord.x / 0.5);', 966 'float ys = floor(gl_FragCoord.y / 0.5);', 967 '//based on staffantans glitch shader for unity https://github.com/staffantan/unityglitch', 968 'vec4 normal = texture2D(uOffsets, p * uSeed * uSeed);', 969 'if (p.y < uDistortionX + uColS && p.y > uDistortionX - uColS * uSeed) {', 970 'if(uSeedX > 0.0){', 971 'p.y = 1.0 - (p.y + uDistortionY);', 972 '}', 973 'else {', 974 'p.y = uDistortionY;', 975 '}', 976 '}', 977 'if (p.x < uDistortionY + uColS && p.x > uDistortionY - uColS * uSeed) {', 978 'if (uSeedY > 0.0){', 979 'p.x = uDistortionX;', 980 '}', 981 'else {', 982 'p.x = 1. - (p.x + uDistortionX);', 983 '}', 984 '}', 985 'p.x += normal.x * uSeedX * (uSeed /5.0);', 986 'p.y += normal.y * uSeedY * (uSeed /5.0);', 987 '//base from RGB shift shader', 988 'vec2 offset = uAmount * vec2( cos(uAngle), sin(uAngle) );', 989 'vec4 cr = texture2D(uTexture, p + offset);', 990 'vec4 cga = texture2D(uTexture, p);', 991 'vec4 cb = texture2D(uTexture, p - offset);', 992 'gl_FragColor = vec4(cr.r, cga.g, cb.b, cga.a);', 993 '}', 994 'else {', 995 'gl_FragColor = texture2D(uTexture, vTexCoord);', 996 '}', 997 '}', 998].join('\n'); 999 1000 1001 1002function GlitchFx() { 1003 this.domElement = document.createElement('div'); 1004 this.domElement.className = 'glitch'; 1005 this.bindMethods(); 1006 this.setupContext(); 1007 this.render(); 1008 this.enabled = true; 1009 this.glitchFrames = 0; 1010 this.glitching = false; 1011 // GlitchFx settings... 1012 // Chance of switching glitch effect on when off. 1013 this.onProbability = 0.005; 1014 // Chance of switching glitch effect off when on. 1015 this.offProbability = 0.2; 1016 // Minimum frames to glitch for. 1017 this.minGlitchedFrames = 5; 1018 // Maximum frames to glitch for. 1019 this.maxGlitchedFrames = 30; 1020 // Chance of updating distortion values. 1021 this.distortionProbability = 0.35; 1022 // Maximum seed value (signed, e.g. between -n and n) 1023 this.maxSeedValue = 0.2; 1024 // Maximum distortion. 1025 this.maxDistortion = 0.2; 1026 // Smoothing applied to random values of `uAmount` uniform. 1027 this.strengthDivisor = 60; 1028} 1029 1030GlitchFx.prototype.enable = function() { 1031 this.enabled = true; 1032} 1033 1034GlitchFx.prototype.disable = function() { 1035 if (this.enabled) { 1036 this.enabled = false; 1037 this.updateProgramUniforms(); 1038 this.program.draw(this.mesh); 1039 } 1040} 1041 1042GlitchFx.prototype.bindMethods = function() { 1043 this.onImageLoaded = this.onImageLoaded.bind(this); 1044 this.resize = this.resize.bind(this); 1045 this.render = this.render.bind(this); 1046} 1047 1048GlitchFx.prototype.setupContext = function() { 1049 try { 1050 // Attempt to create a GL context. 1051 this.canvas = document.createElement('canvas'); 1052 this.context = GL.create(this.canvas); 1053 this.domElement.appendChild(this.canvas); 1054 this.hasWebGL = true; 1055 } catch (error) { 1056 this.hasWebGL = false; 1057 } 1058 if (this.hasWebGL) { 1059 // We can't use background cover, so compute manually on resize. 1060 window.addEventListener('resize', this.resize, false); 1061 // If WebGL is supported, set up the context. 1062 this.enableWebGLExtensions(); 1063 this.program = this.context.program( 1064 Shaders.Glitch.VertexShader, 1065 Shaders.Glitch.FragmentShader 1066 ); 1067 this.texture = this.context.texture(); 1068 this.mesh = this.createQuadMesh(); 1069 this.setupProgramUniforms(); 1070 this.frame = 0; 1071 } 1072} 1073 1074GlitchFx.prototype.createQuadMesh = function() { 1075 return this.context.mesh({ 1076 textures: { 1077 uTexture: this.texture, 1078 uOffsets: this.generateHeightMap(64) 1079 }, 1080 vertices: { aPosition: this.createScreenSpaceQuad() }, 1081 attributes: { aTexCoord: this.createUVCoordinates() } 1082 });
1083} 1084 1085GlitchFx.prototype.createScreenSpaceQuad = function() { 1086 return this.context.vbo([ -1, 1, 1, 1, -1, -1, 1, -1 ]) 1087} 1088 1089GlitchFx.prototype.createUVCoordinates = function() { 1090 return this.context.vbo([ 0, 1, 1, 1, 0, 0, 1, 0 ]); 1091} 1092 1093GlitchFx.prototype.enableWebGLExtensions = function() { 1094 this.context.getExtension('OES_texture_float'); 1095 this.context.getExtension('OES_half_float_linear'); 1096 this.context.getExtension('OES_float_linear'); 1097} 1098 1099GlitchFx.prototype.setupProgramUniforms = function(arguments) { 1100 this.program.uSkip.set(1); 1101 this.program.uAmount.set(0.01); 1102 this.program.uAngle.set(0.01); 1103 this.program.uSeed.set(0.01); 1104 this.program.uSeedX.set(0.01); 1105 this.program.uSeedY.set(0.01); 1106 this.program.uDistortionX.set(0.1); 1107 this.program.uDistortionY.set(0.1); 1108 this.program.uColS.set(0.1); 1109 this.generateTrigger(); 1110} 1111 1112GlitchFx.prototype.updateProgramUniforms = function() { 1113 if (!this.enabled) { 1114 this.glitching = false; 1115 this.program.uSkip.set(1); 1116 return; 1117 } 1118 if (this.glitching) { 1119 this.program.uSeed.set(Math.random()); 1120 this.program.uSkip.set(0); 1121 this.program.uAmount.set(Math.random() / this.strengthDivisor); 1122 this.program.uAngle.set(this.randomFloat(-Math.PI, Math.PI)); 1123 this.program.uSeedX.set(this.randomFloat(-this.maxSeedValue, this.maxSeedValue)); 1124 this.program.uSeedY.set(this.randomFloat(-this.maxSeedValue, this.maxSeedValue)); 1125 if (Math.random() < this.distortionProbability) { 1126 this.program.uDistortionX.set(this.randomFloat(0, this.maxDistortion)); 1127 this.program.uDistortionY.set(this.randomFloat(0, this.maxDistortion)); 1128 } 1129 if (this.glitchFrames > this.minGlitchedFrames) { 1130 if (this.glitchFrames > this.maxGlitchedFrames || Math.random() < this.offProbability) { 1131 this.program.uSkip.set(1); 1132 this.glitching = false; 1133 } 1134 } 1135 this.glitchFrames++; 1136 } else { 1137 this.glitching = Math.random() < this.onProbability; 1138 this.glitchFrames = 0; 1139 } 1140 this.frame++; 1141} 1142 1143GlitchFx.prototype.generateHeightMap = function(size) { 1144 // Fill a Float buffer with 3 channels worth of data (RGB). 1145 var data = new Float32Array( size * size * 3 ); 1146 var length = size * size; 1147 for ( var value, index, i = 0; i < length; i++ ) { 1148 index = i * 3; 1149 value = Math.random(); 1150 data[ index ] = value; 1151 data[ index + 1 ] = value; 1152 data[ index + 2 ] = value; 1153 } 1154 return this.context.texture(data, size, size, { 1155 format: this.context.RGB, 1156 mipmap: false, 1157 min: this.context.NEAREST, 1158 mag: this.context.NEAREST, 1159 s: this.context.REPEAT, 1160 t: this.context.REPEAT 1161 }); 1162} 1163 1164GlitchFx.prototype.generateTrigger = function() { 1165 this.randX = Math.floor(this.randomFloat(120, 240)); 1166} 1167 1168GlitchFx.prototype.randomFloat = function(min, max) { 1169 if (typeof max == 'undefined') { 1170 max = min; 1171 min = 0; 1172 } 1173 return min + Math.random() * (max - min); 1174} 1175 1176// TODO: This mimics contain and not cover. 1177// TODO: Call during view project tween. 1178GlitchFx.prototype.resize = function() { 1179 // var bounds = this.domElement.getBoundingClientRect(); 1180 var ratio1 = this.domElement.offsetWidth / this.domElement.offsetHeight; 1181 var ratio2 = this.image.width / this.image.height; 1182 var landscape = ratio1 < ratio2; 1183 // this.canvas.style.height = landscape ? '100%' : 'auto'; 1184 // this.canvas.style.width = landscape ? 'auto' : '100%'; 1185} 1186 1187GlitchFx.prototype.render = function() { 1188 requestAnimationFrame(this.render); 1189 if (this.enabled) { 1190 this.updateProgramUniforms(); 1191 this.program.draw(this.mesh); 1192 } 1193} 1194 1195GlitchFx.prototype.setImagePath = function(path, with_bg) { 1196 // Always set the background image as a fallback. 1197 1198 if ( with_bg == undefined ) { 1199 with_bg = true; 1200 } 1201 1202 if (with_bg) this.domElement.style.backgroundImage = 'url(' + path + ')'; 1203 if (this.hasWebGL) { 1204 this.image = new Image(); 1205 this.image.onload = this.onImageLoaded; 1206 this.image.src = path; 1207 } 1208} 1209 1210GlitchFx.prototype.onImageLoaded = function() { 1211 this.context.size(this.image.width, this.image.height); 1212 this.texture.set(this.image); 1213 this.resize(); 1214}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.