├── .gitignore ├── .vscode └── settings.json ├── images └── pico-8-post-processing.png ├── nano-villager_html ├── nano-villager.jpg ├── filters │ ├── hq4x │ │ ├── hq4x.png │ │ ├── LICENSE.txt │ │ ├── hq4x.js │ │ └── COPYING.txt │ ├── default.js │ ├── double-vision.js │ ├── lo-res-roto.js │ ├── hi-res-roto.js │ ├── old-television-frame.js │ ├── old-tv-effect.js │ ├── mattias-crt.js │ ├── cmyk-halftone.js │ ├── vcr-distortion.js │ ├── old-tv.js │ ├── led-display.js │ ├── glitch2.js │ ├── gb-classic.js │ ├── distorted-tv.js │ ├── thin-water.js │ ├── bug-in-the-tv.js │ └── pinscreen-video.js ├── LICENSE.md ├── octocat.svg ├── filters.js ├── original-html.html └── index.html ├── package.json ├── LICENSE.md ├── .eslintrc.js ├── README.md └── pico-8-post-processing.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | node_modules 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Pico", 4 | "RGBA", 5 | "Shadertoy", 6 | "mipmap" 7 | ] 8 | } -------------------------------------------------------------------------------- /images/pico-8-post-processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greggman/pico-8-post-processing/HEAD/images/pico-8-post-processing.png -------------------------------------------------------------------------------- /nano-villager_html/nano-villager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greggman/pico-8-post-processing/HEAD/nano-villager_html/nano-villager.jpg -------------------------------------------------------------------------------- /nano-villager_html/filters/hq4x/hq4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greggman/pico-8-post-processing/HEAD/nano-villager_html/filters/hq4x/hq4x.png -------------------------------------------------------------------------------- /nano-villager_html/LICENSE.md: -------------------------------------------------------------------------------- 1 | [Nano Village](https://www.lexaloffle.com/bbs/?pid=79613#p) 2 | 3 | By: [Partnano](https://www.lexaloffle.com/bbs/?uid=42001) 4 | 5 | License: [CC4-BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/) -------------------------------------------------------------------------------- /nano-villager_html/filters/default.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'default', 3 | author: 'gman', 4 | authorUrl: 'https://greggman.github.io', 5 | src: 'https://github.com/greggman/pico-8-post-processing/blob/master/nano-villager_html/filters/default.js', 6 | license: 'MIT', 7 | licenseUrl: 'http://github.com/greggman/pico-8-post-proessings/LICENSE.md', 8 | filter: { 9 | fragmentShader: ` 10 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 11 | { 12 | vec2 uv = fragCoord / iResolution.xy; 13 | fragColor = texture2D(iChannel0, uv); 14 | } 15 | `, 16 | width: 128, 17 | height: 128, 18 | filter: false, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /nano-villager_html/filters/double-vision.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Double Vision', 3 | author: 'CloneDeath', 4 | authorUrl: 'https://www.shadertoy.com/user/CloneDeath', 5 | src: 'https://www.shadertoy.com/view/lsSXD1', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 11 | { 12 | vec2 texcoord = fragCoord.xy / iResolution.xy; 13 | vec2 texcoord2 = (fragCoord.xy + vec2(1, 1)) / iResolution.xy; 14 | 15 | vec4 color = texture2D(iChannel0, texcoord); 16 | vec4 color2 = texture2D(iChannel0, texcoord2); 17 | 18 | fragColor = ((-10.0 * abs(color - color2)) + 1.0) * color; 19 | } 20 | `, 21 | width: -1, 22 | height: -1, 23 | iChannel0: { 24 | filter: 'nearest', 25 | vFlip: true, 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /nano-villager_html/filters/hq4x/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com ) 2 | 3 | Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net ) 4 | 5 | Copyright (C) 2014 Jules Blok ( jules@aerix.nl ) 6 | 7 | This program is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this program; if not, write to the Free Software 19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | -------------------------------------------------------------------------------- /nano-villager_html/filters/lo-res-roto.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'lo-res roto', 3 | author: 'gman', 4 | authorUrl: 'https://greggman.github.io', 5 | src: 'https://github.com/greggman/pico-8-post-processing/blob/master/nano-villager_html/filters/lo-res-roto.js', 6 | license: 'MIT', 7 | licenseUrl: 'http://github.com/greggman/pico-8-post-proessings/LICENSE.md', 8 | filter: { 9 | fragmentShader: ` 10 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 11 | { 12 | vec2 uv = fragCoord / iResolution.xy * 2.0 - 1.0; 13 | uv = vec2( 14 | uv.x * cos(iTime) - uv.y * sin(iTime), 15 | uv.y * cos(iTime) + uv.x * sin(iTime)) * 16 | mix(0.5, 4.0, sin(iTime) * 0.5 + 0.5); 17 | fragColor = texture2D(iChannel0, uv * 0.5 + 0.5); 18 | } 19 | `, 20 | width: 128, 21 | height: 128, 22 | iChannel0: { 23 | filter: 'mipmap', 24 | wrap: 'repeat', 25 | vFlip: true, 26 | }, 27 | }, 28 | }; -------------------------------------------------------------------------------- /nano-villager_html/filters/hi-res-roto.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'hi-res roto', 3 | author: 'gman', 4 | authorUrl: 'https://greggman.github.io', 5 | src: 'https://github.com/greggman/pico-8-post-processing/blob/master/nano-villager_html/filters/hi-res-roto.js', 6 | license: 'MIT', 7 | licenseUrl: 'http://github.com/greggman/pico-8-post-proessings/LICENSE.md', 8 | filter: { 9 | fragmentShader: ` 10 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 11 | { 12 | vec2 uv = fragCoord / iResolution.xy * 2.0 - 1.0; 13 | uv = vec2( 14 | uv.x * cos(iTime) - uv.y * sin(iTime), 15 | uv.y * cos(iTime) + uv.x * sin(iTime)) * 16 | mix(0.5, 4.0, sin(iTime) * 0.5 + 0.5); 17 | fragColor = texture2D(iChannel0, uv * 0.5 + 0.5); 18 | } 19 | `, 20 | width: -2, 21 | height: -2, 22 | iChannel0: { 23 | filter: 'nearest', 24 | wrap: 'repeat', 25 | vFlip: true, 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pico-8-post-processing", 3 | "version": "0.0.1", 4 | "description": "post processing for pico-8", 5 | "main": "pico-8-post-processing.js", 6 | "scripts": { 7 | "eslint": "eslint \"**/*.js\"", 8 | "pre-push": "npm run eslint", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/greggman/pico-8-post-processing.git" 14 | }, 15 | "keywords": [ 16 | "webgl", 17 | "pico8", 18 | "pico-8", 19 | "glsl", 20 | "shadertoy" 21 | ], 22 | "author": "Gregg Tavares", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/greggman/pico-8-post-processing/issues" 26 | }, 27 | "homepage": "https://github.com/greggman/pico-8-post-processing#readme", 28 | "devDependencies": { 29 | "eslint": "^7.5.0", 30 | "eslint-plugin-html": "^6.0.2", 31 | "eslint-plugin-optional-comma-spacing": "0.0.4", 32 | "eslint-plugin-require-trailing-comma": "0.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Gregg Tavares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | -------------------------------------------------------------------------------- /nano-villager_html/filters/old-television-frame.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Old Television Frame', 3 | author: 'Minus256', 4 | authorUrl: 'https://www.shadertoy.com/user/Minus256', 5 | src: 'https://www.shadertoy.com/view/XlVczc', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 11 | { 12 | vec2 fragCord = abs(fragCoord*2.0-iResolution.xy/1.0); 13 | float line = pow(fragCord.x/iResolution.x,20.0)+pow((fragCord.y+(iResolution.x-iResolution.y))/iResolution.x,20.0); 14 | float minphase = abs(0.02*sin(iTime*10.0)+0.2*sin(fragCoord.y)); 15 | float frame = max(min(line+minphase,1.0),0.0); 16 | 17 | 18 | 19 | //------------scene------------// 20 | vec2 uv = fragCoord / iResolution.xy; 21 | vec4 Color = texture2D(iChannel0, uv); 22 | //-----------------------------// 23 | 24 | 25 | 26 | vec4 colorinput = Color; //put fragcolor 27 | 28 | fragColor = colorinput - vec4(vec3(frame),0.0); 29 | } 30 | `, 31 | width: 512, 32 | height: 512, 33 | filter: false, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /nano-villager_html/octocat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /nano-villager_html/filters/old-tv-effect.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Old TV effect', 3 | author: 'mackycheese21', 4 | authorUrl: 'https://www.shadertoy.com/user/mackycheese21', 5 | src: 'https://www.shadertoy.com/view/XldcDf', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | float luma(vec4 color){ 11 | return 0.2126*color.x+0.7152*color.y+0.0722*color.z;//OpenGL 4.0 Shading Language Cookbook, page 154 12 | } 13 | 14 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 15 | { 16 | vec2 uv=fragCoord.xy/iResolution.xy;//iChannelResolution[0].xy; 17 | vec4 sum=vec4(0.0); 18 | float num=0.0; 19 | for(int i=0;i<3;i++){ 20 | vec2 offset=texture2D(iChannel1,mod(vec2(uv.x*124.5523+5230.354323*iTime+2523.254*float(i),uv.y*.5364+624.667*iTime+2523.789*float(i)),1.0)).xy; 21 | offset-=0.5; 22 | offset*=1.9; 23 | sum+=vec4(luma(texture2D(iChannel0,uv+offset))); 24 | num++; 25 | } 26 | fragColor=sum/num; 27 | //fragColor=texture2D(iChannel0,uv); 28 | } 29 | `, 30 | width: 512, 31 | height: 512, 32 | filter: false, 33 | iChannel1: { 34 | src: createNoise(256, 256), 35 | filter: 'mipMap', 36 | wrap: 'repeat', 37 | vFlip: true, 38 | }, 39 | }, 40 | }; 41 | 42 | function createNoise(width, height) { 43 | const data = new Uint8Array(width * height * 4); 44 | for (let i = 0; i < data.length; ++i) { 45 | data[i] = Math.random() * 256; 46 | } 47 | return { 48 | width, 49 | height, 50 | data, 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /nano-villager_html/filters/mattias-crt.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'MattiasCRT ', 3 | author: 'Mattias', 4 | authorUrl: 'https://www.shadertoy.com/user/Mattias', 5 | src: 'https://www.shadertoy.com/view/Ms23DR', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | // Loosely based on postprocessing shader by inigo quilez, License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. 11 | 12 | vec2 curve(vec2 uv) 13 | { 14 | uv = (uv - 0.5) * 2.0; 15 | uv *= 1.1; 16 | uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0); 17 | uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0); 18 | uv = (uv / 2.0) + 0.5; 19 | uv = uv *0.92 + 0.04; 20 | return uv; 21 | } 22 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 23 | { 24 | vec2 q = fragCoord.xy / iResolution.xy; 25 | vec2 uv = q; 26 | uv = curve( uv ); 27 | vec3 oricol = texture2D( iChannel0, vec2(q.x,q.y) ).xyz; 28 | vec3 col; 29 | float x = sin(0.3*iTime+uv.y*21.0)*sin(0.7*iTime+uv.y*29.0)*sin(0.3+0.33*iTime+uv.y*31.0)*0.0017; 30 | 31 | col.r = texture2D(iChannel0,vec2(x+uv.x+0.001,uv.y+0.001)).x+0.05; 32 | col.g = texture2D(iChannel0,vec2(x+uv.x+0.000,uv.y-0.002)).y+0.05; 33 | col.b = texture2D(iChannel0,vec2(x+uv.x-0.002,uv.y+0.000)).z+0.05; 34 | col.r += 0.08*texture2D(iChannel0,0.75*vec2(x+0.025, -0.027)+vec2(uv.x+0.001,uv.y+0.001)).x; 35 | col.g += 0.05*texture2D(iChannel0,0.75*vec2(x+-0.022, -0.02)+vec2(uv.x+0.000,uv.y-0.002)).y; 36 | col.b += 0.08*texture2D(iChannel0,0.75*vec2(x+-0.02, -0.018)+vec2(uv.x-0.002,uv.y+0.000)).z; 37 | 38 | col = clamp(col*0.6+0.4*col*col*1.0,0.0,1.0); 39 | 40 | float vig = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y)); 41 | col *= vec3(pow(vig,0.3)); 42 | 43 | col *= vec3(0.95,1.05,0.95); 44 | col *= 2.8; 45 | 46 | float scans = clamp( 0.35+0.35*sin(3.5*iTime+uv.y*iResolution.y*1.5), 0.0, 1.0); 47 | 48 | float s = pow(scans,1.7); 49 | col = col*vec3( 0.4+0.7*s) ; 50 | 51 | col *= 1.0+0.01*sin(110.0*iTime); 52 | if (uv.x < 0.0 || uv.x > 1.0) 53 | col *= 0.0; 54 | if (uv.y < 0.0 || uv.y > 1.0) 55 | col *= 0.0; 56 | 57 | col*=1.0-0.65*vec3(clamp((mod(fragCoord.x, 2.0)-1.0)*2.0,0.0,1.0)); 58 | 59 | float comp = smoothstep( 0.1, 0.9, sin(iTime) ); 60 | 61 | // Remove the next line to stop cross-fade between original and postprocess 62 | // col = mix( col, oricol, comp ); 63 | 64 | fragColor = vec4(col,1.0); 65 | } 66 | `, 67 | width: -1, 68 | height: -1, 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /nano-villager_html/filters/cmyk-halftone.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'CMYK Halftone', 3 | author: 'tsone', 4 | authorUrl: 'https://www.shadertoy.com/user/tsone', 5 | src: 'https://www.shadertoy.com/view/Mdf3Dn', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | #define DOTSIZE 1.48 11 | #define D2R(d) radians(d) 12 | #define MIN_S 2.5 13 | #define MAX_S 19.0 14 | #define SPEED 0.57 15 | 16 | #define SST 0.888 17 | #define SSQ 0.288 18 | 19 | #define ORIGIN (0.5 * iResolution.xy) 20 | float R; 21 | float S; 22 | 23 | vec4 rgb2cmyki(in vec3 c) 24 | { 25 | float k = max(max(c.r, c.g), c.b); 26 | return min(vec4(c.rgb / k, k), 1.0); 27 | } 28 | 29 | vec3 cmyki2rgb(in vec4 c) 30 | { 31 | return c.rgb * c.a; 32 | } 33 | 34 | vec2 px2uv(in vec2 px) 35 | { 36 | return vec2(px / iResolution.xy); 37 | } 38 | 39 | vec2 grid(in vec2 px) 40 | { 41 | return px - mod(px,S); 42 | } 43 | 44 | vec4 ss(in vec4 v) 45 | { 46 | return smoothstep(SST-SSQ, SST+SSQ, v); 47 | } 48 | 49 | vec4 halftone(in vec2 fc,in mat2 m) 50 | { 51 | vec2 smp = (grid(m*fc) + 0.5*S) * m; 52 | float s = min(length(fc-smp) / (DOTSIZE*0.5*S), 1.0); 53 | vec3 texc = texture2D(iChannel0, px2uv(smp+ORIGIN)).rgb; 54 | texc = pow(texc, vec3(2.2)); // Gamma decode. 55 | vec4 c = rgb2cmyki(texc); 56 | return c+s; 57 | } 58 | 59 | mat2 rotm(in float r) 60 | { 61 | float cr = cos(r); 62 | float sr = sin(r); 63 | return mat2( 64 | cr,-sr, 65 | sr,cr 66 | ); 67 | } 68 | 69 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 70 | { 71 | R = SPEED*0.333*iTime; 72 | S = MIN_S + (MAX_S-MIN_S) * (0.5 - 0.5*cos(SPEED*iTime)); 73 | 74 | if (iMouse.z > 0.5) 75 | { 76 | S = MIN_S + (MAX_S-MIN_S) * 2.0*abs(iMouse.x-ORIGIN.x) / iResolution.x; 77 | R = D2R(180.0 * (iMouse.y-ORIGIN.y) / iResolution.y); 78 | } 79 | 80 | vec2 fc = fragCoord.xy - ORIGIN; 81 | 82 | mat2 mc = rotm(R + D2R(15.0)); 83 | mat2 mm = rotm(R + D2R(75.0)); 84 | mat2 my = rotm(R); 85 | mat2 mk = rotm(R + D2R(45.0)); 86 | 87 | float k = halftone(fc, mk).a; 88 | vec3 c = cmyki2rgb(ss(vec4( 89 | halftone(fc, mc).r, 90 | halftone(fc, mm).g, 91 | halftone(fc, my).b, 92 | halftone(fc, mk).a 93 | ))); 94 | 95 | c = pow(c, vec3(1.0/2.2)); // Gamma encode. 96 | fragColor = vec4(c, 1.0); 97 | } 98 | `, 99 | width: -2, 100 | height: -2, 101 | iChannel0: { 102 | filter: 'linear', 103 | vFlip: true, 104 | }, 105 | }, 106 | }; 107 | -------------------------------------------------------------------------------- /nano-villager_html/filters/vcr-distortion.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'VCR distortion', 3 | author: 'ryk', 4 | authorUrl: 'https://www.shadertoy.com/user/ryk', 5 | src: 'https://www.shadertoy.com/view/ldjGzV', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | float noise(vec2 p) 11 | { 12 | float s = texture2D(iChannel1,vec2(1.,2.*cos(iTime))*iTime*8. + p*1.).x; 13 | s *= s; 14 | return s; 15 | } 16 | 17 | float onOff(float a, float b, float c) 18 | { 19 | return step(c, sin(iTime + a*cos(iTime*b))); 20 | } 21 | 22 | float ramp(float y, float start, float end) 23 | { 24 | float inside = step(start,y) - step(end,y); 25 | float fact = (y-start)/(end-start)*inside; 26 | return (1.-fact) * inside; 27 | 28 | } 29 | 30 | float stripes(vec2 uv) 31 | { 32 | 33 | float noi = noise(uv*vec2(0.5,1.) + vec2(1.,3.)); 34 | return ramp(mod(uv.y*4. + iTime/2.+sin(iTime + sin(iTime*0.63)),1.),0.5,0.6)*noi; 35 | } 36 | 37 | vec3 getVideo(vec2 uv) 38 | { 39 | vec2 look = uv; 40 | float window = 1./(1.+20.*(look.y-mod(iTime/4.,1.))*(look.y-mod(iTime/4.,1.))); 41 | look.x = look.x + sin(look.y*10. + iTime)/50.*onOff(4.,4.,.3)*(1.+cos(iTime*80.))*window; 42 | float vShift = 0.4*onOff(2.,3.,.9)*(sin(iTime)*sin(iTime*20.) + 43 | (0.5 + 0.1*sin(iTime*200.)*cos(iTime))); 44 | look.y = mod(look.y + vShift, 1.); 45 | vec3 video = vec3(texture2D(iChannel0,look)); 46 | return video; 47 | } 48 | 49 | vec2 screenDistort(vec2 uv) 50 | { 51 | uv -= vec2(.5,.5); 52 | uv = uv*1.2*(1./1.2+2.*uv.x*uv.x*uv.y*uv.y); 53 | uv += vec2(.5,.5); 54 | return uv; 55 | } 56 | 57 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 58 | { 59 | vec2 uv = fragCoord.xy / iResolution.xy; 60 | uv = screenDistort(uv); 61 | vec3 video = getVideo(uv); 62 | float vigAmt = 3.+.3*sin(iTime + 5.*cos(iTime*5.)); 63 | float vignette = (1.-vigAmt*(uv.y-.5)*(uv.y-.5))*(1.-vigAmt*(uv.x-.5)*(uv.x-.5)); 64 | 65 | video += stripes(uv); 66 | video += noise(uv*2.)/2.; 67 | video *= vignette; 68 | video *= (12.+mod(uv.y*30.+iTime,1.))/13.; 69 | 70 | fragColor = vec4(video,1.0); 71 | } 72 | `, 73 | width: -1, 74 | height: -1, 75 | iChannel1: { 76 | src: createNoise(256, 256), 77 | filter: 'mipMap', 78 | wrap: 'repeat', 79 | vFlip: true, 80 | }, 81 | }, 82 | }; 83 | 84 | function createNoise(width, height) { 85 | const data = new Uint8Array(width * height * 4); 86 | for (let i = 0; i < data.length; ++i) { 87 | data[i] = Math.random() * 256; 88 | } 89 | return { 90 | width, 91 | height, 92 | data, 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | 3 | module.exports = { 4 | env: { 5 | 'es6': true, 6 | }, 7 | parserOptions: { 8 | 'sourceType': 'module', 9 | 'ecmaVersion': 9, 10 | }, 11 | 12 | plugins: [ 13 | 'eslint-plugin-html', 14 | 'eslint-plugin-optional-comma-spacing', 15 | // "eslint-plugin-one-variable-per-var", 16 | 'eslint-plugin-require-trailing-comma', 17 | ], 18 | extends: 'eslint:recommended', 19 | rules: { 20 | 'one-var': ['error', 'never'], 21 | 'no-eval': 2, 22 | 'no-array-constructor': 2, 23 | 'no-caller': 2, 24 | 'no-catch-shadow': 2, 25 | 'no-extend-native': 2, 26 | 'no-extra-bind': 2, 27 | 'no-implied-eval': 2, 28 | 'no-inner-declarations': 0, 29 | 'no-iterator': 2, 30 | 'no-label-var': 2, 31 | 'no-labels': 2, 32 | 'no-lone-blocks': 0, 33 | 'no-multi-str': 2, 34 | 'no-native-reassign': 2, 35 | 'no-new': 2, 36 | 'no-new-func': 2, 37 | 'no-new-object': 2, 38 | 'no-new-wrappers': 2, 39 | 'no-octal-escape': 2, 40 | 'no-process-exit': 2, 41 | 'no-proto': 2, 42 | 'no-return-assign': 2, 43 | 'no-script-url': 2, 44 | 'no-sequences': 2, 45 | 'no-shadow-restricted-names': 2, 46 | 'no-spaced-func': 2, 47 | 'no-trailing-spaces': 2, 48 | 'no-undef-init': 2, 49 | 'no-unused-expressions': 2, 50 | 'no-use-before-define': 0, 51 | 'no-var': 2, 52 | 'no-with': 2, 53 | 'prefer-const': 2, 54 | 'consistent-return': 2, 55 | 'curly': [2, 'all'], 56 | 'no-extra-parens': [2, 'functions'], 57 | 'eqeqeq': 2, 58 | 'new-cap': 2, 59 | 'new-parens': 2, 60 | 'semi-spacing': [2, {'before': false, 'after': true}], 61 | 'space-infix-ops': 2, 62 | 'space-unary-ops': [2, { 'words': true, 'nonwords': false }], 63 | 'yoda': [2, 'never'], 64 | 65 | 'brace-style': [2, '1tbs', { 'allowSingleLine': false }], 66 | 'camelcase': [0], 67 | 'comma-spacing': 0, 68 | 'comma-dangle': 0, 69 | 'comma-style': [2, 'last'], 70 | 'optional-comma-spacing/optional-comma-spacing': [2, {'after': true}], 71 | 'dot-notation': 0, 72 | 'eol-last': [0], 73 | 'global-strict': [0], 74 | 'key-spacing': [0], 75 | 'no-comma-dangle': [0], 76 | 'no-irregular-whitespace': 2, 77 | 'no-multi-spaces': [0], 78 | 'no-loop-func': 0, 79 | 'no-obj-calls': 2, 80 | 'no-redeclare': [0], 81 | 'no-shadow': [0], 82 | 'no-undef': [2], 83 | 'no-unreachable': 2, 84 | //"one-variable-per-var/one-variable-per-var": [2], 85 | 'quotes': [2, 'single'], 86 | 'require-atomic-updates': 0, 87 | 'require-trailing-comma/require-trailing-comma': [2], 88 | 'require-yield': 0, 89 | 'semi': [2, 'always'], 90 | 'strict': [2, 'global'], 91 | 'space-before-function-paren': [2, 'never'], 92 | 'keyword-spacing': [1, {'before': true, 'after': true, 'overrides': {}} ], 93 | }, 94 | }; 95 | -------------------------------------------------------------------------------- /nano-villager_html/filters/old-tv.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: '[liliilli] Old TV', 3 | author: 'liliilli', 4 | authorUrl: 'https://www.shadertoy.com/user/liliilli', 5 | src: 'https://www.shadertoy.com/view/XtKfDK', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | const float uIntensity = 0.03; // Frame distortion intensity. 0.02 ~ 0.05 recommended. 11 | const float uThreshold = 0.85; // 0.75 ~ 0.90 would be recommended. 12 | const float uMax = 64.0; // Distortion for edge of threshold. 13 | const float uMargin = 8.0; // Margin. 14 | 15 | float GetOverThreadsholdIntensity(const float a, const float t) { 16 | float b = pow(t, 2.0) * (1.0 - (1.0 / uMax)); 17 | return uMax * pow(a - (t - (t / uMax)), 2.0) + b; 18 | } 19 | 20 | bool IsOob(const vec2 inputTexCoord) { 21 | return inputTexCoord.x > 1.0 || inputTexCoord.y > 1.0 22 | || inputTexCoord.x < 0.0 || inputTexCoord.y < 0.0; 23 | } 24 | 25 | vec2 ApplyMargin(const vec2 texel, const float margin) { 26 | vec2 m = vec2(margin * 4.0) / iResolution.xy; 27 | return (texel - 0.5) * (1.0 + m) + 0.5; 28 | } 29 | 30 | vec3 GetColor(const vec3 inputOffset) { 31 | return 0.5 + 0.5 * cos(iTime + (inputOffset * 4.0) + vec3(0, 2, 4)); 32 | } 33 | 34 | float ScaleWithAxis(const float value, const float axis, const float scale) { 35 | return (value - axis) * scale + axis; 36 | } 37 | 38 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 39 | { // Normalized pixel coordinates (from 0 to 1) 40 | vec2 uv = fragCoord/iResolution.xy; 41 | float x = uv.x * 2.0 - 1.0; 42 | float y = uv.y * 2.0 - 1.0; 43 | 44 | // Distort uv coordinate, and if closer to frame bound, do more distortion. 45 | float x_intensity = uIntensity; 46 | float y_intensity = uIntensity; 47 | if (abs(x) >= uThreshold && abs(y) >= uThreshold) { 48 | y_intensity *= GetOverThreadsholdIntensity(abs(x), uThreshold); 49 | x_intensity *= GetOverThreadsholdIntensity(abs(y), uThreshold); 50 | } 51 | else { 52 | y_intensity *= pow(x, 2.0); 53 | x_intensity *= pow(y, 2.0); 54 | } 55 | 56 | // Get texel and apply margin (px) 57 | float y_offset = y_intensity * y; 58 | float x_offset = x_intensity * x; 59 | vec2 finalTexel = ApplyMargin(uv + vec2(x_offset, y_offset), uMargin); 60 | 61 | // ShaderToy does not support border (to be out-of-bound black color), 62 | // so checking texel is out of bound. 63 | if (IsOob(finalTexel) == false) 64 | { 65 | fragColor = texture2D(iChannel0, finalTexel); 66 | //GetColor(finalTexel.xyx) * 67 | //ScaleWithAxis(texture2D(iChannel0, finalTexel).r, 1.0, 1.0), 1.0); 68 | } 69 | } 70 | `, 71 | width: 512, 72 | height: 512, 73 | filter: false, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /nano-villager_html/filters/led-display.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'LED Display', 3 | author: 'CloneDeath', 4 | authorUrl: 'https://www.shadertoy.com/user/CloneDeath', 5 | src: 'https://www.shadertoy.com/view/lsSSD1', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | const float pixelHeight = 100.0; 11 | const float num_samples = 10.0; 12 | const float pixel_vsep = 0.5; 13 | const float scanline_speed = 0.1; 14 | const float scanline_decay = 0.5; 15 | const float BarrelPower = 1.01; 16 | 17 | vec2 Distort(vec2 p) 18 | { 19 | float theta = atan(p.y, p.x); 20 | float radius = length(p); 21 | radius = pow(radius, BarrelPower); 22 | p.x = radius * cos(theta); 23 | p.y = radius * sin(theta); 24 | return 0.5 * (p + 1.0); 25 | } 26 | 27 | 28 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 29 | { 30 | float aspect = iResolution.y / iResolution.x; 31 | vec2 pixelcount = vec2(pixelHeight / aspect, pixelHeight); 32 | vec2 pixelsize = 1.0/pixelcount; 33 | vec2 pixelpos = fragCoord.xy / iResolution.xy; 34 | vec2 longpixelsize = pixelsize * vec2(1.0, 1.0 + pixel_vsep); 35 | 36 | /* Apply barrel distortion */ 37 | vec2 xy = 2.0 * pixelpos - 1.0; 38 | float d = length(xy); 39 | //if (d < 1.0) 40 | { 41 | pixelpos = Distort(xy); 42 | } 43 | 44 | /* Grab pixel */ 45 | vec2 texpos = pixelpos; 46 | texpos.xy = floor(texpos.xy / pixelsize) * pixelsize; 47 | texpos.xy += pixelsize/2.0; 48 | 49 | /* Grab color */ 50 | float count = 0.0; 51 | vec4 color = vec4(0); 52 | for (float x = 0.0; x <= num_samples; x += 1.0){ 53 | for (float y = 0.0; y <= num_samples; y += 1.0){ 54 | count ++; 55 | color += texture2D(iChannel0, texpos + (vec2(x, y) * pixelsize / num_samples)); 56 | } 57 | } 58 | color /= count; 59 | 60 | /* brighten scanlines */ 61 | float line = mod(-iTime * scanline_speed, 1.0); 62 | if (abs(pixelpos.y - line) < pixelsize.y / 4.0){ 63 | color.rgb *= 1.5; 64 | } 65 | 66 | /* make circular */ 67 | vec2 mod_pos = (mod(pixelpos, pixelsize) / pixelsize) - 0.5; 68 | color.rgb *= 1.0 - sqrt(mod_pos.x * mod_pos.x + mod_pos.y * mod_pos.y); 69 | 70 | /* color shift */ 71 | mod_pos += 0.5; 72 | color.r *= pow(1.0 - abs(mod_pos.x - 0.25), 2.0); 73 | color.g *= pow(1.0 - abs(mod_pos.x - 0.5), 2.0); 74 | color.b *= pow(1.0 - abs(mod_pos.x - 0.75), 2.0); 75 | color.rgb *= vec3(0.8, 0.75, 0.9); 76 | color.rgb *= 2.5; 77 | 78 | /* edge darken */ 79 | color.rgb *= 0.9 - sqrt(pow((pixelpos - 0.5).x, 2.0)); 80 | 81 | fragColor = color; 82 | } 83 | `, 84 | width: -2, 85 | height: -2, 86 | iChannel0: { 87 | filter: 'linear', 88 | vFlip: true, 89 | }, 90 | }, 91 | }; 92 | -------------------------------------------------------------------------------- /nano-villager_html/filters.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | /* global history */ 3 | /* global pico8Filter */ 4 | /* global URL */ 5 | /* global URLSearchParams */ 6 | /* global window */ 7 | 8 | import defaultFilter from './filters/default.js'; 9 | import oldTelevisionFrame from './filters/old-television-frame.js'; 10 | import oldTV from './filters/old-tv.js'; 11 | import oldTVEffect from './filters/old-tv-effect.js'; 12 | import glitch2 from './filters/glitch2.js'; 13 | import vcrDistortion from './filters/vcr-distortion.js'; 14 | import distortedTV from './filters/distorted-tv.js'; 15 | import mattiasCRT from './filters/mattias-crt.js'; 16 | import hq4x from './filters/hq4x/hq4x.js'; 17 | import loResRoto from './filters/lo-res-roto.js'; 18 | import hiResRoto from './filters/hi-res-roto.js'; 19 | import bugInTheTV from './filters/bug-in-the-tv.js'; 20 | import ledDisplay from './filters/led-display.js'; 21 | import doubleVision from './filters/double-vision.js'; 22 | import gbClassic from './filters/gb-classic.js'; 23 | import cmykHalftone from './filters/cmyk-halftone.js'; 24 | import thinWater from './filters/thin-water.js'; 25 | import pinscreenVideo from './filters/pinscreen-video.js'; 26 | 27 | const filters = [ 28 | defaultFilter, 29 | oldTelevisionFrame, 30 | oldTV, 31 | oldTVEffect, 32 | glitch2, 33 | vcrDistortion, 34 | distortedTV, 35 | mattiasCRT, 36 | hq4x, 37 | bugInTheTV, 38 | ledDisplay, 39 | doubleVision, 40 | gbClassic, 41 | loResRoto, 42 | hiResRoto, 43 | cmykHalftone, 44 | thinWater, 45 | ]; 46 | 47 | if (document.createElement('canvas').getContext('webgl2')) { 48 | filters.push( 49 | pinscreenVideo, 50 | ); 51 | } 52 | 53 | function setLink(elem, href, content) { 54 | elem.textContent = content || ''; 55 | elem.href = href; 56 | const show = href ? '' : 'none'; 57 | elem.style.display = show; 58 | elem.previousSibling.style.display = show; 59 | } 60 | 61 | // because the script is before the body 😅 62 | window.onload = function() { 63 | const srcElem = document.querySelector('#src'); 64 | const authorElem = document.querySelector('#author'); 65 | const licenseElem = document.querySelector('#license'); 66 | 67 | function setFilter(ndx) { 68 | const filterInfo = filters[ndx]; 69 | const {author, authorUrl, src, license, licenseUrl, filter} = filterInfo; 70 | setLink(srcElem, src, 'src'); 71 | setLink(authorElem, authorUrl, author); 72 | setLink(licenseElem, licenseUrl, license); 73 | pico8Filter.setFilter(filter); 74 | } 75 | 76 | let firstFilterNdx = 7; 77 | const settings = Object.fromEntries(new URLSearchParams(window.location.search).entries()); 78 | if (settings.filter) { 79 | const index = filters.map(f => f.name).indexOf(settings.filter); 80 | if (index >= 0) { 81 | firstFilterNdx = index; 82 | } 83 | } 84 | 85 | 86 | const selectElem = document.querySelector('.filters select'); 87 | filters.forEach((filter) => { 88 | const optionElem = document.createElement('option'); 89 | optionElem.innerText = filter.name; 90 | selectElem.appendChild(optionElem); 91 | }); 92 | selectElem.selectedIndex = firstFilterNdx; 93 | setFilter(firstFilterNdx); 94 | selectElem.addEventListener('change', () => { 95 | const url = new URL(window.location.href); 96 | url.searchParams.set('filter', filters[selectElem.selectedIndex].name); 97 | history.replaceState(null, '', url.href); 98 | setFilter(selectElem.selectedIndex); 99 | }); 100 | }; 101 | 102 | -------------------------------------------------------------------------------- /nano-villager_html/filters/glitch2.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'glitch2', 3 | author: 'Coolok', 4 | authorUrl: 'https://www.shadertoy.com/user/Coolok', 5 | src: 'https://www.shadertoy.com/view/4dXBW2', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | float sat( float t ) { 11 | return clamp( t, 0.0, 1.0 ); 12 | } 13 | 14 | vec2 sat( vec2 t ) { 15 | return clamp( t, 0.0, 1.0 ); 16 | } 17 | 18 | //remaps inteval [a;b] to [0;1] 19 | float remap ( float t, float a, float b ) { 20 | return sat( (t - a) / (b - a) ); 21 | } 22 | 23 | //note: /\\ t=[0;0.5;1], y=[0;1;0] 24 | float linterp( float t ) { 25 | return sat( 1.0 - abs( 2.0*t - 1.0 ) ); 26 | } 27 | 28 | vec3 spectrum_offset( float t ) { 29 | vec3 ret; 30 | float lo = step(t,0.5); 31 | float hi = 1.0-lo; 32 | float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) ); 33 | float neg_w = 1.0-w; 34 | ret = vec3(lo,1.0,hi) * vec3(neg_w, w, neg_w); 35 | return pow( ret, vec3(1.0/2.2) ); 36 | } 37 | 38 | //note: [0;1] 39 | float rand( vec2 n ) { 40 | return fract(sin(dot(n.xy, vec2(12.9898, 78.233)))* 43758.5453); 41 | } 42 | 43 | //note: [-1;1] 44 | float srand( vec2 n ) { 45 | return rand(n) * 2.0 - 1.0; 46 | } 47 | 48 | float mytrunc( float x, float num_levels ) 49 | { 50 | return floor(x*num_levels) / num_levels; 51 | } 52 | vec2 mytrunc( vec2 x, float num_levels ) 53 | { 54 | return floor(x*num_levels) / num_levels; 55 | } 56 | 57 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 58 | { 59 | vec2 uv = fragCoord.xy / iResolution.xy; 60 | uv.y = uv.y; 61 | 62 | float time = mod(iTime*100.0, 32.0)/110.0; // + modelmat[0].x + modelmat[0].z; 63 | 64 | float GLITCH = 0.1 + iMouse.x / iResolution.x; 65 | 66 | float gnm = sat( GLITCH ); 67 | float rnd0 = rand( mytrunc( vec2(time, time), 6.0 ) ); 68 | float r0 = sat((1.0-gnm)*0.7 + rnd0); 69 | float rnd1 = rand( vec2(mytrunc( uv.x, 10.0*r0 ), time) ); //horz 70 | //float r1 = 1.0f - sat( (1.0f-gnm)*0.5f + rnd1 ); 71 | float r1 = 0.5 - 0.5 * gnm + rnd1; 72 | r1 = 1.0 - max( 0.0, ((r1<1.0) ? r1 : 0.9999999) ); //note: weird ass bug on old drivers 73 | float rnd2 = rand( vec2(mytrunc( uv.y, 40.0*r1 ), time) ); //vert 74 | float r2 = sat( rnd2 ); 75 | 76 | float rnd3 = rand( vec2(mytrunc( uv.y, 10.0*r0 ), time) ); 77 | float r3 = (1.0-sat(rnd3+0.8)) - 0.1; 78 | 79 | float pxrnd = rand( uv + time ); 80 | 81 | float ofs = 0.05 * r2 * GLITCH * ( rnd0 > 0.5 ? 1.0 : -1.0 ); 82 | ofs += 0.5 * pxrnd * ofs; 83 | 84 | uv.y += 0.1 * r3 * GLITCH; 85 | 86 | const int NUM_SAMPLES = 20; 87 | const float RCP_NUM_SAMPLES_F = 1.0 / float(NUM_SAMPLES); 88 | 89 | vec4 sum = vec4(0.0); 90 | vec3 wsum = vec3(0.0); 91 | for( int i=0; i 6 | 7 | [Pico-8](https://www.lexaloffle.com/pico-8.php) exports to HTML via 8 | `export somename.html` 9 | 10 | Add this script to the top of that page as in 11 | 12 | ```html 13 | 14 | ``` 15 | 16 | Then below it add your GLSL post processing filter. 17 | 18 | ```html 19 | 32 | ``` 33 | 34 | Filters are based on the same uniforms as [Shadertoy](https://shadertoy.com). In particular you're 35 | expected to write a GLSL shader function like this 36 | 37 | ```glsl 38 | void mainImage(out vec4 fragColor, in vec2 fragCoord) { 39 | vec2 uv = fragCoord / iResolution.xy; 40 | fragColor = texture2D(iChannel0, uv); 41 | } 42 | ``` 43 | 44 | with access to [the same inputs as Shadertoy](https://www.shadertoy.com/howto). 45 | 46 | ## Uniforms 47 | 48 | * `uniform vec3 iResolution;` 49 | 50 | The resolution of the canvas or in other words the output resolution (see [options](#options) below). 51 | 52 | * `uniform float iTime;` 53 | 54 | The time in seconds since the filter was set 55 | 56 | * `uniform float iTimeDelta;` 57 | 58 | The time in seconds since the previous frame 59 | 60 | * `uniform float iFrame;` 61 | 62 | The frame count since the filter was set 63 | 64 | * `uniform float iChannelTime[4];` 65 | 66 | The same as `iTime` 67 | 68 | * `uniform vec4 iMouse;` 69 | 70 | `xy` are the mouse position in canvas coordinates, z is the mouse button. 71 | note: `xy` are only updated when the button is pressed. 72 | 73 | * `uniform vec4 iDate;` 74 | 75 | The date in seconds since January 1st 1970 76 | 77 | * `uniform vec3 iChannelResolution[4];` 78 | 79 | `[0]` is the size of the input texture which is always (128x128) Pico-8. 80 | The other indices are the size of textures you supply. ([see options](#options)) 81 | 82 | * `uniform sampler2D iChannel0;` 83 | 84 | The Pico-8 texture 85 | 86 | * `uniform sampler2D iChannel1;` (and `2` and `3`) 87 | 88 | User supplied textures (see [options](#options) below). 89 | 90 | ## Options 91 | 92 | Call `pico8Filter.setFilter` to set a new filter and pass it an object 93 | with the following properties. 94 | 95 | * `fragmentShader` (string) 96 | 97 | The `mainImage` GLSL function for your filter 98 | 99 | * `width`, `height` (number) 100 | 101 | The resolution to make the canvas. Pico-8's normal canvas 102 | resolution is 128x128 but if you want to post process you probably want 103 | more output pixels than input pixels. The default is 128x128 104 | Setting a dimension to a negative number means to take the canvas's 105 | display size times that amount. In other words, 106 | 107 | ``` 108 | width: 128, // make the canvas's resolution 128 pixels wide 109 | ``` 110 | 111 | vs 112 | 113 | ``` 114 | width: -2, // make the canvas's resolution double its display size 115 | ``` 116 | 117 | * `iChannel0`, `iChannel1`, `iChannel2`, `iChannel3` (object) 118 | 119 | These are user supplied textures, they have 4 options: 120 | 121 | * `filter` (string) 122 | 123 | like Shadertoy can be `nearest` (default), `linear` or `mipmap` 124 | 125 | * `wrap` (string) 126 | 127 | like Shadertoy can be `repeat` or `clamp` (default) 128 | 129 | * `vFlip` (bool) 130 | 131 | true or false to flip the texture. 132 | 133 | * `src` (HTMLElement, string, ImageData-ish) 134 | 135 | If you pass it a string it will assume it's a URL for an image and 136 | load it 137 | 138 | If you pass an HTMLElement like `Image` it will use it immediately 139 | 140 | If you pass it a JavaScript object with something like 141 | 142 | ```js 143 | { 144 | width: 128, 145 | height: 128, 146 | data: new Uint8Array(128 * 128 * 4), 147 | } 148 | ``` 149 | 150 | It will upload that data as RGBA/UNSIGNED_BYTE data 151 | 152 | `iChannel0` is always the texture of the game from pico8 so you can 153 | only use the `filter`, `wrap` and `vFlip` settings. You'll probably 154 | want to set `vFlip` to `true` 155 | 156 | ## Licence 157 | 158 | MIT 159 | 160 | -------------------------------------------------------------------------------- /nano-villager_html/filters/gb-classic.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Gameboy Classic', 3 | author: 'jilski', 4 | authorUrl: 'https://www.shadertoy.com/user/jilski', 5 | src: 'https://www.shadertoy.com/view/MdyfDR', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | /* 11 | 12 | Nintendo GameBoy Classic (DMG) Post-Effect 13 | Written by jilski for the strangeness project (http://strangeness.jilski.com) 14 | June 2018 15 | 16 | Strangeness is a monthly live music & visuals concert experience taking place in Berlin. 17 | While acoustic instruments are the musical core of the show - which resolves 18 | around space and retro 8-bit video games - the projection into the room is handled 19 | by a sound analysis and video synthesis system based on the Vulkan API and lots of shaders. 20 | It all reacts to the music and is supposed to make sense. 21 | 22 | This effect depends on the live music volume and other parameters. This has been 23 | simplified for Shadertoy. I left "blend_factor" and loudness in as dummies, as well as 24 | pix_size. 25 | 26 | Since I've taken a lot of inspiration from shaders on this side and am using some 27 | re-written variations for my own show so I thought it'd be very fair to give back 28 | a bit. 29 | 30 | Yeah and you're all invited to strangeness. 31 | */ 32 | 33 | vec3 rgb(int r, int g, int b) 34 | { 35 | return vec3(float(r)/256.,float(g)/256.,float(b)/256.); 36 | } 37 | 38 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 39 | { 40 | // blend_factor goes from 0 to 1 41 | // here's a curve to show the effect of transitioning 42 | float blend_factor = 1.0-pow(0.5+0.5*cos(iTime/10.0),2.0); 43 | 44 | // originally, this is the volume of the music... not sure how to get the volume of the video playback... so it's a stud 45 | float loudness = cos(iTime/0.5); 46 | 47 | // original gameboy dmg resolution: 160*144 48 | float pix_size = 8.0; 49 | 50 | 51 | // i took pictures of my own gameboy classic and measured the rgb then adjusted to taste and memory :D 52 | //vec3 palette[4] = vec3[](rgb(180,210,46), rgb(155,195,40), rgb(108,151,35), rgb(54,112,30)); 53 | //vec3 line = palette[0]*1.6; // line are the lines between pixels. they are brighter. 54 | vec3 line = texture2D(iChannel1, vec2(0,0)).rgb * 1.6; 55 | 56 | vec2 coord = fragCoord/iResolution.xy; 57 | 58 | vec2 resolution = vec2(iResolution.x/pix_size,iResolution.y/pix_size); 59 | 60 | vec2 uv = mix(coord,(floor(coord*resolution) / resolution),blend_factor); 61 | vec2 pix = uv * resolution; 62 | 63 | vec3 orig = texture2D(iChannel0,uv).rgb; 64 | vec3 col = line; 65 | vec2 line_width = vec2(1.0/iResolution.x,1.0/iResolution.y); 66 | 67 | vec2 pix_thresh = mix(vec2(0.),line_width,blend_factor); 68 | 69 | bool is_pixel = (coord.x-uv.x) > pix_thresh.x && (coord.y-uv.y) > pix_thresh.y; 70 | 71 | // good old times when some pixel columns were broken. remember? :D 72 | bool is_not_broken = int(pix.x) != int(resolution.x)-2 73 | && (int(pix.x) != int(resolution.x)-4 || fract(4.0*sin(iTime/10.))>0.95) && int(pix.x) != 1; 74 | 75 | if ( is_pixel && is_not_broken ) 76 | { 77 | float val = (orig.r+orig.g+orig.b)/3.0; 78 | int shade = int(min(3.0,4.0*log(1.0+val*1.71))); // log(1+x*(e-1)) 79 | //col = palette[shade]; 80 | col = texture2D(iChannel1, (vec2(shade, 0) + 0.5) / iChannelResolution[1].xy).rgb; 81 | 82 | // the following is optional: it's just some gradient across the screen. 83 | // like a bit of sunlight screwing up the contrast :D 84 | vec2 offs = 3.*vec2(0.5+0.5*sin(iTime), 0.5+0.5*cos(iTime/1.2)); 85 | vec2 cen = vec2(0.5,0.5) + offs; 86 | col += vec3(0.1,0.14,0.01)*(0.5+0.5*sin(atan(cen.x,cen.y))); // slight loss of contrast, moving with time 87 | col -= vec3(0.8,1.0,0.5)*5.0*sin(loudness)*length(uv-coord); // slight pixel 3d-ness 88 | } 89 | 90 | // slight vignette 91 | col *= 1.1 - 0.3*distance(coord,vec2(0.5,0.5)); 92 | 93 | col = mix(orig,col,blend_factor); 94 | 95 | fragColor = vec4(col,1.0); 96 | } 97 | `, 98 | width: -1, 99 | height: -1, 100 | iChannel0: { 101 | filter: 'linear', 102 | vFlip: true, 103 | }, 104 | iChannel1: { 105 | filter: 'nearest', 106 | src: { 107 | width: 4, 108 | height: 1, 109 | data: new Uint8Array([ 110 | 180, 210, 46, 255, 111 | 155, 195, 40, 255, 112 | 108, 151, 35, 255, 113 | 54, 112, 30, 255, 114 | ]), 115 | }, 116 | }, 117 | }, 118 | }; 119 | -------------------------------------------------------------------------------- /nano-villager_html/filters/distorted-tv.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Distorted TV', 3 | author: 'ehj1', 4 | authorUrl: 'https://www.shadertoy.com/user/ehj1', 5 | src: 'https://www.shadertoy.com/view/ldXGW4', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | // change these values to 0.0 to turn off individual effects 11 | float vertJerkOpt = 1.0; 12 | float vertMovementOpt = 1.0; 13 | float bottomStaticOpt = 1.0; 14 | float scalinesOpt = 1.0; 15 | float rgbOffsetOpt = 1.0; 16 | float horzFuzzOpt = 1.0; 17 | 18 | // Noise generation functions borrowed from: 19 | // https://github.com/ashima/webgl-noise/blob/master/src/noise2D.glsl 20 | 21 | vec3 mod289(vec3 x) { 22 | return x - floor(x * (1.0 / 289.0)) * 289.0; 23 | } 24 | 25 | vec2 mod289(vec2 x) { 26 | return x - floor(x * (1.0 / 289.0)) * 289.0; 27 | } 28 | 29 | vec3 permute(vec3 x) { 30 | return mod289(((x*34.0)+1.0)*x); 31 | } 32 | 33 | float snoise(vec2 v) 34 | { 35 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 36 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 37 | -0.577350269189626, // -1.0 + 2.0 * C.x 38 | 0.024390243902439); // 1.0 / 41.0 39 | // First corner 40 | vec2 i = floor(v + dot(v, C.yy) ); 41 | vec2 x0 = v - i + dot(i, C.xx); 42 | 43 | // Other corners 44 | vec2 i1; 45 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 46 | //i1.y = 1.0 - i1.x; 47 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 48 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 49 | // x1 = x0 - i1 + 1.0 * C.xx ; 50 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 51 | vec4 x12 = x0.xyxy + C.xxzz; 52 | x12.xy -= i1; 53 | 54 | // Permutations 55 | i = mod289(i); // Avoid truncation effects in permutation 56 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) 57 | + i.x + vec3(0.0, i1.x, 1.0 )); 58 | 59 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 60 | m = m*m ; 61 | m = m*m ; 62 | 63 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 64 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 65 | 66 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 67 | vec3 h = abs(x) - 0.5; 68 | vec3 ox = floor(x + 0.5); 69 | vec3 a0 = x - ox; 70 | 71 | // Normalise gradients implicitly by scaling m 72 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 73 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 74 | 75 | // Compute final noise value at P 76 | vec3 g; 77 | g.x = a0.x * x0.x + h.x * x0.y; 78 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 79 | return 130.0 * dot(m, g); 80 | } 81 | 82 | float staticV(vec2 uv) { 83 | float staticHeight = snoise(vec2(9.0,iTime*1.2+3.0))*0.3+5.0; 84 | float staticAmount = snoise(vec2(1.0,iTime*1.2-6.0))*0.1+0.3; 85 | float staticStrength = snoise(vec2(-9.75,iTime*0.6-3.0))*2.0+2.0; 86 | return (1.0-step(snoise(vec2(5.0*pow(iTime,2.0)+pow(uv.x*7.0,1.2),pow((mod(iTime,100.0)+100.0)*uv.y*0.3+3.0,staticHeight))),staticAmount))*staticStrength; 87 | } 88 | 89 | 90 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 91 | { 92 | 93 | vec2 uv = fragCoord.xy/iResolution.xy; 94 | 95 | float jerkOffset = (1.0-step(snoise(vec2(iTime*1.3,5.0)),0.8))*0.05; 96 | 97 | float fuzzOffset = snoise(vec2(iTime*15.0,uv.y*80.0))*0.003; 98 | float largeFuzzOffset = snoise(vec2(iTime*1.0,uv.y*25.0))*0.004; 99 | 100 | float vertMovementOn = (1.0-step(snoise(vec2(iTime*0.2,8.0)),0.4))*vertMovementOpt; 101 | float vertJerk = (1.0-step(snoise(vec2(iTime*1.5,5.0)),0.6))*vertJerkOpt; 102 | float vertJerk2 = (1.0-step(snoise(vec2(iTime*5.5,5.0)),0.2))*vertJerkOpt; 103 | float yOffset = abs(sin(iTime)*4.0)*vertMovementOn+vertJerk*vertJerk2*0.3; 104 | float y = mod(uv.y+yOffset,1.0); 105 | 106 | 107 | float xOffset = (fuzzOffset + largeFuzzOffset) * horzFuzzOpt; 108 | 109 | float staticVal = 0.0; 110 | 111 | for (float y = -1.0; y <= 1.0; y += 1.0) { 112 | float maxDist = 5.0/200.0; 113 | float dist = y/200.0; 114 | staticVal += staticV(vec2(uv.x,uv.y+dist))*(maxDist-abs(dist))*1.5; 115 | } 116 | 117 | staticVal *= bottomStaticOpt; 118 | 119 | float red = texture2D( iChannel0, vec2(uv.x + xOffset -0.01*rgbOffsetOpt,y)).r+staticVal; 120 | float green = texture2D( iChannel0, vec2(uv.x + xOffset, y)).g+staticVal; 121 | float blue = texture2D( iChannel0, vec2(uv.x + xOffset +0.01*rgbOffsetOpt,y)).b+staticVal; 122 | 123 | vec3 color = vec3(red,green,blue); 124 | float scanline = sin(uv.y*800.0)*0.04*scalinesOpt; 125 | color -= scanline; 126 | 127 | fragColor = vec4(color,1.0); 128 | } 129 | `, 130 | width: -1, 131 | height: -1, 132 | }, 133 | }; -------------------------------------------------------------------------------- /nano-villager_html/filters/hq4x/hq4x.js: -------------------------------------------------------------------------------- 1 | // from: https://github.com/CrossVR/hqx-shader 2 | 3 | const hq4xShader = ` 4 | /* 5 | * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com ) 6 | * 7 | * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net ) 8 | * 9 | * Copyright (C) 2014 Jules Blok ( jules@aerix.nl ) 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU Lesser General Public 22 | * License along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 | */ 25 | #define SCALE 4.0 26 | 27 | const mat3 yuv_matrix = mat3(0.299, 0.587, 0.114, -0.169, -0.331, 0.5, 0.5, -0.419, -0.081); 28 | const vec3 yuv_threshold = vec3(48.0/255.0, 7.0/255.0, 6.0/255.0); 29 | const vec3 yuv_offset = vec3(0, 0.5, 0.5); 30 | 31 | bool diff(vec3 yuv1, vec3 yuv2) 32 | { 33 | bvec3 res = greaterThan(abs((yuv1 + yuv_offset) - (yuv2 + yuv_offset)), yuv_threshold); 34 | return res.x || res.y || res.z; 35 | } 36 | 37 | mat3 transpose(mat3 m) { 38 | return mat3( 39 | vec3(m[0][0], m[1][0], m[2][0]), 40 | vec3(m[0][1], m[1][1], m[2][1]), 41 | vec3(m[0][2], m[1][2], m[2][2])); 42 | } 43 | 44 | void mainImage(out vec4 fragColor, in vec2 fragCoord) 45 | { 46 | vec2 TexCoord = fragCoord / iResolution.xy; 47 | vec2 TextureSize = iChannelResolution[0].xy; 48 | vec2 ps = 1.0/TextureSize; 49 | float dx = ps.x; 50 | float dy = ps.y; 51 | 52 | // +----+----+----+ 53 | // | | | | 54 | // | w1 | w2 | w3 | 55 | // +----+----+----+ 56 | // | | | | 57 | // | w4 | w5 | w6 | 58 | // +----+----+----+ 59 | // | | | | 60 | // | w7 | w8 | w9 | 61 | // +----+----+----+ 62 | 63 | vec4 vTexCoord[4]; 64 | vTexCoord[0].zw = ps; 65 | vTexCoord[0].xy = TexCoord.xy; 66 | vTexCoord[1] = TexCoord.xxxy + vec4(-dx, 0, dx, -dy); // w1 | w2 | w3 67 | vTexCoord[2] = TexCoord.xxxy + vec4(-dx, 0, dx, 0); // w4 | w5 | w6 68 | vTexCoord[3] = TexCoord.xxxy + vec4(-dx, 0, dx, dy); // w7 | w8 | w9 69 | 70 | #define Texture iChannel0 71 | #define LUT iChannel1 72 | 73 | vec2 fp = fract(vTexCoord[0].xy*TextureSize); 74 | vec2 quad = sign(-0.5 + fp); 75 | mat3 yuv = transpose(yuv_matrix); 76 | 77 | /*float*/ dx = vTexCoord[0].z; 78 | /*float*/ dy = vTexCoord[0].w; 79 | vec3 p1 = texture2D(Texture, vTexCoord[0].xy).rgb; 80 | vec3 p2 = texture2D(Texture, vTexCoord[0].xy + vec2(dx, dy) * quad).rgb; 81 | vec3 p3 = texture2D(Texture, vTexCoord[0].xy + vec2(dx, 0) * quad).rgb; 82 | vec3 p4 = texture2D(Texture, vTexCoord[0].xy + vec2(0, dy) * quad).rgb; 83 | // mat4x3 pixels = mat4x3(p1, p2, p3, p4); 84 | mat4 pixels = mat4(vec4(p1, 0), vec4(p2, 0), vec4(p3, 0), vec4(p4, 0)); 85 | 86 | 87 | vec3 w1 = yuv * texture2D(Texture, vTexCoord[1].xw).rgb; 88 | vec3 w2 = yuv * texture2D(Texture, vTexCoord[1].yw).rgb; 89 | vec3 w3 = yuv * texture2D(Texture, vTexCoord[1].zw).rgb; 90 | 91 | vec3 w4 = yuv * texture2D(Texture, vTexCoord[2].xw).rgb; 92 | vec3 w5 = yuv * p1; 93 | vec3 w6 = yuv * texture2D(Texture, vTexCoord[2].zw).rgb; 94 | 95 | vec3 w7 = yuv * texture2D(Texture, vTexCoord[3].xw).rgb; 96 | vec3 w8 = yuv * texture2D(Texture, vTexCoord[3].yw).rgb; 97 | vec3 w9 = yuv * texture2D(Texture, vTexCoord[3].zw).rgb; 98 | 99 | bvec3 pattern[3]; 100 | pattern[0] = bvec3(diff(w5, w1), diff(w5, w2), diff(w5, w3)); 101 | pattern[1] = bvec3(diff(w5, w4), false , diff(w5, w6)); 102 | pattern[2] = bvec3(diff(w5, w7), diff(w5, w8), diff(w5, w9)); 103 | bvec4 cross = bvec4(diff(w4, w2), diff(w2, w6), diff(w8, w4), diff(w6, w8)); 104 | 105 | vec2 index; 106 | index.x = dot(vec3(pattern[0]), vec3(1, 2, 4)) + 107 | dot(vec3(pattern[1]), vec3(8, 0, 16)) + 108 | dot(vec3(pattern[2]), vec3(32, 64, 128)); 109 | index.y = dot(vec4(cross), vec4(1, 2, 4, 8)) * (SCALE * SCALE) + 110 | dot(floor(fp * SCALE), vec2(1, SCALE)); 111 | 112 | vec2 step = 1.0 / vec2(256.0, 16.0 * (SCALE * SCALE)); 113 | vec2 offset = step / 2.0; 114 | vec4 weights = texture2D(LUT, index * step + offset); 115 | float sum = dot(weights, vec4(1)); 116 | vec3 res = (pixels * (weights / sum)).xyz; 117 | 118 | fragColor.rgb = res; 119 | fragColor.a = 1.0; 120 | 121 | } 122 | `; 123 | 124 | export default { 125 | name: 'hq4x', 126 | author: 'various', 127 | authorUrl: 'https://github.com/CrossVR/hqx-shader', 128 | src: 'https://github.com/CrossVR/hqx-shader', 129 | license: 'LGPL2.1', 130 | licenseUrl: 'filters/hq4x/LICENSE.txt', 131 | filter: { 132 | fragmentShader: hq4xShader, 133 | width: -1, 134 | height: -1, 135 | iChannel1: { 136 | src: 'filters/hq4x/hq4x.png', 137 | wrap: 'clamp', 138 | filter: 'nearest', 139 | }, 140 | }, 141 | }; 142 | 143 | 144 | -------------------------------------------------------------------------------- /nano-villager_html/filters/thin-water.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Thin Water', 3 | author: 'khlorghaal', 4 | authorUrl: 'https://www.shadertoy.com/user/khlorghaal', 5 | src: 'https://www.shadertoy.com/view/Mt3Bz7', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | //author khlorghaal 11 | //most rights reserved 12 | 13 | const float 14 | SPEED=.30, 15 | TURBULENCE=.25, 16 | ADVECTION=.2, 17 | DEPTH=.04, 18 | DROP_AGGRESSIVE= .1510; 19 | #define DROP_COUNT 2 20 | const vec3 21 | LIGHT= vec3(-.5,1.,.3), 22 | COLOR_SUN= vec3(1.1,1.,1.)*12., 23 | COLOR_SKY= vec3(.1,.14,.2)*.8; 24 | 25 | //#define DEBUG_NORMAL 26 | //#define DEBUG_LIGHT 27 | 28 | #define PHI 1.61803399 29 | #define TAU 6.28318531 30 | #define time iTime 31 | #define ASPECT (float(iResolution.x)/float(iResolution.y)) 32 | float lum(vec3 rgb){ return dot(rgb,vec3(0.299, 0.587, 0.114)); } 33 | float nmapu(float x){ return x*.5+.5; } 34 | vec2 nmapu(vec2 x){ return x*.5+.5; } 35 | float nmaps(float x){ return x*2.-1.; } 36 | vec2 nmaps(vec2 x){ return x*2.-1.; } 37 | float tri(float x){ return abs(nmaps(fract(x))); } 38 | vec2 tri(vec2 x){ return abs(nmaps(fract(x))); } 39 | vec2 gnse2(vec2 p){ return fract(tan(p*vec2(PHI,PHI*PHI)*512.)*512.*PHI); } 40 | 41 | float wave(float x){ 42 | float x0= x; 43 | x= nmapu(sin( x*45. - time*24. )); 44 | x= pow(x,24.);//sharpen 45 | x/= x0*32.+.125;//falloff 46 | x*= exp(-x0*2.);//damping 47 | return x; 48 | } 49 | float depthf(vec2 p){ 50 | float bottom= lum(texture2D(iChannel0,p).rgb);//luminance approximates depth 51 | vec2 flow= vec2(0.,time*SPEED); 52 | p+= flow; 53 | float drops; 54 | #if DROP_COUNT>0 55 | { 56 | vec2 p_= p; 57 | p_.x*=ASPECT; 58 | const float A= DROP_AGGRESSIVE; 59 | const float fdc= float(DROP_COUNT); 60 | for(int i=0;i!=DROP_COUNT;i++){ 61 | //ringbuffer analogy 62 | float fi= float(i); 63 | float offset= time*fdc*A; 64 | float seed= fi+floor(offset); 65 | float fade= tri(offset/(fdc+1.)/A);//fixme i cant get fadeout right 66 | vec2 locus= gnse2(vec2(seed))*3.-1.; 67 | locus.x*=ASPECT; 68 | vec2 d= locus-p_; 69 | d= tri(d);//spatial loop, must be continuous 70 | //drops+= max(0.,.5-length(d))*fade; 71 | drops+= wave(length(d))*fade; 72 | } 73 | } 74 | #endif 75 | p+= bottom*ADVECTION;//divergence 76 | float surface= lum(texture2D(iChannel1,p*TURBULENCE).rgb)*TURBULENCE; 77 | return surface*DEPTH+drops*.1; 78 | //return (surface-bottom)*DEPTH; 79 | } 80 | 81 | void mainImage( out vec4 fragColor, in vec2 fragCoord ){ 82 | vec2 uv= fragCoord/iResolution.xy; 83 | vec2 uv0= uv; 84 | 85 | float d= 0.04; 86 | float d0= depthf(uv); 87 | vec2 grad= (vec2( 88 | depthf(uv+vec2(d,0)), 89 | depthf(uv+vec2(0,d)) 90 | )-d0)/d; 91 | vec3 N= normalize(vec3(grad.x,grad.y,1.)); 92 | 93 | //environment 94 | float L= max(0., reflect(normalize(LIGHT), N).z ); 95 | //improv lobes (dangerous fun) 96 | float sun= pow(L,14.); 97 | float sky= pow(L+.5,1.4); 98 | vec3 specular= vec3(COLOR_SUN)*sun + vec3(COLOR_SKY)*sky; 99 | 100 | uv+= refract(vec3(0.,0.,1.), N, 1.4).xy*DEPTH; 101 | 102 | vec3 col= pow(texture2D(iChannel0, uv).rgb, vec3(1./2.2)); 103 | col-= col*COLOR_SKY;//ambience inversion 104 | //funky gamma for style 105 | col+= specular; 106 | col= pow(col+.1,vec3(2.8)); 107 | 108 | 109 | #ifdef DEBUG_LIGHT 110 | col= vec3(specular); 111 | #endif 112 | #ifdef DEBUG_NORMAL 113 | col= N*.5+.5; 114 | #endif 115 | //col= vec3(0.,nse2(uv0)); 116 | fragColor = vec4(col,1.0); 117 | } 118 | `, 119 | width: -1, 120 | height: -1, 121 | iChannel0: { 122 | filter: 'linear', 123 | vFlip: true, 124 | }, 125 | iChannel1: { 126 | src: blur(blur(createNoise(256, 256))), 127 | filter: 'mipMap', 128 | wrap: 'repeat', 129 | }, 130 | }, 131 | }; 132 | 133 | function applyKernel(kernel, imageData) { 134 | const {data, width, height} = imageData; 135 | const src = data.slice(); 136 | 137 | function getValue(x, y, channel) { 138 | return src[(x + width) % width + ((y + height) % height * width) * 4 + channel]; 139 | } 140 | 141 | for (let i = 0; i < data.length; ++i) { 142 | const p = i / 4 | 0; 143 | const xx = p % width; 144 | const yy = p / width | 0; 145 | const channel = p % 4; 146 | let total = 0; 147 | let totalF = 0; 148 | for (const {x, y, f} of kernel) { 149 | total += getValue(xx + x, yy + y, channel) * f; 150 | totalF += f; 151 | } 152 | data[i] = total / totalF; 153 | } 154 | return imageData; 155 | } 156 | 157 | function blur(imageData) { 158 | const kernel = [ 159 | {x: -1, y: -1, f: 1 / 16, }, 160 | {x: 1, y: -1, f: 1 / 16, }, 161 | {x: -1, y: 1, f: 1 / 16, }, 162 | {x: 1, y: 1, f: 1 / 16, }, 163 | {x: -1, y: 0, f: 1 / 8, }, 164 | {x: 1, y: 0, f: 1 / 8, }, 165 | {x: 0, y: -1, f: 1 / 8, }, 166 | {x: 0, y: 1, f: 1 / 8, }, 167 | {x: 0, y: 0, f: 1 / 4, }, 168 | ]; 169 | return applyKernel(kernel, imageData); 170 | } 171 | 172 | function createNoise(width, height) { 173 | const data = new Uint8Array(width * height * 4); 174 | for (let i = 0; i < data.length; ++i) { 175 | data[i] = Math.random() * 256; 176 | } 177 | 178 | return { 179 | width, 180 | height, 181 | data, 182 | }; 183 | } 184 | -------------------------------------------------------------------------------- /nano-villager_html/filters/bug-in-the-tv.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'There\'s a bug in the TV', 3 | author: 'thiagoborn', 4 | authorUrl: 'https://www.shadertoy.com/user/thiagoborn', 5 | src: 'https://www.shadertoy.com/view/WsBGRW', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | filter: { 9 | fragmentShader: ` 10 | /* 11 | Inspired by 12 | https://www.shadertoy.com/view/ldXGW4 by https://www.shadertoy.com/user/ehj1 13 | https://www.shadertoy.com/view/XtK3W3 by https://www.shadertoy.com/user/dyvoid 14 | https://www.shadertoy.com/view/Xdl3D8 by https://www.shadertoy.com/user/jmpep 15 | https://www.shadertoy.com/view/ldjGzV by https://www.shadertoy.com/user/ryk 16 | */ 17 | 18 | float rand2d(vec2 co) { 19 | return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); 20 | } 21 | 22 | float rand(float n) { 23 | return fract(sin(n) * 43758.5453123); 24 | } 25 | 26 | float noise(float p) { 27 | float fl = floor(p); 28 | float fc = fract(p); 29 | return mix(rand(fl), rand(fl + 1.0), fc); 30 | } 31 | 32 | float map(float val, float amin, float amax, float bmin, float bmax) { 33 | float n = (val - amin) / (amax-amin); 34 | float m = bmin + n * (bmax-bmin); 35 | return m; 36 | } 37 | 38 | float snoise(float p){ 39 | return map(noise(p),0.0,1.0,-1.0,1.0); 40 | } 41 | 42 | float threshold(float val,float cut){ 43 | float v = clamp(abs(val)-cut,0.0,1.0); 44 | v = sign(val) * v; 45 | float scale = 1.0 / (1.0 - cut); 46 | return v * scale; 47 | } 48 | 49 | #define CURVE 50 | #define SCANS 51 | #define FLICKS 52 | #define GRAINS 53 | #define YBUG 54 | #define DIRTY 55 | #define STRIP 56 | #define COLOR 57 | #define BLINK 58 | #define VIG 59 | 60 | float FREQUENCY = 11.0; 61 | 62 | vec2 uv_curve(vec2 uv) { 63 | uv = (uv - 0.5) * 2.0; 64 | uv *= 1.2; 65 | uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0); 66 | uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0); 67 | uv /= 1.15; 68 | uv = (uv / 2.0) + 0.5; 69 | return uv; 70 | } 71 | 72 | vec3 color(sampler2D tex, vec2 uv){ 73 | vec3 color = texture2D(iChannel0,uv).rgb; 74 | #ifdef COLOR 75 | float bw = (color.r + color.g + color.b) / 3.0; 76 | color = mix(color,vec3(bw,bw,bw),.95); 77 | float p = 1.5; 78 | color.r = pow(color.r,p); 79 | color.g = pow(color.g,p-0.1); 80 | color.b = pow(color.b,p); 81 | #endif 82 | return color; 83 | } 84 | 85 | vec3 ghost(sampler2D tex, vec2 uv){ 86 | #ifdef FLICKS 87 | 88 | float n1 = threshold(snoise(iTime*10.),.85); 89 | float n2 = threshold(snoise(2000.0+iTime*10.),.85); 90 | float n3 = threshold(snoise(3000.0+iTime*10.),.85); 91 | 92 | vec2 or = vec2(0.,0.); 93 | vec2 og = vec2(0,0.); 94 | vec2 ob = vec2(0.,0); 95 | 96 | float os = .05; 97 | or += vec2(n1*os,0.); 98 | og += vec2(n2*os,0.); 99 | ob += vec2(0.,n3*os); 100 | 101 | float r = color(iChannel0,uv + or).r; 102 | float g = color(iChannel0,uv + og).g; 103 | float b = color(iChannel0,uv + ob).b; 104 | vec3 color = vec3(r,g,b); 105 | return color; 106 | #else 107 | return texture2D(iChannel0,uv).rgb; 108 | #endif 109 | } 110 | 111 | vec2 uv_ybug(vec2 uv){ 112 | float n4 = clamp(noise(200.0+iTime*2.)*14.,0.,2.); 113 | uv.y += n4; 114 | uv.y = mod(uv.y,1.); 115 | return uv; 116 | } 117 | 118 | vec2 uv_hstrip(vec2 uv){ 119 | float vnoise = snoise(iTime*6.); 120 | float hnoise = threshold(snoise(iTime*10.),.5); 121 | 122 | float line = (sin(uv.y*10.+vnoise)+1.)/2.; 123 | line = (clamp(line,.9,1.)-.9)*10.; 124 | 125 | uv.x += line * 0.03 * hnoise; 126 | uv.x = mod(uv.x,1.); 127 | return uv; 128 | } 129 | 130 | 131 | 132 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 133 | { 134 | float t = float(int(iTime * FREQUENCY)); 135 | 136 | vec2 uv = fragCoord / iResolution.xy; 137 | 138 | if(iMouse.z>0.){ 139 | fragColor = texture2D(iChannel0,uv); 140 | return; 141 | } 142 | 143 | #ifdef CURVE 144 | uv = uv_curve(uv); 145 | #endif 146 | 147 | vec2 ouv = uv; 148 | 149 | #ifdef GRAINS 150 | float xn = threshold(snoise(iTime*10.),.7) * 0.05; 151 | float yn = threshold(snoise((500.0+iTime)*10.),.7) * 0.05; 152 | 153 | float r = rand2d(uv+(t+100.0)*.01); 154 | uv = uv + vec2(xn,yn) * r; 155 | #endif 156 | 157 | 158 | #ifdef YBUG 159 | uv = uv_ybug(uv); 160 | #endif 161 | 162 | #ifdef STRIP 163 | uv = uv_hstrip(uv); 164 | #endif 165 | 166 | 167 | vec2 onePixel = vec2(0.0, 1.0) / iResolution.xy * 3.; 168 | #ifdef BLUR 169 | vec3 colorA = ghost(iChannel0,uv + onePixel,or,og,ob); 170 | vec3 colorB = ghost(iChannel0,uv - onePixel,or,og,ob); 171 | vec3 colorC = ghost(iChannel0,uv,or,og,ob); 172 | vec3 color = (colorA+colorB+colorC)/3.0; 173 | #else 174 | vec3 color = ghost(iChannel0,uv); 175 | #endif 176 | 177 | //color = colorC; 178 | 179 | float scanA = (sin(uv.y*3.1415*iResolution.y/2.7)+1.)/2.; 180 | float scanB = (sin(uv.y*3.1415*1.)+1.)/2.; 181 | #ifdef SCANS 182 | color *= .75 + scanA * .25; 183 | //color *= .5 + scanC * .5; 184 | //color *= scanB; 185 | #endif 186 | 187 | #ifdef BLINK 188 | float blink = .96 + .04*(sin(iTime*100.)+1.)/2.; 189 | color *= blink; 190 | #endif 191 | 192 | #ifdef VIG 193 | float vig = 44.0 * (ouv.x * (1.0-ouv.x) * ouv.y * (1.0-ouv.y)); 194 | vig *= mix( 0.7, 1.0, rand(t + 0.5)); 195 | color *= .6 + .4*vig; 196 | #endif 197 | 198 | #ifdef DIRTY 199 | color *= 1.0 + rand2d(uv+t*.01) * 0.2; 200 | #endif 201 | 202 | vec3 backColor = vec3(.4,.4,.4); 203 | if (ouv.x < 0.0 || ouv.x > 1.0) 204 | color = backColor; 205 | if (ouv.y < 0.0 || ouv.y > 1.0) 206 | color = backColor; 207 | 208 | fragColor = vec4(color,1.0); 209 | } 210 | `, 211 | width: -2, 212 | height: -2, 213 | iChannel0: { 214 | filter: 'linear', 215 | vFlip: true, 216 | }, 217 | }, 218 | }; 219 | -------------------------------------------------------------------------------- /nano-villager_html/filters/pinscreen-video.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'pinscreen video', 3 | author: 'szczm_', 4 | authorUrl: 'https://www.shadertoy.com/user/szczm_', 5 | src: 'https://www.shadertoy.com/view/wt33W2', 6 | license: 'CC-BY-NC-SA', 7 | licenseUrl: 'http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US', 8 | needsWebGL2: true, 9 | filter: { 10 | fragmentShader: ` 11 | /////////////////////////////// 12 | // TEXT BELOW, OPTIONS FIRST // 13 | /////////////////////////////// 14 | 15 | 16 | // Temporal Anti Aliasing samples. Make it as high as you can! But be careful. 17 | // 12 should be good enough. 128 would be awesome. 1 is for my laptop. 18 | #define TAA_SAMPLES 1 19 | 20 | // Comment out to disable Post Processing 21 | //#define PP 22 | 23 | // Comment out to disable tonemapping 24 | //#define TONEMAPPING 25 | 26 | 27 | 28 | ////////////////// 29 | // TEXT HEEEERE // 30 | ////////////////// 31 | 32 | /*************************************\ 33 | 34 | pinscreen video — saturday sketch #5 35 | — Matthias Scherba (szczm_) 36 | 37 | 38 | This took a lot more than I expected. I had a note that said: 39 | "pintable shader" 40 | and I didn't really plan to make it look this good. Sorry :( 41 | 42 | 43 | Includes: 44 | - video pinscreen! 45 | - nice camera paths 46 | - nicely commented code because I really like this one 47 | - Lambert + Blinn-Phong shading 48 | - temporal antiaiasing... I guess? 49 | - Reinhardt tonemapping 50 | - hemispherical ambient occlusion 51 | - nice post processing 52 | 53 | 54 | And I'm surprised how much of this I made without checking external resources. 55 | 56 | Fun fact: there are no shadows, it's just lighting (w/ attenuation) and AO. 57 | 58 | Sad fact: no motion vectors on the video so no real motion blur on the pins :( 59 | 60 | Fun fact: apparently pin art is a patented toy! 61 | 62 | Any questions? Feedback! Let me know! 63 | 64 | 65 | Signing out - 66 | 67 | - actually I'm not signing out, who does these days 68 | 69 | 70 | P.S.: I wonder if I'm currently holding the record for the total amount of times 71 | anyone ever listened to this commercial, from start to finish. 72 | 73 | P.S. 2: Now, I'm not saying you should try swapping the video... I'm not. |: 74 | 75 | \***************************************************/ 76 | 77 | 78 | 79 | ////////////////// 80 | // CODE HEREEEE // 81 | ////////////////// 82 | 83 | const int MAX_STEPS = 150; 84 | const float MIN_DIST = 0.5; 85 | const float MAX_DIST = 20.0; 86 | const float EPSILON = 5e-4; 87 | 88 | 89 | float time; 90 | // time is a con- 91 | struct DistMat { 92 | float dist; 93 | int mat; 94 | }; 95 | // ...see what I did there? 96 | 97 | 98 | // SDFunctions 99 | float planeSDF(vec3 p, vec3 n) { 100 | return length(p) * dot(normalize(p), n); 101 | } 102 | 103 | float boxSDF(vec3 p, vec3 b) { 104 | vec3 q = abs(p) - b; 105 | return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0); 106 | } 107 | 108 | 109 | // Gotta fix those for my drivers... 110 | #define TAU 6.28318530718 111 | float sinf(float arg) { return sin(mod(arg, TAU)); } 112 | float cosf(float arg) { return cos(mod(arg, TAU)); } 113 | // ...there. 114 | 115 | 116 | mat3 rotY(float angle) { 117 | return mat3( 118 | vec3(cos(angle), 0.0, sin(angle)), 119 | vec3(0.0, 1.0, 0.0), 120 | vec3(-sin(angle), 0.0, cos(angle)) 121 | ); 122 | } 123 | 124 | 125 | // Pin spacing. 126 | const float spacing = 0.04; 127 | 128 | 129 | // Calculates right coordinates from worldspace and returns a texel of the video. 130 | vec3 getVideoTexel(vec3 p) { 131 | 132 | p.x = -p.x; 133 | p.xz += vec2(3.0, 2.3); 134 | 135 | const vec2 modDomain = vec2(spacing) * 0.5; 136 | 137 | vec3 pp = p; 138 | pp.xz = mod(pp.xz + modDomain, modDomain * 2.0) - modDomain; 139 | 140 | // What I'm doing here is called a pro gamer move. 141 | // we are taking the difference from original coordinates and using it 142 | // as the UVs. Each box is the same, but each is different. B) 143 | vec2 uv = (p.xz - pp.xz) * 0.2; 144 | uv.x *= iChannelResolution[0].y / iChannelResolution[0].x; 145 | 146 | vec3 tex = pow(texture(iChannel0, uv).rgb, vec3(2.2)); 147 | 148 | return tex * step(0.01, uv.x) * step(0.01, 1.0 - uv.x) * step(0.01, uv.y) * step(0.01, 1.0 - uv.y); 149 | } 150 | 151 | float luminance(vec3 col) { 152 | // YUV luminance… I think 153 | return dot(col, vec3(0.299, 0.587, 0.114)); 154 | } 155 | 156 | 157 | DistMat sum(DistMat a, DistMat b) { 158 | if (a.dist < b.dist) 159 | return a; 160 | else 161 | return b; 162 | } 163 | 164 | DistMat intersect(DistMat a, DistMat b) { 165 | if (a.dist < b.dist) 166 | return b; 167 | else 168 | return a; 169 | } 170 | 171 | 172 | DistMat sceneSDF(vec3 p) 173 | { 174 | vec3 op = p; 175 | 176 | float texel = luminance(getVideoTexel(p)); 177 | 178 | p.x = -p.x; 179 | p.xz += vec2(3.0, 2.3); 180 | 181 | const vec2 modDomain = vec2(spacing) * 0.5; 182 | p.xz = mod(p.xz + modDomain, modDomain * 2.0) - modDomain; 183 | 184 | // Pin height in just the right proportions. 185 | float h = (texel - 0.015) * 0.14; 186 | 187 | // Pinscreen 188 | DistMat scene = DistMat(boxSDF(p - vec3(0.0, h, 0.0), vec3(spacing * 0.4, h, spacing * 0.4)) - 0.004, 0); 189 | 190 | // Room 191 | scene = sum(scene, DistMat(-boxSDF(op - vec3(0.0, 8.0, 0.0), vec3(5.15, 8.0, 5.15)), 1)); 192 | 193 | // Simple. 194 | return scene; 195 | } 196 | 197 | 198 | DistMat shortestDistanceToSurface(vec3 eye, vec3 marchingDirection, float start, float end) { 199 | float depth = start; 200 | int mat = 0; 201 | int i; 202 | 203 | for (i = 0; i < MAX_STEPS; i++) { 204 | DistMat dist = sceneSDF(eye + depth * marchingDirection); 205 | 206 | // Relax, distance. You don't want to miss your opportunities. 207 | depth += dist.dist * 0.3; 208 | mat = dist.mat; 209 | 210 | // abs prevents premature e...xiting when inside something 211 | if (abs(dist.dist) < EPSILON) { 212 | break; 213 | } 214 | 215 | if (depth >= end) { 216 | depth = end; 217 | break; 218 | } 219 | } 220 | 221 | return DistMat(depth, mat); 222 | } 223 | 224 | 225 | // Maths. Skip this. 226 | vec3 rayDirection(float fieldOfView, vec2 size, vec2 fragCoord) { 227 | vec2 xy = fragCoord - size / 2.0; 228 | float z = size.y / tan(radians(fieldOfView) / 2.0); 229 | return normalize(vec3(xy, -z)); 230 | } 231 | 232 | 233 | // Cooler maths. But you know this. 234 | vec3 normal(vec3 p, vec3 viewDir) { 235 | return normalize(vec3( 236 | sceneSDF(vec3(p.x + EPSILON, p.y, p.z)).dist - sceneSDF(vec3(p.x - EPSILON, p.y, p.z)).dist, 237 | sceneSDF(vec3(p.x, p.y + EPSILON, p.z)).dist - sceneSDF(vec3(p.x, p.y - EPSILON, p.z)).dist, 238 | sceneSDF(vec3(p.x, p.y, p.z + EPSILON)).dist - sceneSDF(vec3(p.x, p.y, p.z - EPSILON)).dist 239 | )); 240 | } 241 | 242 | 243 | // Simple hash functions. How do you create a hash function? 244 | // Put it on the screen. Type random numbers until it works good enough. 245 | // Done. 246 | float hash(float i) { 247 | return fract(sinf(785172.9189*i) * 20412.021401); 248 | } 249 | 250 | float hash(vec3 i) { 251 | return hash(dot(i, vec3(2154.9251, 9253.5219, 5021.2510))); 252 | } 253 | 254 | 255 | // Not too uniform because the above comment is sadly true. 256 | vec3 randomPointOnHemisphere(vec3 dir, float seed) { 257 | float a = hash(seed); 258 | float b = hash(seed + 1.0); 259 | float c = hash(seed + 2.0); 260 | 261 | vec3 point = normalize(-1.0 + 2.0 * vec3(a, b, c)); 262 | 263 | // Point it in the right direction. 264 | return point * sign(dot(dir, point)); 265 | } 266 | 267 | 268 | // Hemispherical AO! \o/ 269 | float ao(vec3 p, vec3 n) { 270 | float accu = 0.0; 271 | float mult = 0.0; 272 | 273 | // One sample is enough with TAA and a big viewing distance. 274 | // I mean the viewport, not the screen. Pfsh. 275 | for (float i = 0.0; i < 1.0; i++) 276 | for (float depth = 0.05; depth <= 0.25; depth += 0.05) { 277 | vec3 dir = normalize(n + randomPointOnHemisphere(n, i + depth * 10.0 + hash(p) + time)); 278 | float dist = sceneSDF(p + depth * dir).dist; 279 | accu += dist; 280 | mult += depth; 281 | } 282 | 283 | // Weighted average because yeah. 284 | return clamp(accu / mult, 0.0, 1.0); 285 | } 286 | 287 | 288 | // Hmmm. 289 | vec3 lightPosition() { 290 | return vec3(1.5, 4.0, 1.0); 291 | } 292 | 293 | // Huh. 294 | vec3 lightColor() { 295 | return vec3(1.0, 0.95, 0.9); 296 | } 297 | 298 | // Herf. 299 | float lightIntensity() { 300 | return 50.0; 301 | } 302 | 303 | 304 | vec3 lighting(in vec3 position, in vec3 normal, in vec2 coords, in vec3 viewDir, in int mat) 305 | { 306 | vec3 lightPos = lightPosition(); 307 | vec3 lightCol = lightColor(); 308 | 309 | float lightAtten = lightIntensity() / pow(distance(position, lightPos), 2.0); 310 | 311 | // The floor/wall pattern/ 312 | float zig = abs(mod(position.x * 4.0, 2.0) - 1.0) + abs(mod(position.y * 4.0 - 1.0, 2.0) - 1.0) + abs(mod(position.z * 4.0, 2.0) - 1.0); 313 | zig = step(1.0, zig); 314 | 315 | // "Left" is gold, "right" is white "marble". 316 | vec3 color = mix(vec3(1.0, 0.94, 0.75) * 0.47, vec3(0.72), zig); 317 | float shininess = mix(0.4, 0.1, zig); 318 | float roughness = mix(50.0, 0.1, zig); 319 | 320 | // mat 0 is our pinscreen. I'm not really using the material system in the end. 321 | if (mat == 0) { 322 | color = getVideoTexel(position) + 0.15; 323 | roughness = 0.2; 324 | shininess = 0.05; 325 | } 326 | 327 | // _lighting_ 328 | vec3 lightDir = normalize(lightPos - position); 329 | 330 | 331 | // Diffuse terms, e.g. "I like you as a friend." 332 | float ndotl = dot(normal, normalize(lightDir)); 333 | 334 | float diffuseLambert = max(0.0, ndotl); 335 | 336 | 337 | // Specular terms. 338 | float ndoth = dot(normal, normalize(-viewDir + lightDir)); 339 | float ldotr = dot(lightDir, reflect(viewDir, -normal)); 340 | 341 | float specularBlinnPhong = pow(max(ndoth, 0.0), roughness); 342 | float specularPhong = pow(max(ldotr, 0.0), roughness); 343 | 344 | 345 | // Ambient Occlusion 346 | float ao = ao(position, normal); 347 | 348 | 349 | // Finals. I hate finals. 350 | // Joking. I finished school. Too cool (B 351 | 352 | float ambient = 0.6; 353 | float diffuse = diffuseLambert * (1.0 - ambient); 354 | 355 | // You can swap specularBlinnPhong for specularPhong. That's your decision. 356 | float specular = specularBlinnPhong * shininess; 357 | 358 | 359 | // Uncomment and play with the below line to check out specific terms! 360 | // return vec3(ao); 361 | 362 | 363 | // aaand boom. 364 | return vec3(ambient * color + (diffuse * color + specular) * ao * lightAtten) * lightCol; 365 | } 366 | 367 | 368 | // Boring maths again 369 | mat4 viewMatrix(vec3 eye, vec3 center, vec3 up) { 370 | vec3 f = normalize(center - eye); 371 | vec3 s = normalize(cross(f, up)); 372 | vec3 u = cross(s, f); 373 | return mat4( 374 | vec4(s, 0.0), 375 | vec4(u, 0.0), 376 | vec4(-f, 0.0), 377 | vec4(0.0, 0.0, 0.0, 1) 378 | ); 379 | } 380 | 381 | 382 | float getTake() { 383 | const float take_duration = 9.0; 384 | const float take_count = 3.0; 385 | 386 | // Uncomment below line to force a take! 387 | // return 1.0; 388 | 389 | return float(int(mod(time / take_duration, take_count) * take_duration) / int(take_duration)); 390 | } 391 | 392 | vec3 getEye() { 393 | vec3 eye1 = rotY(0.65 + sinf(1.0 * time * TAU * 0.02) * 0.5) * vec3(-3.0, 7.0, -4.0); 394 | vec3 eye2 = rotY(time * 0.15) * vec3(3.0, 0.8, 2.0); 395 | vec3 eye3 = vec3(sin(time * 0.2) * 4.5, 2.0, cos(time * 0.2) * 1.0); 396 | // Three-eyed beast. 397 | 398 | 399 | float take = getTake(); 400 | 401 | vec3 eye = mix(eye1, eye2, step(1.0, take)); 402 | eye = mix(eye, eye3, step(2.0, take)); 403 | 404 | return eye; 405 | } 406 | 407 | float getFov() { 408 | float take = getTake(); 409 | 410 | float fov = mix(60.0, 90.0, step(1.0, take)); 411 | fov = mix(fov, 30.0, step(2.0, take)); 412 | 413 | return fov; 414 | } 415 | 416 | 417 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 418 | { 419 | // This global variable is what makes the TAA tick. 420 | time = iTime; 421 | 422 | 423 | // The left one, or the right one? I hope the right one. 424 | 425 | vec2 coord = fragCoord / iResolution.xy; 426 | 427 | vec3 color = vec3(0.0); 428 | 429 | float dt = 0.0; 430 | float da = 0.0; 431 | 432 | 433 | // Just for safety. 434 | float tTime = iTime; 435 | 436 | 437 | #define DS float(TAA_SAMPLES) 438 | #define DDT 0.001 439 | 440 | // Let's play with time. 441 | for (dt = -DS*0.5*DDT; dt < DS*0.5*DDT; dt += DDT) 442 | { 443 | // Angle by which a sample will be offset 444 | da = dt / DDT; 445 | 446 | 447 | // Wheeee~ 448 | time = tTime + dt; 449 | 450 | 451 | // Every game needs it. 452 | vec3 cameraShake = vec3(cosf(time * 3.3), sinf(time * 3.7), cosf(time * 4.3)) * 0.015 * (getFov() / 60.0); 453 | // getFov() / 60.0 is just to weaken the shake for smaller FoVs 454 | 455 | vec3 eye = getEye() + cameraShake; 456 | mat4 viewToWorld = viewMatrix(eye, vec3(0.0, -0.5, 0.0) + cameraShake, vec3(0.0, 1.0, 0.0)); 457 | 458 | // Offset the sample a bit for every sample, free AA! \o/ 459 | vec2 sampleOffset = vec2(cos(da), sin(da)) * 0.4; 460 | 461 | vec3 viewDir = rayDirection(getFov(), iResolution.xy, fragCoord.xy + sampleOffset); 462 | vec3 worldDir = (viewToWorld * vec4(viewDir, 0.0)).xyz; 463 | 464 | 465 | // Hit it! 466 | DistMat hitInfo = shortestDistanceToSurface(eye, worldDir, MIN_DIST, MAX_DIST); 467 | 468 | 469 | // Magenta helps every time. 470 | vec3 c = vec3(1.0, 0.0, 1.0); 471 | 472 | if (hitInfo.dist < MAX_DIST) 473 | { 474 | vec3 p = eye + hitInfo.dist * worldDir; 475 | vec3 n = normal(p, worldDir); 476 | 477 | c = lighting(p, n, fragCoord.xy / iResolution.xy, worldDir, hitInfo.mat); 478 | } 479 | 480 | color += c; 481 | } 482 | 483 | 484 | // I wrote this a long time ago and for most values of DS it just works soooo 485 | color /= DS; 486 | 487 | 488 | float l = luminance(color); 489 | 490 | #ifdef TONEMAPPING 491 | l *= 4.0; 492 | l /= 1.0 + l; 493 | 494 | // That's Reinhardt tonemapping... I think 495 | color /= l; 496 | #endif 497 | 498 | #ifdef PP 499 | // Cool hue changing vignette 500 | color = pow(color, mix(vec3(1.0), vec3(1.0, 1.1, 1.25), 0.1 + length(coord - 0.5))); 501 | 502 | // Cool DARKNESS vignette 503 | color *= mix(1.0, 0.5, pow(length(coord - 0.5), 2.0)); 504 | 505 | // Cool grain- okay, noise. 506 | color += 0.1 * pow(1.0 - l, 1.5) * hash(vec3(coord, time)); 507 | 508 | // Cool. 509 | color = mix(color, vec3(1.0), 0.05); 510 | #endif 511 | 512 | 513 | // Throw it out! 514 | fragColor = vec4(color, 1.0); 515 | } 516 | `, 517 | width: -1, 518 | height: -1, 519 | iChannel0: { 520 | filter: 'linear', 521 | vFlip: true, 522 | }, 523 | }, 524 | }; 525 | -------------------------------------------------------------------------------- /pico-8-post-processing.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2020 Gregg Tavares 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | (function() { 24 | 25 | /* global console */ 26 | /* global HTMLCanvasElement */ 27 | /* global HTMLElement */ 28 | /* global Image */ 29 | /* global location */ 30 | /* global performance */ 31 | /* global URL */ 32 | /* global window */ 33 | 34 | function addLineNumbers(str) { 35 | return str.split('\n').map((line, ndx) => `${ndx + 1}: ${line}`).join('\n'); 36 | } 37 | 38 | /** 39 | * Checks whether the url's origin is the same so that we can set the `crossOrigin` 40 | * @param {string} url url to image 41 | * @returns {boolean} true if the window's origin is the same as image's url 42 | * @private 43 | */ 44 | function urlIsSameOrigin(url) { 45 | const localOrigin = (new URL(location.href)).origin; 46 | const urlOrigin = (new URL(url, location.href)).origin; 47 | return urlOrigin === localOrigin; 48 | } 49 | 50 | function createShader(gl, type, src) { 51 | const shader = gl.createShader(type); 52 | gl.shaderSource(shader, src); 53 | gl.compileShader(shader); 54 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 55 | console.error(`${gl.getShaderInfoLog(shader)}\n${addLineNumbers(src)}`); 56 | gl.deleteShader(shader); 57 | return null; 58 | } 59 | return shader; 60 | } 61 | 62 | function createProgram(gl, vSrc, fSrc) { 63 | let fs; 64 | let program; 65 | 66 | const vs = createShader(gl, gl.VERTEX_SHADER, vSrc); 67 | if (vs) { 68 | fs = createShader(gl, gl.FRAGMENT_SHADER, fSrc); 69 | if (fs) { 70 | program = gl.createProgram(); 71 | gl.attachShader(program, vs); 72 | gl.attachShader(program, fs); 73 | gl.bindAttribLocation(program, 0, 'position'); 74 | gl.linkProgram(program); 75 | if (gl.getProgramParameter(program, gl.LINK_STATUS)) { 76 | gl.deleteShader(vs); 77 | gl.deleteShader(fs); 78 | return program; 79 | } 80 | console.error(`${gl.getProgramInfoLog(program)}\n=== [ vertex shader ] ===\n${addLineNumbers(vSrc)}\n=== [ fragment shader ] ===\n${addLineNumbers(fSrc)}`); 81 | } 82 | } 83 | if (program) { 84 | gl.deleteProgram(program); 85 | } 86 | if (fs) { 87 | gl.deleteShader(fs); 88 | } 89 | if (vs) { 90 | gl.deleteShader(vs); 91 | } 92 | return null; 93 | } 94 | 95 | function createFilterProgram(gl, fSrc) { 96 | const isWebGL2 = !!gl.bindBufferBase; 97 | if (isWebGL2 && !fSrc.includes('texture2D')) { 98 | const prg = createProgram(gl, vs2, getFragmentShader2(fSrc)); 99 | if (prg) { 100 | return prg; 101 | } 102 | } 103 | return createProgram(gl, vs, getFragmentShader(fSrc)); 104 | } 105 | 106 | const vs = ` 107 | attribute vec4 position; 108 | void main() { 109 | gl_Position = position; 110 | } 111 | `; 112 | 113 | const vs2 = `#version 300 es 114 | in vec4 position; 115 | void main() { 116 | gl_Position = position; 117 | } 118 | `; 119 | 120 | function getFragmentShader(mainImageSrc) { 121 | return ` 122 | #ifdef GL_FRAGMENT_PRECISION_HIGH 123 | precision highp float; 124 | #else 125 | precision mediump float; 126 | #endif 127 | 128 | uniform vec3 iResolution; 129 | uniform float iTime; 130 | uniform float iTimeDelta; 131 | uniform float iFrame; 132 | uniform float iChannelTime[4]; 133 | uniform vec4 iMouse; 134 | uniform vec4 iDate; 135 | uniform float iSampleRate; 136 | uniform vec3 iChannelResolution[4]; 137 | uniform sampler2D iChannel0; 138 | uniform sampler2D iChannel1; 139 | uniform sampler2D iChannel2; 140 | uniform sampler2D iChannel3; 141 | 142 | ${mainImageSrc} 143 | 144 | void main() { 145 | vec4 fragColor; 146 | mainImage(fragColor, gl_FragCoord.xy); 147 | gl_FragColor = fragColor; 148 | } 149 | `; 150 | } 151 | 152 | function getFragmentShader2(mainImageSrc) { 153 | return `#version 300 es 154 | #ifdef GL_FRAGMENT_PRECISION_HIGH 155 | precision highp float; 156 | #else 157 | precision mediump float; 158 | #endif 159 | 160 | uniform vec3 iResolution; 161 | uniform float iTime; 162 | uniform float iTimeDelta; 163 | uniform float iFrame; 164 | uniform float iChannelTime[4]; 165 | uniform vec4 iMouse; 166 | uniform vec4 iDate; 167 | uniform float iSampleRate; 168 | uniform vec3 iChannelResolution[4]; 169 | uniform sampler2D iChannel0; 170 | uniform sampler2D iChannel1; 171 | uniform sampler2D iChannel2; 172 | uniform sampler2D iChannel3; 173 | 174 | ${mainImageSrc} 175 | 176 | out vec4 oOutColor; 177 | void main() { 178 | vec4 fragColor; 179 | mainImage(fragColor, gl_FragCoord.xy); 180 | oOutColor = fragColor; 181 | } 182 | `; 183 | } 184 | 185 | const uniformNames = [ 186 | 'iResolution', 187 | 'iTime', 188 | 'iTimeDelta', 189 | 'iFrame', 190 | 'iChannelTimev', 191 | 'iMouse', 192 | 'iDate', 193 | 'iSampleRate', 194 | 'iChannelResolution', 195 | 'iChannel0', 196 | 'iChannel1', 197 | 'iChannel2', 198 | 'iChannel3', 199 | ]; 200 | 201 | const defaultFilter = { 202 | fragmentShader: ` 203 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 204 | { 205 | // Normalized pixel coordinates (from 0 to 1) 206 | vec2 uv = fragCoord/iResolution.xy; 207 | 208 | // Time varying pixel color 209 | fragColor = texture2D(iChannel0, uv); 210 | } 211 | `, 212 | width: 128, 213 | height: 128, 214 | filter: false, 215 | iChannel0: { 216 | filter: 'nearest', 217 | wrap: 'clamp', 218 | vFlip: true, 219 | }, 220 | iChannel1: null, 221 | iChannel2: null, 222 | iChannel3: null, 223 | }; 224 | 225 | let haveNewFilter = true; 226 | let currentFilter = { 227 | ...defaultFilter, 228 | }; 229 | 230 | 231 | // emulate just enough of the Canvas API for Pico-8 232 | // which is fortunately is only 2 functions! 233 | class Pico8FilterRenderingContext { 234 | constructor(canvas) { 235 | this.canvas = canvas; 236 | const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); 237 | this.gl = gl; 238 | this.then = 0; 239 | this.frame = 0; 240 | this.width = 128; 241 | this.height = 128; 242 | this.textureDimensions = [ 243 | 128, 128, 1, 244 | 1, 1, 1, 245 | 1, 1, 1, 246 | 1, 1, 1, 247 | ]; 248 | 249 | // NOTE: Given there is only 1 texture 250 | // and 1 buffer the code assumes it never 251 | // has to re-bind them. 252 | 253 | this.buf = gl.createBuffer(); 254 | gl.bindBuffer(gl.ARRAY_BUFFER, this.buf); 255 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 256 | -1, -1, 257 | 1, -1, 258 | -1, 1, 259 | 260 | -1, 1, 261 | 1, -1, 262 | 1, 1, 263 | ]), gl.STATIC_DRAW); 264 | const posLoc = 0; 265 | gl.enableVertexAttribArray(posLoc); 266 | gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0); 267 | 268 | this.textures = []; 269 | for (let i = 0; i < 4; ++i) { 270 | gl.activeTexture(gl.TEXTURE0 + i); 271 | const tex = gl.createTexture(); 272 | this.textures.push(tex); 273 | gl.bindTexture(gl.TEXTURE_2D, tex); 274 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 275 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 276 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 277 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 278 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 279 | } 280 | 281 | const mouse = { 282 | x: 0, 283 | y: 0, 284 | left: 0, 285 | right: 0, 286 | }; 287 | this.mouse = mouse; 288 | 289 | function handleMouseMove(e) { 290 | const rect = canvas.getBoundingClientRect(); 291 | mouse.x = (e.clientX - rect.left) * gl.drawingBufferWidth / rect.width; 292 | mouse.y = gl.drawingBufferHeight - (e.clientY - rect.top) * gl.drawingBufferHeight / rect.height - 1; 293 | mouse.left = 1; 294 | mouse.right = 1; 295 | } 296 | 297 | function handleMouseUp() { 298 | mouse.left = 0; 299 | mouse.right = 0; 300 | window.removeEventListener('mousemove', handleMouseMove); 301 | window.removeEventListener('mouseup', handleMouseUp); 302 | } 303 | 304 | canvas.addEventListener('mousedown', function() { 305 | window.addEventListener('mousemove', handleMouseMove); 306 | window.addEventListener('mouseup', handleMouseUp); 307 | }); 308 | 309 | } 310 | createImageData(width, height) { 311 | return { 312 | width: width, 313 | height: height, 314 | data: new Uint8Array(width * height * 4), 315 | }; 316 | } 317 | putImageData(imageData, x, y) { 318 | if (x !== 0 || y !== 0) { 319 | throw new Error('offset to putImageData not support'); 320 | } 321 | const {gl, canvas} = this; 322 | 323 | this.setupTexture(0, currentFilter.iChannel0, imageData); 324 | 325 | if (haveNewFilter) { 326 | haveNewFilter = false; 327 | this.updateFilter(); 328 | } 329 | 330 | { 331 | const newWidth = this.width < 0 332 | ? canvas.clientWidth * -this.width 333 | : this.width; 334 | const newHeight = this.height < 0 335 | ? canvas.clientHeight * -this.height 336 | : this.height; 337 | if (canvas.width !== newWidth || canvas.height !== canvas.height) { 338 | canvas.width = newWidth; 339 | canvas.height = newHeight; 340 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 341 | } 342 | } 343 | 344 | const now = performance.now() - this.startTime; 345 | const timeDelta = now - this.then; 346 | this.then = now; 347 | const loc = this.uniformLocations; 348 | const mouse = this.mouse; 349 | gl.uniform3f(loc.iResolution, gl.drawingBufferWidth, gl.drawingBufferHeight, 1); 350 | gl.uniform1f(loc.iTime, now * 0.001); 351 | gl.uniform1f(loc.iTimeDelta, timeDelta * 0.001); 352 | gl.uniform1f(loc.iFrame, this.frame++); 353 | gl.uniform1fv(loc.iChannelTime, [now, now, now, now]); 354 | gl.uniform4f(loc.iMouse, mouse.x, mouse.y, mouse.left, mouse.right); // no idea what z and w are 355 | gl.uniform1f(loc.iDate, Date.now() * 0.001); 356 | gl.uniform1f(loc.iSampleRate, 1); // not used 357 | gl.uniform3fv(loc.iChannelResolution, this.textureDimensions); 358 | gl.uniform1i(loc.iChannel0, 0); 359 | gl.uniform1i(loc.iChannel1, 1); 360 | gl.uniform1i(loc.iChannel2, 2); 361 | gl.uniform1i(loc.iChannel3, 3); 362 | 363 | gl.drawArrays(gl.TRIANGLES, 0, 6); 364 | } 365 | updateFilter() { 366 | const gl = this.gl; 367 | const newPrg = createFilterProgram(gl, currentFilter.fragmentShader); 368 | if (newPrg) { 369 | if (this.prg) { 370 | gl.deleteProgram(this.prg); 371 | } 372 | this.uniformLocations = {}; 373 | for (const name of uniformNames) { 374 | this.uniformLocations[name] = gl.getUniformLocation(newPrg, name); 375 | } 376 | this.prg = newPrg; 377 | gl.useProgram(newPrg); 378 | const texFilter = currentFilter.filter ? gl.LINEAR : gl.NEAREST; 379 | gl.activeTexture(gl.TEXTURE0 + 0); 380 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texFilter); 381 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texFilter); 382 | this.frame = 0; 383 | this.startTime = performance.now(); 384 | this.then = this.startTime; 385 | this.width = currentFilter.width || 128; 386 | this.height = currentFilter.height || 128; 387 | } 388 | 389 | this.setupTexture(1, currentFilter.iChannel1); 390 | this.setupTexture(2, currentFilter.iChannel2); 391 | this.setupTexture(3, currentFilter.iChannel3); 392 | } 393 | setupTexture(unit, options, src = null) { 394 | const gl = this.gl; 395 | gl.activeTexture(gl.TEXTURE0 + unit); 396 | if (!options) { 397 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 398 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 399 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 400 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 401 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 402 | return; 403 | } 404 | src = src ? src : options.src; 405 | if (!src) { 406 | throw new Error(`no 'src' for iChannel${unit}`); 407 | } 408 | 409 | const {filter, wrap, vFlip} = options; 410 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, vFlip); 411 | 412 | if (typeof src === 'string') { 413 | const img = new Image(); 414 | img.onload = () => { 415 | this.setupTexture(unit, { 416 | ...options, 417 | src: img, 418 | }); 419 | }; 420 | img.onerror = () => { 421 | console.error(`failed to load image: ${src}`); 422 | }; 423 | if (urlIsSameOrigin(src)) { 424 | img.crossOrigin = 'anonymous'; 425 | } 426 | img.src = src; 427 | return; 428 | } else if (src instanceof HTMLElement) { 429 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src); 430 | } else { 431 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, src.width, src.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, src.data); 432 | } 433 | 434 | { 435 | const offset = unit * 3; 436 | this.textureDimensions[offset ] = src.width; 437 | this.textureDimensions[offset + 1] = src.height; 438 | } 439 | 440 | switch (wrap ? wrap.toLowerCase() : 'clamp') { 441 | case 'clamp': 442 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 443 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 444 | break; 445 | case 'repeat': 446 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 447 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 448 | break; 449 | default: 450 | throw new Error(`unknown wrap mode: ${wrap} for iChannel${unit}`); 451 | } 452 | 453 | switch (filter ? filter.toLowerCase() : 'nearest') { 454 | case 'nearest': 455 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 456 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 457 | break; 458 | case 'linear': 459 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 460 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 461 | break; 462 | case 'mipmap': 463 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 464 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 465 | gl.generateMipmap(gl.TEXTURE_2D); 466 | break; 467 | default: 468 | throw new Error(`unknown filter mode: ${filter} for iChannel${unit}`); 469 | } 470 | } 471 | } 472 | 473 | HTMLCanvasElement.prototype.getContext = function(origFn) { 474 | 475 | return function(type, ...args) { 476 | // only if the id = 'canvas', otherwise we can't create more 2D canvases. 477 | if (type === '2d' && this.id === 'canvas') { 478 | return new Pico8FilterRenderingContext(this); 479 | } else { 480 | return origFn.call(this, type, ...args); 481 | } 482 | }; 483 | 484 | }(HTMLCanvasElement.prototype.getContext); 485 | 486 | window.pico8Filter = { 487 | setFilter(options) { 488 | const newFilter = {...defaultFilter}; 489 | for (const [key, value] of Object.entries(options)) { 490 | if (!(key in newFilter)) { 491 | console.error('no such option:', key); 492 | return; 493 | } 494 | if (typeof (defaultFilter[key]) !== typeof (value)) { 495 | console.error(`value is wrong type for ${key}, expected ${typeof defaultFilter[key]} was ${typeof value}`); 496 | return; 497 | } 498 | newFilter[key] = value; 499 | } 500 | currentFilter = newFilter; 501 | haveNewFilter = true; 502 | }, 503 | }; 504 | 505 | }()); 506 | 507 | -------------------------------------------------------------------------------- /nano-villager_html/filters/hq4x/COPYING.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | -------------------------------------------------------------------------------- /nano-villager_html/original-html.html: -------------------------------------------------------------------------------- 1 | 2 | PICO-8 Cartridge 3 | 4 | 678 | 679 | 736 | 737 | 738 | 739 | 740 |
741 | 742 | 743 | 744 | 745 | 746 |
747 |
748 |
749 | 750 |
751 |
752 |
753 |
754 |
755 | 756 |
759 | 760 |
761 | 763 |
764 | 765 | 804 | 805 |
806 | 807 |
808 |
809 | 810 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 |
845 | 846 | 847 | -------------------------------------------------------------------------------- /nano-villager_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | PICO-8 Post Processing 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 73 | 747 | 748 | 805 | 806 | 807 | 808 | 809 |
810 | 811 | 812 | 813 | 814 | 815 |
816 |
817 |
818 | 819 |
820 |
821 |
822 |
823 |
824 | 825 |
828 | 829 |
830 | 832 |
833 | 834 | 873 | 874 |
875 | 876 |
877 |
878 | 879 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 |
914 |
915 |
filter:
916 |
917 | src 918 | author: 919 | license: 920 | 921 |
922 | 923 |
924 |
925 |
926 | game:Nano Village 927 | author:Partnano 928 | license:CC4-BY-NC-SA 929 |
930 |
931 | 932 | 933 | 934 | --------------------------------------------------------------------------------