├── AUTHORS ├── .travis.yml ├── exponential ├── README.md ├── index.css ├── index.html ├── index.mjs └── shaders.mjs ├── index.test.mjs ├── talk-webgl ├── tri.png ├── README.md ├── slides.html ├── slides.css ├── slides.js └── SLIDES.md ├── .mailmap ├── texture-mapping ├── js.png ├── index.css ├── README.md ├── image-loader.mjs ├── index.html ├── index.mjs └── shaders.mjs ├── cube ├── index.css ├── README.md ├── index.html ├── index.mjs └── shaders.mjs ├── fractal ├── index.css ├── index.html ├── fractal.mjs └── shaders.mjs ├── heartstar ├── index.css ├── index.html ├── index.mjs └── shaders.mjs ├── herzchen ├── index.css ├── index.html ├── index.mjs └── shaders.mjs ├── prime-spiral ├── index.css ├── index.html ├── prime-sieve.mjs ├── index.mjs └── shaders.mjs ├── raymarching ├── index.css ├── README.md ├── index.html ├── index.mjs └── shaders.mjs ├── triangle ├── index.css ├── index.html ├── shaders.mjs └── index.mjs ├── morphing-stars ├── index.css ├── index.html ├── index.mjs └── shaders.mjs ├── raymarching-shadows ├── index.css ├── README.md ├── index.html ├── index.mjs └── shaders.mjs ├── patterns ├── index.css ├── index.html ├── index.mjs └── shaders.mjs ├── shape-morph-3d ├── index.css ├── index.html ├── index.mjs └── shaders.mjs ├── dither-cam ├── index.css ├── index.html ├── shaders.mjs └── index.mjs ├── webcam-triangle-grid ├── index.css ├── index.html ├── grid-geometry.mjs ├── shaders.mjs └── index.mjs ├── webcam-triangle-grid-2 ├── index.css ├── index.html ├── shaders.mjs └── index.mjs ├── sobel-edge-detection ├── index.css ├── index.html ├── shaders.mjs └── index.mjs ├── server.js ├── lib ├── test-framework │ ├── array-utils.mjs │ ├── shader-compile-test.mjs │ └── test.mjs ├── glea │ ├── index.html │ ├── clock.mjs │ ├── perspective.mjs │ ├── geometry.mjs │ ├── README.md │ └── glsl-utils.mjs ├── prism │ └── prism.css └── phenomenon-1.5.1 │ ├── index.d.ts │ └── phenomenon.mjs ├── scripts └── authors ├── .gitattributes ├── webcam ├── index.html ├── index.css ├── shaders.mjs └── index.mjs ├── webcam-bender ├── index.html ├── index.css ├── shaders.mjs └── index.mjs ├── image-slider ├── components │ ├── image-slider.css │ ├── shaders.mjs │ ├── easings.mjs │ └── image-slider.mjs ├── index.html ├── index.mjs └── index.css ├── CONTRIBUTING.md ├── index.css ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── fractal.js ├── moire.js ├── helloworld.js ├── pastelflowerweekend.js ├── .gitignore ├── index.html ├── package.json ├── shapes.js ├── helloworld.html ├── LICENSE ├── hello-3d.html ├── moire.html ├── pastelflowerweekend.html ├── shapes.html ├── blacklineweekend.js ├── relaxing-cubes.js ├── moire2.html ├── multiple-shaders.js ├── blacklineweekend.html ├── fractal.html ├── hello-3d.js ├── relaxing-cubes.html ├── multiple-shaders.html ├── README.md ├── CODE_OF_CONDUCT.md └── minigl.js /AUTHORS: -------------------------------------------------------------------------------- 1 | Lea Rosema 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: node 3 | -------------------------------------------------------------------------------- /exponential/README.md: -------------------------------------------------------------------------------- 1 | # Mad math science in progress 2 | -------------------------------------------------------------------------------- /index.test.mjs: -------------------------------------------------------------------------------- 1 | import './lib/glea/math3d.test.mjs'; 2 | -------------------------------------------------------------------------------- /talk-webgl/tri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learosema/hello-webgl/HEAD/talk-webgl/tri.png -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Lea Rosema 2 | Lea Rosema -------------------------------------------------------------------------------- /texture-mapping/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learosema/hello-webgl/HEAD/texture-mapping/js.png -------------------------------------------------------------------------------- /cube/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | width: 100vw; 7 | height: 100vh; 8 | display: block; 9 | } -------------------------------------------------------------------------------- /fractal/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /exponential/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /heartstar/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /herzchen/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /prime-spiral/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /raymarching/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /triangle/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /morphing-stars/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /texture-mapping/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /raymarching-shadows/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /patterns/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | -------------------------------------------------------------------------------- /shape-morph-3d/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | -------------------------------------------------------------------------------- /texture-mapping/README.md: -------------------------------------------------------------------------------- 1 | # Texture mapping 2 | 3 | This is a work in progress. 4 | 5 | ## Resources 6 | 7 | https://webglfundamentals.org/webgl/lessons/webgl-image-processing.html -------------------------------------------------------------------------------- /cube/README.md: -------------------------------------------------------------------------------- 1 | # This is an example for a cube 2 | 3 | * perspective projection is done in the shader code. 4 | * cube geometry is provided in the [geometry](../lib/glea/geometry.mjs) module. -------------------------------------------------------------------------------- /dither-cam/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | 11 | video { 12 | display: none; 13 | } 14 | -------------------------------------------------------------------------------- /webcam-triangle-grid/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | 11 | video { 12 | display: none; 13 | } -------------------------------------------------------------------------------- /webcam-triangle-grid-2/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | 11 | video { 12 | display: none; 13 | } -------------------------------------------------------------------------------- /sobel-edge-detection/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | 11 | video { 12 | display: none; 13 | } 14 | -------------------------------------------------------------------------------- /raymarching-shadows/README.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | Lighting and shadow via Raymarching. Based on the article and demo by Inigo Quilez: 4 | 5 | * https://www.iquilezles.org/www/articles/rmshadows/rmshadows.htm 6 | * https://www.shadertoy.com/view/lsKcDD 7 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const PORT = 1337; 2 | const HOST = '0.0.0.0' 3 | 4 | const express = require('express'); 5 | const app = express(); 6 | 7 | app.use(express.static('.')); 8 | app.listen(PORT, HOST, () => console.log(`Server listening at ${HOST}:${PORT}`)); 9 | -------------------------------------------------------------------------------- /talk-webgl/README.md: -------------------------------------------------------------------------------- 1 | # Talk: Creative Coding with WebGL 2 | 3 | These are the slides to my talk for HH.JS. 4 | 5 | * Watch the [slides](https://terabaud.github.io/hello-webgl/talk-webgl/slides.html) 6 | * or just read the [SLIDES.md](SLIDES.md) 7 | 8 | -------------------------------------------------------------------------------- /texture-mapping/image-loader.mjs: -------------------------------------------------------------------------------- 1 | export function loadImage(url) { 2 | return new Promise((resolve, reject) => { 3 | const img = new Image(); 4 | img.src = url; 5 | img.onload = () => { 6 | resolve(img); 7 | }; 8 | img.onerror = () => { 9 | reject(img); 10 | }; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /lib/test-framework/array-utils.mjs: -------------------------------------------------------------------------------- 1 | export function arrayEquals(a, b) { 2 | if (!a || !b) { 3 | return false; 4 | } 5 | if (a.length !== b.length) { 6 | return false; 7 | } 8 | for (let i = 0; i < a.length; i++) { 9 | if (a[i] !== b[i]) { 10 | return false; 11 | } 12 | } 13 | return true; 14 | } -------------------------------------------------------------------------------- /scripts/authors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Generate an AUTHORS file based on the output of git shortlog. It uses ABC 4 | # order, strips out leading spaces and numbers, then filters out specific 5 | # authors. 6 | 7 | git shortlog -se \ 8 | | perl -spe 's/^\s+\d+\s+//' \ 9 | | sed -e '/^CommitSyncScript.*$/d' \ 10 | > AUTHORS -------------------------------------------------------------------------------- /raymarching/README.md: -------------------------------------------------------------------------------- 1 | This is an example for raymarching, a technique for rendering images. In this technique, a ray is cast through each pixel and intersect it with the surfaces in the scene. 2 | 3 | ## Resources 4 | 5 | * https://www.iquilezles.org/www/articles/raymarchingdf/raymarchingdf.htm 6 | * A detailed course on raymarching: https://github.com/ajweeks/RaymarchingWorkshop 7 | -------------------------------------------------------------------------------- /patterns/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cube/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /shape-morph-3d/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /raymarching/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Raymarching Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /fractal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Animated Julia Fractal 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /texture-mapping/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Texture mapping demo 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /dither-cam/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Dither cam 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /herzchen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Herzchen 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /heartstar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hearts 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /exponential/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Exponential 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /prime-spiral/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Prime Spiral 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /triangle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hello World! 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /morphing-stars/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hello World! 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sobel-edge-detection/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | More Webcam art :) 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /webcam-triangle-grid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Webcam controlled triangle grid 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /webcam-triangle-grid-2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Webcam controlled triangle grid - 2 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /webcam/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Webcam demo 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /raymarching-shadows/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Infinite field of jumping cookie dough | Raymarching with lights and shadows 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /webcam-bender/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Webcam Bender 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /image-slider/components/image-slider.css: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | :host([hidden]) { 6 | display: none; 7 | } 8 | 9 | :host { 10 | display: flex; 11 | width: 100vw; 12 | height: 100vh; 13 | 14 | background: #000; 15 | justify-content: center; 16 | align-items: center; 17 | } 18 | 19 | canvas { 20 | margin: auto; 21 | width: 100vmin; 22 | height: 100vmin; 23 | } 24 | 25 | button { 26 | display: none; 27 | } 28 | 29 | ::slotted(*) { 30 | display: none; 31 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing. 4 | 5 | Currently, this is a one-woman project and there are no strict coding conventions yet. 6 | 7 | The only prerequisite is to follow the Code of Conduct. 8 | 9 | Feel free to fork and help improving the project by creating a pull request. 10 | 11 | Don't be shy. Aside to code changes, help improving the documentation, 12 | this contribution guideline or the description of open issues is highly appreciated. 13 | 14 | Cheers, 15 | 16 | Lea 17 | -------------------------------------------------------------------------------- /webcam/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | 11 | video { 12 | display: none; 13 | } 14 | 15 | button { 16 | position: fixed; 17 | cursor: pointer; 18 | left: 50%; 19 | margin-left: -64px; 20 | bottom: 16px; 21 | height: 64px; 22 | width: 128px; 23 | font-size: 48px; 24 | font-family: sans-serif; 25 | font-weight: bold; 26 | background: rgba(255, 255, 255, .67); 27 | border-radius: 8px; 28 | text-align: center; 29 | } -------------------------------------------------------------------------------- /webcam-bender/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | canvas { 6 | display: block; 7 | width: 100vw; 8 | height: 100vh; 9 | } 10 | 11 | video { 12 | display: none; 13 | } 14 | 15 | button { 16 | position: fixed; 17 | cursor: pointer; 18 | left: 50%; 19 | margin-left: -64px; 20 | bottom: 16px; 21 | height: 64px; 22 | width: 128px; 23 | font-size: 48px; 24 | font-family: sans-serif; 25 | font-weight: bold; 26 | background: rgba(255, 255, 255, .67); 27 | border-radius: 8px; 28 | text-align: center; 29 | } -------------------------------------------------------------------------------- /prime-spiral/prime-sieve.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Get primes up to N via the Sieve of Eratosthenes algorithm 3 | * 4 | * @param {Number} N prime limit 5 | * @returns {Array} Array of primes up to N 6 | */ 7 | export function primeSieve(N = 100) { 8 | const data = new Array(N + 1).fill(true); 9 | data[0] = false; 10 | data[1] = false 11 | for (let i = 2; i <= (Math.sqrt(N)|0); i++) { 12 | if (!data[i]) { 13 | continue; 14 | } 15 | for (let j = i * i; j <= N; j += i) { 16 | data[j] = false; 17 | } 18 | } 19 | return data; 20 | } -------------------------------------------------------------------------------- /triangle/shaders.mjs: -------------------------------------------------------------------------------- 1 | const glsl = x => { 2 | return x[0].trim(); 3 | } 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | varying vec3 vcolor; 8 | 9 | void main() { 10 | gl_FragColor = vec4(vcolor, 1.0); //vec4(1.0, 0, 0, 1.0); 11 | } 12 | `; 13 | 14 | export const vert = glsl` 15 | precision highp float; 16 | attribute vec2 position; 17 | attribute vec3 color; 18 | uniform float time; 19 | varying vec3 vcolor; 20 | 21 | void main () { 22 | vcolor = color; //vec3(1.0, 0, 1.0); 23 | gl_Position = vec4(position + sin(position.x - position.y + time) * .3, 0, 1.0); 24 | } 25 | `; -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | color: #201d1c; 4 | max-width: 800px; 5 | margin: 0 auto; 6 | padding: 16px; 7 | } 8 | 9 | body, body .markdown-body { 10 | color: #201d1c; 11 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 12 | font-size: 24px; 13 | line-height: 1.8; 14 | } 15 | 16 | h1, h2, h3, h4, h5 { 17 | color: #27f; 18 | } 19 | 20 | @media (prefers-color-scheme: dark) { 21 | body { 22 | background: #222; 23 | } 24 | 25 | body .markdown-body { 26 | color: #f7f7f7; 27 | } 28 | 29 | h1, h2, h3, h4, h5 { 30 | color: #4cf; 31 | } 32 | 33 | .markdown-body a { 34 | color: #6ef; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /lib/test-framework/shader-compile-test.mjs: -------------------------------------------------------------------------------- 1 | // todo: maybe look at github.com/node-3d/webgl-raub to get it headless? 2 | 3 | export function compileTest(frag, vert, contextType) { 4 | const canvas = document.createElement('canvas'); 5 | const gl = canvas.getContext(contextType || ("webgl" || "experimental-webgl")); 6 | const program = gl.createProgram(); 7 | gl.attachShader(program, shader); 8 | gl.linkProgram(program); 9 | gl.validateProgram(program); 10 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 11 | const info = gl.getProgramInfoLog(program); 12 | throw "Could not compile WebGL program. \n\n" + info; 13 | } 14 | } -------------------------------------------------------------------------------- /talk-webgl/slides.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Creative Coding with WebGL 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /exponential/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 11 | } 12 | }).create(); 13 | 14 | window.addEventListener('resize', () => { 15 | glea.resize(); 16 | }); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | loop(0); 29 | -------------------------------------------------------------------------------- /heartstar/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 11 | } 12 | }).create(); 13 | 14 | window.addEventListener('resize', () => { 15 | glea.resize(); 16 | }); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | loop(0); 29 | -------------------------------------------------------------------------------- /patterns/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 11 | } 12 | }).create(); 13 | 14 | window.addEventListener('resize', () => { 15 | glea.resize(); 16 | }); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | loop(0); 29 | -------------------------------------------------------------------------------- /raymarching/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 11 | } 12 | }).create(); 13 | 14 | window.addEventListener('resize', () => { 15 | glea.resize(); 16 | }); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | loop(0); 29 | -------------------------------------------------------------------------------- /fractal.js: -------------------------------------------------------------------------------- 1 | w=a.width=innerWidth 2 | h=a.height=innerHeight 3 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 4 | g.enable(g.DEPTH_TEST) 5 | g.depthFunc(g.LEQUAL) 6 | 7 | $prog(g, [vertexShader, fragmentShader]) 8 | posBuf = $buf([1,1,0,-1,1,0,1,-1,0,-1,-1,0]) 9 | pos = $attr("pos") 10 | 11 | 12 | ~function scene(time) { 13 | time=time*1e-3 14 | 15 | // clear screen 16 | g.clearColor(1/5, 1/5, 1/5, 1) 17 | g.clear(g.COLOR_BUFFER_BIT | g.DEPTH_BUFFER_BIT) 18 | 19 | // bind attributes to buffers 20 | $bind(pos, posBuf, 3) 21 | 22 | // set uniforms 23 | $uni("time", time) 24 | $uniV("resolution", [w,h]) 25 | 26 | // draw 27 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 28 | requestAnimationFrame(scene) 29 | }(0) 30 | -------------------------------------------------------------------------------- /moire.js: -------------------------------------------------------------------------------- 1 | w=a.width=innerWidth 2 | h=a.height=innerHeight 3 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 4 | g.enable(g.DEPTH_TEST) 5 | g.depthFunc(g.LEQUAL) 6 | 7 | $prog(g, [vertexShader, fragmentShader]) 8 | posBuf = $buf([1,1,0,-1,1,0,1,-1,0,-1,-1,0]) 9 | pos = $attr("pos") 10 | 11 | 12 | ~function scene(time) { 13 | time=time*1e-3 14 | 15 | // clear screen 16 | g.clearColor(1/5, 1/5, 1/5, 1) 17 | g.clear(g.COLOR_BUFFER_BIT | g.DEPTH_BUFFER_BIT) 18 | 19 | // bind attributes to buffers 20 | $bind(pos, posBuf, 3) 21 | 22 | // set uniforms 23 | $uni("time", time) 24 | $uniV("resolution", [w,h]) 25 | 26 | // draw 27 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 28 | requestAnimationFrame(scene) 29 | }(0) 30 | -------------------------------------------------------------------------------- /morphing-stars/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 11 | } 12 | }).create(); 13 | 14 | window.addEventListener('resize', () => { 15 | glea.resize(); 16 | }); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | loop(0); 29 | -------------------------------------------------------------------------------- /shape-morph-3d/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 11 | } 12 | }).create(); 13 | 14 | window.addEventListener('resize', () => { 15 | glea.resize(); 16 | }); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | loop(0); 29 | -------------------------------------------------------------------------------- /helloworld.js: -------------------------------------------------------------------------------- 1 | w=a.width=innerWidth 2 | h=a.height=innerHeight 3 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 4 | g.enable(g.DEPTH_TEST) 5 | g.depthFunc(g.LEQUAL) 6 | 7 | $prog(g, [vertexShader, fragmentShader]) 8 | posBuf = $buf([1,1,0,-1,1,0,1,-1,0,-1,-1,0]) 9 | pos = $attr("pos") 10 | 11 | 12 | ~function scene(time) { 13 | time=time*1e-3 14 | 15 | // clear screen 16 | g.clearColor(1/5, 1/5, 1/5, 1) 17 | g.clear(g.COLOR_BUFFER_BIT | g.DEPTH_BUFFER_BIT) 18 | 19 | // bind attributes to buffers 20 | $bind(pos, posBuf, 3) 21 | 22 | // set uniforms 23 | $uni("time", time) 24 | $uniV("resolution", [w,h]) 25 | 26 | // draw 27 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 28 | requestAnimationFrame(scene) 29 | }(0) 30 | -------------------------------------------------------------------------------- /raymarching-shadows/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 11 | } 12 | }).create(); 13 | 14 | window.addEventListener('resize', () => { 15 | glea.resize(); 16 | }); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | loop(0); 29 | -------------------------------------------------------------------------------- /pastelflowerweekend.js: -------------------------------------------------------------------------------- 1 | w=a.width=innerWidth 2 | h=a.height=innerHeight 3 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 4 | g.enable(g.DEPTH_TEST) 5 | g.depthFunc(g.LEQUAL) 6 | 7 | $prog(g, [vertexShader, fragmentShader]) 8 | posBuf = $buf([1,1,0,-1,1,0,1,-1,0,-1,-1,0]) 9 | pos = $attr("pos") 10 | 11 | 12 | ~function scene(time) { 13 | time=time*1e-3 14 | 15 | // clear screen 16 | g.clearColor(1/5, 1/5, 1/5, 1) 17 | g.clear(g.COLOR_BUFFER_BIT | g.DEPTH_BUFFER_BIT) 18 | 19 | // bind attributes to buffers 20 | $bind(pos, posBuf, 3) 21 | 22 | // set uniforms 23 | $uni("time", time) 24 | $uniV("resolution", [w,h]) 25 | 26 | // draw 27 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 28 | requestAnimationFrame(scene) 29 | }(0) 30 | -------------------------------------------------------------------------------- /triangle/index.mjs: -------------------------------------------------------------------------------- 1 | import GLea from '../lib/glea/glea.mjs'; 2 | import { frag, vert } from './shaders.mjs'; 3 | 4 | const glea = new GLea({ 5 | shaders: [ 6 | GLea.fragmentShader(frag), 7 | GLea.vertexShader(vert) 8 | ], 9 | buffers: { 10 | 'position': GLea.buffer(2, [0.0, 0.7, -.9, -.8, .8, -.7]), 11 | 'color': GLea.buffer(3, [1, 0, 0, 0, 1, 0, 1,0, 1]) 12 | } 13 | }).create(); 14 | 15 | window.addEventListener('resize', () => { 16 | glea.resize(); 17 | }); 18 | 19 | function loop(time) { 20 | const { gl, program } = glea; 21 | glea.clear(); 22 | glea.uni('width', glea.width); 23 | glea.uni('height', glea.height); 24 | glea.uni('time', time * .005); 25 | gl.drawArrays(gl.TRIANGLES, 0, 3); 26 | requestAnimationFrame(loop); 27 | } 28 | 29 | loop(0); 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | # nodejs 46 | node_modules 47 | *.log -------------------------------------------------------------------------------- /lib/glea/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /herzchen/index.mjs: -------------------------------------------------------------------------------- 1 | import GLea from '../lib/glea/glea.mjs'; 2 | 3 | import { frag, vert } from './shaders.mjs'; 4 | 5 | let texture = null; 6 | 7 | const glea = new GLea({ 8 | shaders: [ 9 | GLea.fragmentShader(frag), 10 | GLea.vertexShader(vert) 11 | ], 12 | buffers: { 13 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 14 | } 15 | }).create(); 16 | 17 | function loop(time) { 18 | const { gl } = glea; 19 | glea.clear(); 20 | glea.uni('width', glea.width); 21 | glea.uni('height', glea.height); 22 | glea.uni('time', time * .005); 23 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 24 | requestAnimationFrame(loop); 25 | } 26 | 27 | function setup() { 28 | const { gl } = glea; 29 | window.addEventListener('resize', () => { 30 | glea.resize(); 31 | }); 32 | loop(0); 33 | } 34 | 35 | setup(); 36 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 |
13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-webgl", 3 | "version": "1.0.0", 4 | "description": "This [repository](https://github.com/terabaud/hello-webgl/) features my experiments with WebGL.", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "start": "node server", 11 | "test": "node -r esm index.test.mjs" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/terabaud/hello-webgl.git" 16 | }, 17 | "author": "Lea Rosema", 18 | "license": "WTFPL", 19 | "bugs": { 20 | "url": "https://github.com/terabaud/hello-webgl/issues" 21 | }, 22 | "homepage": "https://github.com/terabaud/hello-webgl#readme", 23 | "keywords": [ 24 | "webgl" 25 | ], 26 | "devDependencies": { 27 | "esm": "^3.2.25", 28 | "express": "^4.17.1" 29 | }, 30 | "dependencies": {} 31 | } 32 | -------------------------------------------------------------------------------- /shapes.js: -------------------------------------------------------------------------------- 1 | w=a.width=innerWidth 2 | h=a.height=innerHeight 3 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 4 | g.enable(g.DEPTH_TEST) 5 | g.depthFunc(g.LEQUAL) 6 | 7 | $prog(g,[vertexShader,fragmentShader]) 8 | pos=$attr("pos") 9 | triBuf=$buf([0,1,0,-1,-1,0,1,-1,0]) 10 | quadBuf=$buf([1,1,0,-1,1,0,1,-1,0,-1,-1,0]) 11 | P=perspective(45,w/h,0.1,100) 12 | MV=mI() 13 | 14 | ~function scene(time) { 15 | time/=1e3 16 | // function to set the uniforms 17 | unis=function(){ 18 | $uni("time",time) 19 | $uniV("resolution",[w,h]) 20 | $uniM("MV",MV) 21 | $uniM("P",P) 22 | } 23 | // clear screen 24 | g.clearColor(1/5,1/5,1/5,1) 25 | g.clear(g.COLOR_BUFFER_BIT|g.DEPTH_BUFFER_BIT) 26 | 27 | // draw trigon 28 | MV=mT(-1.5,0,-7) 29 | $bind(pos,triBuf,3) 30 | unis() 31 | g.drawArrays(g.TRIANGLE_STRIP,0,3) 32 | 33 | // draw square 34 | MV=mT(1.5,0,-7) 35 | $bind(pos,quadBuf,3) 36 | unis() 37 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 38 | 39 | requestAnimationFrame(scene) 40 | }(0) 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /helloworld.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Lea Rosema 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /hello-3d.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /fractal/fractal.mjs: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Phenomenon from '../lib/phenomenon-1.5.1/phenomenon.mjs'; 4 | import { frag, vert } from './shaders.mjs' 5 | const canvas = document.querySelector('canvas'); 6 | const phenomenon = new Phenomenon({ 7 | canvas, 8 | contextType: 'webgl', 9 | settings: { 10 | devicePixelRatio: window.devicePixelRatio 11 | } 12 | }); 13 | 14 | phenomenon.add('first', { 15 | uniforms: { 16 | time: { 17 | type: 'float', 18 | value: 0.0 19 | }, 20 | width: { 21 | type: 'float', 22 | value: canvas.width * window.devicePixelRatio 23 | }, 24 | height: { 25 | type: 'float', 26 | value: canvas.height * window.devicePixelRatio 27 | } 28 | }, 29 | vertex: vert, 30 | fragment: frag, 31 | mode: 4, 32 | geometry: {vertices: [ 33 | {x: -1, y: -1, z: 0}, 34 | {x: -1, y: 1, z: 0}, 35 | {x: 1, y: -1, z: 0}, 36 | {x: 1, y: -1, z: 0}, 37 | {x: -1, y: 1, z: 0}, 38 | {x: 1, y: 1, z: 0} 39 | ]}, 40 | onRender: instance => { 41 | instance.uniforms.time.value += 0.1; 42 | } 43 | }); -------------------------------------------------------------------------------- /webcam-triangle-grid/grid-geometry.mjs: -------------------------------------------------------------------------------- 1 | /* More modelling in ASCII art :) 2 | 3 | x--x--x 4 | | /| /| 5 | |/ |/ | 6 | x--x--x 7 | | /| /| 8 | |/ |/ | 9 | x--x--x 10 | */ 11 | 12 | /** 13 | * Create 2D grid geometry 14 | * 15 | * @param delta {number} point distance, default 0.1 16 | * @returns {Array} flattened array of 2D coordinates 17 | */ 18 | export function grid(deltaX = 0.1, deltaY = 0.1, xMin = -1, yMin = -1, xMax = 1, yMax = 1) { 19 | const dimX = Math.round((xMax - xMin) / deltaX); 20 | const dimY = Math.round((yMax - yMin) / deltaY); 21 | const squares = Array(dimX * dimY).fill(0).map((_, idx) => { 22 | const col = idx % dimX; 23 | const row = (idx / dimX)|0; 24 | const x0 = xMin + deltaX * col; 25 | const y0 = yMin + deltaY * row; 26 | const x1 = x0 + deltaX; 27 | const y1 = y0 + deltaY; 28 | // return two triangles per square 29 | return [ 30 | x0, y0, x1, y0, x0, y1, 31 | x0, y1, x1, y0, x1, y1 32 | ]; 33 | }); 34 | // for MS Edge support, let's do a slightly more complicated 35 | // thing than just 36 | // return squares.flat(); 37 | return Array.prototype.concat.apply([], squares); 38 | } 39 | -------------------------------------------------------------------------------- /webcam-triangle-grid/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x; 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec2 position; 7 | attribute float direction; 8 | 9 | uniform float time; 10 | uniform float width; 11 | uniform float height; 12 | uniform sampler2D image; 13 | 14 | varying vec4 vTexColor; 15 | 16 | const float PI = 3.1415926535; 17 | 18 | vec4 invert(vec4 color) { 19 | return vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, 1.0); 20 | } 21 | 22 | vec4 shuffleRB(vec4 color) { 23 | return vec4(color.z, color.y, color.x, 1.0); 24 | } 25 | 26 | 27 | void main() { 28 | vec2 randomVector = vec2(cos(direction * 2.0 * PI), sin(direction * 2.0 * PI)) * sin(time * .1) * .05; 29 | vec2 texCoords = (1.0 - position) * .5 + randomVector; 30 | vTexColor = shuffleRB(texture2D(image, texCoords)); 31 | gl_Position = vec4(position, 0.0, 1.0); 32 | } 33 | `; 34 | 35 | export const frag = glsl` 36 | precision highp float; 37 | 38 | uniform float width; 39 | uniform float height; 40 | uniform float time; 41 | 42 | varying vec4 vTexColor; 43 | 44 | void main() { 45 | gl_FragColor = vTexColor; 46 | } 47 | `; 48 | -------------------------------------------------------------------------------- /moire.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /webcam-bender/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x; 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec2 position; 7 | 8 | uniform float time; 9 | uniform float width; 10 | uniform float height; 11 | 12 | void main() { 13 | gl_Position = vec4(position, 0.0, 1.0); 14 | } 15 | `; 16 | 17 | export const frag = glsl` 18 | precision highp float; 19 | 20 | uniform float width; 21 | uniform float height; 22 | uniform float time; 23 | 24 | uniform sampler2D image; 25 | 26 | // normalize coords and correct for aspect ratio 27 | vec2 normalizeScreenCoords() 28 | { 29 | float aspectRatio = width / height; 30 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 31 | result.x *= aspectRatio; 32 | return result; 33 | } 34 | 35 | float deform(vec2 p) { 36 | return (1.0 - sqrt(p.x * p.x + p.y * p.y)) * (.125 + sin(time * .1) * .125); 37 | } 38 | 39 | vec4 invert(vec4 color) { 40 | return vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, 1.0); 41 | } 42 | 43 | void main() { 44 | vec2 p = normalizeScreenCoords(); 45 | vec2 texCoords = 1.0 - gl_FragCoord.xy / vec2(width, height); 46 | vec4 texColor = texture2D(image, texCoords); 47 | gl_FragColor = invert(texColor); 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /lib/glea/clock.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * a port of the THREE.Clock class 3 | * https://github.com/mrdoob/three.js/blob/dev/src/core/Clock.js 4 | */ 5 | 6 | export default class Clock { 7 | 8 | constructor(autoStart = true) { 9 | this.startTime = 0; 10 | this.oldTime = 0; 11 | this._elapsedTime = 0; 12 | this.autoStart = autoStart; 13 | this.running = autoStart; 14 | } 15 | 16 | getDelta() { 17 | const { running, autoStart } = this; 18 | if (autoStart && !running) { 19 | this.start(); 20 | return 0; 21 | } 22 | if (running) { 23 | const newTime = (typeof performance === 'undefined' ? Date : performance).now(); 24 | const diff = (newTime - this.oldTime) / 1000; 25 | this.oldTime = newTime; 26 | this._elapsedTime += diff; 27 | return diff; 28 | } 29 | return 0; 30 | } 31 | 32 | get elapsedTime() { 33 | this.getDelta(); 34 | return this._elapsedTime; 35 | } 36 | 37 | start() { 38 | this.startTime = (typeof performance === 'undefined' ? Date : performance).now(); 39 | this.oldTime = this.startTime; 40 | this.running = true; 41 | this._elapsedTime = 0; 42 | } 43 | 44 | stop() { 45 | this.getDelta(); 46 | this.running = false; 47 | this.autoStart = false; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /pastelflowerweekend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /shapes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /blacklineweekend.js: -------------------------------------------------------------------------------- 1 | concat=function(x,y){return Array.prototype.push.apply(x,y),x} 2 | w=a.width=innerWidth 3 | h=a.height=innerHeight 4 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 5 | g.enable(g.DEPTH_TEST) 6 | 7 | // background 8 | p1=$prog(g,[vs1,fs1]) 9 | bkgBuf=$buf(sq(32)) 10 | 11 | // foreground 12 | p2=$prog(g,[vs2,fs2]) 13 | diceBuf=$buf(qb()) 14 | P=perspective(45,w/h,0.1,100) 15 | 16 | ~function scene(time,m) { 17 | time/=1e3 18 | // clear screen 19 | g.clearColor(1/5,1/5,1/5,1) 20 | g.clear(g.COLOR_BUFFER_BIT|g.DEPTH_BUFFER_BIT) 21 | 22 | // program 1 23 | $prog(g,p1) 24 | $attr("pos") 25 | $bind("pos",bkgBuf,3) 26 | $uni("time",time) 27 | $uniV("resolution",[w,h]) 28 | $uniM("M",mX(P, mT(0,0,-15))) 29 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 30 | $attrOff("pos") 31 | 32 | // program 2 33 | $prog(g,p2) 34 | $attr("pos") 35 | $bind("pos", diceBuf,3) 36 | // set uniforms 37 | $uni("time",time) 38 | $uniV("resolution",[w,h]) 39 | // create transformation/rotation matrix 40 | // mX is matrix multiplication, mT is transformation, mR* is rotatio 41 | $uniM("M",mX(P,mX(mT(sin(time*2)*3,cos(time*3)*1.5,sin(time)*5-10), 42 | mX(mX(mRx(time*0.1),mRy(time*4)),mRz(time*0.6))))) 43 | g.drawArrays(g.TRIANGLES,0,36) 44 | $attrOff("pos") 45 | 46 | requestAnimationFrame(scene) 47 | }(0) 48 | -------------------------------------------------------------------------------- /patterns/shaders.mjs: -------------------------------------------------------------------------------- 1 | // this is just for code highlighting in VSCode 2 | // via the glsl-literal extension 3 | const glsl = x => x; 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | const float PI = 3.141592654; 8 | 9 | uniform float width; 10 | uniform float height; 11 | uniform float time; 12 | 13 | vec2 repeat(in vec2 p, in vec2 c) { 14 | return mod(p, c) - 0.5 * c; 15 | } 16 | 17 | // normalize coords and correct for aspect ratio 18 | vec2 normalizeScreenCoords() { 19 | float aspectRatio = width / height; 20 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 21 | result.x *= aspectRatio; 22 | return result; 23 | } 24 | 25 | float rand() { 26 | return fract(sin(dot(gl_FragCoord.xy + sin(time),vec2(12.9898,78.233))) * 43758.5453); 27 | } 28 | 29 | vec3 palette(float x) { 30 | return vec3( 31 | .5 + .5 * sin(-.1 + x*PI), 32 | .5 + .5 * sin(2.0 + .5 * x*PI), 33 | .5 * .5 * sin(3.0 + x*PI)); 34 | } 35 | 36 | 37 | void main() { 38 | vec2 p0 = normalizeScreenCoords(); 39 | vec2 p = repeat(p0, vec2(0.25)); 40 | gl_FragColor = vec4((.75 + .25 * sin(p.x * p.y * 180.0 + time * .5)) * palette(time *.01 + .1 * p0.x * p0.y), 1.0); 41 | } 42 | ` 43 | 44 | export const vert = glsl` 45 | precision mediump float; 46 | attribute vec2 position; 47 | 48 | void main () { 49 | gl_Position = vec4(position, 0, 1.0); 50 | } 51 | ` -------------------------------------------------------------------------------- /relaxing-cubes.js: -------------------------------------------------------------------------------- 1 | concat=function(x,y){return Array.prototype.push.apply(x,y),x} 2 | w=a.width=innerWidth 3 | h=a.height=innerHeight 4 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 5 | g.enable(g.DEPTH_TEST) 6 | 7 | // background 8 | p1=$prog(g,[vs1,fs1]) 9 | bkgBuf=$buf(sq(32)) 10 | 11 | // foreground 12 | p2=$prog(g,[vs2,fs2]) 13 | diceBuf=$buf(qb(0.44)) 14 | P=perspective(45,w/h,0.1,100) 15 | 16 | ~function scene(time,m,i,j) { 17 | time/=1e3 18 | // clear screen 19 | g.clearColor(1/5,1/5,1/5,1) 20 | g.clear(g.COLOR_BUFFER_BIT|g.DEPTH_BUFFER_BIT) 21 | 22 | // program 1 23 | $prog(g,p1) 24 | $attr("pos") 25 | $bind("pos",bkgBuf,3) 26 | $uni("time",time) 27 | $uniV("resolution",[w,h]) 28 | $uniM("M",mX(P, mT(0,0,-15))) 29 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 30 | $attrOff("pos") 31 | 32 | // program 2 33 | $prog(g,p2) 34 | $attr("pos") 35 | $bind("pos", diceBuf,3) 36 | // set uniforms 37 | $uni("time",time) 38 | $uniV("resolution",[w,h]) 39 | for(i=10;i--;)for(j=10;j--;) // 10x10 cubes 40 | m=mX(P,mT(0,0,-10)),// translate 41 | m=mX(m,mT((-5+i)*2+sin(time+(i+j)*0.3)+sin(time),(-5+j)*2+cos(time+(i+j)*0.3),sin(i+j+time)*4.5)), // translate 42 | m=mX(m,mRz(time+(i+j)*0.2)), // rotate around z-axis 43 | m=mX(m,mRy((time+i+j)*0.2)), // rotate around y-axis 44 | $uniM("M",m), 45 | g.drawArrays(g.TRIANGLES,0,36) 46 | $attrOff("pos") 47 | requestAnimationFrame(scene) 48 | }(0) 49 | -------------------------------------------------------------------------------- /moire2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 39 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /cube/index.mjs: -------------------------------------------------------------------------------- 1 | import { cube } from '../lib/glea/geometry.mjs'; 2 | import { frag, vert } from './shaders.mjs'; 3 | import GLea from '../lib/glea/glea.mjs'; 4 | 5 | const Color = { 6 | red: [1, 0, 0], 7 | green: [0, 1, 0], 8 | blue: [0, 0, 1], 9 | yellow: [1, 1, 0], 10 | pink: [1,0,1], 11 | cyan: [0, 1, 1], 12 | white: [1, 1, 1] 13 | }; 14 | 15 | const { red, green, blue, yellow, pink, cyan } = Color; 16 | 17 | const glea = new GLea({ 18 | shaders: [ 19 | GLea.vertexShader(vert), 20 | GLea.fragmentShader(frag) 21 | ], 22 | buffers: { 23 | position: GLea.buffer(3, cube(0.25)), 24 | color: GLea.buffer(3, [ 25 | ...Array(6).fill(red), 26 | ...Array(6).fill(green), 27 | ...Array(6).fill(blue), 28 | ...Array(6).fill(pink), 29 | ...Array(6).fill(cyan), 30 | ...Array(6).fill(yellow) 31 | ].flat()) 32 | } 33 | }).create(); 34 | 35 | window.addEventListener('resize', () => { 36 | glea.resize(); 37 | }); 38 | 39 | function loop(time) { 40 | const { gl } = glea; 41 | const { sin, cos } = Math; 42 | glea.clear(); 43 | glea.uni('width', glea.width); 44 | glea.uni('height', glea.height); 45 | glea.uni('time', time * .01); 46 | gl.drawArrays(gl.TRIANGLES, 0, 36); 47 | requestAnimationFrame(loop); 48 | } 49 | 50 | function setup() { 51 | const { gl } = glea; 52 | gl.clearColor(1/6, 1/6, 1/6, 1); 53 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 54 | gl.enable(gl.DEPTH_TEST) 55 | loop(0); 56 | } 57 | 58 | setup(); -------------------------------------------------------------------------------- /lib/glea/perspective.mjs: -------------------------------------------------------------------------------- 1 | import { Vec3 } from './math3d.mjs'; 2 | 3 | // glOrtho(left, right, bottom, top, zNear, zFar) 4 | export function ortho(l, r, b, t, zn, zf){ 5 | const tx=-(r+l)/(r-l); 6 | const ty=-(t+b)/(t-b); 7 | const tz=-(zf+zn)/(zf-zn); 8 | 9 | return [2 / (r - l), 0, 0, 10 | 0, 0, 2 / (t - b), 0, 11 | 0, 0, 0, -2 / (zf - zn), 12 | 0, tx, ty, tz, 1]; 13 | } 14 | 15 | // glFrustum(left, right, bottom, top, zNear, zFar) 16 | export function frustum(left, right, bottom, top, zNear, zFar) { 17 | const t1 = 2 * zNear; 18 | const t2 = right - left; 19 | const t3 = top - bottom; 20 | const t4 = zFar - zNear; 21 | return [t1 / t2, 0, 0, 0, 22 | 0, t1 / t3, 0, 0, 23 | (right + left) / t2, (top + bottom) / t3, (-zFar - zNear) / t4, -1, 24 | 0, 0, (-t1*zFar) / t4, 0]; 25 | } 26 | 27 | // gluPerspective(fieldOfView, aspectRatio, zNear, zFar) 28 | export function perspective(fieldOfView, aspectRatio, zNear, zFar) { 29 | const y = zNear*Math.tan(fieldOfView* Math.PI / 360); 30 | const x = y * aspectRatio; 31 | return frustum(-x,x,-y,y,zNear, zFar) 32 | } 33 | 34 | // gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) 35 | export function lookAt(eye, 36 | center, 37 | up){ 38 | const c = Vec3(eye.x - center.x, eye.y - center.y, eye.z - center.z); 39 | const a = up.cross(z).normalize(); 40 | const b = z.cross(x).normalize(); 41 | return [a.x, b.x, c.x,0, 42 | a.y, b.y, c.y,0, 43 | a.z, b.z, c.z,0, 44 | 0, 0, 0, 1]; 45 | } 46 | -------------------------------------------------------------------------------- /lib/glea/geometry.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a square (2 triangles) 3 | * 4 | * @name square 5 | * @param {number} size 6 | */ 7 | export function square(size = 1) { 8 | const s = size * .5; 9 | return [ 10 | -s, -s, 0, 11 | s, -s, 0, 12 | -s, s, 0, 13 | -s, s, 0, 14 | s, -s, 0, 15 | s, s, 0]; 16 | } 17 | 18 | /** 19 | * Create a box with the sizes a * b * c, 20 | * centered at (0, 0, 0), 2 triangles per side. 21 | * 22 | * @name box 23 | * @param {number} sizeA 24 | * @param {number} sizeB 25 | * @param {number} sizeC 26 | */ 27 | export function box(sizeA = 1.0, sizeB = 1.0, sizeC = 1.0) { 28 | const a = sizeA * .5; 29 | const b = sizeB * .5; 30 | const c = sizeC * .5; 31 | const vertices = [ 32 | [-a, -b, -c], 33 | [ a, -b, -c], 34 | [-a, b, -c], 35 | [ a, b, -c], 36 | [-a, -b, c], 37 | [ a, -b, c], 38 | [-a, b, c], 39 | [ a, b, c] 40 | ]; 41 | // 0______1 42 | // 4/|____5/| 43 | // |2|____|_|3 44 | // |/ ____|/ 45 | // 6 7 46 | 47 | const faces = [ 48 | // back 49 | [0, 2, 1], [2, 3, 1], 50 | // front 51 | [5, 7, 4], [7, 6, 4], 52 | // left 53 | [4, 6, 0], [6, 2, 0], 54 | // right 55 | [7, 5, 1], [1, 3, 7], 56 | // top 57 | [1, 5, 0], [5, 4, 0], 58 | // bottom 59 | [2, 6, 3], [6, 7, 3] 60 | ]; 61 | const result = faces.flat().map(vertexIndex => vertices[vertexIndex]).flat(); 62 | return result; 63 | } 64 | 65 | export function cube(size = 1.0) { 66 | return box(size, size, size); 67 | } 68 | -------------------------------------------------------------------------------- /lib/glea/README.md: -------------------------------------------------------------------------------- 1 | # GLea.js - GL experimental assets 2 | 3 | Glea.js is a WebGL library with a minimal footprint in modern modular JavaScript. It provides helper functions for creating a WebGL program, compiling shaders and passing data from JavaScript to the shader language. 4 | 5 | ## Usage 6 | 7 | ``` 8 | import GLea from '../lib/glea/glea.mjs'; 9 | 10 | const glea = new GLea({ 11 | shaders: [ 12 | GLea.fragmentShader(frag), 13 | GLea.vertexShader(vert) 14 | ], 15 | buffers: { 16 | position: Glea.buffer(2, [1, 1, 1, 0, 0, 0]) 17 | } 18 | }).create(); 19 | ``` 20 | 21 | ### Options 22 | 23 | - `canvas`: optional, if not specified, `document.querySelector('canvas')` is used 24 | - `gl`: optional, if not specified, `canvas.getContext(contextType)` is used 25 | - `contextType`: optional, default `webgl` (or `experimental-webgl`, you don't need to prefix it yourself) 26 | - `glOptions`: additional options to pass to `canvas.getContext` 27 | - `shaders`: array that takes a fragmentShader and a vertexShader in the above form 28 | - `buffers`: an object with attributes and buffers. You can access the buffers via an attribute named as the Object keys. 29 | 30 | ### Properties 31 | 32 | - `glea.gl`: the `WebGLRenderingContext` 33 | - `glea.width`: viewport width 34 | - `glea.height`: viewport height 35 | 36 | ### Methods 37 | 38 | - `glea.create()` - creates the WebGLRenderingContext, compiles and links shaders, registers attributes and buffers. 39 | - `glea.resize()` - resizes the WebGL viewport to the current canvas client size. Call this inside a `resize` event listener. 40 | -------------------------------------------------------------------------------- /webcam/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x; 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec2 position; 7 | 8 | uniform float time; 9 | uniform float width; 10 | uniform float height; 11 | 12 | void main() { 13 | gl_Position = vec4(position, 0.0, 1.0); 14 | } 15 | `; 16 | 17 | export const frag = glsl` 18 | precision highp float; 19 | 20 | uniform float width; 21 | uniform float height; 22 | uniform float time; 23 | 24 | uniform sampler2D image; 25 | 26 | // normalize coords and correct for aspect ratio 27 | vec2 normalizeScreenCoords() 28 | { 29 | float aspectRatio = width / height; 30 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 31 | result.x *= aspectRatio; 32 | return result; 33 | } 34 | 35 | float deform(vec2 p) { 36 | return sin(time * .1 + p.x) * cos(time * .1 + p.y) * 0.1; 37 | } 38 | 39 | vec4 invert(vec4 color) { 40 | return vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, 1.0); 41 | } 42 | 43 | void main() { 44 | vec2 p = normalizeScreenCoords(); 45 | vec2 texCoords = 1.0 - gl_FragCoord.xy / vec2(width, height); 46 | vec4 texColor = texture2D(image, texCoords + deform(p)); 47 | vec4 rainbowColor = vec4(.5 + .5 * sin(time * .3 + p.x * 3.0 - p.y * 2.0) * .5, .5 + .5 * sin(.5 + time * .2 + p.y * p.x * 3.0), .5 + sin(.6 + time * .1 + p.x * p.y * 1.5), 1.0); 48 | gl_FragColor = mix(mix(texColor, invert(texColor), .5 + .5 * sin(time + sin(p.x * 4.0) + p.y * 8.0)), rainbowColor, .5 + sin(time + p.x * p.y) * .25); 49 | // vec4(1.0, 0, 0, 1.0); 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /multiple-shaders.js: -------------------------------------------------------------------------------- 1 | concat=function(x,y){return Array.prototype.push.apply(x,y),x} 2 | w=a.width=innerWidth 3 | h=a.height=innerHeight 4 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 5 | g.enable(g.DEPTH_TEST) 6 | 7 | // background 8 | p1=$prog(g,[vs1,fs1]) 9 | bkgBuf=$buf(sq(12)) 10 | 11 | // foreground 12 | p2=$prog(g,[vs2,fs2]) 13 | colors=[] 14 | for(i=36;i--;) 15 | concat(colors,[ 16 | [1,0,0], 17 | [1,1,0], 18 | [0,1,0], 19 | [0,1,1], 20 | [0,0,1], 21 | [1,0,1], 22 | [1,1,1] 23 | ][i%7]) 24 | colorBuf=$buf(colors) 25 | diceBuf=$buf(qb()) 26 | P=perspective(45,w/h,0.1,100) 27 | 28 | ~function scene(time,m) { 29 | time/=1e3 30 | // clear screen 31 | g.clearColor(1/5,1/5,1/5,1) 32 | g.clear(g.COLOR_BUFFER_BIT|g.DEPTH_BUFFER_BIT) 33 | 34 | // program 1 35 | $prog(g,p1) 36 | $attr("pos") 37 | $bind("pos",bkgBuf,3) 38 | $uni("time",time) 39 | $uniV("resolution",[w,h]) 40 | $uniM("M",mX(P, mT(0,0,-15))) 41 | g.drawArrays(g.TRIANGLE_STRIP,0,4) 42 | $attrOff("pos") 43 | 44 | // program 2 45 | $prog(g,p2) 46 | $attr("pos") 47 | $attr("color") 48 | $bind("pos", diceBuf,3) 49 | $bind("color", colorBuf,3) 50 | // set uniforms 51 | $uni("time",time) 52 | $uniV("resolution",[w,h]) 53 | // create transformation/rotation matrix 54 | // mX is matrix multiplication, mT is transformation, mR* is rotatio 55 | $uniM("M",mX(P,mX(mT(sin(time*2)*3,cos(time*3)*1.5,sin(time)*5-10), 56 | mX(mX(mRx(time*3.5),mRy(time*2)),mRz(time*1.6))))) 57 | g.drawArrays(g.TRIANGLES,0,36) 58 | $attrOff("color") 59 | $attrOff("pos") 60 | 61 | requestAnimationFrame(scene) 62 | }(0) 63 | -------------------------------------------------------------------------------- /lib/test-framework/test.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Minimal testing framework 3 | */ 4 | 5 | let indent = 0; 6 | 7 | function indentation() { 8 | return Array(indent + 1).join(' '); 9 | } 10 | 11 | /** 12 | * Describe function 13 | * 14 | * @param {string} description 15 | * @param {function} testBody 16 | */ 17 | export function describe(description, testBody) { 18 | const underline = Array(description.length + 1).join('-'); 19 | console.log(); 20 | console.log(indentation(), description); 21 | console.log(indentation(), underline); 22 | if (typeof testBody === "function") { 23 | indent++; 24 | testBody(); 25 | indent--; 26 | } 27 | } 28 | 29 | /** 30 | * Test function. 31 | * 32 | * @param {string} description Description of the test 33 | * @param {Function} testBody test body function 34 | */ 35 | export function test(description, testBody) { 36 | const assert = (condition, ...args) => { 37 | if (! condition) { 38 | throw new Error(); 39 | } 40 | }; 41 | console.log(indentation(), '[ TEST ]', description); 42 | try { 43 | testBody(assert); 44 | } catch (ex) { 45 | // when the test fails somewhere, 46 | // run the test again with the native console.assert. 47 | // This way, line numbers are shown on assertion errors. 48 | const nativeAssert = console.assert.bind(); 49 | testBody(nativeAssert); 50 | if (typeof process !== 'undefined') { 51 | // In node.js, exit with code -1 52 | process.exit(-1); 53 | } 54 | } 55 | } 56 | 57 | export function shouldThrow(assert, message, testBody) { 58 | try { 59 | testBody(); 60 | assert(false, message); 61 | } catch (ex) { 62 | assert(true, message); 63 | } 64 | } -------------------------------------------------------------------------------- /webcam-triangle-grid-2/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x; 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec2 position; 7 | attribute float direction; 8 | 9 | uniform float time; 10 | uniform float width; 11 | uniform float height; 12 | uniform sampler2D image; 13 | 14 | varying vec4 vTexColor; 15 | 16 | const float PI = 3.1415926535; 17 | 18 | vec4 invert(vec4 color) { 19 | return vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, 1.0); 20 | } 21 | 22 | vec4 shuffleRB(vec4 color) { 23 | return vec4(color.z, color.y, color.x, 1.0); 24 | } 25 | 26 | 27 | float rand(vec2 n) { 28 | return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); 29 | } 30 | 31 | float noise(vec2 n) { 32 | vec2 d = vec2(0.0, 1.0); 33 | vec2 b = floor(n); 34 | vec2 f = smoothstep(vec2(0.0), vec2(1.0), fract(n)); 35 | return mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y); 36 | } 37 | 38 | vec2 radial(float x) { 39 | return vec2(cos(x), sin(x)); 40 | } 41 | 42 | void main() { 43 | vec2 posDistortion = radial(rand(position) * 2.0 * PI) * sin(time * .1) * .05; 44 | vec2 texDistortion = radial(direction * 2.0 * PI) * sin(1.0 + time * .1) * .05; 45 | vec2 texCoords = (1.0 - position) * .5 + texDistortion; 46 | vTexColor = shuffleRB(texture2D(image, texCoords)); 47 | gl_Position = vec4(1.5 * (position + posDistortion), 0.0, 1.0); 48 | } 49 | `; 50 | 51 | export const frag = glsl` 52 | precision highp float; 53 | 54 | uniform float width; 55 | uniform float height; 56 | uniform float time; 57 | 58 | varying vec4 vTexColor; 59 | 60 | void main() { 61 | gl_FragColor = vTexColor; 62 | } 63 | `; 64 | -------------------------------------------------------------------------------- /blacklineweekend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 34 | 49 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /image-slider/components/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x[0].trim(); 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec2 position; 7 | 8 | uniform float time; 9 | uniform float width; 10 | uniform float height; 11 | 12 | void main() { 13 | gl_Position = vec4(position, 0.0, 1.0); 14 | } 15 | `; 16 | 17 | export const frag = glsl` 18 | precision highp float; 19 | 20 | uniform float width; 21 | uniform float height; 22 | uniform float time; 23 | 24 | uniform float animationStep; 25 | 26 | uniform sampler2D texture1; 27 | uniform sampler2D texture2; 28 | 29 | uniform ivec2 textureSize1; 30 | uniform ivec2 textureSize2; 31 | 32 | // normalize coords and correct for aspect ratio 33 | vec2 normalizeScreenCoords() 34 | { 35 | float aspectRatio = width / height; 36 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 37 | result.x *= aspectRatio; 38 | return result; 39 | } 40 | 41 | vec4 invert(vec4 color) { 42 | return vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, 1.0); 43 | } 44 | 45 | float rand() { 46 | return fract(sin(dot(gl_FragCoord.xy + sin(time),vec2(12.9898,78.233))) * 43758.5453); 47 | } 48 | 49 | void main() { 50 | vec2 p = normalizeScreenCoords(); 51 | // float x = .5 + .5 * sin(time * .25); 52 | float x = clamp(animationStep, 0.0, 1.0); 53 | float y = 1.0 - x; 54 | float deform = rand() * .04 + sin(time * 1.2 + p.x * 11.0 - p.y * sin(p.x * 2.0) * 13.0) * .01; 55 | vec2 texCoords = vec2(gl_FragCoord.x / width, 1.0 - (gl_FragCoord.y / height)); 56 | vec4 tex1Color = texture2D(texture1, texCoords + x * deform); 57 | vec4 tex2Color = texture2D(texture2, texCoords + y * deform); 58 | gl_FragColor = mix(tex1Color, tex2Color, x); 59 | } 60 | `; 61 | -------------------------------------------------------------------------------- /fractal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /hello-3d.js: -------------------------------------------------------------------------------- 1 | // 2 | // 0---1 3 | // /| /| 4 | // 4---5 | 5 | // | | | | 6 | // | 3-|-2 7 | // |/ |/ 8 | // 7---6 9 | // 10 | // +-----+-----+-----+-----+-----+-----+ 11 | // |back |right|down |left |up |front| 12 | // +-----+-----+-----+-----+-----+-----+ 13 | faces= "013321126651236673374403014145546674" 14 | vertices="000100110010001101111011" 15 | // |0 |1 |2 |3 |4 |5 |6 |7 | 16 | concat=function(x,y){return Array.prototype.push.apply(x,y),x} 17 | w=a.width=innerWidth 18 | h=a.height=innerHeight 19 | g=a.getContext('webgl')||a.getContext('experimental-webgl') 20 | g.enable(g.DEPTH_TEST) 21 | $prog(g,[vertexShader,fragmentShader]) 22 | pos=$attr("pos") 23 | dice=[] 24 | for(i=faces.length;i--;) 25 | concat(dice,function(j){return[ 26 | vertices[ j ]==1?1:-1, 27 | vertices[j+1]==1?1:-1, 28 | vertices[j+2]==1?1:-1, 29 | ]}(faces[i]*3)) 30 | diceBuf=$buf(dice) 31 | color=$attr("color") 32 | colors=[] 33 | for(i=36;i--;) 34 | concat(colors,[ 35 | [1,0,0], 36 | [1,1,0], 37 | [0,1,0], 38 | [0,1,1], 39 | [0,0,1], 40 | [1,0,1], 41 | [1,1,1] 42 | ][i%7]) 43 | colorBuf=$buf(colors) 44 | P=perspective(45,w/h,0.1,1000) 45 | 46 | $bind("pos",diceBuf,3) 47 | $bind("color",colorBuf,3) 48 | ~function scene(time) { 49 | time/=1e3 50 | // clear screen 51 | g.clearColor(1/5,1/5,1/5,1) 52 | g.clear(g.COLOR_BUFFER_BIT|g.DEPTH_BUFFER_BIT) 53 | // create transformation/rotation matrix 54 | // mX is matrix multiplication, mT is transformation, mR* is rotation 55 | M=mX(P,mX(mT(sin(time*2)*3,cos(time*3)*1.5,sin(time)*5-10), 56 | mX(mX(mRx(time*3.5),mRy(time*2)),mRz(time*1.6)))) 57 | 58 | // set uniforms 59 | $uni("time",time) 60 | $uniV("resolution",[w,h]) 61 | $uniM("M",M) 62 | g.drawArrays(g.TRIANGLES,0,36) 63 | requestAnimationFrame(scene) 64 | }(0) 65 | -------------------------------------------------------------------------------- /relaxing-cubes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 34 | 49 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /image-slider/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Image Slider component 9 | 10 | 11 | 12 |
    13 |
  • a cute kitten
  • 14 |
  • another cute kitten
  • 15 |
  • such a cute kitten
  • 16 |
  • an even more cute kitten
  • 17 |
  • another even more cute kitten
  • 18 |
  • super cute kitten
  • 19 |
  • super dooper cute kitten
  • 20 |
  • awww such a cute kitten
  • 21 |
  • kitten
  • 22 |
23 |
24 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /prime-spiral/index.mjs: -------------------------------------------------------------------------------- 1 | import GLea from '../lib/glea/glea.mjs'; 2 | 3 | import { frag, vert } from './shaders.mjs'; 4 | import { primeSieve } from './prime-sieve.mjs'; 5 | 6 | let texture = null; 7 | 8 | const glea = new GLea({ 9 | shaders: [ 10 | GLea.fragmentShader(frag), 11 | GLea.vertexShader(vert) 12 | ], 13 | buffers: { 14 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 15 | } 16 | }).create(); 17 | 18 | function loop(time) { 19 | const { gl } = glea; 20 | glea.clear(); 21 | glea.uni('width', glea.width); 22 | glea.uni('height', glea.height); 23 | glea.uni('time', time * .005); 24 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 25 | requestAnimationFrame(loop); 26 | } 27 | 28 | function setup() { 29 | const { gl } = glea; 30 | const primes = primeSieve(400); 31 | const imageData = new ImageData(primes.length, 1); 32 | for (let i = 0; i < primes.length; i++) { 33 | imageData.data[i * 4 + 0] = primes[i] ? 0xff : 0x00; 34 | imageData.data[i * 4 + 1] = primes[i] ? 0xff : 0x00; 35 | imageData.data[i * 4 + 2] = primes[i] ? 0xff : 0x00; 36 | imageData.data[i * 4 + 3] = 0xff; 37 | } 38 | texture = gl.createTexture(); 39 | 40 | gl.bindTexture(gl.TEXTURE_2D, texture); 41 | 42 | // Set the parameters so we can render any size image. 43 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 44 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 45 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 46 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 47 | 48 | // Upload the image into the texture. 49 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageData); 50 | 51 | window.addEventListener('resize', () => { 52 | glea.resize(); 53 | }); 54 | loop(0); 55 | } 56 | 57 | setup(); 58 | -------------------------------------------------------------------------------- /image-slider/index.mjs: -------------------------------------------------------------------------------- 1 | import ImageSlider from './components/image-slider.mjs'; 2 | 3 | ImageSlider.register(); 4 | 5 | const nav = document.querySelector('nav'); 6 | const imageSlider = document.querySelector('image-slider'); 7 | 8 | nav.addEventListener('click', (e) => { 9 | e.preventDefault(); 10 | const active = nav.querySelector('.active'); 11 | if (active) { 12 | active.classList.remove('active'); 13 | } 14 | if (e.target.nodeName === 'A') { 15 | const a = e.target; 16 | const newIndex = a.getAttribute('href').slice(-1); 17 | a.classList.add('active'); 18 | imageSlider.setAttribute('index', newIndex); 19 | } 20 | }); 21 | 22 | function prevImage() { 23 | const active = nav.querySelector('.active'); 24 | if (active) { 25 | active.classList.remove('active'); 26 | } 27 | const currentIndex = parseInt(imageSlider.getAttribute('index'), 10) || 1; 28 | const numImages = [...imageSlider.querySelectorAll('img')].length; 29 | const newIndex = (currentIndex > 1) ? (currentIndex-1) : numImages; 30 | document.querySelector(`[href="#image${newIndex}"]`).classList.add('active'); 31 | imageSlider.setAttribute('index', newIndex); 32 | } 33 | 34 | function nextImage() { 35 | const active = nav.querySelector('.active'); 36 | if (active) { 37 | active.classList.remove('active'); 38 | } 39 | const currentIndex = parseInt(imageSlider.getAttribute('index'), 10) || 1; 40 | const numImages = [...imageSlider.querySelectorAll('img')].length; 41 | const newIndex = (currentIndex < numImages) ? (currentIndex + 1) : 1; 42 | document.querySelector(`[href="#image${newIndex}"]`).classList.add('active'); 43 | imageSlider.setAttribute('index', newIndex); 44 | } 45 | 46 | window.addEventListener('keyup', (e) => { 47 | if (imageSlider.fading === true) { 48 | return; 49 | } 50 | if (e.keyCode === 37) { 51 | prevImage(); 52 | } 53 | if (e.keyCode === 39) { 54 | nextImage(); 55 | } 56 | }); -------------------------------------------------------------------------------- /multiple-shaders.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 38 | 58 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /sobel-edge-detection/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x; 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec2 position; 7 | 8 | uniform float time; 9 | uniform float width; 10 | uniform float height; 11 | 12 | void main() { 13 | gl_Position = vec4(position, 0.0, 1.0); 14 | } 15 | `; 16 | 17 | export const frag = glsl` 18 | precision highp float; 19 | 20 | uniform float width; 21 | uniform float height; 22 | uniform float time; 23 | 24 | uniform sampler2D image; 25 | 26 | // normalize coords and correct for aspect ratio 27 | vec2 normalizeScreenCoords() 28 | { 29 | float aspectRatio = width / height; 30 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 31 | result.x *= aspectRatio; 32 | return result; 33 | } 34 | 35 | float deform(vec2 p, float factor) { 36 | return sin(time * .1 + factor * p.x) * cos(time * .1 + factor * p.y); 37 | } 38 | 39 | vec4 invert(vec4 color) { 40 | return vec4(1.0 - color.rgb, 1.0); 41 | } 42 | 43 | vec4 grey(vec4 color) { 44 | float val = (color.x + color.y + color.z) / 3.0; 45 | 46 | return vec4(vec3(pow(val, .125)), 1.0); 47 | } 48 | 49 | vec2 getTexCoords(vec2 position) { 50 | return 1.0 - position.xy / vec2(width, height); 51 | } 52 | 53 | vec4 sobel(in sampler2D tex, in vec2 coord) { 54 | float w = 1.0 / width; 55 | float h = 1.0 / height; 56 | vec4 n0 = texture2D(tex, coord + vec2(-w, -h)); 57 | vec4 n1 = texture2D(tex, coord + vec2( 0, -h)); 58 | vec4 n2 = texture2D(tex, coord + vec2( w, -h)); 59 | vec4 n3 = texture2D(tex, coord + vec2(-w, 0)); 60 | vec4 n4 = texture2D(tex, coord); 61 | vec4 n5 = texture2D(tex, coord + vec2( w, 0)); 62 | vec4 n6 = texture2D(tex, coord + vec2(-w, h)); 63 | vec4 n7 = texture2D(tex, coord + vec2( 0, h)); 64 | vec4 n8 = texture2D(tex, coord + vec2( w, h)); 65 | vec4 edgeH = n2 + (2.0 * n5) + n8 - (n0 + (2.0 * n3) + n6); 66 | vec4 edgeV = n0 + (2.0 * n1) + n2 - (n6 + (2.0 * n7) + n8); 67 | vec4 sobel = sqrt((edgeH * edgeH) + (edgeV * edgeV)); 68 | return sobel; 69 | } 70 | 71 | void main() { 72 | vec2 p = normalizeScreenCoords(); 73 | vec2 coord = 1.0 - gl_FragCoord.xy / vec2(width, height); 74 | vec4 result = sobel(image, coord); 75 | gl_FragColor = invert(result); 76 | } 77 | `; 78 | -------------------------------------------------------------------------------- /image-slider/index.css: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | background: #000; 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 9 | } 10 | 11 | /** 12 | * the cool thing about Web components is that 13 | * we can provide a fully functional no-js fallback of the component 14 | */ 15 | 16 | image-slider { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | display: flex; 21 | width: 100vw; 22 | height: 100vh; 23 | justify-content: center; 24 | align-items: center; 25 | } 26 | 27 | image-slider ul { 28 | list-style: none; 29 | padding: 0; 30 | margin: 0; 31 | } 32 | 33 | image-slider ul li { 34 | margin: 0; 35 | padding: 0; 36 | } 37 | 38 | image-slider ul li img { 39 | display: block; 40 | width: 100vmin; 41 | height: 100vmin; 42 | } 43 | 44 | 45 | image-slider:not([index]) ul li:not(:first-child), 46 | image-slider[index="1"] ul li:not(:nth-child(1)), 47 | image-slider[index="2"] ul li:not(:nth-child(2)), 48 | image-slider[index="3"] ul li:not(:nth-child(3)), 49 | image-slider[index="4"] ul li:not(:nth-child(4)), 50 | image-slider[index="5"] ul li:not(:nth-child(5)), 51 | image-slider[index="6"] ul li:not(:nth-child(6)), 52 | image-slider[index="7"] ul li:not(:nth-child(7)), 53 | image-slider[index="8"] ul li:not(:nth-child(8)), 54 | image-slider[index="9"] ul li:not(:nth-child(9)) { 55 | display: none; 56 | } 57 | 58 | 59 | nav { 60 | position: absolute; 61 | bottom: 16px; 62 | left: 0; 63 | right: 0; 64 | display: block; 65 | } 66 | 67 | nav ul { 68 | list-style: none; 69 | padding: 0; 70 | margin: 0; 71 | display: flex; 72 | flex-wrap: wrap-reverse; 73 | justify-content: center; 74 | } 75 | 76 | nav ul li { 77 | padding: 0; 78 | margin: 16px; 79 | } 80 | 81 | nav ul li a { 82 | font-variant-numeric: tabular-nums; 83 | display: block; 84 | padding: 8px; 85 | width: 40px; 86 | font-size: 16px; 87 | border: 2px solid #fff; 88 | background: #127; 89 | text-align: center; 90 | border-radius: 50%; 91 | color: #fff; 92 | text-decoration: none; 93 | font-weight: bold; 94 | } 95 | 96 | nav ul li a.active { 97 | background: #fff; 98 | color: #127; 99 | border: 2px solid #fff; 100 | 101 | } -------------------------------------------------------------------------------- /herzchen/shaders.mjs: -------------------------------------------------------------------------------- 1 | // this is just for code highlighting in VSCode 2 | // via the glsl-literal extension 3 | const glsl = x => x; 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | uniform float time; 8 | uniform float width; 9 | uniform float height; 10 | uniform sampler2D texture; 11 | const float PI = 3.141592654; 12 | const float DEG = PI / 180.0; 13 | const int ITERS = 10000; 14 | 15 | 16 | vec2 coords() { 17 | float vmin = min(width, height); 18 | return vec2((gl_FragCoord.x - width * .5) / vmin, 19 | (gl_FragCoord.y - height * .5) / vmin); 20 | } 21 | 22 | vec2 rotate(vec2 p, float a) { 23 | return vec2(p.x * cos(a) - p.y * sin(a), 24 | p.x * sin(a) + p.y * cos(a)); 25 | } 26 | 27 | vec2 repeat(in vec2 p, in vec2 c) { 28 | return mod(p, c) - 0.5 * c; 29 | } 30 | 31 | // Distance functions by Inigo Quilez 32 | // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm 33 | float circle(in vec2 p, in vec2 pos, float radius) { 34 | return length((p - pos)) - radius; 35 | } 36 | 37 | // function from https://www.shadertoy.com/view/3ll3zr 38 | float sdHeart(in vec2 p, float s) { 39 | p /= s; 40 | vec2 q = p; 41 | q.x *= 0.5 + .5 * q.y; 42 | q.y -= abs(p.x) * .63; 43 | return (length(q) - .7) * s; 44 | } 45 | 46 | 47 | float distanceField(vec2 p) { 48 | float d = 10000.0; 49 | for (int i = 0; i < 30; i++) { 50 | vec2 motion = vec2(cos(float(i) + time * .2), sin(float(i) + time * .3)) * (3.0); 51 | vec2 point = vec2(cos(float(i) * 3.0) * 10.0, sin(float(i) * 7.0) * 10.0) + motion; 52 | d = min(d, sdHeart(p - point, 1.0 + sin(float(i) + time) *.2)); 53 | } 54 | return d; 55 | } 56 | 57 | vec3 shade(in vec2 p) 58 | { 59 | vec3 background = vec3(.5, .2, .7); 60 | vec3 foreground = vec3(.8, .7, .9); 61 | float sdf = distanceField(p); 62 | if (sdf < 0.0) { 63 | return foreground; 64 | } 65 | 66 | vec3 col = background; 67 | 68 | // Darken around surface 69 | col = mix(col, col*1.0-exp(-10.0 * abs(sdf)), 0.4); 70 | 71 | // repeating lines 72 | col *= 0.8 + 0.1*cos(.5*sdf - time); 73 | return col; 74 | } 75 | 76 | 77 | void main () { 78 | vec2 p0 = coords(); 79 | float zoom = 25.0 + sin(time * .05)*5.0; 80 | vec2 p1 = p0 * zoom; 81 | vec3 col = shade(p1); 82 | gl_FragColor = vec4(col, 1.0); 83 | } 84 | ` 85 | 86 | export const vert = glsl` 87 | precision mediump float; 88 | attribute vec2 position; 89 | 90 | void main () { 91 | gl_Position = vec4(position, 0, 1.0); 92 | } 93 | ` -------------------------------------------------------------------------------- /texture-mapping/index.mjs: -------------------------------------------------------------------------------- 1 | import { cube } from "../lib/glea/geometry.mjs"; 2 | import { loadImage } from "./image-loader.mjs"; 3 | import { frag, vert } from "./shaders.mjs"; 4 | import GLea from "../lib/glea/glea.mjs"; 5 | 6 | const Color = { 7 | red: [1, 0, 0], 8 | green: [0, 1, 0], 9 | blue: [0, 0, 1], 10 | yellow: [1, 1, 0], 11 | pink: [1, 0, 1], 12 | cyan: [0, 1, 1], 13 | white: [1, 1, 1], 14 | orange: [1, 0.5, 0], 15 | skyblue: [0.25, 0.5, 1], 16 | }; 17 | 18 | const { red, green, blue, yellow, pink, cyan, skyblue, orange } = Color; 19 | 20 | const glea = new GLea({ 21 | shaders: [GLea.vertexShader(vert), GLea.fragmentShader(frag)], 22 | buffers: { 23 | position: GLea.buffer(3, cube(0.5)), 24 | texCoord: GLea.buffer( 25 | 2, 26 | Array(6) 27 | .fill([0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0]) 28 | .flat() 29 | ), 30 | color: GLea.buffer( 31 | 3, 32 | [ 33 | ...Array(6).fill(red), 34 | ...Array(6).fill(green), 35 | ...Array(6).fill(blue), 36 | ...Array(6).fill(pink), 37 | ...Array(6).fill(skyblue), 38 | ...Array(6).fill(orange), 39 | ].flat() 40 | ), 41 | }, 42 | }).create(); 43 | 44 | function setup() { 45 | const { gl } = glea; 46 | gl.clearColor(1 / 6, 1 / 6, 1 / 6, 1); 47 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 48 | gl.enable(gl.DEPTH_TEST); 49 | gl.enable(gl.CULL_FACE); 50 | window.addEventListener("resize", () => { 51 | glea.resize(); 52 | }); 53 | } 54 | 55 | function loop(time) { 56 | const { gl } = glea; 57 | glea.clear(); 58 | glea.uni("width", glea.width); 59 | glea.uni("height", glea.height); 60 | glea.uni("time", time * 0.01); 61 | gl.drawArrays(gl.TRIANGLES, 0, 36); 62 | requestAnimationFrame(loop); 63 | } 64 | 65 | setup(); 66 | loadImage("js.png").then((image) => { 67 | const { gl } = glea; 68 | const texture = gl.createTexture(); 69 | gl.bindTexture(gl.TEXTURE_2D, texture); 70 | 71 | // Set the parameters so we can render any size image. 72 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 73 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 74 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 75 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 76 | 77 | // Upload the image into the texture. 78 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 79 | 80 | loop(0); 81 | }); 82 | -------------------------------------------------------------------------------- /lib/glea/glsl-utils.mjs: -------------------------------------------------------------------------------- 1 | const importStatement = /import\('(\w+)'\);/ 2 | export const glsl = x => x[0].trim() + '\n'; 3 | const normalizeScreenCoords = glsl` 4 | // normalize coords and correct for aspect ratio 5 | vec2 normalizeScreenCoords() 6 | { 7 | float aspectRatio = width / height; 8 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 9 | result.x *= aspectRatio; 10 | return result; 11 | } 12 | `; 13 | 14 | const coords = glsl` 15 | vec2 coords() { 16 | float vmin = min(width, height); 17 | return vec2((gl_FragCoord.x - width * .5) / vmin, 18 | (gl_FragCoord.y - height * .5) / vmin); 19 | } 20 | `; 21 | 22 | const invert = glsl` 23 | // invert a color 24 | vec4 invert(vec4 color) { 25 | return vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, 1.0); 26 | } 27 | `; 28 | 29 | export const repeat = glsl` 30 | vec2 repeat(in vec2 p, in vec2 c) { 31 | return mod(p, c) - 0.5 * c; 32 | } 33 | `; 34 | 35 | export const rotate = glsl` 36 | vec2 rotate(vec2 p, float a) { 37 | return vec2(p.x * cos(a) - p.y * sin(a), 38 | p.x * sin(a) + p.y * cos(a)); 39 | } 40 | `; 41 | 42 | export const hsb2rgb = glsl` 43 | // Function from Iñigo Quiles 44 | // https://www.shadertoy.com/view/MsS3Wc 45 | vec3 hsb2rgb( in vec3 c ){ 46 | vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 47 | 6.0)-3.0)-1.0, 48 | 0.0, 49 | 1.0 ); 50 | rgb = rgb*rgb*(3.0-2.0*rgb); 51 | return c.z * mix( vec3(1.0), rgb, c.y); 52 | } 53 | `; 54 | 55 | export const noise = glsl` 56 | vec2 noise(vec2 p) { 57 | return fract(1234.1234 * sin(1234.1234 * (fract(1234.1234 * p) + p.yx))); 58 | } 59 | `; 60 | 61 | export const heart = glsl` 62 | // distance function from https://www.shadertoy.com/view/3ll3zr 63 | float heart(vec2 p, float s) { 64 | p /= s; 65 | vec2 q = p; 66 | q.x *= 0.5 + .5 * q.y; 67 | q.y -= abs(p.x) * .63; 68 | return (length(q) - .7) * s; 69 | } 70 | `; 71 | 72 | export const hexagram = glsl` 73 | // distance function from Iñigo Quiles https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm 74 | float hexagram(in vec2 p, in float r) { 75 | const vec4 k = vec4(-0.5,0.8660254038,0.5773502692,1.7320508076); 76 | p = abs(p); 77 | p -= 2.0*min(dot(k.xy,p),0.0)*k.xy; 78 | p -= 2.0*min(dot(k.yx,p),0.0)*k.yx; 79 | p -= vec2(clamp(p.x,r*k.z,r*k.w),r); 80 | return length(p)*sign(p.y); 81 | } 82 | ` 83 | 84 | 85 | 86 | export const GLSLUtils = { 87 | coords, 88 | normalizeScreenCoords, 89 | 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /fractal/shaders.mjs: -------------------------------------------------------------------------------- 1 | const glsl = x => x; 2 | 3 | export const frag = glsl` 4 | precision highp float; 5 | uniform vec3 color; 6 | 7 | uniform float time; 8 | uniform float width; 9 | uniform float height; 10 | 11 | #define PI 3.141592654 12 | #define ITERS 100 13 | 14 | // taken from wikipedia and adapted to GLSL 15 | // https://en.wikipedia.org/wiki/Julia_set 16 | float julia(vec2 p, vec2 c, float n) { 17 | float x = p.x; 18 | float y = p.y; 19 | for (int i = 0; i < ITERS; i++) { 20 | if (x * x + y * y >= 4.0) { 21 | return float(i); 22 | } 23 | float x1 = x * x - y * y; 24 | float y1 = 2.0 * x * y; 25 | x = x1 + c.x; 26 | y = y1 + c.y; 27 | } 28 | return -1.0; 29 | } 30 | 31 | // by @mattdesl 32 | float hue2rgb(float f1, float f2, float hue) { 33 | if (hue < 0.0) 34 | hue += 1.0; 35 | else if (hue > 1.0) 36 | hue -= 1.0; 37 | float res; 38 | if ((6.0 * hue) < 1.0) 39 | res = f1 + (f2 - f1) * 6.0 * hue; 40 | else if ((2.0 * hue) < 1.0) 41 | res = f2; 42 | else if ((3.0 * hue) < 2.0) 43 | res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; 44 | else 45 | res = f1; 46 | return res; 47 | } 48 | 49 | vec3 hsl2rgb(vec3 hsl) { 50 | vec3 rgb; 51 | if (hsl.y == 0.0) { 52 | rgb = vec3(hsl.z); // Luminance 53 | } else { 54 | float f2; 55 | if (hsl.z < 0.5) 56 | f2 = hsl.z * (1.0 + hsl.y); 57 | else 58 | f2 = hsl.z + hsl.y - hsl.y * hsl.z; 59 | float f1 = 2.0 * hsl.z - f2; 60 | rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); 61 | rgb.g = hue2rgb(f1, f2, hsl.x); 62 | rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); 63 | } 64 | return rgb; 65 | } 66 | 67 | void main () { 68 | float vmin = min(width, height); 69 | vec3 black = vec3(0.0); 70 | vec3 white = vec3(1.0); 71 | vec3 c = vec3(1.0, 0.0, 1.0); 72 | vec2 p = vec2((gl_FragCoord.x - width * .5) / vmin, 73 | (gl_FragCoord.y - height * .5) / vmin); 74 | float t = sin(time / 100.0) * PI; 75 | float j = julia(p, 0.7885 * vec2(cos(t), sin(t)), 2.0); 76 | vec3 color = j < 0.0 ? black : 77 | hsl2rgb(vec3(mod(time / 1e2 + j * 2.0 / float(ITERS), 1.0), 1.0, .7 - 2.0 * j / float(ITERS))); 78 | gl_FragColor = vec4(vec3(color), 1.0); 79 | } 80 | `; 81 | 82 | export const vert = glsl` 83 | precision mediump float; 84 | attribute vec3 aPosition; 85 | 86 | uniform mat4 uProjectionMatrix; 87 | uniform mat4 uModelMatrix; 88 | uniform mat4 uViewMatrix; 89 | 90 | void main () { 91 | gl_Position = vec4(aPosition, 1.0); 92 | } 93 | ` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello WebGL World! 2 | 3 | [![Build Status](https://travis-ci.org/terabaud/hello-webgl.svg?branch=master)](https://travis-ci.org/terabaud/hello-webgl) 4 | 5 | This [repository](https://github.com/terabaud/hello-webgl/) features my experiments with WebGL. 6 | Most demos are using [GLea](lib/glea/), a minimalistic WebGL library written by myself. 7 | 8 | * [Live Demos](https://terabaud.github.io/hello-webgl/) 9 | * [Slides of my talk about WebGL](https://terabaud.github.io/hello-webgl/talk-webgl/) 10 | 11 | # Demos 12 | 13 | * [Triangle](triangle/) 14 | * [Patterns using modulo](patterns/) 15 | * [Morphing Stars](morphing-stars/) 16 | * [Fractal](fractal/), using [Phenomenon](https://github.com/vaneenige/phenomenon/) 17 | * [Cube](cube/) 18 | * [Texture mapping](texture-mapping/) 19 | * [Access the webcam via webgl and apply distortions](webcam/) 20 | * [Webcam Triangle Grid](webcam-triangle-grid/) 21 | * [Webcam Triangle Grid variation with additional vertex motion](webcam-triangle-grid-2/) 22 | * [Webcam Sobel edge detection](sobel-edge-detection/) ([check out the wikipedia article](https://en.wikipedia.org/wiki/Sobel_operator)) 23 | * [Webcam ordered dithering](dither-cam/) ([wikipedia](https://en.wikipedia.org/wiki/Ordered_dithering)) 24 | * [Raymarching](raymarching/) 25 | * [Infinite rocks (raymarching with shadows)](raymarching-shadows/) 26 | * [Shape morphing 3D geometries via glsl mix](shape-morph-3d/) 27 | * [Rainbow fractal with complex numbers and the series of greatest prime factors](exponential/) 28 | * [Image Slider component](image-slider/) 29 | 30 | # Running it locally 31 | 32 | Type `npm install` and `npm start` to start a local development server. 33 | 34 | Currently, there is no transpiling/bundling toolchain like Webpack and Babel/TypeScript. 35 | 36 | It's just node [express](https://expressjs.com) serving static files. As other out-of-the-box web servers require some additional configuration, I made a simple server.js on my own. 37 | 38 | # Older Demos 39 | 40 | The older demos use an esoteric coding style library I wrote in the 2015s. It provides helper functions for compiling shaders and matrix/vector maths. The code itself of the library is hard to read, but the examples may be useful. Most of the examples just draw a square to the fullscreen and let the fragment shader do its work. 41 | 42 | * [Glitchy Mandelbrot](fractal.html) 43 | * [Black Line Weekend](blacklineweekend.html) 44 | * [Pastel Flower Weekend](pastelflowerweekend.html) 45 | * [Moire](moire.html) 46 | * [Moire 2](moire2.html) 47 | * [3D Cube](hello-3d.html) 48 | * [Shapes](shapes.html) 49 | * [Multiple Shaders](multiple-shaders.html) 50 | * [Relaxing Cubes](relaxing-cubes.html) 51 | -------------------------------------------------------------------------------- /image-slider/components/easings.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Easing Functions from https://gist.github.com/gre/1650294 3 | * 4 | * inspired from http://gizma.com/easing/ 5 | * only considering the t value for the range [0, 1] => [0, 1] 6 | */ 7 | 8 | /** 9 | * no easing, no acceleration 10 | * 11 | * @param {number} t time 12 | */ 13 | export function linear(t) { 14 | return t; 15 | } 16 | 17 | /** 18 | * accelerating from zero velocity 19 | * 20 | * @param {number} t time 21 | */ 22 | export function easeInQuad(t) { 23 | return t * t; 24 | } 25 | 26 | /** 27 | * decelerating to zero velocity 28 | * 29 | * @param {number} t time 30 | */ 31 | export function easeOutQuad(t) { 32 | return t * (2 - t); 33 | } 34 | 35 | /** 36 | * acceleration until halfway, then deceleration 37 | * 38 | * @param {number} t time 39 | */ 40 | export function easeInOutQuad(t) { 41 | return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t; 42 | } 43 | 44 | /** 45 | * accelerating from zero velocity 46 | * 47 | * @param {number} t time 48 | */ 49 | export function easeInCubic(t) { 50 | return t * t * t; 51 | } 52 | 53 | /** 54 | * decelerating to zero velocity 55 | * 56 | * @param {number} t time 57 | */ 58 | 59 | export function easeOutCubic(t) { 60 | return (--t) * t * t + 1; 61 | } 62 | 63 | /** 64 | * acceleration until halfway, then deceleration 65 | * 66 | * @param {number} t time 67 | */ 68 | export function easeInOutCubic(t) { 69 | return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; 70 | } 71 | 72 | /** 73 | * accelerating from zero velocity 74 | * 75 | * @param {number} t time 76 | */ 77 | export function easeInQuart(t) { 78 | return t * t * t * t; 79 | } 80 | 81 | /** 82 | * decelerating to zero velocity 83 | * 84 | * @param {number} t time 85 | */ 86 | export function easeOutQuart(t) { 87 | return 1 - (--t) * t * t * t; 88 | } 89 | 90 | /** 91 | * acceleration until halfway, then deceleration 92 | * 93 | * @param {number} t time 94 | */ 95 | export function easeInOutQuart(t) { 96 | return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t; 97 | } 98 | 99 | /** 100 | * accelerating from zero velocity 101 | * @param {number} t time 102 | */ 103 | export function easeInQuint(t) { 104 | return t * t * t * t * t; 105 | } 106 | 107 | /** 108 | * decelerating to zero velocity 109 | * 110 | * @param {number} t time 111 | */ 112 | export function easeOutQuint(t) { 113 | return 1 + (--t) * t * t * t * t; 114 | } 115 | 116 | /** 117 | * acceleration until halfway, then deceleration 118 | * 119 | * @param {number} t time 120 | */ 121 | export function easeInOutQuint(t) { 122 | return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t; 123 | } -------------------------------------------------------------------------------- /cube/shaders.mjs: -------------------------------------------------------------------------------- 1 | const glsl = x => x; 2 | 3 | export const vert = glsl` 4 | precision highp float; 5 | attribute vec3 position; 6 | attribute vec3 color; 7 | 8 | varying vec4 vpos; 9 | varying vec4 vcolor; 10 | varying mat4 vM; 11 | uniform float time; 12 | uniform float width; 13 | uniform float height; 14 | 15 | const float PI = 3.1415926535; 16 | 17 | mat4 translate(vec3 p) { 18 | return mat4( 19 | vec4(1.0, 0.0, 0.0, 0.0), 20 | vec4(0.0, 1.0, 0.0, 0.0), 21 | vec4(0.0, 0.0, 1.0, 0.0), 22 | vec4(p.x, p.y, p.z, 1.0) 23 | ); 24 | } 25 | 26 | 27 | mat4 rotX(float angle) { 28 | float S = sin(angle); 29 | float C = cos(angle); 30 | return mat4( 31 | vec4(1.0, 0, 0, 0), 32 | vec4(0 , C, S, 0), 33 | vec4(0 ,-S, C, 0), 34 | vec4(0 , 0, 0, 1.0) 35 | ); 36 | } 37 | 38 | mat4 rotY(float angle) { 39 | float S = sin(angle); 40 | float C = cos(angle); 41 | return mat4( 42 | vec4(C, 0 ,-S, 0), 43 | vec4(0, 1.0, 0, 0), 44 | vec4(S, 0 , C, 0), 45 | vec4(0, 0 , 0, 1.0) 46 | ); 47 | } 48 | 49 | mat4 rotZ(float angle) { 50 | float S = sin(angle); 51 | float C = cos(angle); 52 | return mat4( 53 | vec4( C, S, 0 , 0), 54 | vec4(-S, C, 0 , 0), 55 | vec4( 0, 0, 1.0, 0), 56 | vec4( 0, 0, 0 , 1.0) 57 | ); 58 | } 59 | 60 | // glFrustum(left, right, bottom, top, zNear, zFar) 61 | mat4 frustum(float left, float right, float bottom, float top, float zNear, float zFar) { 62 | float t1 = 2.0 * zNear; 63 | float t2 = right - left; 64 | float t3 = top - bottom; 65 | float t4 = zFar - zNear; 66 | return mat4( 67 | vec4(t1 / t2, 0, 0, 0), 68 | vec4(0, t1 / t3, 0, 0), 69 | vec4((right + left) / t2, (top + bottom) / t3, (-zFar - zNear) / t4, -1.0), 70 | vec4(0, 0, (-t1*zFar) / t4, 0)); 71 | } 72 | 73 | // gluPerspective(fieldOfView, aspectRatio, zNear, zFar) 74 | mat4 perspective(float fieldOfView, float aspectRatio, float zNear, float zFar) { 75 | float y = zNear * tan(fieldOfView * PI / 360.0); 76 | float x = y * aspectRatio; 77 | return frustum(-x, x, -y, y, zNear, zFar); 78 | } 79 | 80 | void main() { 81 | vpos = vec4(position, 1.0); 82 | mat4 perspectiveMat = perspective(45.0, width / height, 0.1, 1000.0); 83 | mat4 translateMat = translate(vec3(sin(2.0 * time * 1e-2) * 0.05, sin(3.0 * time * 1e-2) * .1, -1.6 + sin(time * .1))); 84 | mat4 M = perspectiveMat * translateMat * rotX(time * 0.1) * rotY(time * 0.1) * rotZ(time * 0.25); 85 | vM = M; 86 | gl_Position = M * vec4(position, 1.0); 87 | vcolor = vec4(color, 1.0); 88 | } 89 | `; 90 | 91 | export const frag = glsl` 92 | precision highp float; 93 | varying vec4 vcolor; 94 | varying vec4 vpos; 95 | varying mat4 vM; 96 | uniform float width; 97 | uniform float height; 98 | uniform float time; 99 | 100 | void main() { 101 | vec4 v = vM * vpos; 102 | gl_FragColor = (vcolor + normalize(v) - (1.0 - cos(v.x + time * 0.1) * sin(v.y * v.z * 2.5 + time * .01) * .5)*.25); 103 | } 104 | `; 105 | -------------------------------------------------------------------------------- /prime-spiral/shaders.mjs: -------------------------------------------------------------------------------- 1 | // this is just for code highlighting in VSCode 2 | // via the glsl-literal extension 3 | const glsl = x => x; 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | uniform float time; 8 | uniform float width; 9 | uniform float height; 10 | uniform sampler2D texture; 11 | const float PI = 3.141592654; 12 | const float DEG = PI / 180.0; 13 | const int ITERS = 10000; 14 | 15 | 16 | vec2 coords() { 17 | float vmin = min(width, height); 18 | return vec2((gl_FragCoord.x - width * .5) / vmin, 19 | (gl_FragCoord.y - height * .5) / vmin); 20 | } 21 | 22 | vec2 rotate(vec2 p, float a) { 23 | return vec2(p.x * cos(a) - p.y * sin(a), 24 | p.x * sin(a) + p.y * cos(a)); 25 | } 26 | 27 | vec2 repeat(in vec2 p, in vec2 c) { 28 | return mod(p, c) - 0.5 * c; 29 | } 30 | 31 | // Distance functions by Inigo Quilez 32 | // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm 33 | float circle(in vec2 p, in vec2 pos, float radius) { 34 | return length((p - pos)) - radius; 35 | } 36 | 37 | float distanceField(vec2 p) { 38 | float d = 10000.0; 39 | for (int i = 0; i < 400; i++) { 40 | vec2 point = vec2(cos(float(i)) * float(i), sin(float(i)) * float(i)); 41 | d = min(d, circle(p, point, 3.0)); 42 | } 43 | return d; 44 | } 45 | 46 | // check if is prime, works for numbers 1 .. 900 47 | bool isPrime(int n) { 48 | if (n <= 3) { 49 | return (n > 1) ? true : false; 50 | } 51 | if (mod(float(n), 2.0) == 0.0 || mod(float(n), 3.0) == 0.0) { 52 | return false; 53 | } 54 | int i = 5; 55 | for (int j = 0; j < 30; j++) { 56 | if (i * i > n) { 57 | return true; 58 | } 59 | if (mod(float(n), float(i)) == 0.0 || mod(float(n), float(i + 2)) == 0.0) { 60 | return false; 61 | } 62 | i += 6; 63 | } 64 | return false; 65 | } 66 | 67 | float primeDistanceField(vec2 p) { 68 | float d = 10000.0; 69 | for (int i = 0; i < 250; i++) { 70 | if (isPrime(i)) { 71 | vec2 point = vec2(cos(float(i)) * float(i), sin(float(i)) * float(i)); 72 | d = min(d, circle(p, point, 3.0)); 73 | } 74 | } 75 | return d; 76 | } 77 | 78 | vec3 shade(in vec2 p) 79 | { 80 | vec3 background = vec3(.1, .3, .7); 81 | vec3 foreground = vec3(.7, .3, .1); 82 | float sdf = distanceField(p); 83 | if (sdf < 0.0) { 84 | return foreground; 85 | } 86 | 87 | vec3 col = background; 88 | 89 | // Darken around surface 90 | col = mix(col, col*1.0-exp(-10.0 * abs(sdf)), 0.4); 91 | 92 | // repeating lines 93 | col *= 0.8 + 0.2*cos(.5*sdf - time * .5); 94 | return col; 95 | } 96 | 97 | 98 | void main () { 99 | vec2 p0 = coords(); 100 | float zoom = 300.0 + sin(time * .05)*200.0; 101 | vec2 p1 = rotate(p0 * zoom, time * DEG); 102 | vec3 col = shade(p1); 103 | gl_FragColor = vec4(col, 1.0); 104 | } 105 | ` 106 | 107 | export const vert = glsl` 108 | precision mediump float; 109 | attribute vec2 position; 110 | 111 | void main () { 112 | gl_Position = vec4(position, 0, 1.0); 113 | } 114 | ` -------------------------------------------------------------------------------- /dither-cam/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x; 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec2 position; 7 | 8 | uniform float time; 9 | uniform float width; 10 | uniform float height; 11 | 12 | void main() { 13 | gl_Position = vec4(position, 0.0, 1.0); 14 | } 15 | `; 16 | 17 | export const frag = glsl` 18 | precision highp float; 19 | 20 | uniform float width; 21 | uniform float height; 22 | uniform float time; 23 | 24 | uniform sampler2D texture0; 25 | uniform sampler2D texture1; 26 | 27 | // normalize coords and correct for aspect ratio 28 | vec2 normalizeScreenCoords() 29 | { 30 | float aspectRatio = width / height; 31 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 32 | result.x *= aspectRatio; 33 | return result; 34 | } 35 | 36 | float deform(vec2 p, float factor) { 37 | return sin(time * .1 + factor * p.x) * cos(time * .1 + factor * p.y); 38 | } 39 | 40 | vec4 invert(vec4 color) { 41 | return vec4(1.0 - color.rgb, 1.0); 42 | } 43 | 44 | vec4 grey(vec4 color) { 45 | float val = (color.x + color.y + color.z) / 3.0; 46 | 47 | return vec4(vec3(pow(val, .125)), 1.0); 48 | } 49 | 50 | vec2 getTexCoords(vec2 position) { 51 | return 1.0 - position.xy / vec2(width, height); 52 | } 53 | 54 | // dumb closest color strategy 55 | vec4 closestColor(vec4 color) { 56 | return vec4( 57 | clamp(floor(color.x * 4. + .5) * 85., 0., 255.) / 255., 58 | clamp(floor(color.y * 4. + .5) * 85., 0., 255.) / 255., 59 | clamp(floor(color.z * 4. + .5) * 85., 0., 255.) / 255., 60 | 1. 61 | ); 62 | } 63 | 64 | // luminance strategy: (TODO) 65 | float luminance(vec4 color) { 66 | return 0.2126*color.x + 0.7152*color.y + 0.0722*color.z; 67 | } 68 | 69 | vec4 getEgaColor(int index) { 70 | float fi = float(index); 71 | float b = clamp((2. * mod(fi, 2.) + mod(floor(fi / 8.), 2.)) * (1./3.), 0., 1.); 72 | float g = clamp((2. * mod(floor(fi / 2.), 2.) + mod(floor(fi / 16.), 2.)) * (1./3.), 0., 1.); 73 | float r = clamp((2. * mod(floor(fi / 4.), 2.) + mod(floor(fi / 32.), 2.)) * .333, 0., 1.); 74 | return vec4(r, g, b, 1.); 75 | } 76 | 77 | vec4 closestColorLum(vec4 color) { 78 | float l = luminance(color); 79 | float lResult = 9999.0; 80 | vec4 result = vec4(0.); 81 | for (int i = 0; i < 64; i++) { 82 | vec4 colorI = getEgaColor(i); 83 | float lI = luminance(colorI); 84 | if (abs(lI - l) < abs(lResult - l)) { 85 | lResult = lI; 86 | result = colorI; 87 | } 88 | } 89 | return result; 90 | } 91 | 92 | 93 | // hue distance strategy (TODO) 94 | 95 | 96 | 97 | void main() { 98 | vec2 p = normalizeScreenCoords(); 99 | vec2 coord = 1.0 - gl_FragCoord.xy / vec2(width, height); 100 | bool isEven = mod(gl_FragCoord.x + gl_FragCoord.y, 2.) < 1.; 101 | gl_FragColor = closestColor(step( 102 | texture2D(texture0, gl_FragCoord.xy / 8.).r, 103 | isEven ? closestColor(texture2D(texture1, coord)) : 104 | closestColorLum(texture2D(texture1, coord)) 105 | )); 106 | } 107 | `; 108 | -------------------------------------------------------------------------------- /sobel-edge-detection/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | let video = document.querySelector('video'); 5 | let fallbackImage = null; 6 | 7 | let texture = null; 8 | 9 | 10 | const glea = new GLea({ 11 | glOptions: { 12 | preserveDrawingBuffer: true 13 | }, 14 | shaders: [ 15 | GLea.fragmentShader(frag), 16 | GLea.vertexShader(vert) 17 | ], 18 | buffers: { 19 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 20 | } 21 | }).create(); 22 | 23 | window.addEventListener('resize', () => { 24 | glea.resize(); 25 | }); 26 | 27 | function loop(time) { 28 | const { gl } = glea; 29 | // Upload the image into the texture. 30 | // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 31 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 32 | glea.clear(); 33 | glea.uni('width', glea.width); 34 | glea.uni('height', glea.height); 35 | glea.uni('time', time * .005); 36 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 37 | requestAnimationFrame(loop); 38 | } 39 | 40 | function accessWebcam(video) { 41 | return new Promise((resolve, reject) => { 42 | const mediaConstraints = { audio: false, video: { width: 1280, height: 720, brightness: {ideal: 2} } }; 43 | navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => { 44 | video.srcObject = mediaStream; 45 | video.onloadedmetadata = (e) => { 46 | video.play(); 47 | resolve(video); 48 | } 49 | }).catch(err => { 50 | reject(err); 51 | }); 52 | }); 53 | } 54 | 55 | function loadImage(url) { 56 | return new Promise((resolve, reject) => { 57 | const img = new Image(); 58 | img.crossOrigin = 'Anonymous'; 59 | img.src = url; 60 | img.onload = () => { 61 | resolve(img); 62 | }; 63 | img.onerror = () => { 64 | reject(img); 65 | }; 66 | }); 67 | } 68 | 69 | function takeScreenshot() { 70 | const { canvas } = glea; 71 | const anchor = document.createElement('a'); 72 | anchor.setAttribute('download', 'selfie.jpg'); 73 | anchor.setAttribute('href', canvas.toDataURL('image/jpeg', 0.92)); 74 | anchor.click(); 75 | } 76 | 77 | async function setup() { 78 | const { gl } = glea; 79 | try { 80 | await accessWebcam(video); 81 | } catch (ex) { 82 | video = null; 83 | console.error(ex.message); 84 | } 85 | if (! video) { 86 | try { 87 | fallbackImage = await loadImage('https://placekitten.com/1280/720') 88 | } catch (ex) { 89 | console.error(ex.message); 90 | return false; 91 | } 92 | } 93 | texture = gl.createTexture(); 94 | gl.bindTexture(gl.TEXTURE_2D, texture); 95 | 96 | // Set the parameters so we can render any size image. 97 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 98 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 99 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 100 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 101 | 102 | // Upload the image into the texture. 103 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 104 | loop(0); 105 | } 106 | 107 | setup(); 108 | -------------------------------------------------------------------------------- /webcam/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | let video = document.querySelector('video'); 5 | let fallbackImage = null; 6 | 7 | let texture = null; 8 | 9 | 10 | const glea = new GLea({ 11 | glOptions: { 12 | preserveDrawingBuffer: true 13 | }, 14 | shaders: [ 15 | GLea.fragmentShader(frag), 16 | GLea.vertexShader(vert) 17 | ], 18 | buffers: { 19 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 20 | } 21 | }).create(); 22 | 23 | window.addEventListener('resize', () => { 24 | glea.resize(); 25 | }); 26 | 27 | function loop(time) { 28 | const { gl } = glea; 29 | // Upload the image into the texture. 30 | // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 31 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video); 32 | glea.clear(); 33 | glea.uni('width', glea.width); 34 | glea.uni('height', glea.height); 35 | glea.uni('time', time * .005); 36 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 37 | requestAnimationFrame(loop); 38 | } 39 | 40 | function accessWebcam(video) { 41 | return new Promise((resolve, reject) => { 42 | const mediaConstraints = { audio: false, video: { width: 1280, height: 720 } }; 43 | navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => { 44 | video.srcObject = mediaStream; 45 | video.onloadedmetadata = (e) => { 46 | video.play(); 47 | resolve(video); 48 | } 49 | }).catch(err => { 50 | reject(err); 51 | }); 52 | }); 53 | } 54 | 55 | function loadImage(url) { 56 | return new Promise((resolve, reject) => { 57 | const img = new Image(); 58 | img.crossOrigin = 'Anonymous'; 59 | img.src = url; 60 | img.onload = () => { 61 | resolve(img); 62 | }; 63 | img.onerror = () => { 64 | reject(img); 65 | }; 66 | }); 67 | } 68 | 69 | function takeScreenshot() { 70 | const { canvas } = glea; 71 | const anchor = document.createElement('a'); 72 | anchor.setAttribute('download', 'selfie.jpg'); 73 | anchor.setAttribute('href', canvas.toDataURL('image/jpeg', 0.92)); 74 | anchor.click(); 75 | } 76 | 77 | async function setup() { 78 | const { gl } = glea; 79 | try { 80 | await accessWebcam(video); 81 | } catch (ex) { 82 | video = null; 83 | console.error(ex.message); 84 | } 85 | if (! video) { 86 | try { 87 | fallbackImage = await loadImage('https://placekitten.com/1280/720') 88 | } catch (ex) { 89 | console.error(ex.message); 90 | return false; 91 | } 92 | } 93 | texture = gl.createTexture(); 94 | gl.bindTexture(gl.TEXTURE_2D, texture); 95 | 96 | // Set the parameters so we can render any size image. 97 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 98 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 99 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 100 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 101 | 102 | // Upload the image into the texture. 103 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 104 | 105 | const saveButton = document.querySelector('button'); 106 | saveButton.addEventListener('click', takeScreenshot); 107 | loop(0); 108 | } 109 | 110 | setup(); 111 | -------------------------------------------------------------------------------- /webcam-bender/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | let video = document.querySelector('video'); 5 | let fallbackImage = null; 6 | 7 | let texture = null; 8 | 9 | 10 | const glea = new GLea({ 11 | glOptions: { 12 | preserveDrawingBuffer: true 13 | }, 14 | shaders: [ 15 | GLea.fragmentShader(frag), 16 | GLea.vertexShader(vert) 17 | ], 18 | buffers: { 19 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 20 | } 21 | }).create(); 22 | 23 | window.addEventListener('resize', () => { 24 | glea.resize(); 25 | }); 26 | 27 | function loop(time) { 28 | const { gl } = glea; 29 | // Upload the image into the texture. 30 | // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 31 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video); 32 | glea.clear(); 33 | glea.uni('width', glea.width); 34 | glea.uni('height', glea.height); 35 | glea.uni('time', time * .005); 36 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 37 | requestAnimationFrame(loop); 38 | } 39 | 40 | function accessWebcam(video) { 41 | return new Promise((resolve, reject) => { 42 | const mediaConstraints = { audio: false, video: { width: 1280, height: 720 } }; 43 | navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => { 44 | video.srcObject = mediaStream; 45 | video.onloadedmetadata = (e) => { 46 | video.play(); 47 | resolve(video); 48 | } 49 | }).catch(err => { 50 | reject(err); 51 | }); 52 | }); 53 | } 54 | 55 | function loadImage(url) { 56 | return new Promise((resolve, reject) => { 57 | const img = new Image(); 58 | img.crossOrigin = 'Anonymous'; 59 | img.src = url; 60 | img.onload = () => { 61 | resolve(img); 62 | }; 63 | img.onerror = () => { 64 | reject(img); 65 | }; 66 | }); 67 | } 68 | 69 | function takeScreenshot() { 70 | const { canvas } = glea; 71 | const anchor = document.createElement('a'); 72 | anchor.setAttribute('download', 'selfie.jpg'); 73 | anchor.setAttribute('href', canvas.toDataURL('image/jpeg', 0.92)); 74 | anchor.click(); 75 | } 76 | 77 | async function setup() { 78 | const { gl } = glea; 79 | try { 80 | await accessWebcam(video); 81 | } catch (ex) { 82 | video = null; 83 | console.error(ex.message); 84 | } 85 | if (! video) { 86 | try { 87 | fallbackImage = await loadImage('https://placekitten.com/1280/720') 88 | } catch (ex) { 89 | console.error(ex.message); 90 | return false; 91 | } 92 | } 93 | texture = gl.createTexture(); 94 | gl.bindTexture(gl.TEXTURE_2D, texture); 95 | 96 | // Set the parameters so we can render any size image. 97 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 98 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 99 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 100 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 101 | 102 | // Upload the image into the texture. 103 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 104 | 105 | const saveButton = document.querySelector('button'); 106 | saveButton.addEventListener('click', takeScreenshot); 107 | loop(0); 108 | } 109 | 110 | setup(); 111 | -------------------------------------------------------------------------------- /lib/prism/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.16.0 2 | https://prismjs.com/download.html#themes=prism-solarizedlight&languages=markup+css+clike+javascript */ 3 | /* 4 | Solarized Color Schemes originally by Ethan Schoonover 5 | http://ethanschoonover.com/solarized 6 | 7 | Ported for PrismJS by Hector Matos 8 | Website: https://krakendev.io 9 | Twitter Handle: https://twitter.com/allonsykraken) 10 | */ 11 | 12 | /* 13 | SOLARIZED HEX 14 | --------- ------- 15 | base03 #002b36 16 | base02 #073642 17 | base01 #586e75 18 | base00 #657b83 19 | base0 #839496 20 | base1 #93a1a1 21 | base2 #eee8d5 22 | base3 #fdf6e3 23 | yellow #b58900 24 | orange #cb4b16 25 | red #dc322f 26 | magenta #d33682 27 | violet #6c71c4 28 | blue #268bd2 29 | cyan #2aa198 30 | green #859900 31 | */ 32 | 33 | code[class*="language-"], 34 | pre[class*="language-"] { 35 | color: #657b83; /* base00 */ 36 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 37 | font-size: 1em; 38 | text-align: left; 39 | white-space: pre; 40 | word-spacing: normal; 41 | word-break: normal; 42 | word-wrap: normal; 43 | 44 | line-height: 1.5; 45 | 46 | -moz-tab-size: 4; 47 | -o-tab-size: 4; 48 | tab-size: 4; 49 | 50 | -webkit-hyphens: none; 51 | -moz-hyphens: none; 52 | -ms-hyphens: none; 53 | hyphens: none; 54 | } 55 | 56 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 57 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 58 | background: #073642; /* base02 */ 59 | } 60 | 61 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 62 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 63 | background: #073642; /* base02 */ 64 | } 65 | 66 | /* Code blocks */ 67 | pre[class*="language-"] { 68 | padding: 1em; 69 | margin: .5em 0; 70 | overflow: auto; 71 | border-radius: 0.3em; 72 | } 73 | 74 | :not(pre) > code[class*="language-"], 75 | pre[class*="language-"] { 76 | background-color: #fdf6e3; /* base3 */ 77 | } 78 | 79 | /* Inline code */ 80 | :not(pre) > code[class*="language-"] { 81 | padding: .1em; 82 | border-radius: .3em; 83 | } 84 | 85 | .token.comment, 86 | .token.prolog, 87 | .token.doctype, 88 | .token.cdata { 89 | color: #93a1a1; /* base1 */ 90 | } 91 | 92 | .token.punctuation { 93 | color: #586e75; /* base01 */ 94 | } 95 | 96 | .namespace { 97 | opacity: .7; 98 | } 99 | 100 | .token.property, 101 | .token.tag, 102 | .token.boolean, 103 | .token.number, 104 | .token.constant, 105 | .token.symbol, 106 | .token.deleted { 107 | color: #268bd2; /* blue */ 108 | } 109 | 110 | .token.selector, 111 | .token.attr-name, 112 | .token.string, 113 | .token.char, 114 | .token.builtin, 115 | .token.url, 116 | .token.inserted { 117 | color: #2aa198; /* cyan */ 118 | } 119 | 120 | .token.entity { 121 | color: #657b83; /* base00 */ 122 | background: #eee8d5; /* base2 */ 123 | } 124 | 125 | .token.atrule, 126 | .token.attr-value, 127 | .token.keyword { 128 | color: #859900; /* green */ 129 | } 130 | 131 | .token.function, 132 | .token.class-name { 133 | color: #b58900; /* yellow */ 134 | } 135 | 136 | .token.regex, 137 | .token.important, 138 | .token.variable { 139 | color: #cb4b16; /* orange */ 140 | } 141 | 142 | .token.important, 143 | .token.bold { 144 | font-weight: bold; 145 | } 146 | .token.italic { 147 | font-style: italic; 148 | } 149 | 150 | .token.entity { 151 | cursor: help; 152 | } 153 | 154 | -------------------------------------------------------------------------------- /webcam-triangle-grid/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import { grid } from './grid-geometry.mjs'; 3 | import GLea from '../lib/glea/glea.mjs'; 4 | 5 | let video = document.querySelector('video'); 6 | let fallbackImage = null; 7 | let texture = null; 8 | 9 | const mesh = grid(.05, .05); 10 | const numVertices = mesh.length / 2; 11 | 12 | const glea = new GLea({ 13 | glOptions: { 14 | preserveDrawingBuffer: true 15 | }, 16 | shaders: [ 17 | GLea.fragmentShader(frag), 18 | GLea.vertexShader(vert) 19 | ], 20 | buffers: { 21 | 'position': GLea.buffer(2, mesh), 22 | 'direction': GLea.buffer(1, Array(numVertices).fill(0).map(_ => Math.random())) 23 | } 24 | }).create(); 25 | 26 | function loop(time) { 27 | const { gl } = glea; 28 | // Upload the image into the texture. 29 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels); 30 | // void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, HTMLVideoElement? pixels); 31 | // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 32 | 33 | // the use of texSubImage2D vs texImage2D is faster 34 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 35 | glea.clear(); 36 | glea.uni('width', glea.width); 37 | glea.uni('height', glea.height); 38 | glea.uni('time', time * .005); 39 | gl.drawArrays(gl.TRIANGLES, 0, numVertices); 40 | requestAnimationFrame(loop); 41 | } 42 | 43 | function accessWebcam(video) { 44 | return new Promise((resolve, reject) => { 45 | const mediaConstraints = { audio: false, video: { width: 1280, height: 720 } }; 46 | navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => { 47 | video.srcObject = mediaStream; 48 | video.onloadedmetadata = (e) => { 49 | video.play(); 50 | resolve(video); 51 | } 52 | }).catch(err => { 53 | reject(err); 54 | }); 55 | }); 56 | } 57 | 58 | function loadImage(url) { 59 | return new Promise((resolve, reject) => { 60 | const img = new Image(); 61 | img.crossOrigin = 'Anonymous'; 62 | img.src = url; 63 | img.onload = () => { 64 | resolve(img); 65 | }; 66 | img.onerror = () => { 67 | reject(img); 68 | }; 69 | }); 70 | } 71 | 72 | async function setup() { 73 | const { gl } = glea; 74 | try { 75 | await accessWebcam(video); 76 | } catch (ex) { 77 | video = null; 78 | console.error(ex.message); 79 | } 80 | if (! video) { 81 | try { 82 | fallbackImage = await loadImage('https://placekitten.com/1280/720') 83 | } catch (ex) { 84 | console.error(ex.message); 85 | return false; 86 | } 87 | } 88 | texture = gl.createTexture(); 89 | gl.bindTexture(gl.TEXTURE_2D, texture); 90 | 91 | // Set the parameters so we can render any size image. 92 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 93 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 94 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 95 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 96 | 97 | // Upload the image into the texture. 98 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 99 | 100 | window.addEventListener('resize', () => { 101 | glea.resize(); 102 | }); 103 | loop(0); 104 | } 105 | 106 | setup(); 107 | 108 | -------------------------------------------------------------------------------- /exponential/shaders.mjs: -------------------------------------------------------------------------------- 1 | // this is just for code highlighting in VSCode 2 | // via the glsl-literal extension 3 | const glsl = x => x; 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | uniform float time; 8 | uniform float width; 9 | uniform float height; 10 | 11 | const int ITERS = 120; 12 | const float PI = 3.141592654; 13 | const float DEG = PI / 180.0; 14 | 15 | vec2 coords() { 16 | float vmin = min(width, height); 17 | return vec2((gl_FragCoord.x - width * .5) / vmin, 18 | (gl_FragCoord.y - height * .5) / vmin); 19 | } 20 | 21 | vec2 rotate(vec2 p, float a) { 22 | return vec2(p.x * cos(a) - p.y * sin(a), 23 | p.x * sin(a) + p.y * cos(a)); 24 | } 25 | 26 | vec2 repeat(in vec2 p, in vec2 c) { 27 | return mod(p, c) - 0.5 * c; 28 | } 29 | 30 | int gpf(int num) { 31 | int result = 1; 32 | int limit = int(sqrt(float(num))); 33 | for (int i = 0; i < ITERS; i++) { 34 | int factor = (i == 0) ? 2 : (1 + i * 2); 35 | if (factor > limit) { 36 | break; 37 | } 38 | for (int j = 0; j < ITERS; j++) { 39 | if (int(mod(float(num), float(factor))) != 0) { 40 | break; 41 | } 42 | num = int(num / factor); 43 | result = factor; 44 | } 45 | if (factor > num) { 46 | break; 47 | } 48 | } 49 | if (num > 1) { 50 | result = num; 51 | } 52 | return result; 53 | } 54 | 55 | vec2 complexMul(vec2 a, vec2 b) { 56 | return vec2(a.x * b.x - a.y * b.y, a.y * b.x + a.x * b.y); 57 | } 58 | 59 | vec2 complexPow(vec2 a, int n) { 60 | vec2 result = vec2(1.0, 0.0); 61 | for (int i = 0; i < ITERS; i++) { 62 | if (i == n) { 63 | break; 64 | } 65 | result = complexMul(result, a); 66 | } 67 | return result; 68 | } 69 | 70 | // by @mattdesl 71 | float hue2rgb(float f1, float f2, float hue) { 72 | if (hue < 0.0) 73 | hue += 1.0; 74 | else if (hue > 1.0) 75 | hue -= 1.0; 76 | float res; 77 | if ((6.0 * hue) < 1.0) 78 | res = f1 + (f2 - f1) * 6.0 * hue; 79 | else if ((2.0 * hue) < 1.0) 80 | res = f2; 81 | else if ((3.0 * hue) < 2.0) 82 | res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; 83 | else 84 | res = f1; 85 | return res; 86 | } 87 | 88 | vec3 hsl2rgb(vec3 hsl) { 89 | vec3 rgb; 90 | if (hsl.y == 0.0) { 91 | rgb = vec3(hsl.z); // Luminance 92 | } else { 93 | float f2; 94 | if (hsl.z < 0.5) 95 | f2 = hsl.z * (1.0 + hsl.y); 96 | else 97 | f2 = hsl.z + hsl.y - hsl.y * hsl.z; 98 | float f1 = 2.0 * hsl.z - f2; 99 | rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); 100 | rgb.g = hue2rgb(f1, f2, hsl.x); 101 | rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); 102 | } 103 | return rgb; 104 | } 105 | 106 | // exponential generating function EG(gpf; z) = Σn=1∞ gpf(n) z^n / (n+1)! 107 | vec2 exponential(vec2 p) { 108 | vec2 x = vec2(0.0, 0.0); 109 | int j = 1; 110 | for (int i = 0; i < 17; i++) { 111 | j = j * (i + 2); 112 | x = x + float(gpf(i + 1)) * complexPow(p * 8.0, int(i + 1)) / float(j); 113 | } 114 | return x; 115 | } 116 | 117 | void main () { 118 | vec2 p00 = coords(); 119 | vec2 p0 = rotate(p00, time *.01); 120 | vec2 exp = exponential(p0) *.1; 121 | vec3 col = hsl2rgb(vec3(sin(p0.x + time*.1) * atan(exp.x, exp.y), 1.0, .7 - length(exp))); 122 | gl_FragColor = vec4(col, 1.0); 123 | } 124 | ` 125 | 126 | export const vert = glsl` 127 | precision mediump float; 128 | attribute vec2 position; 129 | 130 | void main () { 131 | gl_Position = vec4(position, 0, 1.0); 132 | } 133 | ` -------------------------------------------------------------------------------- /talk-webgl/slides.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Ubuntu&display=swap'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | background: papayawhip; 9 | color: #ff6f00; 10 | font-family: 'Ubuntu', sans-serif; 11 | font-size: calc(14px + (20 - 14) * ((100vw - 300px) / (1600 - 300))); 12 | line-height: 1.5; 13 | margin: 0; 14 | width: 100vw; 15 | height: 100vh; 16 | } 17 | 18 | main { 19 | width: 100%; 20 | height: 100%; 21 | overflow-y: scroll; 22 | overflow-x: hidden; 23 | } 24 | 25 | @media (min-height: 400px) { 26 | main { 27 | scroll-snap-type: y mandatory; 28 | } 29 | } 30 | 31 | nav { 32 | position: fixed; 33 | width: 100%; 34 | z-index: 2; 35 | bottom: 1rem; 36 | padding: 0; 37 | display: flex; 38 | justify-content: center; 39 | } 40 | 41 | nav ul { 42 | list-style: none; 43 | padding: 0; 44 | margin: 0; 45 | display: flex; 46 | max-width: 100vw; 47 | flex-wrap: wrap; 48 | } 49 | 50 | nav li { 51 | display: inline-block; 52 | margin: 0; 53 | padding: 0; 54 | } 55 | 56 | nav li a { 57 | font-size: 0; 58 | width: calc(1vw + 1vh + .5vmin); 59 | height: calc(1vw + 1vh + .5vmin); 60 | border-radius: 50%; 61 | margin: calc(.25vw + .25vh + .125vmin); 62 | background: darkred; 63 | border: 2px solid #ffffff; 64 | display: block 65 | } 66 | 67 | nav li a:focus, nav li.current a { 68 | background: #ffaf32; 69 | animation: pulse 2s ease-in-out infinite; 70 | } 71 | 72 | @keyframes pulse { 73 | 50% { transform: scale(1.5); } 74 | } 75 | 76 | 77 | 78 | .slide { 79 | scroll-snap-align: start; 80 | max-width: 1440px; 81 | min-height: 100vh; 82 | margin: 0 auto; 83 | font-size: 1em; 84 | padding: 1em; 85 | } 86 | 87 | @media (min-width: 768px) { 88 | .slide__image { 89 | float: left; 90 | width: 45vmin; 91 | margin: 0 2vmin 0 0; 92 | text-align: center; 93 | } 94 | } 95 | 96 | .slide__image img { 97 | max-width: 40vmin; 98 | max-height: 40vmin; 99 | } 100 | 101 | 102 | .slide h1, h2, h3, h4, h5, h6 { 103 | padding: 0; 104 | line-height: 1.5; 105 | color: darkred; 106 | } 107 | 108 | h1, h2, h3, h3, h4, h5, h6,p, ul { 109 | margin: 0.25em 0; 110 | } 111 | @media (min-width: 769px) { 112 | .slide h1 { 113 | font-size: 3.0em; 114 | } 115 | 116 | .slide h2 { 117 | font-size: 2.2em; 118 | } 119 | 120 | .slide h3, h4, h5, h6, p, ul { 121 | font-size: 1.6em; 122 | } 123 | } 124 | 125 | @media (max-width: 768px) { 126 | .slide h1 { 127 | font-size: 2.0em; 128 | } 129 | 130 | .slide h2 { 131 | font-size: 1.8em; 132 | } 133 | 134 | .slide h3, h4, h5, h6, p, ul { 135 | font-size: 1.4em; 136 | } 137 | } 138 | 139 | .slide ul { 140 | padding: 0 0 0 1em; 141 | } 142 | 143 | .clock { 144 | position: fixed; 145 | bottom: 1em; 146 | right: 1em; 147 | } 148 | 149 | a { 150 | color: #ff2266; 151 | word-break: break-all; 152 | } 153 | 154 | p > code, li > code { 155 | background: #fdf6e3; 156 | color: #ff2266; 157 | } 158 | 159 | .triangle { 160 | width: 250px; 161 | height: 250px; 162 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 250 250'%3E%3Cdefs%3E%3Cpattern id='pixels' viewBox='0 0 10 10' width='4%25' height='4%25'%3E%3Crect x='0' y='0' width='9' height='9' stroke='%23fff' fill='%23f00' /%3E%3C/pattern%3E%3C/defs%3E%3Cpath d='M10,200l100,-180l120,200Z' stroke='%23000' stroke-lineWidth='2' fill='url(%23pixels)'/%3E%3C/svg%3E"); 163 | } -------------------------------------------------------------------------------- /webcam-triangle-grid-2/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import { grid } from '../webcam-triangle-grid/grid-geometry.mjs'; 3 | import GLea from '../lib/glea/glea.mjs'; 4 | 5 | let video = document.querySelector('video'); 6 | let fallbackImage = null; 7 | let texture = null; 8 | 9 | const mesh = grid(.1, .1); 10 | const numVertices = mesh.length / 2; 11 | 12 | const glea = new GLea({ 13 | glOptions: { 14 | preserveDrawingBuffer: true 15 | }, 16 | shaders: [ 17 | GLea.fragmentShader(frag), 18 | GLea.vertexShader(vert) 19 | ], 20 | buffers: { 21 | 'position': GLea.buffer(2, mesh), 22 | 'direction': GLea.buffer(1, Array(numVertices).fill(0).map(_ => Math.random())) 23 | } 24 | }).create(); 25 | 26 | function loop(time) { 27 | const { gl } = glea; 28 | // Upload the image into the texture. 29 | // void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels); 30 | // void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, HTMLVideoElement? pixels); 31 | // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 32 | 33 | // the use of texSubImage2D vs texImage2D is faster 34 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 35 | glea.clear(); 36 | glea.uni('width', glea.width); 37 | glea.uni('height', glea.height); 38 | glea.uni('time', time * .005); 39 | gl.drawArrays(gl.TRIANGLES, 0, numVertices); 40 | requestAnimationFrame(loop); 41 | } 42 | 43 | function accessWebcam(video) { 44 | return new Promise((resolve, reject) => { 45 | const mediaConstraints = { audio: false, video: { width: 1280, height: 720 } }; 46 | navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => { 47 | video.srcObject = mediaStream; 48 | video.onloadedmetadata = (e) => { 49 | video.play(); 50 | resolve(video); 51 | } 52 | }).catch(err => { 53 | reject(err); 54 | }); 55 | }); 56 | } 57 | 58 | function loadImage(url) { 59 | return new Promise((resolve, reject) => { 60 | const img = new Image(); 61 | img.crossOrigin = 'Anonymous'; 62 | img.src = url; 63 | img.onload = () => { 64 | resolve(img); 65 | }; 66 | img.onerror = () => { 67 | reject(img); 68 | }; 69 | }); 70 | } 71 | 72 | async function setup() { 73 | const { gl } = glea; 74 | try { 75 | await accessWebcam(video); 76 | } catch (ex) { 77 | video = null; 78 | console.error(ex.message); 79 | } 80 | if (! video) { 81 | try { 82 | fallbackImage = await loadImage('https://placekitten.com/1280/720') 83 | } catch (ex) { 84 | console.error(ex.message); 85 | return false; 86 | } 87 | } 88 | texture = gl.createTexture(); 89 | gl.bindTexture(gl.TEXTURE_2D, texture); 90 | 91 | // Set the parameters so we can render any size image. 92 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 93 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 94 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 95 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 96 | 97 | // Upload the image into the texture. 98 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 99 | 100 | window.addEventListener('resize', () => { 101 | glea.resize(); 102 | }); 103 | loop(0); 104 | } 105 | 106 | setup(); 107 | 108 | -------------------------------------------------------------------------------- /texture-mapping/shaders.mjs: -------------------------------------------------------------------------------- 1 | // for syntax highlighting (glsl-literal extension) 2 | const glsl = x => x; 3 | 4 | export const vert = glsl` 5 | precision highp float; 6 | attribute vec3 position; 7 | attribute vec2 texCoord; 8 | attribute vec3 color; 9 | 10 | varying vec3 vcolor; 11 | varying vec4 vpos; 12 | varying vec2 vtexCoord; 13 | varying mat4 vM; 14 | uniform float time; 15 | uniform float width; 16 | uniform float height; 17 | 18 | const float PI = 3.1415926535; 19 | 20 | mat4 translate(vec3 p) { 21 | return mat4( 22 | vec4(1.0, 0.0, 0.0, 0.0), 23 | vec4(0.0, 1.0, 0.0, 0.0), 24 | vec4(0.0, 0.0, 1.0, 0.0), 25 | vec4(p.x, p.y, p.z, 1.0) 26 | ); 27 | } 28 | 29 | 30 | mat4 rotX(float angle) { 31 | float S = sin(angle); 32 | float C = cos(angle); 33 | return mat4( 34 | vec4(1.0, 0, 0, 0), 35 | vec4(0 , C, S, 0), 36 | vec4(0 ,-S, C, 0), 37 | vec4(0 , 0, 0, 1.0) 38 | ); 39 | } 40 | 41 | mat4 rotY(float angle) { 42 | float S = sin(angle); 43 | float C = cos(angle); 44 | return mat4( 45 | vec4(C, 0 ,-S, 0), 46 | vec4(0, 1.0, 0, 0), 47 | vec4(S, 0 , C, 0), 48 | vec4(0, 0 , 0, 1.0) 49 | ); 50 | } 51 | 52 | mat4 rotZ(float angle) { 53 | float S = sin(angle); 54 | float C = cos(angle); 55 | return mat4( 56 | vec4( C, S, 0 , 0), 57 | vec4(-S, C, 0 , 0), 58 | vec4( 0, 0, 1.0, 0), 59 | vec4( 0, 0, 0 , 1.0) 60 | ); 61 | } 62 | 63 | // glFrustum(left, right, bottom, top, zNear, zFar) 64 | mat4 frustum(float left, float right, float bottom, float top, float zNear, float zFar) { 65 | float t1 = 2.0 * zNear; 66 | float t2 = right - left; 67 | float t3 = top - bottom; 68 | float t4 = zFar - zNear; 69 | return mat4( 70 | vec4(t1 / t2, 0, 0, 0), 71 | vec4(0, t1 / t3, 0, 0), 72 | vec4((right + left) / t2, (top + bottom) / t3, (-zFar - zNear) / t4, -1.0), 73 | vec4(0, 0, (-t1*zFar) / t4, 0)); 74 | } 75 | 76 | // gluPerspective(fieldOfView, aspectRatio, zNear, zFar) 77 | mat4 perspective(float fieldOfView, float aspectRatio, float zNear, float zFar) { 78 | float y = zNear * tan(fieldOfView * PI / 360.0); 79 | float x = y * aspectRatio; 80 | return frustum(-x, x, -y, y, zNear, zFar); 81 | } 82 | 83 | void main() { 84 | mat4 perspectiveMat = perspective(45.0, width / height, 0.1, 1000.0); 85 | mat4 translateMat = translate(vec3(sin(2.0 * time * 1e-2) * 0.05, sin(3.0 * time * 1e-2) * .1, -1.6 + sin(time * .1))); 86 | mat4 M = perspectiveMat * translateMat * rotX(time * 0.1) * rotY(time * 0.1) * rotZ(time * 0.01); 87 | 88 | vM = M; 89 | vpos = vec4(position, 1.0); 90 | vtexCoord = texCoord; 91 | vcolor = color; 92 | gl_Position = M * vec4(position, 1.0); 93 | } 94 | `; 95 | 96 | export const frag = glsl` 97 | precision highp float; 98 | varying vec4 vpos; 99 | varying mat4 vM; 100 | varying vec2 vtexCoord; 101 | varying vec3 vcolor; 102 | 103 | uniform float width; 104 | uniform float height; 105 | uniform float time; 106 | 107 | uniform sampler2D image; 108 | 109 | // normalize coords and correct for aspect ratio 110 | vec2 normalizeScreenCoords() 111 | { 112 | float aspectRatio = width / height; 113 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 114 | result.x *= aspectRatio; 115 | return result; 116 | } 117 | 118 | 119 | void main() { 120 | vec2 p = normalizeScreenCoords(); 121 | vec4 a = texture2D(image, vtexCoord); 122 | vec4 b = vec4(a.x * vcolor, 1.0); 123 | gl_FragColor = mix(a, b, vec4(.5 * (1.0 + sin(p.x * p.y * (3.0 + 2.0 * sin(time)) + time * .25)))); 124 | } 125 | `; 126 | -------------------------------------------------------------------------------- /lib/phenomenon-1.5.1/index.d.ts: -------------------------------------------------------------------------------- 1 | interface AttributeProps { 2 | name: string; 3 | size: number; 4 | data?: any; 5 | } 6 | interface UniformProps { 7 | type: string; 8 | value: Array; 9 | location?: WebGLUniformLocation; 10 | } 11 | interface GeometryProps { 12 | vertices?: Array>; 13 | normal?: Array>; 14 | } 15 | interface BufferProps { 16 | location: number; 17 | buffer: WebGLBuffer; 18 | size: number; 19 | } 20 | interface InstanceProps { 21 | attributes?: Array; 22 | vertex?: string; 23 | fragment?: string; 24 | geometry?: GeometryProps; 25 | mode?: number; 26 | modifiers?: object; 27 | multiplier?: number; 28 | uniforms: { 29 | [key: string]: UniformProps; 30 | }; 31 | } 32 | interface RendererProps { 33 | canvas?: HTMLCanvasElement; 34 | context?: object; 35 | contextType?: string; 36 | settings?: object; 37 | } 38 | /** 39 | * Class representing an instance. 40 | */ 41 | declare class Instance { 42 | gl: WebGLRenderingContext; 43 | vertex: string; 44 | fragment: string; 45 | program: WebGLProgram; 46 | uniforms: { 47 | [key: string]: UniformProps; 48 | }; 49 | geometry: GeometryProps; 50 | attributes: Array; 51 | attributeKeys: Array; 52 | multiplier: number; 53 | modifiers: Array; 54 | buffers: Array; 55 | uniformMap: object; 56 | mode: number; 57 | onRender?: Function; 58 | /** 59 | * Create an instance. 60 | */ 61 | constructor(props: InstanceProps); 62 | /** 63 | * Compile a shader. 64 | */ 65 | compileShader(type: number, source: string): WebGLShader; 66 | /** 67 | * Create a program. 68 | */ 69 | prepareProgram(): void; 70 | /** 71 | * Create uniforms. 72 | */ 73 | prepareUniforms(): void; 74 | /** 75 | * Create buffer attributes. 76 | */ 77 | prepareAttributes(): void; 78 | /** 79 | * Prepare a single attribute. 80 | */ 81 | prepareAttribute(attribute: AttributeProps): void; 82 | /** 83 | * Create a buffer with an attribute. 84 | */ 85 | prepareBuffer(attribute: AttributeProps): void; 86 | /** 87 | * Render the instance. 88 | */ 89 | render(renderUniforms: object): void; 90 | /** 91 | * Destroy the instance. 92 | */ 93 | destroy(): void; 94 | } 95 | /** 96 | * Class representing a Renderer. 97 | */ 98 | declare class Renderer { 99 | clearColor: Array; 100 | onRender: Function; 101 | onSetup: Function; 102 | uniformMap: object; 103 | gl: WebGLRenderingContext; 104 | canvas: HTMLCanvasElement; 105 | devicePixelRatio: number; 106 | instances: Map; 107 | position: { 108 | x: number; 109 | y: number; 110 | z: number; 111 | }; 112 | uniforms: { 113 | [key: string]: UniformProps; 114 | }; 115 | shouldRender: boolean; 116 | /** 117 | * Create a renderer. 118 | */ 119 | constructor(props: RendererProps); 120 | /** 121 | * Handle resize events. 122 | */ 123 | resize(): void; 124 | /** 125 | * Toggle the active state of the renderer. 126 | */ 127 | toggle(shouldRender: boolean): void; 128 | /** 129 | * Render the total scene. 130 | */ 131 | render(): void; 132 | /** 133 | * Add an instance to the renderer. 134 | */ 135 | add(key: string, settings: InstanceProps): Instance; 136 | /** 137 | * Remove an instance from the renderer. 138 | */ 139 | remove(key: string): void; 140 | /** 141 | * Destroy the renderer and its instances. 142 | */ 143 | destroy(): void; 144 | } 145 | export default Renderer; -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * Deliberate use of dead or rejected names. 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, posting via an official social media account, or acting as an appointed 53 | representative at an online or offline event. Representation of a project may be 54 | further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at terabaud@gmail.com. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | 76 | For answers to common questions about this code of conduct, see 77 | https://www.contributor-covenant.org/faq 78 | -------------------------------------------------------------------------------- /raymarching/shaders.mjs: -------------------------------------------------------------------------------- 1 | // identity function for the glsl-literal vscode extension 2 | // enables code highlighting for glsl code. 3 | const glsl = x => x 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | #define ITERS 64 8 | 9 | uniform float width; 10 | uniform float height; 11 | uniform float time; 12 | 13 | // Calculate cameras "orthonormal basis", i.e. its transform matrix components 14 | vec3 getCameraRayDir(vec2 uv, vec3 camPos, vec3 camTarget) { 15 | vec3 camForward = normalize(camTarget - camPos); 16 | vec3 camRight = normalize(cross(vec3(0.0, 1.0, 0.0), camForward)); 17 | vec3 camUp = normalize(cross(camForward, camRight)); 18 | 19 | float fPersp = 2.0; 20 | vec3 vDir = normalize(uv.x * camRight + uv.y * camUp + camForward * fPersp); 21 | 22 | return vDir; 23 | } 24 | 25 | // distance function for a sphere 26 | float sphere(vec3 p, float r) 27 | { 28 | return length(p) - r; 29 | } 30 | 31 | float deformation(vec3 pos) { 32 | return sin(time * .1) * 0.4 * sin(time * .3 + pos.x * 4.0) * 33 | sin(time *.2 + pos.y * 3.0) * 34 | sin(time * .3 + pos.z * 2.0); 35 | } 36 | 37 | float scene(vec3 pos) 38 | { 39 | float t = sphere(pos - vec3(0.0, 0.0, 10.0), 3.0) + deformation(pos); 40 | return t; 41 | } 42 | 43 | // cast a ray along a direction and return 44 | // the distance to the first thing it hits 45 | // if nothing was hit, return -1 46 | float castRay(vec3 rayOrigin, vec3 rayDir) 47 | { 48 | float t = 0.0; // Stores current distance along ray 49 | for (int i = 0; i < ITERS; i++) 50 | { 51 | float res = scene(rayOrigin + rayDir * t); 52 | if (res < (0.001*t)) 53 | { 54 | return t; 55 | } 56 | t += res; 57 | } 58 | return -1.0; 59 | } 60 | 61 | // I tried to achieve a somewhat cloudy background 62 | // maybe better with perlin noise. 63 | vec3 background(vec3 rayDir) { 64 | return vec3(0.30, 0.36, 0.60) - 65 | (sin(time *.1 + rayDir.x * rayDir.z * 10.0) * 66 | sin(time *.2 + rayDir.y * rayDir.z * 15.0) * .1 + rayDir.y * .7); 67 | } 68 | 69 | 70 | 71 | // calculate normal: 72 | vec3 calcNormal(vec3 pos) 73 | { 74 | // Center sample 75 | float c = scene(pos); 76 | // Use offset samples to compute gradient / normal 77 | vec2 eps_zero = vec2(0.001, 0.0); 78 | return normalize(vec3( scene(pos + eps_zero.xyy), scene(pos + eps_zero.yxy), scene(pos + eps_zero.yyx) ) - c); 79 | } 80 | 81 | // Visualize depth based on the distance 82 | vec3 render(vec3 rayOrigin, vec3 rayDir) 83 | { 84 | float t = castRay(rayOrigin, rayDir); 85 | if (t == -1.0) { 86 | return background(rayDir); 87 | } 88 | // shading based on the distance 89 | // vec3 col = vec3(4.0 - t * 0.35) * vec3(.7, 0, 1.0); 90 | 91 | // shading based on the normals 92 | vec3 pos = rayOrigin + rayDir * t; 93 | vec3 N = calcNormal(pos); 94 | vec3 L = normalize(vec3(sin(time *.1), 2.0, -0.5)); 95 | // L is vector from surface point to light 96 | // N is surface normal. N and L must be normalized! 97 | float NoL = max(dot(N, L), 0.0); 98 | vec3 LDirectional = vec3(1.0, 0.9, 0.8) * NoL; 99 | vec3 LAmbient = vec3(0.3); 100 | vec3 col = vec3(.7, .2, 1.0); 101 | vec3 diffuse = col * (LDirectional + LAmbient); 102 | return diffuse; 103 | } 104 | 105 | // normalize coords and correct for aspect ratio 106 | vec2 normalizeScreenCoords() 107 | { 108 | float aspectRatio = width / height; 109 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 110 | result.x *= aspectRatio; 111 | return result; 112 | } 113 | 114 | void main() { 115 | vec3 camPos = vec3(0, 0, -1.0); 116 | vec3 camTarget = vec3(0); 117 | vec2 uv = normalizeScreenCoords(); 118 | vec3 rayDir = getCameraRayDir(uv, camPos, camTarget); 119 | vec3 col = render(camPos, rayDir); 120 | gl_FragColor = vec4(col, 1.0); 121 | } 122 | ` 123 | 124 | export const vert = glsl` 125 | precision highp float; 126 | attribute vec2 position; 127 | 128 | void main() { 129 | gl_Position = vec4(position, 0.0, 1.0); 130 | } 131 | ` -------------------------------------------------------------------------------- /morphing-stars/shaders.mjs: -------------------------------------------------------------------------------- 1 | // this is just for code highlighting in VSCode 2 | // via the glsl-literal extension 3 | const glsl = x => x; 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | uniform float time; 8 | uniform float width; 9 | uniform float height; 10 | 11 | const float PI = 3.141592654; 12 | const float DEG = PI / 180.0; 13 | 14 | vec2 coords() { 15 | float vmin = min(width, height); 16 | return vec2((gl_FragCoord.x - width * .5) / vmin, 17 | (gl_FragCoord.y - height * .5) / vmin); 18 | } 19 | 20 | vec2 rotate(vec2 p, float a) { 21 | return vec2(p.x * cos(a) - p.y * sin(a), 22 | p.x * sin(a) + p.y * cos(a)); 23 | } 24 | 25 | vec2 repeat(in vec2 p, in vec2 c) { 26 | return mod(p, c) - 0.5 * c; 27 | } 28 | 29 | // Distance functions by Inigo Quilez 30 | // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm 31 | float circle(in vec2 p, in vec2 pos, float radius) { 32 | return length(p - pos) - radius; 33 | } 34 | 35 | float box(in vec2 p, in vec2 pos, in vec2 b) { 36 | vec2 d = abs(p - pos) - b; 37 | return length(max(d, vec2(0))) + min(max(d.x, d.y), 0.0); 38 | } 39 | 40 | float triangle(in vec2 p, in float h) { 41 | const float k = sqrt(3.0); 42 | p.x = abs(p.x) - h; 43 | p.y = p.y + h / k; 44 | if( p.x + k*p.y > 0.0 ) p = vec2(p.x-k*p.y,-k*p.x-p.y)/2.0; 45 | p.x -= clamp( p.x, -2.0, 0.0 ); 46 | return -length(p)*sign(p.y); 47 | } 48 | 49 | float hexagon(in vec2 p, in vec2 pos, in float r) { 50 | const vec3 k = vec3(-0.866025404,0.5,0.577350269); 51 | p = abs(p - pos); 52 | p -= 2.0*min(dot(k.xy,p),0.0)*k.xy; 53 | p -= vec2(clamp(p.x, -k.z*r, k.z*r), r); 54 | return length(p) * sign(p.y); 55 | } 56 | 57 | float hexagram(in vec2 p, in vec2 pos, in float r) { 58 | const vec4 k=vec4(-0.5,0.8660254038,0.5773502692,1.7320508076); 59 | p = abs(p - pos); 60 | p -= 2.0*min(dot(k.xy,p),0.0)*k.xy; 61 | p -= 2.0*min(dot(k.yx,p),0.0)*k.yx; 62 | p -= vec2(clamp(p.x,r*k.z,r*k.w),r); 63 | return length(p)*sign(p.y); 64 | } 65 | 66 | float distanceField(vec2 p) { 67 | float hexa = hexagon(p, vec2(0, 0), 1.5); 68 | float star = hexagram(p, vec2(0, 0), .5); 69 | float x = (1.0 + sin(time * 0.5)) * .5; 70 | return mix(hexa, star, x); 71 | } 72 | 73 | // by @mattdesl 74 | float hue2rgb(float f1, float f2, float hue) { 75 | if (hue < 0.0) 76 | hue += 1.0; 77 | else if (hue > 1.0) 78 | hue -= 1.0; 79 | float res; 80 | if ((6.0 * hue) < 1.0) 81 | res = f1 + (f2 - f1) * 6.0 * hue; 82 | else if ((2.0 * hue) < 1.0) 83 | res = f2; 84 | else if ((3.0 * hue) < 2.0) 85 | res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; 86 | else 87 | res = f1; 88 | return res; 89 | } 90 | 91 | vec3 hsl2rgb(vec3 hsl) { 92 | vec3 rgb; 93 | if (hsl.y == 0.0) { 94 | rgb = vec3(hsl.z); // Luminance 95 | } else { 96 | float f2; 97 | if (hsl.z < 0.5) 98 | f2 = hsl.z * (1.0 + hsl.y); 99 | else 100 | f2 = hsl.z + hsl.y - hsl.y * hsl.z; 101 | float f1 = 2.0 * hsl.z - f2; 102 | rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); 103 | rgb.g = hue2rgb(f1, f2, hsl.x); 104 | rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); 105 | } 106 | return rgb; 107 | } 108 | 109 | vec3 Stars(in vec2 p, in vec3 background) { 110 | float sdf = distanceField(p); 111 | float hue = floor(10.0 * (1.0 + 0.98 * cos(time * .125 + sdf))) / 10.0; 112 | vec3 fill = hsl2rgb(vec3(hue, 1.0, .5)); 113 | //vec3 fill = vec3(1.0, clamp(cos(time * 2.0 - sdf * 20.0) * .5, 0.0, .25) * 4.0, 0.0); 114 | return sdf < 0.0 ? fill : background; 115 | } 116 | 117 | void main () { 118 | vec2 p0 = coords(); 119 | float zoom = 4.0; 120 | vec2 p1 = rotate(p0 * zoom, 45.0 * DEG); 121 | vec2 p2 = rotate(repeat(p1, vec2(1.75, 2.0)), 0.0 * DEG); 122 | vec3 background = vec3(0, 0, 0); 123 | vec3 col = Stars(p2, background); 124 | gl_FragColor = vec4(col, 1.0); 125 | } 126 | ` 127 | 128 | export const vert = glsl` 129 | precision mediump float; 130 | attribute vec2 position; 131 | 132 | void main () { 133 | gl_Position = vec4(position, 0, 1.0); 134 | } 135 | ` -------------------------------------------------------------------------------- /heartstar/shaders.mjs: -------------------------------------------------------------------------------- 1 | // this is just for code highlighting in VSCode 2 | // via the glsl-literal extension 3 | const glsl = x => x; 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | uniform float time; 8 | uniform float width; 9 | uniform float height; 10 | 11 | const float PI = 3.141592654; 12 | const float DEG = PI / 180.0; 13 | 14 | vec2 coords() { 15 | float vmin = min(width, height); 16 | return vec2((gl_FragCoord.x - width * .5) / vmin, 17 | (gl_FragCoord.y - height * .5) / vmin); 18 | } 19 | 20 | vec2 rotate(vec2 p, float a) { 21 | return vec2(p.x * cos(a) - p.y * sin(a), 22 | p.x * sin(a) + p.y * cos(a)); 23 | } 24 | 25 | vec2 repeat(in vec2 p, in vec2 c) { 26 | return mod(p, c) - 0.5 * c; 27 | } 28 | 29 | // Distance functions by Inigo Quilez 30 | // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm 31 | float circle(in vec2 p, in vec2 pos, float radius) { 32 | return length(p - pos) - radius; 33 | } 34 | 35 | // function from https://www.shadertoy.com/view/3ll3zr 36 | float heart(vec2 p, float s) { 37 | p /= s; 38 | vec2 q = p; 39 | q.x *= 0.5 + .5 * q.y; 40 | q.y -= abs(p.x) * .63; 41 | return (length(q) - .7) * s; 42 | } 43 | 44 | float box(in vec2 p, in vec2 pos, in vec2 b) { 45 | vec2 d = abs(p - pos) - b; 46 | return length(max(d, vec2(0))) + min(max(d.x, d.y), 0.0); 47 | } 48 | 49 | float triangle(in vec2 p, in float h) { 50 | const float k = sqrt(3.0); 51 | p.x = abs(p.x) - h; 52 | p.y = p.y + h / k; 53 | if( p.x + k*p.y > 0.0 ) p = vec2(p.x-k*p.y,-k*p.x-p.y)/2.0; 54 | p.x -= clamp( p.x, -2.0, 0.0 ); 55 | return -length(p)*sign(p.y); 56 | } 57 | 58 | float hexagon(in vec2 p, in vec2 pos, in float r) { 59 | const vec3 k = vec3(-0.866025404,0.5,0.577350269); 60 | p = abs(p - pos); 61 | p -= 2.0*min(dot(k.xy,p),0.0)*k.xy; 62 | p -= vec2(clamp(p.x, -k.z*r, k.z*r), r); 63 | return length(p) * sign(p.y); 64 | } 65 | 66 | float hexagram(in vec2 p, in vec2 pos, in float r) { 67 | const vec4 k=vec4(-0.5,0.8660254038,0.5773502692,1.7320508076); 68 | p = abs(p - pos); 69 | p -= 2.0*min(dot(k.xy,p),0.0)*k.xy; 70 | p -= 2.0*min(dot(k.yx,p),0.0)*k.yx; 71 | p -= vec2(clamp(p.x,r*k.z,r*k.w),r); 72 | return length(p)*sign(p.y); 73 | } 74 | 75 | float distanceField(vec2 p) { 76 | float star = hexagram(p, vec2(0, 0), .25); 77 | float heart = heart(p, .5); 78 | float x = clamp(.5 + sin(time * .5) * 1.0, 0.0, 1.0); 79 | return mix(heart, star, x); 80 | } 81 | 82 | // by @mattdesl 83 | float hue2rgb(float f1, float f2, float hue) { 84 | if (hue < 0.0) 85 | hue += 1.0; 86 | else if (hue > 1.0) 87 | hue -= 1.0; 88 | float res; 89 | if ((6.0 * hue) < 1.0) 90 | res = f1 + (f2 - f1) * 6.0 * hue; 91 | else if ((2.0 * hue) < 1.0) 92 | res = f2; 93 | else if ((3.0 * hue) < 2.0) 94 | res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; 95 | else 96 | res = f1; 97 | return res; 98 | } 99 | 100 | vec3 hsl2rgb(vec3 hsl) { 101 | vec3 rgb; 102 | if (hsl.y == 0.0) { 103 | rgb = vec3(hsl.z); // Luminance 104 | } else { 105 | float f2; 106 | if (hsl.z < 0.5) 107 | f2 = hsl.z * (1.0 + hsl.y); 108 | else 109 | f2 = hsl.z + hsl.y - hsl.y * hsl.z; 110 | float f1 = 2.0 * hsl.z - f2; 111 | rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); 112 | rgb.g = hue2rgb(f1, f2, hsl.x); 113 | rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); 114 | } 115 | return rgb; 116 | } 117 | 118 | vec3 scene(in vec2 p, in vec3 background) { 119 | float sdf = distanceField(p); 120 | float hue = floor(10.0 * (1.0 + 0.98 * cos(time * .125 + sdf))) / 10.0; 121 | vec3 fill = vec3(1.0, clamp(cos(time * .5 - sdf * 80.0) * .5, 0.0, .25) * 3.0, clamp(cos(time * .5 - sdf * 40.0) * .5, 0.0, .25) * 1.0); 122 | return sdf < 0.0 ? fill : background; 123 | } 124 | 125 | void main () { 126 | vec2 p0 = coords(); 127 | vec3 background = vec3(0, 0, 0); 128 | vec3 col = scene(p0, background); 129 | gl_FragColor = vec4(col, 1.0); 130 | } 131 | ` 132 | 133 | export const vert = glsl` 134 | precision mediump float; 135 | attribute vec2 position; 136 | 137 | void main () { 138 | gl_Position = vec4(position, 0, 1.0); 139 | } 140 | ` -------------------------------------------------------------------------------- /dither-cam/index.mjs: -------------------------------------------------------------------------------- 1 | import { frag, vert } from './shaders.mjs'; 2 | import GLea from '../lib/glea/glea.mjs'; 3 | 4 | let video = document.querySelector('video'); 5 | let fallbackImage = null; 6 | 7 | let texture0 = null; 8 | let texture1 = null; 9 | 10 | const glea = new GLea({ 11 | glOptions: { 12 | preserveDrawingBuffer: true 13 | }, 14 | shaders: [ 15 | GLea.fragmentShader(frag), 16 | GLea.vertexShader(vert) 17 | ], 18 | buffers: { 19 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 20 | } 21 | }).create(); 22 | 23 | window.addEventListener('resize', () => { 24 | glea.resize(); 25 | }); 26 | 27 | function loop(time) { 28 | const { gl } = glea; 29 | // Upload the image into the texture. 30 | // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 31 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 32 | glea.clear(); 33 | glea.uni('width', glea.width); 34 | glea.uni('height', glea.height); 35 | glea.uni('time', time * .005); 36 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 37 | requestAnimationFrame(loop); 38 | } 39 | 40 | function accessWebcam(video) { 41 | return new Promise((resolve, reject) => { 42 | const mediaConstraints = { audio: false, video: { width: 1280, height: 720, brightness: {ideal: 2} } }; 43 | navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => { 44 | video.srcObject = mediaStream; 45 | video.onloadedmetadata = (e) => { 46 | video.play(); 47 | resolve(video); 48 | } 49 | }).catch(err => { 50 | reject(err); 51 | }); 52 | }); 53 | } 54 | 55 | function loadImage(url) { 56 | return new Promise((resolve, reject) => { 57 | const img = new Image(); 58 | img.crossOrigin = 'Anonymous'; 59 | img.src = url; 60 | img.onload = () => { 61 | resolve(img); 62 | }; 63 | img.onerror = () => { 64 | reject(img); 65 | }; 66 | }); 67 | } 68 | 69 | function takeScreenshot() { 70 | const { canvas } = glea; 71 | const anchor = document.createElement('a'); 72 | anchor.setAttribute('download', 'selfie.jpg'); 73 | anchor.setAttribute('href', canvas.toDataURL('image/jpeg', 0.92)); 74 | anchor.click(); 75 | } 76 | 77 | function createBayerImageData() { 78 | const imageData = new ImageData(8, 8); 79 | // TODO: figure out the wikipedia pseudocode for generating this 80 | // https://en.wikipedia.org/wiki/Ordered_dithering 81 | const bayer = [ 82 | 0 , 48 , 12 , 60 , 3 , 51 , 15 , 63, 83 | 32 , 16 , 44 , 28 , 35 , 19 , 47 , 31, 84 | 8 , 56 , 4 , 52 , 11 , 59 , 7 , 55, 85 | 40 , 24 , 36 , 20 , 43 , 27 , 39 , 23, 86 | 2 , 50 , 14 , 62 , 1 , 49 , 13 , 61, 87 | 34 , 18 , 46 , 30 , 33 , 17 , 45 , 29, 88 | 10 , 58 , 6 , 54 , 9 , 57 , 5 , 53, 89 | 42 , 26 , 38 , 22 , 41 , 25 , 37 , 21 90 | ]; 91 | for (let i = 0; i < 64; i++) { 92 | imageData.data[i * 4 + 0] = bayer[i] * 4; 93 | imageData.data[i * 4 + 1] = bayer[i] * 4; 94 | imageData.data[i * 4 + 2] = bayer[i] * 4; 95 | imageData.data[i * 4 + 3] = 255; 96 | } 97 | return imageData; 98 | } 99 | 100 | 101 | 102 | 103 | 104 | async function setup() { 105 | const { gl } = glea; 106 | try { 107 | await accessWebcam(video); 108 | } catch (ex) { 109 | video = null; 110 | console.error(ex.message); 111 | } 112 | if (! video) { 113 | try { 114 | fallbackImage = await loadImage('https://placekitten.com/1280/720') 115 | } catch (ex) { 116 | console.error(ex.message); 117 | return false; 118 | } 119 | } 120 | texture0 = glea.createTexture(0, { 121 | textureWrapS: 'repeat', 122 | textureWrapT: 'repeat', 123 | textureMinFilter: 'nearest', 124 | textureMagFilter: 'nearest' 125 | }); 126 | const bayerData = createBayerImageData(); 127 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bayerData); 128 | texture1 = glea.createTexture(1); 129 | 130 | glea.uniI('texture0', 0); 131 | glea.uniI('texture1', 1); 132 | // Upload the image into the texture. 133 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); 134 | loop(0); 135 | } 136 | 137 | setup(); 138 | 139 | -------------------------------------------------------------------------------- /lib/phenomenon-1.5.1/phenomenon.mjs: -------------------------------------------------------------------------------- 1 | var t=["x","y","z"],e=function(t){Object.assign(this,{uniforms:{},geometry:{vertices:[{x:0,y:0,z:0}]},mode:0,modifiers:{},attributes:[],multiplier:1,buffers:[]}),Object.assign(this,t),this.prepareProgram(),this.prepareUniforms(),this.prepareAttributes()};e.prototype.compileShader=function(t,e){var i=this.gl.createShader(t);return this.gl.shaderSource(i,e),this.gl.compileShader(i),i},e.prototype.prepareProgram=function(){var t=this.gl,e=this.vertex,i=this.fragment,r=t.createProgram();t.attachShader(r,this.compileShader(35633,e)),t.attachShader(r,this.compileShader(35632,i)),t.linkProgram(r),t.useProgram(r),this.program=r},e.prototype.prepareUniforms=function(){for(var t=Object.keys(this.uniforms),e=0;e').map((slide, idx) => { 15 | const div = document.createElement('div'); 16 | div.innerHTML = slide; 17 | const images = [...div.querySelectorAll('img')]; 18 | images.forEach(image => { 19 | image.parentElement.setAttribute('class', 'slide__image'); 20 | }); 21 | const anchors = [...div.querySelectorAll('a')]; 22 | anchors.forEach(anchor => { 23 | anchor.setAttribute('target', '_blank'); 24 | }); 25 | return { 26 | id: 'slide' + idx, 27 | title: (div.querySelector('h1') || {}).textContent, 28 | content: div.innerHTML 29 | }; 30 | }); 31 | } 32 | 33 | async run() { 34 | await this.getSlides(); 35 | this.render(); 36 | this.mounted(); 37 | } 38 | 39 | mounted() { 40 | Prism.highlightAll(); 41 | if (document.location.hash && document.querySelector(document.location.hash)) { 42 | this.goTo(document.location.hash); 43 | } else { 44 | window.scrollTo(0, 0); 45 | } 46 | this.el.addEventListener('scroll', this.handleScroll); 47 | this.handleScroll(); 48 | window.addEventListener('keyup', this.handleKeyStroke); 49 | this.timeLeft = 20 * 60; 50 | } 51 | 52 | startClock() { 53 | this.displayClock(); 54 | if (! this.timer) { 55 | this.timer = window.setInterval(() => { 56 | if (this.timeLeft > 0) { 57 | this.timeLeft-= 1; 58 | } 59 | this.displayClock(); 60 | }, 1000); 61 | } 62 | } 63 | 64 | 65 | displayClock() { 66 | const timeLeft = this.timeLeft || 0; 67 | document.querySelector('.clock').textContent = 68 | `${(timeLeft / 60)|0}:${(timeLeft % 60).toString().padStart(2, '0')}`; 69 | } 70 | 71 | getScrollPositions() { 72 | const scrollPositions = [ 73 | ...document.querySelectorAll('.slide') 74 | ].map(slide => { 75 | const rect = slide.getBoundingClientRect(); 76 | return rect.top; 77 | }).map(Math.round); 78 | return scrollPositions; 79 | } 80 | 81 | getScrollIndex() { 82 | const h = document.body.getBoundingClientRect().height; 83 | return this.getScrollPositions().findIndex(x => x > -h + 1 && x < h); 84 | } 85 | 86 | handleScroll() { 87 | const currentPos = this.getScrollIndex(); 88 | const hash = '#slide' + currentPos; 89 | if (hash !== document.location.hash) { 90 | history.replaceState(null, null, hash); 91 | } 92 | const currentDot = document.querySelector('li.current'); 93 | const newDot = document.querySelectorAll('nav li')[currentPos]; 94 | if (newDot !== currentDot) { 95 | if (currentDot) { 96 | currentDot.setAttribute('class', ''); 97 | } 98 | if (newDot) { 99 | newDot.setAttribute('class', 'current'); 100 | } 101 | if (currentPos >= 1) { 102 | this.startClock(); 103 | } 104 | } 105 | } 106 | 107 | handleKeyStroke(e) { 108 | const SPACE = 32; 109 | const ENTER = 13; 110 | const LEFT = 37; 111 | const RIGHT = 39; 112 | if (e.keyCode === SPACE || e.keyCode === ENTER || e.keyCode === RIGHT) { 113 | this.startClock(); 114 | this.nextSlide(); 115 | return; 116 | } 117 | if (e.keyCode === LEFT) { 118 | this.prevSlide(); 119 | return; 120 | } 121 | e.preventDefault(); 122 | } 123 | 124 | goTo(hash) { 125 | const hashElement = document.querySelector(hash); 126 | if (hashElement) { 127 | const position = Math.round(hashElement.getBoundingClientRect().top + this.el.scrollTop); 128 | this.el.scrollTo(0, position); 129 | if (hash !== document.location.hash) { 130 | history.replaceState(null, null, hash) 131 | } 132 | } 133 | } 134 | 135 | nextSlide() { 136 | const numSlides = document.querySelectorAll('.slide').length; 137 | const current = this.getScrollIndex(); 138 | if (current + 1 < numSlides) { 139 | this.goTo('#slide' + (current + 1)); 140 | } 141 | } 142 | 143 | prevSlide() { 144 | const current = this.getScrollIndex(); 145 | if (current - 1 >= 0) { 146 | this.goTo('#slide' + (current - 1)); 147 | } 148 | } 149 | 150 | render() { 151 | this.el.innerHTML = ` 152 | 157 | 158 | ${this.slides.map((slide) => ` 159 |
160 | ${slide.content} 161 |
`).join('')} 162 | 163 |
164 | `; 165 | } 166 | 167 | } 168 | 169 | const app = new App(); 170 | app.run(); -------------------------------------------------------------------------------- /raymarching-shadows/shaders.mjs: -------------------------------------------------------------------------------- 1 | // identity function for the glsl-literal vscode extension 2 | // enables code highlighting for glsl code. 3 | const glsl = x => x 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | #define ITERS 64 8 | 9 | uniform float width; 10 | uniform float height; 11 | uniform float time; 12 | 13 | // Calculate cameras "orthonormal basis", i.e. its transform matrix components 14 | vec3 getCameraRayDir(vec2 uv, vec3 camPos, vec3 camTarget) { 15 | vec3 camForward = normalize(camTarget - camPos); 16 | vec3 camRight = normalize(cross(vec3(0.0, 1.0, 0.0), camForward)); 17 | vec3 camUp = normalize(cross(camForward, camRight)); 18 | 19 | float fPersp = 2.0; 20 | vec3 vDir = normalize(uv.x * camRight + uv.y * camUp + camForward * fPersp); 21 | 22 | return vDir; 23 | } 24 | 25 | // distance function for a sphere 26 | float sphere(vec3 p, float r) { 27 | return length(p) - r; 28 | } 29 | 30 | float plane(vec3 p) { 31 | return p.y; 32 | } 33 | 34 | float addToScene(float a, float b) { 35 | return min(a, b); 36 | } 37 | 38 | float deformation(in vec3 pos) { 39 | return .0125 * 40 | sin(-time + pos.x * 35.0) * 41 | sin(-time * 2.0 - pos.z * 37.0); 42 | } 43 | 44 | vec3 repeatXZ(in vec3 pos) { 45 | return vec3( fract(pos.x+0.5)-0.5, pos.y, fract(pos.z+0.5) - 0.5); 46 | } 47 | 48 | float scene(in vec3 pos) { 49 | float t = 1e7; 50 | t = addToScene(t, sphere(repeatXZ(pos - vec3(0, .6 + sin(time*.5 + pos.x / 4.0 + pos.x / 4.0) * .5, 0)), .3 + deformation(pos) + .1 * sin(time * .1))); 51 | t = addToScene(t, plane(pos)); 52 | return t; 53 | } 54 | 55 | 56 | // cast a ray along a direction and return 57 | // the distance to the first thing it hits 58 | // if nothing was hit, return -1 59 | float castRay(vec3 rayOrigin, vec3 rayDir) 60 | { 61 | float t = 0.0; // Stores current distance along ray 62 | for (int i = 0; i < ITERS; i++) 63 | { 64 | float res = scene(rayOrigin + rayDir * t); 65 | if (res < (0.0005*t)) 66 | { 67 | return t; 68 | } 69 | t += res; 70 | } 71 | return -1.0; 72 | } 73 | 74 | // I tried to achieve a somewhat cloudy background 75 | // maybe better with perlin noise. 76 | vec3 background(vec3 rayDir) { 77 | return vec3(0.2, 0.1, 0.00) * (sin(time *.1 + rayDir.x * rayDir.z * 10.0) * 78 | sin(time *.2 + rayDir.y * rayDir.z * 15.0) * .1 + rayDir.y * .7) * .5; 79 | } 80 | 81 | // calculate normal: 82 | vec3 calcNormal(vec3 pos) { 83 | // Center sample 84 | float c = scene(pos); 85 | // Use offset samples to compute gradient / normal 86 | vec2 eps_zero = vec2(0.001, 0.0); 87 | return normalize(vec3( scene(pos + eps_zero.xyy), scene(pos + eps_zero.yxy), scene(pos + eps_zero.yyx) ) - c); 88 | } 89 | 90 | // gamma correction 91 | vec3 gammaCorrect(vec3 col) { 92 | return pow(col, vec3(1.0 / 2.2)); 93 | } 94 | 95 | // https://www.iquilezles.org/www/articles/rmshadows/rmshadows.htm 96 | float softshadow(in vec3 ro, in vec3 rd, float mint, float maxt, float k) { 97 | float res = 1.0; 98 | float ph = 1e10; 99 | float t = mint; 100 | for (int i=0; i < ITERS; i++) { 101 | float h = scene(ro + rd * t); 102 | float y = h * h / (2.0 * ph); 103 | float d = sqrt(h * h - y * y); 104 | res = min(res, k * d / max(0.0, t - y) ); 105 | ph = h; 106 | if (res < 5e-4 || t >= maxt) { 107 | break; 108 | } 109 | t += h; 110 | } 111 | return clamp(res, 0.0, 1.0); 112 | } 113 | 114 | // calculate ambient occlusion 115 | float ambientOcclusion( in vec3 pos, in vec3 nor ) 116 | { 117 | float occ = 0.0; 118 | float sca = 1.0; 119 | for(int i=0; i<5; i++) 120 | { 121 | float h = 0.001 + 0.15*float(i)/4.0; 122 | float d = scene( pos + h*nor ); 123 | occ += (h-d)*sca; 124 | sca *= 0.95; 125 | } 126 | return clamp( 1.0 - 1.5*occ, 0.0, 1.0 ); 127 | } 128 | 129 | 130 | // Visualize depth based on the distance 131 | vec3 render(vec3 rayOrigin, vec3 rayDir) 132 | { 133 | float t = castRay(rayOrigin, rayDir); 134 | if (t == -1.0) { 135 | return background(rayDir); 136 | } 137 | 138 | vec3 material = vec3(1.0, 0.5, 0.2); 139 | 140 | // key light 141 | vec3 pos = rayOrigin + rayDir * t; 142 | vec3 nor = calcNormal(pos); 143 | vec3 lig = normalize(vec3(5.0, 10.0, 5.6)); 144 | float dif = clamp(dot(nor, lig), 0.0, 1.0) * softshadow(pos, lig, 0.00001, 3.0, 32.0); 145 | 146 | vec3 col = material * 4.0 * dif * vec3(1.00,0.70,0.5); 147 | 148 | // ambient light 149 | float occ = ambientOcclusion(pos, nor); 150 | float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 ); 151 | col += material * amb * occ * vec3(0.08, 0.04, 0); 152 | 153 | // fog 154 | col *= exp( -0.0005*t*t*t ); 155 | return col; 156 | } 157 | 158 | // normalize coords and correct for aspect ratio 159 | vec2 normalizeScreenCoords() { 160 | float aspectRatio = width / height; 161 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 162 | result.x *= aspectRatio; 163 | return result; 164 | } 165 | 166 | float rand() { 167 | return fract(sin(dot(gl_FragCoord.xy + sin(time),vec2(12.9898,78.233))) * 43758.5453); 168 | } 169 | 170 | void main() { 171 | vec3 camPos = vec3(3.5 + sin(time * .1) * 2.0, 3.0 + .5 * cos(time *.2), -3.0); 172 | vec3 camTarget = vec3(0); 173 | vec2 uv = normalizeScreenCoords(); 174 | vec3 rayDir = getCameraRayDir(uv, camPos, camTarget); 175 | vec3 col = render(camPos, rayDir); 176 | gl_FragColor = vec4(gammaCorrect(mix(col, vec3(rand()), .0125)), 1.0); 177 | } 178 | ` 179 | 180 | export const vert = glsl` 181 | precision highp float; 182 | attribute vec2 position; 183 | 184 | void main() { 185 | gl_Position = vec4(position, 0.0, 1.0); 186 | } 187 | ` -------------------------------------------------------------------------------- /shape-morph-3d/shaders.mjs: -------------------------------------------------------------------------------- 1 | // identity function for the glsl-literal vscode extension 2 | // enables code highlighting for glsl code. 3 | const glsl = x => x 4 | 5 | export const frag = glsl` 6 | precision highp float; 7 | #define ITERS 64 8 | 9 | uniform float width; 10 | uniform float height; 11 | uniform float time; 12 | 13 | // Calculate cameras "orthonormal basis", i.e. its transform matrix components 14 | vec3 getCameraRayDir(vec2 uv, vec3 camPos, vec3 camTarget) { 15 | vec3 camForward = normalize(camTarget - camPos); 16 | vec3 camRight = normalize(cross(vec3(0.0, 1.0, 0.0), camForward)); 17 | vec3 camUp = normalize(cross(camForward, camRight)); 18 | 19 | float fPersp = 2.0; 20 | vec3 vDir = normalize(uv.x * camRight + uv.y * camUp + camForward * fPersp); 21 | 22 | return vDir; 23 | } 24 | 25 | // distance function for a sphere 26 | float sphere(vec3 p, float r) { 27 | return length(p) - r; 28 | } 29 | 30 | float box(vec3 p, vec3 b) 31 | { 32 | vec3 q = abs(p) - b; 33 | return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); 34 | } 35 | 36 | float plane(vec3 p) { 37 | return p.y; 38 | } 39 | 40 | float addToScene(float a, float b) { 41 | return min(a, b); 42 | } 43 | 44 | float deformation(in vec3 pos) { 45 | return .00625 * 46 | sin(-time * .3 + pos.x * 15.0) * 47 | sin(-time * .3 + pos.z * 15.0) * 48 | sin(-time * .3 - pos.y * 15.0); 49 | } 50 | 51 | vec3 repeatXZ(in vec3 pos) { 52 | return vec3( fract(pos.x+0.5)-0.5, pos.y, fract(pos.z+0.5) - 0.5); 53 | } 54 | 55 | float scene(in vec3 pos) { 56 | float t = 1e7; 57 | float b1 = box(pos - vec3(-1.5, 1.2, 0), vec3(.8)); 58 | float s1 = sphere(pos - vec3(-1.5, 1.2, 0), .5); 59 | float b2 = box(pos - vec3(1.5, 1.2, 0), vec3(.8)); 60 | float s2 = sphere(pos - vec3(1.5, 1.2, .6), .5); 61 | 62 | t = addToScene(t, mix(b1, s1, .5 + .5 * sin(time * .2)) + deformation(pos)); 63 | t = addToScene(t, mix(b2, s2, .5 + .5 * cos(time * .2)) + deformation(pos)); 64 | 65 | //t = addToScene(t, sphere(repeatXZ(pos - vec3(0, .6 + sin(time*.5 + pos.x / 4.0 + pos.x / 4.0) * .5, 0)), .3 + deformation(pos) + .1 * sin(time * .1))); 66 | //t = addToScene(t, box(pos - vec3(0, 1.2, 0), vec3(.8)) + deformation(pos)); 67 | t = addToScene(t, plane(pos)); 68 | return t; 69 | } 70 | 71 | 72 | // cast a ray along a direction and return 73 | // the distance to the first thing it hits 74 | // if nothing was hit, return -1 75 | float castRay(vec3 rayOrigin, vec3 rayDir) 76 | { 77 | float t = 0.0; // Stores current distance along ray 78 | for (int i = 0; i < ITERS; i++) 79 | { 80 | float res = scene(rayOrigin + rayDir * t); 81 | if (res < (0.0005*t)) 82 | { 83 | return t; 84 | } 85 | t += res; 86 | } 87 | return -1.0; 88 | } 89 | 90 | // I tried to achieve a somewhat cloudy background 91 | // maybe better with perlin noise. 92 | vec3 background(vec3 rayDir) { 93 | return vec3(0.2, 0.1, 0.00) * (sin(time *.1 + rayDir.x * rayDir.z * 10.0) * 94 | sin(time *.2 + rayDir.y * rayDir.z * 15.0) * .1 + rayDir.y * .7) * .5; 95 | } 96 | 97 | // calculate normal: 98 | vec3 calcNormal(vec3 pos) { 99 | // Center sample 100 | float c = scene(pos); 101 | // Use offset samples to compute gradient / normal 102 | vec2 eps_zero = vec2(0.001, 0.0); 103 | return normalize(vec3( scene(pos + eps_zero.xyy), scene(pos + eps_zero.yxy), scene(pos + eps_zero.yyx) ) - c); 104 | } 105 | 106 | // gamma correction 107 | vec3 gammaCorrect(vec3 col) { 108 | return pow(col, vec3(1.0 / 2.2)); 109 | } 110 | 111 | // https://www.iquilezles.org/www/articles/rmshadows/rmshadows.htm 112 | float softshadow(in vec3 ro, in vec3 rd, float mint, float maxt, float k) { 113 | float res = 1.0; 114 | float ph = 1e10; 115 | float t = mint; 116 | for (int i=0; i < ITERS; i++) { 117 | float h = scene(ro + rd * t); 118 | float y = h * h / (2.0 * ph); 119 | float d = sqrt(h * h - y * y); 120 | res = min(res, k * d / max(0.0, t - y) ); 121 | ph = h; 122 | if (res < 5e-4 || t >= maxt) { 123 | break; 124 | } 125 | t += h; 126 | } 127 | return clamp(res, 0.0, 1.0); 128 | } 129 | 130 | // calculate ambient occlusion 131 | float ambientOcclusion( in vec3 pos, in vec3 nor ) 132 | { 133 | float occ = 0.0; 134 | float sca = 1.0; 135 | for(int i=0; i<5; i++) 136 | { 137 | float h = 0.001 + 0.15*float(i)/4.0; 138 | float d = scene( pos + h*nor ); 139 | occ += (h-d)*sca; 140 | sca *= 0.95; 141 | } 142 | return clamp( 1.0 - 1.5*occ, 0.0, 1.0 ); 143 | } 144 | 145 | 146 | // Visualize depth based on the distance 147 | vec3 render(vec3 rayOrigin, vec3 rayDir) 148 | { 149 | float t = castRay(rayOrigin, rayDir); 150 | if (t == -1.0) { 151 | return background(rayDir); 152 | } 153 | 154 | vec3 material = vec3(0.7, 0.6, 1.0); 155 | 156 | // key light 157 | vec3 pos = rayOrigin + rayDir * t; 158 | vec3 nor = calcNormal(pos); 159 | vec3 lig = normalize(vec3(5.0, 10.0, 5.6)); 160 | float dif = clamp(dot(nor, lig), 0.0, 1.0) * softshadow(pos, lig, 0.00001, 3.0, 32.0); 161 | 162 | vec3 col = material * 4.0 * dif * vec3(1.00,0.70,0.5); 163 | 164 | // ambient light 165 | float occ = ambientOcclusion(pos, nor); 166 | float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 ); 167 | col += material * amb * occ * vec3(0.08, 0.04, 0); 168 | 169 | // fog 170 | col *= exp( -0.0005*t*t*t ); 171 | return col; 172 | } 173 | 174 | // normalize coords and correct for aspect ratio 175 | vec2 normalizeScreenCoords() { 176 | float aspectRatio = width / height; 177 | vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); 178 | result.x *= aspectRatio; 179 | return result; 180 | } 181 | 182 | float rand() { 183 | return fract(sin(dot(gl_FragCoord.xy + sin(time),vec2(12.9898,78.233))) * 43758.5453); 184 | } 185 | 186 | void main() { 187 | vec3 camPos = vec3(3.5 + sin(time * .1) * 2.0, 3.0, 3.0 + .5 * cos(time *.1) * 1.0); 188 | vec3 camTarget = vec3(0); 189 | vec2 uv = normalizeScreenCoords(); 190 | vec3 rayDir = getCameraRayDir(uv, camPos, camTarget); 191 | vec3 col = render(camPos, rayDir); 192 | gl_FragColor = vec4(gammaCorrect(mix(col, vec3(rand()), .0125)), 1.0); 193 | } 194 | ` 195 | 196 | export const vert = glsl` 197 | precision highp float; 198 | attribute vec2 position; 199 | 200 | void main() { 201 | gl_Position = vec4(position, 0.0, 1.0); 202 | } 203 | ` -------------------------------------------------------------------------------- /talk-webgl/SLIDES.md: -------------------------------------------------------------------------------- 1 | # Creative Coding with WebGL 2 | 3 | --- 4 | 5 | # Creative Coding with WebGL 6 | 7 | ![Lea Rosema](https://avatars0.githubusercontent.com/u/949950?s=460&v=4) 8 | 9 | ## Hi! I'm Lea Rosema 10 | 11 | Junior Frontend Developer 12 | 13 | SinnerSchrader 14 | 15 | --- 16 | 17 | # What is WebGL? 18 | 19 | - it's not a 3D engine 20 | - it's about drawing points, lines, triangles 21 | - it's a low-level API to run code on the GPU 22 | 23 | --- 24 | 25 | # Shaders 26 | 27 | ![A triangle](tri.png) 28 | 29 | ## Drawing shapes with shaders 30 | 31 | The vertex shader computes vertex positions 32 | 33 | The fragment Shader handles rasterization 34 | 35 | --- 36 | 37 | # GL Shader Language 38 | 39 | ## How does it look like? 40 | 41 | - GPU-specific language GL Shader language (GLSL) 42 | - It's like C++ with a `void main()` 43 | - ...but with built-in datatypes and functions useful for 2D/3D 44 | 45 | --- 46 | 47 | # Vertex shader code 48 | 49 | ```glsl 50 | attribute vec3 position; 51 | 52 | void main() { 53 | gl_Position = vec4(position, 1.0); 54 | } 55 | ``` 56 | 57 | - via the position attribute, the shader gets data from a buffer 58 | - the shader is run for each position in the position buffer 59 | - the vertex position is set via `gl_Position` 60 | 61 | --- 62 | 63 | # Fragment shader code 64 | 65 | ```glsl 66 | precision highp float; 67 | 68 | void main() { 69 | vec2 p = gl_FragCoord.xy; 70 | gl_FragColor = vec4(1.0, 0.5, 0, 1.0); 71 | } 72 | ``` 73 | 74 | - The fragment shader is run for each fragment (pixel) 75 | - the pixel coordinate can be read from `gl_FragCoord` 76 | - the output color is set in `gl_FragColor` 77 | 78 | --- 79 | 80 | # Passing Data from JS 81 | 82 | - `attribute`: the vertex shader pulls a value from a buffer and stores it in here 83 | - `uniform`: pass variables you set in JS before you execute the shader 84 | - `varying`: pass values from the vertex to the fragment shader 85 | 86 | --- 87 | 88 | # Let's try GLSL 89 | 90 | ## [DEMO: Draw a triangle](https://codepen.io/terabaud/pen/OKVpYV?editors=0010) 91 | 92 | --- 93 | 94 | # GL Shader Language 95 | 96 | ## Datatypes 97 | 98 | - primitives (`bool`, `int`, `float`) 99 | - vectors (`vec2`, `vec3`, `vec4`) 100 | - matrices (`mat2`, `mat3`, `mat4`) 101 | - texture data (`sampler2D`) 102 | 103 | --- 104 | 105 | # GL Shader Language 106 | 107 | ## Cool built-in functions 108 | 109 | - `sin`, `cos`, `atan` 110 | - Linear Interpolation (`mix`) 111 | - Vector arithmetics (`+`, `-`, `*`, `/`, `dot`, `cross`, `length`) 112 | - Matrix arithmetics (`+`, `-`, `*`) 113 | 114 | --- 115 | 116 | # Running it in JS 117 | 118 | ## Get the WebGL Context 119 | 120 | ```js 121 | const gl = canvas.getContext("webgl"); 122 | ``` 123 | 124 | ...just like initializing a 2D canvas 125 | 126 | --- 127 | 128 | # Running it in JS 129 | 130 | ## Compile the Shaders 131 | 132 | ```js 133 | const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 134 | gl.shaderSource(fragmentShader, fragmentCode); 135 | gl.compileShader(fragmentShader); 136 | 137 | const vertexShader = gl.createShader(gl.VERTEX_SHADER); 138 | gl.shaderSource(vertexShader, vertexCode); 139 | gl.compileShader(vertexShader); 140 | ``` 141 | 142 | Like in C++, you have to compile your shaders first. 143 | 144 | --- 145 | 146 | # Running it in JS 147 | 148 | ## Create the program 149 | 150 | ```js 151 | const program = gl.createProgram(); 152 | gl.attachShader(program, vertexShader); 153 | gl.attachShader(program, fragmentShader); 154 | gl.linkProgram(); 155 | ``` 156 | 157 | Also like in C++, the two shaders are linked into a `WebGLProgram`. 158 | 159 | You can check if the program is valid via `program.validateProgram()` 160 | 161 | --- 162 | 163 | # Running it in JS 164 | 165 | ## Defining attributes for the vertex shader 166 | 167 | ```js 168 | const positionLoc = this.gl.getAttribLocation(program, "position"); 169 | this.gl.enableVertexAttribArray(positionLoc); 170 | ``` 171 | 172 | Activate your attribute via `enableVertexAttribArray` 173 | 174 | --- 175 | 176 | # Running it in JS 177 | 178 | ## Assign a buffer to the attribute 179 | 180 | ```js 181 | // provide 2D data for a triangle 182 | const data = [-1, -1, -1, 1, 1, -1]; 183 | const buffer = gl.createBuffer(); 184 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 185 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); 186 | ``` 187 | 188 | Create a buffer and provide data in a `Float32Array` 189 | 190 | --- 191 | 192 | # Running it in JS 193 | 194 | ## Set the attribute pointer 195 | 196 | ```js 197 | const recordSize = 2; 198 | const stride = 0; // 0 = advance through the buffer by recordSize * sizeof(data type) 199 | const offset = 0; // the starting point in the buffer 200 | const type = gl.FLOAT; // data type 201 | const normalized = false; // normalize the data (unused for gl.FLOAT) 202 | gl.vertexAttribPointer( 203 | positionLoc, 204 | recordSize, 205 | type, 206 | normalized, 207 | stride, 208 | offset 209 | ); 210 | ``` 211 | 212 | Assign an attribute to a buffer 213 | 214 | --- 215 | 216 | # Running it in JS 217 | 218 | ## Passing uniform variables 219 | 220 | ```js 221 | const uTime = gl.getUniformLocation(program, "time"); 222 | gl.uniform1f(uTime, tickCount); 223 | ``` 224 | 225 | - Possible types: floats, ints, bools, vectors, matrices 226 | - Pass variables from JavaScript to WebGL 227 | - For example: pass the screen resolution, elapsed time, mouse position 228 | 229 | --- 230 | 231 | # Running it in JS 232 | 233 | ## Draw 234 | 235 | ```js 236 | createBuffers(); 237 | setAttributes(); 238 | 239 | function animLoop(time = 0) { 240 | setUniforms(); 241 | gl.drawArrays(gl.TRIANGLES); 242 | requestAnimationFrame(animLoop); 243 | } 244 | 245 | animLoop(); 246 | ``` 247 | 248 | --- 249 | 250 | # Useful GLSL functions 251 | 252 | ```glsl 253 | // normalize coords and set (0, 0) to center 254 | vec2 coords() { 255 | float vmin = min(width, height); 256 | return vec2((gl_FragCoord.x - width * .5) / vmin, 257 | (gl_FragCoord.y - height * .5) / vmin); 258 | } 259 | 260 | //rotate 261 | vec2 rotate(vec2 p, float a) { 262 | return vec2(p.x * cos(a) - p.y * sin(a), 263 | p.x * sin(a) + p.y * cos(a)); 264 | } 265 | 266 | //repeat 267 | vec2 repeat(in vec2 p, in vec2 c) { 268 | return mod(p, c) - 0.5 * c; 269 | } 270 | ``` 271 | 272 | --- 273 | 274 | # Putting it all together 275 | 276 | - [DEMO](https://codepen.io/terabaud/pen/eqNjjY?editors=0010) 277 | 278 | --- 279 | 280 | # Thank you 👩‍💻 281 | 282 | ## Feedback and Questions 283 | 284 | - talk to me afterwards 🙂 285 | - DM me on twitter (`@terabaud`) 286 | - or file an issue in my repo 287 | 288 | ## Resources 289 | 290 | - [https://github.com/terabaud/hello-webgl/](https://github.com/terabaud/hello-webgl/) 291 | - [https://terabaud.github.io/hello-webgl/talk-webgl/](https://terabaud.github.io/hello-webgl/talk-webgl/) 292 | - [https://github.com/vaneenige/phenomenon/](https://github.com/vaneenige/phenomenon/) 293 | - [https://webglfundamentals.org](https://webglfundamentals.org) 294 | - [https://thebookofshaders.com/](https://thebookofshaders.com/) 295 | -------------------------------------------------------------------------------- /minigl.js: -------------------------------------------------------------------------------- 1 | // minigl.js - minimal gl utils - (c) 2018 Lea Rosema 2 | // 3 | // Provides some simple helper functions for webgl. 4 | // 5 | // Includes sugar around the WebGL API, 6 | // vector/matrix operations and perspective 7 | // utility functions. 8 | // 9 | // Notice: 10 | // 1. Fun project with esoteric coding style. 11 | // Mean ugly dirty code by design :D 12 | // 2. Excerpts out of it might be useful. 13 | // In #js1k/#js13kgames projects, for example. 14 | // 3. Work in progress, far from complete. 15 | 16 | // Laziness 17 | Object.getOwnPropertyNames(Math).map(function(p) { 18 | window[p] = Math[p]; 19 | }); 20 | 21 | // a square 22 | function sq(s){ 23 | s=s||1 24 | return[s,s,0,-s,s,0,s,-s,0,-s,-s,0] 25 | } 26 | 27 | // a cube 28 | function qb(s,r,f,v,i,j){ 29 | f="013321126651236673374403014145546674" 30 | v="000100110010001101111011" 31 | for(r=[],s=s||1,i=f.length;i--;) 32 | Array.prototype.push.apply(r,function(j){return[ 33 | v[ j ]==1?s:-s, 34 | v[j+1]==1?s:-s, 35 | v[j+2]==1?s:-s, 36 | ]}(f[i]*3)) 37 | return r 38 | } 39 | 40 | // Identity Matrix 41 | function mI(){ 42 | return[1,0,0,0, 43 | 0,1,0,0, 44 | 0,0,1,0, 45 | 0,0,0,1] 46 | } 47 | 48 | // Transformation matrix 49 | function mT(x,y,z,r){ 50 | return r=mI(),r.splice(12,3,x,y,z),r 51 | } 52 | 53 | // Scale matrix 54 | function mS(x,y,z){ 55 | return[x,0,0,0, 56 | 0,y,0,0, 57 | 0,0,z,0, 58 | 0,0,0,1] 59 | } 60 | 61 | // Matrix multiplication 62 | function mX(a,b,c,i,j,k,l){c=[] 63 | for(i=l=sqrt(a.length)|0;i--;) 64 | for(k=l;k--;) 65 | for(j=l;j--;){ 66 | if (!c[i+k*l])c[i+k*l]=0 67 | c[i+k*l]+=a[i+j*l]*b[j+k*l] 68 | } 69 | return c 70 | } 71 | 72 | // Matrix determinant (until 3x3), todo: gaussian elimination for >3x3 ;) 73 | function mDet(a,n){ 74 | if(n=sqrt(a)<4) 75 | return[1,a[0],a[0]*a[3]-a[2]*a[1], 76 | a[0]*a[4]*a[8]+a[3]*a[7]*a[2]+a[6]*a[1]*a[5]- 77 | a[6]*a[1]*a[5]-a[3]*a[1]*a[8]-a[0]*a[7]*a[5]][n|0] 78 | } 79 | 80 | // Matrix for rotation around x-axis 81 | function mRx(a){ 82 | return[1,0,0,0, 83 | 0,cos(a),sin(a),0, 84 | 0,-sin(a),cos(a),0, 85 | 0,0,0,1] 86 | } 87 | 88 | // Matrix for rotation around y-axis 89 | function mRy(a){ 90 | return[cos(a),0,-sin(a),0, 91 | 0,1,0,0, 92 | sin(a),0,cos(a),0, 93 | 0,0,0,1] 94 | } 95 | 96 | // Matrix for rotation around z-axis 97 | function mRz(a){ 98 | return[cos(a),sin(a),0,0, 99 | -sin(a),cos(a),0,0, 100 | 0,0,1,0, 101 | 0,0,0,1] 102 | } 103 | 104 | 105 | // Vector cross product 106 | function vX(a,b){ 107 | return[a[1]*b[2]-a[2]*b[1], 108 | a[2]*b[0]-a[0]*b[2], 109 | a[0]*b[1]-a[1]*b[0]] 110 | } 111 | 112 | // Vector length 113 | function vL(v){ 114 | return sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) 115 | } 116 | 117 | // Unit vector 118 | function v1(v){ 119 | return l=vL(v),[v[0]/l,v[1]/l,v[2]/l] 120 | } 121 | 122 | // glOrtho(left, right, bottom, top, zNear, zFar) 123 | function ortho(l,r,b,t,zn,zf,tx,ty,tz){ 124 | return tx=-(r+l)/(r-l), 125 | ty=-(t+b)/(t-b), 126 | tz=-(zf+zn)/(zf-zn), 127 | [2/(r-l),0,0, 128 | 0,0,2/(t-b),0, 129 | 0,0,0,-2/(zf-zn), 130 | 0,tx,ty,tz,1] 131 | } 132 | 133 | // glFrustum(left, right, bottom, top, zNear, zFar) 134 | function frustum(l,r,b,t,zn,zf,t1,t2,t3,t4){ 135 | return t1=2*zn,t2=r-l,t3=t-b,t4=zf-zn, 136 | [t1/t2,0,0,0, 137 | 0,t1/t3,0,0, 138 | (r+l)/t2,(t+b)/t3,(-zf-zn)/t4,-1, 139 | 0,0,(-t1*zf)/t4,0] 140 | } 141 | 142 | // gluPerspective(fieldOfView, aspectRatio, zNear, zFar) 143 | function perspective(fovy,ar,zn,zf,x,y){ 144 | return y=zn*Math.tan(fovy*Math.PI/360), 145 | x=y*ar,frustum(-x,x,-y,y,zn,zf) 146 | } 147 | 148 | // gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) 149 | function lookAt(ex,ey,ez,cx,cy,cz,ux,uy,uz,c,u,x,y,z){ 150 | return c=[cx,cy,cz], 151 | u=[ux,uy,uz], 152 | z=v1([ex-cx,ey-cy,ez-cz]), 153 | x=v1(vX(u,z)),y=v1(vX(z,x)), 154 | [x[0],y[0],z[0],0, 155 | x[1],y[1],z[1],0, 156 | x[2],y[2],z[2],0, 157 | 0,0,0,1] 158 | } 159 | 160 | // create shader 161 | // $sh(domelement) 162 | function $sh(el,s){ 163 | s=g.createShader(/frag/.test(el.type)?g.FRAGMENT_SHADER:g.VERTEX_SHADER) 164 | g.shaderSource(s,el.textContent) 165 | g.compileShader(s) 166 | if (!g.getShaderParameter(s, g.COMPILE_STATUS)) { 167 | throw Error(el.id+": "+g.getShaderInfoLog(s)) 168 | } 169 | return s 170 | } 171 | 172 | // create or switch shader program 173 | // define: $prog(glContext, array of shader dom elements, [array of attributes]) 174 | // switch: $prog(glContext, program, [obj of uniforms], [obj of buffers]) 175 | function $prog(g,s,a,c,p,P,i){ 176 | window.g=g 177 | P="Program" 178 | if(/WebGLProgram/.test(s)){ 179 | // switch program 180 | g["use"+P](window.p=s) 181 | if(a&&a.length)for(i=a.length;i--;)$attr(a[i]) 182 | return p 183 | } 184 | // define program 185 | p=g["create"+P]() 186 | s.map(function(s){g.attachShader(p,$sh(s))}) 187 | g["link"+P](p) 188 | g["use"+P](p) 189 | // pollute the global namespace a bit. 190 | window.p=p 191 | // set attributes 192 | if(a&&a.length)for(i=a.length;i--;)$attr(a[i]) 193 | return p 194 | } 195 | 196 | // create buffer 197 | // $buf([1,0,1,0,...]) 198 | function $buf(v,b){ 199 | b=g.createBuffer() 200 | g.bindBuffer(g.ARRAY_BUFFER, b) 201 | g.bufferData(g.ARRAY_BUFFER, new Float32Array(v), g.STATIC_DRAW) 202 | return b 203 | } 204 | 205 | // get reference to Attribute 206 | // $attr("name") 207 | function $attr(l,r){ 208 | return r=g.getAttribLocation(p, l),g.enableVertexAttribArray(r),r 209 | } 210 | 211 | function $attrOff(l,r){ 212 | return r=g.getAttribLocation(p, l),g.disableVertexAttribArray(r),r 213 | } 214 | 215 | 216 | // bind attribute to buffer 217 | // $bind(attr, buffer) 218 | function $bind(a,b,s,t,n) { 219 | t=t||g.FLOAT 220 | n=n||false 221 | g.bindBuffer(g.ARRAY_BUFFER,b) 222 | g.vertexAttribPointer(typeof a=="string"?g.getAttribLocation(p,a):a,s,t,n,0,0) 223 | } 224 | 225 | // set uniform matrix 226 | function $uniM(n,d,l){ 227 | l=g.getUniformLocation(p,n) 228 | g["uniformMatrix"+sqrt(d.length)+"fv"](l,false,new Float32Array(d)) 229 | return l 230 | } 231 | 232 | // set uniform vector 233 | function $uniV(n,d,l){ 234 | l=g.getUniformLocation(p,n) 235 | g["uniform"+d.length+"fv"](l,new Float32Array(d)) 236 | return l 237 | } 238 | 239 | // set uniform float 240 | function $uni(n,d,l){ 241 | l=g.getUniformLocation(p,n) 242 | if(typeof(d)=="number")g.uniform1f(l,d) 243 | return l 244 | } 245 | 246 | // Create texture from image 247 | function $tex2d(img,p,i,t){ 248 | p=p||[ 249 | g.TEXTURE_2D, g.TEXTURE_WRAP_S, g.CLAMP_TO_EDGE, 250 | g.TEXTURE_2D, g.TEXTURE_WRAP_T, g.CLAMP_TO_EDGE, 251 | g.TEXTURE_2D, g.TEXTURE_MIN_FILTER, g.LINEAR, 252 | g.TEXTURE_2D, g.TEXTURE_MAG_FILTER, g.LINEAR 253 | ] 254 | t=g.createTexture() 255 | g.bindTexture(g.TEXTURE_2D,t) 256 | for(i=(p.length/3)|0;i--;)gl.texParameteri(p[i*3],p[i*3+1],p[i*3+2]) 257 | g.texImage2D(g.TEXTURE_2D, 0, g.RGBA, g.RGBA, g.UNSIGNED_BYTE, img) 258 | } 259 | 260 | // Simple image preloader 261 | // load images (u = array of urls), call back f([array of image objects]) when ready 262 | function $load(u,f,i,j,k){ 263 | for(k=j=u.length;j--;) 264 | i=new Image(), 265 | i.src=u[j], 266 | i.onload=function(){if(!--k)f(u)}, 267 | u[j]=i 268 | } 269 | 270 | -------------------------------------------------------------------------------- /image-slider/components/image-slider.mjs: -------------------------------------------------------------------------------- 1 | import GLea from '../../lib/glea/glea.mjs'; 2 | import { easeInOutCubic } from './easings.mjs'; 3 | import { frag, vert } from './shaders.mjs'; 4 | 5 | /** 6 | * JavaScript implementation of GLSL clamp, clamps the value to [min, max] 7 | * @param {number} value value to be clamped 8 | * @param {number} min minimum value 9 | * @param {number} max maximum value 10 | */ 11 | const clamp = (value, min, max) => Math.min(Math.max(value, min), max); 12 | 13 | /** 14 | * Identity function for html-literal (use with lit-html vscode extension) 15 | */ 16 | const html = x => x; 17 | 18 | export default class ImageSlider extends HTMLElement { 19 | 20 | constructor() { 21 | super(); 22 | this._animationDelay = 1000; 23 | this.imageContainer = this.querySelector('[slot]'); 24 | this.animationLoop = this.animationLoop.bind(this); 25 | this.onContextLost = this.onContextLost.bind(this); 26 | this.onContextRestored = this.onContextRestored.bind(this); 27 | this.attachShadow({ mode: 'open' }); 28 | this.initialized = false; 29 | this.prevIndex = 1; 30 | this.indexChangedTime = 0; 31 | } 32 | 33 | /** 34 | * registers the custom web component 35 | * @returns {void} 36 | */ 37 | static register() { 38 | try { 39 | customElements.define('image-slider', ImageSlider); 40 | } catch (ex) { 41 | console.error('Custom elements are not supported.') 42 | } 43 | } 44 | 45 | /** 46 | * returns the attributes ovserved by the component 47 | * @returns {string[]} 48 | */ 49 | static get observedAttributes() { 50 | return ['index', 'autoplay', 'animation-delay']; 51 | } 52 | 53 | get animationDelay() { 54 | return _animationDelay; 55 | } 56 | 57 | /** 58 | * get autoplay 59 | */ 60 | get autoplay() { 61 | return this.hasAttribute('autoplay'); 62 | } 63 | 64 | /** 65 | * set autoplay attribute 66 | */ 67 | set autoplay(value) { 68 | if (Boolean(value) === true) { 69 | this.setAttribute('autoplay', 'autoplay'); 70 | } else { 71 | this.removeAttribute('autoplay'); 72 | } 73 | } 74 | 75 | /** 76 | * set index attribute 77 | */ 78 | get index() { 79 | if (this.hasAttribute('index')) { 80 | const imageIndex = parseInt(this.getAttribute('index') || '1', 10); 81 | return isNaN(imageIndex) ? 1 : imageIndex; 82 | } 83 | return null; 84 | } 85 | 86 | /** 87 | * get index attribute 88 | */ 89 | set index(value) { 90 | if (value === null || typeof value === 'undefined') { 91 | this.removeAttribute('index'); 92 | return; 93 | } 94 | if (typeof value !== 'number') { 95 | value = clamp(parseInt(value, 10), 1, this.images.length); 96 | if (isNaN(value)) { 97 | value = 1; 98 | } 99 | } 100 | this.setAttribute('index', value.toString(10)); 101 | } 102 | 103 | /** 104 | * get fading state 105 | * @returns {boolean} true if fading 106 | */ 107 | get fading() { 108 | if (this.sameTextures) return false; 109 | return performance.now() - this.indexChangedTime < this._animationDelay; 110 | } 111 | 112 | 113 | /** 114 | * Called when an attribute is changed 115 | * 116 | * @param {string} name 117 | * @param {string} oldValue 118 | * @param {string} newValue 119 | */ 120 | attributeChangedCallback(name, oldValue, newValue) { 121 | if (name === 'index' && oldValue !== null && oldValue !== newValue) { 122 | // keep track of the time the index is changed 123 | // so we can pass it to the shader 124 | this.indexChangedTime = performance.now(); 125 | const prev = parseInt(oldValue || '1', 10); 126 | this.prevIndex = isNaN(prev) ? 1 : prev; 127 | this.updateTextures(); 128 | } 129 | if (name === 'animation-delay') { 130 | const returnValue = parseInt(newValue, 10); 131 | this._animationDelay = isNaN(returnValue) ? 1000 : returnValue; 132 | } 133 | } 134 | 135 | /** 136 | * Load images 137 | * @returns {Promise} returns image objects when they are loaded 138 | */ 139 | async loadImages() { 140 | const imgs = [...this.imageContainer.querySelectorAll('img')]; 141 | const returnValue = await Promise.all(imgs.map(img => { 142 | return new Promise((resolve, reject) => { 143 | const image = new Image(); 144 | image.crossOrigin = 'anonymous'; 145 | image.src = img.src; 146 | image.onload = () => resolve(image); 147 | image.onerror = reject; 148 | }) 149 | })); 150 | this.indexChangedTime = performance.now(); 151 | return returnValue; 152 | } 153 | 154 | /** 155 | * load CSS 156 | * 157 | * @returns {Promise} css embedded in style tag 158 | */ 159 | async loadCss() { 160 | const cssResponse = await fetch('./components/image-slider.css'); 161 | const css = await cssResponse.text(); 162 | return ``; 163 | } 164 | 165 | /** 166 | * called when the component is attached to the dom 167 | */ 168 | async connectedCallback() { 169 | if (! this.initialized) { 170 | this.initialized = true; 171 | this.css = await this.loadCss(); 172 | this.images = await this.loadImages(); 173 | this.render(); 174 | this.canvas = this.shadowRoot.querySelector('canvas'); 175 | this.initWebGL(); 176 | this.canvas.addEventListener('webglcontextlost', this.onContextLost); 177 | this.canvas.addEventListener('webglcontextrestored', this.onContextRestored); 178 | } 179 | } 180 | 181 | /** 182 | * called when the component is detached from the dom 183 | */ 184 | disconnectedCallback() { 185 | cancelAnimationFrame(this.frame); 186 | this.canvas.removeEventListener('webglcontextlost', this.onContextLost); 187 | this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored); 188 | this.glea.destroy(); 189 | this.initialized = false; 190 | } 191 | 192 | /** 193 | * creates the WebGLRenderingContext, buffers, attributes, shaders and uploads images as textures 194 | */ 195 | initWebGL() { 196 | this.glea = new GLea({ 197 | canvas: this.canvas, 198 | shaders: [ 199 | GLea.fragmentShader(frag), 200 | GLea.vertexShader(vert) 201 | ], 202 | buffers: { 203 | 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) 204 | } 205 | }).create(); 206 | const { glea } = this; 207 | const { gl } = glea; 208 | const image1 = this.images[this.index - 1]; 209 | const image2 = this.images[this.index - 1]; 210 | this.sameTextures = true; 211 | this.texture1 = glea.createTexture(0); 212 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image1); 213 | 214 | this.texture2 = glea.createTexture(1); 215 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image2); 216 | 217 | glea.uniI('texture1', 0); 218 | glea.uniI('texture2', 1); 219 | 220 | glea.uniIV('textureSize1', [image1.width, image1.height]); 221 | glea.uniIV('textureSize2', [image2.width, image2.height]); 222 | 223 | glea.uni('animationStep', 0); 224 | 225 | window.addEventListener('resize', () => { 226 | glea.resize(); 227 | }); 228 | this.animationLoop(); 229 | } 230 | 231 | /** 232 | * upload images to WebGL textures 233 | */ 234 | updateTextures() { 235 | const { glea, images, index, prevIndex } = this; 236 | const { gl } = glea; 237 | if (this.texture1 && this.texture2) { 238 | const image1 = images[prevIndex - 1]; 239 | const image2 = images[index - 1]; 240 | this.sameTextures = (prevIndex === index); 241 | if (image1 && image1.complete) { 242 | glea.setActiveTexture(0, this.texture1); 243 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, image1); 244 | } 245 | if (image2 && image2.complete) { 246 | glea.setActiveTexture(1, this.texture2); 247 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, image2); 248 | } 249 | glea.uni('animationStep', 0); 250 | } 251 | } 252 | 253 | /** 254 | * Animation loop 255 | * @param {number} time something like performance.now() 256 | */ 257 | animationLoop(time = 0) { 258 | const { glea } = this; 259 | const { gl } = glea; 260 | const animationTime = (performance.now() - this.indexChangedTime) / this._animationDelay; 261 | if (animationTime <= 1) { 262 | const animationStep = this.sameTextures ? 0 : easeInOutCubic(clamp(animationTime, 0, 1)); 263 | glea.clear(); 264 | glea.uni('width', glea.width); 265 | glea.uni('height', glea.height); 266 | glea.uni('time', time * .005); 267 | glea.uni('animationStep', animationStep); 268 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 269 | } 270 | this.frame = requestAnimationFrame(this.animationLoop); 271 | } 272 | 273 | /** 274 | * ContextLost event 275 | * 276 | * @param {WebGLContextEvent} e 277 | */ 278 | onContextLost(e) { 279 | e.preventDefault(); 280 | cancelAnimationFrame(this.frame); 281 | } 282 | 283 | /** 284 | * ContextRestored event 285 | */ 286 | onContextRestored() { 287 | this.initWebGL(); 288 | } 289 | 290 | /** 291 | * render component's the shadow root 292 | */ 293 | render() { 294 | const { css } = this; 295 | this.shadowRoot.innerHTML = css + html` 296 | 297 | 298 | `; 299 | } 300 | 301 | } --------------------------------------------------------------------------------