├── .gitignore ├── .npmignore ├── README.md ├── assets └── fonts │ ├── SpaceGrotesk-Bold.woff │ ├── SpaceGrotesk-Light.woff │ ├── SpaceGrotesk-Medium.woff │ ├── SpaceGrotesk-Regular.woff │ └── SpaceGrotesk-SemiBold.woff ├── docs ├── cheat-sheet.md ├── cloning.md ├── command-line.md ├── exercises.md ├── images │ ├── pies.png │ ├── wall-1.png │ ├── wall-2.png │ ├── wall-sketch.png │ ├── xyz-1.png │ └── xyz-2.png └── installation.md ├── package-lock.json ├── package.json └── src ├── 2d ├── 00-pink-square.js ├── 01-grid.js ├── 02-grid-margin.js ├── 03-grid-random.js ├── 04-grid-colors.js ├── 05-grid-konami.js ├── 06-grid-painting.js ├── 07-advanced-wall-drawing.js ├── 08-grid-animation.js ├── 09-noise-lines.js └── 10-pie-charts.js ├── LICENSE.md ├── slides ├── computational-fur.js ├── slides-background-penplotter.js ├── slides-background.js ├── sol-lewitt-wall-drawing-273.js ├── three-concepts-meshes.js ├── three-concepts-two-meshes.js └── three-concepts.js └── webgl ├── 00-cube.js ├── 01-isometric.js ├── 02-cubes.js ├── 03-cubes-lighting.js ├── 04-cubes-animation.js ├── 05-seamless-loop.js ├── 06-shader.js ├── 07-shader-noise.js ├── 08-vertex-shader.js └── 09-blob.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | tmp/ 7 | *.mp4 8 | *.gif -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creative Coding & Generative Art with JavaScript 2 | 3 | This repository includes code & resources for students attending my *Generative Art & Creative Coding with JavaScript* workshops in 2018. 4 | 5 | # Contents 6 | 7 | - [Tools & Prerequisites](#tools--prerequisites) 8 | 9 | - [Installing the Prerequisites](#installing-the-prerequisites) 10 | 11 | - [Complete Installation Guide](#complete-installation-guide) 12 | 13 | - [Installing `canvas-sketch` CLI](#installing-canvas-sketch-cli) 14 | 15 | - [Command-Line Tips & Suggestions](#command-line-tips--suggestions) 16 | 17 | - [Cloning & Running Examples](#cloning--running-examples) 18 | 19 | - [Other Modules for Creative Coding](#other-modules-for-creative-coding) 20 | 21 | - [Cheat Sheet](#cheat-sheet) 22 | 23 | - [Exercises](#exercises) 24 | 25 | - [Further Reading](#further-reading) 26 | 27 | # Tools & Prerequisites 28 | 29 | Here is a list of tools, software and libraries that will be used during the workshop. 30 | 31 | | Tool | Documentation | Description | 32 | |---|---|---| 33 | | *Code Editor* | | A JavaScript code editor, [VSCode](https://code.visualstudio.com/) is recommended 34 | | *Browser* | | A modern browser, [Chrome](https://www.google.com/chrome/) is recommended 35 | | *Canvas API* | [[docs](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)] | The HTML5 `` API, built into all browsers 36 | | *Command-Line* | [[guide](./docs/command-line.md)] | A command-line application like Terminal (macOS) or [cmder](http://cmder.net/) (Windows) 37 | | [Node.js](https://nodejs.org/en/) (v8+) | [[docs](https://nodejs.org/dist/latest-v8.x/docs/api/)] | Used for running command-line JavaScript tools 38 | | [npm](https://npmjs.com/) (v5+) | [[docs](https://nodejs.org/dist/latest-v8.x/docs/api/)] | Used to install third-party dependencies and tools 39 | | [`canvas-sketch`](https://github.com/mattdesl/canvas-sketch/) | [[docs](https://github.com/mattdesl/canvas-sketch/tree/master/docs)] | A development tool & framework for Generative Art 40 | | [`canvas-sketch-util`](https://github.com/mattdesl/canvas-sketch-util/) | [[docs](https://github.com/mattdesl/canvas-sketch-util/tree/master/docs)] | Utilities for Math & Random Number Generation 41 | | [ThreeJS](https://threejs.org/) | [[docs](https://threejs.org/docs/)] | A 3D rendering engine for WebGL 42 | 43 | # Installing the Prerequisites 44 | 45 | Before starting the workshop, make sure you have installed and set up: 46 | 47 | - A browser, Chrome recommended 48 | - A code editor, VSCode recommended 49 | - A terminal application (like Terminal, cmder or cmd.exe) 50 | - Node.js 8+ and npm 5+ 51 | 52 | If you already have these tools installed, you can use the `--version` flag to make sure you have at least `node@8` and `npm@5`: 53 | 54 | ```sh 55 | npm --version 56 | node --version 57 | ``` 58 | 59 | ### Complete Installation Guide 60 | 61 | If you haven't installed these yet, you can find more instructions here: 62 | 63 | - [Installation Guide](./docs/installation.md) 64 | 65 | # Installing `canvas-sketch` CLI 66 | 67 | We will be using [`canvas-sketch`](https://github.com/mattdesl/canvas-sketch/) and its command-line interface (CLI) during the workshop. 68 | 69 | To install the CLI with npm, use the `--global` or `-g` flag like so: 70 | 71 | ```sh 72 | npm install canvas-sketch-cli --global 73 | ``` 74 | 75 | > :bulb: Note the `-cli` suffix in the name; this tells npm to install the CLI tool, not the code library. 76 | 77 | # Command-Line Tips & Suggestions 78 | 79 | If you are new to the command-line, you can read more details here: 80 | 81 | - [Command-Line Tips & Suggestions](./docs/command-line.md) 82 | 83 | # Cloning & Running Examples 84 | 85 | During the workshop, you won't need to clone and run this repository locally. However, if you wish to do so, you can find more instructions here: 86 | 87 | - [Cloning & Running Examples](./docs/cloning.md) 88 | 89 | # Cheat Sheet 90 | 91 | I've also included a small "cheat sheet" that you can use as a reference if you are forgetting some of the patterns and recipes discussed during the workshop. 92 | 93 | - [Cheat Sheet](./docs/cheat-sheet.md) 94 | 95 | # Exercises 96 | 97 | Below are a couple exercises you can tackle during lunch and free coding periods: 98 | 99 | - [Exercises](./docs/exercises.md) 100 | 101 | # Other Modules for Creative Coding 102 | 103 | This workshop encourages students to make use of [npm](https://www.npmjs.com) modules to build complex and interesting artworks. 104 | 105 | If you find a module you want to use, like [point-in-polygon](https://www.npmjs.com/package/point-in-polygon), you can install it from your project folder like so: 106 | 107 | ```sh 108 | npm install point-in-polygon 109 | ``` 110 | 111 | Below are some nice modules for creative coding & generative art: 112 | 113 | - [load-asset](https://www.npmjs.com/package/load-asset) - a utility to load images and other assets with async/await 114 | - [point-in-polygon](https://www.npmjs.com/package/point-in-polygon) - test if 2D point is within a polygon 115 | - [nice-color-palettes](https://www.npmjs.com/package/nice-color-palettes) - a collection of 1000 beautiful color palettes 116 | - [gl-matrix](https://www.npmjs.com/package/gl-matrix) - 2D and 3D vector & matrix math utilities 117 | - [poisson-disk-sampling](https://www.npmjs.com/package/poisson-disk-sampling) - can be used for 2D and 3D object placements 118 | - [delaunay-triangulate](https://www.npmjs.com/package/delaunay-triangulate) - 2D and 3D triangulation 119 | - [simplify-path](https://www.npmjs.com/package/simplify-path) - simplify a 2D polyline path 120 | - [chaikin-smooth](https://www.npmjs.com/package/chaikin-smooth) - smooth a 2D polyline path 121 | - [earcut](https://www.npmjs.com/package/earcut) - fast 2D and 3D polygon triangulation 122 | - [voronoi-diagram](https://www.npmjs.com/package/voronoi-diagram) - for 2D and 3D voronoi diagrams 123 | - [svg-mesh-3d](https://github.com/mattdesl/svg-mesh-3d) - convert SVG path string to a 3D mesh 124 | - [eases](https://www.npmjs.com/package/eases) - a set of common easing functions 125 | - [bezier-easing](https://www.npmjs.com/package/bezier-easing) - create cubic bezier curve functions 126 | - [glsl-noise](https://www.npmjs.com/package/glsl-noise) - noise functions as a GLSL module (used with glslify) 127 | - [glsl-hsl2rgb](https://www.npmjs.com/package/glsl-hsl2rgb) - HSL to RGB function as a GLSL module (used with glslify) 128 | 129 | # Further Reading 130 | 131 | More links to generative art & creative coding: 132 | 133 | - [Vanilla Canvas2D Demo](https://codepen.io/mattdesl/pen/BMGZJZ) 134 | 135 | - Generative Art 136 | 137 | - [Generative Artistry](https://generativeartistry.com/) 138 | 139 | - [Anders Hoff](https://inconvergent.net/#writing) — Writing on Generative Art 140 | 141 | - [Tyler Hobbs](http://www.tylerlhobbs.com/writings) — Writing on Generative Art 142 | 143 | - [My Blog](https://mattdesl.svbtle.com/) — Writing on Creative Coding & Generative Art 144 | 145 | - GLSL & Shaders 146 | 147 | - [The Book of Shaders](https://thebookofshaders.com/) 148 | 149 | - [Lesson: GLSL Shader Basics](https://github.com/Jam3/jam3-lesson-webgl-shader-intro) 150 | 151 | - [Lesson: Custom Shaders in ThreeJS](https://github.com/Jam3/jam3-lesson-webgl-shader-threejs) 152 | 153 | - Math 154 | 155 | - [Linear Interpolation](https://mattdesl.svbtle.com/linear-interpolation) — intro to `lerp` 156 | 157 | - [math-as-code](https://github.com/Jam3/math-as-code) — A cheat sheet for mathematical notation in code form 158 | 159 | - More Resources 160 | 161 | - [awesome-creative-coding](https://github.com/terkelg/awesome-creative-coding) — a large list of resources 162 | 163 | - [graphics-resources](https://github.com/mattdesl/graphics-resources) — a large list of papers & study material 164 | 165 | - Tools 166 | 167 | - [giftool.surge.sh](https://giftool.surge.sh/) — A simple tool for creating looping GIF animations from a folder of PNG frames 168 | 169 | - [cubic-bezier.com](http://cubic-bezier.com) — A cubic Bezier curve editor, useful alongside the [bezier-easing](https://www.npmjs.com/package/bezier-easing) module 170 | 171 | - [ThreeJS Editor](https://threejs.org/editor/) — An online scene editor for ThreeJS 172 | 173 | - Communities 174 | 175 | - [creative-dev Slack team](https://creative-dev.herokuapp.com/) 176 | 177 | - [#plottertwitter](https://twitter.com/hashtag/plottertwitter?lang=en), [#generative](https://twitter.com/hashtag/generative?lang=en), [#webgl](https://twitter.com/hashtag/webgl?lang=en) and similar hashtags on Twitter, Instagram etc. 178 | 179 | # License 180 | 181 | This repository has a dual license. 182 | 183 | The textual documentation and markdown files are all licensed as MIT. 184 | 185 | The JavaScript source files have been released under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/) (CC BY-NC-SA 4.0), see [src/LICENSE.md](./src/LICENSE.md) for details. 186 | -------------------------------------------------------------------------------- /assets/fonts/SpaceGrotesk-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/assets/fonts/SpaceGrotesk-Bold.woff -------------------------------------------------------------------------------- /assets/fonts/SpaceGrotesk-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/assets/fonts/SpaceGrotesk-Light.woff -------------------------------------------------------------------------------- /assets/fonts/SpaceGrotesk-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/assets/fonts/SpaceGrotesk-Medium.woff -------------------------------------------------------------------------------- /assets/fonts/SpaceGrotesk-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/assets/fonts/SpaceGrotesk-Regular.woff -------------------------------------------------------------------------------- /assets/fonts/SpaceGrotesk-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/assets/fonts/SpaceGrotesk-SemiBold.woff -------------------------------------------------------------------------------- /docs/cheat-sheet.md: -------------------------------------------------------------------------------- 1 | #### :closed_book: [workshop-generative-art](../README.md) → Cheat Sheet 2 | 3 | --- 4 | 5 | # Cheat Sheet 6 | 7 | Here you will find some 'recipes' and patterns that you can use in creative coding and generative art. 8 | 9 | ## New Array 10 | 11 | You can use `Array.from(new Array(count))` to create a dense array at a fixed size. For example, to generate an array of 20 random numbers: 12 | 13 | ```js 14 | const count = 20; 15 | const randoms = Array.from(new Array(count)).map(() => Math.random()); 16 | ``` 17 | 18 | ## Array with values `0...1` (inclusive) 19 | 20 | Using `index / (listLength - 1)`, you can get a *t* value between 0 and 1. 21 | 22 | Example: 23 | 24 | ```js 25 | const count = 20; 26 | const values = Array.from(new Array(count)).map((_, i) => { 27 | const t = i / (count - 1); 28 | // Do something with the 't' param... 29 | return t; 30 | }); 31 | ``` 32 | 33 | To guard against a `count` of <= 1: 34 | 35 | ```js 36 | const count = 20; 37 | const values = Array.from(new Array(count)).map((_, i) => { 38 | const t = count <= 1 ? 0 : (i / (count - 1)); 39 | return t; 40 | }); 41 | ``` 42 | 43 | ## Grids & UV Coordinates 44 | 45 | During the workshop, when I say "UV coordinates" I am referring to coordinates that vary from `(0, 0)` (top left) to `(1, 1)` (bottom right). 46 | 47 | For example, to create a UV grid with nested for loops: 48 | 49 | ```js 50 | const count = 5; 51 | const points = []; 52 | for (let x = 0; x < count; x++) { 53 | for (let y = 0; y < count; y++) { 54 | const u = count <= 1 ? 0.5 : (x / (count - 1)); 55 | const v = count <= 1 ? 0.5 : (y / (count - 1)); 56 | points.push([ u, v ]); 57 | } 58 | } 59 | ``` 60 | 61 | ## Padding with Margins using Linear Interpolation 62 | 63 | If you have UV coordinates for a surface between `(0, 0)` (top left) and `(1, 1)` (bottom right), you can use linear interpolation to get back pixel values for each coordinate: 64 | 65 | ```js 66 | const { lerp } = require('canvas-sketch-util/math'); 67 | 68 | // ... 69 | 70 | const x = lerp(minX, maxX, u); 71 | const y = lerp(minY, maxY, v); 72 | ``` 73 | 74 | Let's say you want a margin of `20px` in your `[ width, height ]` artwork, you can do this: 75 | 76 | ```js 77 | const margin = 20; 78 | const x = lerp(margin, width - margin, u); 79 | const y = lerp(margin, height - margin, v); 80 | ``` 81 | 82 | ## Noise from UV Coordinates 83 | 84 | If you have UV coordinates between `0..1`, you can get back a *simplex noise* signal from those coordinates that smoothly varies between `-1...1`. 85 | 86 | ```js 87 | const random = require('canvas-sketch-util/random'); 88 | 89 | const frequency = 1; 90 | const amplitude = 1; 91 | 92 | const n = amplitude * random.noise2D(u * frequency, v * frequency); 93 | ``` 94 | 95 | The `frequency` changes how chaotic the noise signal will be, and the `amplitude` can be used to scale the value to something smaller or larger than `-1..1` range. 96 | 97 | ## Animated Noise 98 | 99 | By using a higher dimension noise function, you can animate the noise by a `{ time }` or `{ playhead }` variable from `canvas-sketch` props. 100 | 101 | ```js 102 | const settings = { 103 | // Enable animation & time props 104 | animate: true 105 | }; 106 | 107 | canvasSketch(() => { 108 | return ({ context, time }) => { 109 | const frequency = 1; 110 | const amplitude = 1; 111 | 112 | // Use 3D instead of 2D noise 113 | const n = amplitude * random.noise3D(u * frequency, v * frequency, time); 114 | }; 115 | }, settings); 116 | ``` 117 | 118 | ## `0..1` to `-1...1` 119 | 120 | If *t* is between 0 and 1 (inclusive) and you want to map it to -1 to 1 (inclusive), you can use this: 121 | 122 | ```js 123 | const n = t * 2 - 1; 124 | ``` 125 | 126 | ## `-1..1` to `0...1` 127 | 128 | If *t* is between -1 and 1 (inclusive) and you want to map it to 0 to 1 (inclusive), you can use this: 129 | 130 | ```js 131 | const n = t * 0.5 + 0.5; 132 | ``` 133 | 134 | ## Mapping One Range of Numbers to Another 135 | 136 | You can also use the `mapRange` function in `canvas-sketch-util/math` to map one range of values to another: 137 | 138 | ```js 139 | const { mapRange } = require('canvas-sketch-util/math'); 140 | 141 | // Our input lies between -1..1 142 | const value = -0.25; 143 | 144 | // Map the input range -1..1 to the output range 25..50 145 | const n = mapRange(value, -1, 1, 25, 50); 146 | ``` 147 | 148 | This is equivalent to: 149 | 150 | ```js 151 | const { inverseLerp, lerp } = require('canvas-sketch-util/math'); 152 | 153 | // Get a 0..1 represenation of the value within the given range 154 | const t = inverseLerp(-1, 1, value); 155 | 156 | // Now interpolate that to our output range of 25..50 157 | const n = lerp(25, 50, t); 158 | ``` 159 | 160 | ## Fill a Circle in Canvas 2D 161 | 162 | ```js 163 | context.beginPath(); 164 | context.arc(x, y, radius, 0, Math.PI * 2, false); 165 | context.fill(); 166 | ``` 167 | 168 | ## Rotating Shapes in Canvas 2D 169 | 170 | If you'd like to draw a shape rotated from its centre point, you need to first translate to the point you wish to draw the shape, then rotate, then offset by the center point of the shape. For example: 171 | 172 | ```js 173 | // Draw a rotated rectangle 174 | const x = 25; 175 | const y = 50; 176 | const rectWidth = 100; 177 | const rectHeight = 30; 178 | const rotation = Math.PI / 4; 179 | 180 | context.save(); 181 | context.translate(x, y); 182 | context.rotate(rotation); 183 | context.translate(-rectWidth / 2, -rectHeight / 2); 184 | context.fillRect(0, 0, rectWidth, rectHeight); 185 | context.restore(); 186 | ``` 187 | 188 | ## Creating a 2D Unit Vector from an Angle 189 | 190 | A unit vector is a really handy construct for doing generative art. You can create one from an angle like so: 191 | 192 | ```js 193 | // angle is in radians 194 | const normal = [ Math.cos(angle), Math.sin(angle) ]; 195 | ``` 196 | 197 | ## Creating a Line Segment from a 2D Normal 198 | 199 | If you have a 2D unit normal (or an angle, see the previous recipe), you can create a line segment of a specific length and thickness like so: 200 | 201 | ```js 202 | const { vec2 } = require('gl-matrix'); 203 | 204 | const normal = [ /* a 2D unit vector ... */ ]; 205 | const length = 2; 206 | const thickness = 4; 207 | 208 | // The center of the line segment 209 | const center = [ 250, 100 ]; 210 | 211 | // Extrude in either direction 212 | const line = [ -1, 1 ].map(dir => vec2.scaleAndAdd([], center, normal, dir * length)); 213 | 214 | // Draw line segment 215 | context.beginPath(); 216 | line.forEach(([ x, y ]) => context.lineTo(x, y)); 217 | context.lineWidth = thickness; 218 | context.stroke(); 219 | ``` 220 | 221 | ## Looping Motion in `-1..1` Range 222 | 223 | To create a looping motion from `-1..1` you can use `Math.sin()`, like so: 224 | 225 | ```js 226 | const motionSpeed = 0.5; 227 | const v = Math.sin(time * motionSpeed); 228 | ``` 229 | 230 | You can map this value into `0..1` space and/or interpolate it to another range. 231 | 232 | ## Ping-Pong Motion in `0..1` Range 233 | 234 | When you have a defined sketch `{ duration }` and you are using the `{ playhead }` prop, you can use `Math.sin()` to get a ping-pong motion from `0..1` which slowly varies from 0, to 1, and then back to zero. 235 | 236 | ```js 237 | const v = Math.sin(playhead * Math.PI); 238 | ``` 239 | 240 | You can invert this with `1.0 - v` if you need it to vary from 1, to 0, and then back to 1. 241 | 242 | ## Isometric ThreeJS Camera 243 | 244 | In your setup, replace the perspective camera with: 245 | 246 | ```js 247 | const camera = new THREE.OrthographicCamera(); 248 | ``` 249 | 250 | In the `resize` function, replace the perspective camera configuration with: 251 | 252 | ```js 253 | const aspect = viewportWidth / viewportHeight; 254 | 255 | // Ortho zoom 256 | const zoom = 1.0; 257 | 258 | // Bounds 259 | camera.left = -zoom * aspect; 260 | camera.right = zoom * aspect; 261 | camera.top = zoom; 262 | camera.bottom = -zoom; 263 | 264 | // Near/Far 265 | camera.near = -100; 266 | camera.far = 100; 267 | 268 | // Set position & look at world center 269 | camera.position.set(zoom, zoom, zoom); 270 | camera.lookAt(new THREE.Vector3()); 271 | 272 | // Update the camera 273 | camera.updateProjectionMatrix(); 274 | ``` 275 | 276 | ## 3D Coordinate System 277 | 278 | Here's a small reference you can use to remember XYZ axes in ThreeJS. 279 | 280 | 281 | 282 | ## Third-Party GLSL Modules 283 | 284 | With [glslify](https://github.com/glslify/glslify) (built-in to canvas-sketch CLI), we can import GLSL modules and snippets directly form npm into our shader code. 285 | 286 | For example, after running `npm install glsl-noise`, you can use this: 287 | 288 | ```js 289 | // Import glslify 290 | const glsl = require('glslify'); 291 | 292 | // Wrap your GLSL string with glslify function 293 | const frag = glsl(` 294 | precision highp float; 295 | 296 | // We can now import GLSL snippets from npm into this shader 297 | #pragma glslify: noise = require('glsl-noise/simplex/3d'); 298 | 299 | uniform float time; 300 | varying vec2 vUv; 301 | 302 | void main () { 303 | float d = noise(vec3(vUv, time)); 304 | vec3 color = vec3(d); 305 | gl_FragColor = vec4(color, 1.0); 306 | } 307 | `); 308 | ``` 309 | 310 | ## Looping Noise 311 | 312 | You can use the following to blend 2D noise seamlessly in a GIF/MP4 loop. 313 | 314 | ```js 315 | function loopNoise (x, y, t, scale = 1) { 316 | const duration = scale; 317 | const current = t * scale; 318 | return ((duration - current) * random.noise3D(x, y, current) + current * random.noise3D(x, y, current - duration)) / duration; 319 | } 320 | ``` 321 | 322 | ## 323 | 324 | #### [← Back to Documentation](../README.md) -------------------------------------------------------------------------------- /docs/cloning.md: -------------------------------------------------------------------------------- 1 | #### :closed_book: [workshop-generative-art](../README.md) → Cloning & Running Examples 2 | 3 | --- 4 | 5 | # Cloning & Running Examples 6 | 7 | The [`src/`](../src/) folder contains some code examples similar to the artworks we will be creating during the workshop. 8 | 9 | Each artwork in the `src` folder is contained in a single JavaScript file, and can be run locally with the [canvas-sketch-cli](https://github.com/mattdesl/canvas-sketch-cli) tool. 10 | 11 | # Contents 12 | 13 | - [Cloning & Installing Dependencies](#cloning--installing-dependencies) 14 | 15 | - [Installing `canvas-sketch-cli`](#installing-canvas-sketch-cli) 16 | 17 | - [Running & Editing a Sketch](#running--editing-a-sketch) 18 | 19 | - [Creating New Sketches](#creating-new-sketches) 20 | 21 | - [Bundling to a Static Website](#bundling-to-a-static-website) 22 | 23 | # Cloning & Installing Dependencies 24 | 25 | The first step is to clone this repository. Navigate with `cd` to a folder of your choice (such as `~/Desktop`) and then run: 26 | 27 | ```sh 28 | git clone https://github.com/mattdesl/workshop-generative-art.git 29 | ``` 30 | 31 | This will create a new folder called `workshop-generative-art` and download all the source code into it. Next, move into the folder and install its dependencies: 32 | 33 | ```sh 34 | # Move into the folder 35 | cd workshop-generative-art 36 | 37 | # Install its dependencies 38 | npm install 39 | ``` 40 | 41 | # Installing `canvas-sketch-cli` 42 | 43 | If you haven't already, you will need to install the `canvas-sketch` command-line tool *globally* like so: 44 | 45 | ```sh 46 | npm install canvas-sketch-cli --global 47 | ``` 48 | 49 | > :bulb: Note the `-cli` suffix in the name; this tells npm to install the CLI tool, not the code library. 50 | 51 | Once installed, you won't need to run this again unless you want to update to a new version of `canvas-sketch-cli`. 52 | 53 | # Running & Editing a Sketch 54 | 55 | Once you've installed the CLI tool globally, `cd` into this repository folder and you can run each individual sketch with the `canvas-sketch` command, like so: 56 | 57 | ```sh 58 | canvas-sketch src/2d/01-grid.js --open 59 | ``` 60 | 61 | The optional `--open` flag will open your default browser to the development server's URL, which is the same as [http://localhost:9966](http://localhost:9966). 62 | 63 | Now, edit your JavaScript files and the browser will reload automatically. 64 | 65 | # Creating New Sketches 66 | 67 | You can create a new sketch with the `--new` flag, which will write out a plain sketch file and start a development server so you can then edit it: 68 | 69 | ```sh 70 | canvas-sketch src/my-new-sketch.js --new --open 71 | ``` 72 | 73 | # Bundling to a Static Website 74 | 75 | Once you are happy with your sketch, you can create a static website by bundling it up to a single HTML file. You can use the `--build` command, and `--inline` which will wrap all the JavaScript into an inline script tag so that you end up with just a single file for your site. 76 | 77 | ```sh 78 | canvas-sketch src/my-new-sketch.js --build --inline 79 | ``` 80 | 81 | Try double-clicking the exported HTML file to see your website. This file can be shared on your favourite website host, like [Neocities](https://neocities.org/). 82 | 83 | You can also turn on debugging (source maps) with the `--no-compress` option. 84 | 85 | ## 86 | 87 | #### [← Back to Documentation](../README.md) -------------------------------------------------------------------------------- /docs/command-line.md: -------------------------------------------------------------------------------- 1 | #### :closed_book: [workshop-generative-art](../README.md) → Command-Line Tips & Suggestions 2 | 3 | --- 4 | 5 | # Command-Line Tips & Suggestions 6 | 7 | In this workshop we will only use a few operations from the command-line: 8 | 9 | ## `cd` 10 | 11 | To change directory, you can use the `cd` command: 12 | 13 | ```sh 14 | # Set directory to './some-folder/' 15 | cd some-folder/ 16 | 17 | # Set directory up one 18 | cd ../ 19 | 20 | # Set directory up one and into foobar/ 21 | cd ../foobar 22 | ``` 23 | 24 | ## `mkdir` 25 | 26 | To make a new directory, you can use `mkdir` command. The following will create a new folder in your current working directory called `foo-bar`: 27 | 28 | ```sh 29 | mkdir foo-bar 30 | ``` 31 | 32 | ## `npm` 33 | 34 | We will use the `npm` command to install and use third-party dependencies. This command only exists after you install Node.js and npm. 35 | 36 | To install a code dependency, like [`three`](http://npmjs.com/package/three) (ThreeJS), you can use `npm install` like so: 37 | 38 | ```sh 39 | # To install ThreeJS 40 | npm install three 41 | 42 | # To uninstall ThreeJS 43 | npm uninstall three 44 | 45 | # To install multiple dependencies, just list all of them 46 | npm install three canvas-sketch-util 47 | ``` 48 | 49 | To install a global command-line tool, like `canvas-sketch-cli`, you can use the `--global` (or `-g`) flag: 50 | 51 | ```sh 52 | npm install canvas-sketch-cli --global 53 | ``` 54 | 55 | ## `ls` or `dir` 56 | 57 | You can list all files in your current working directory with the `ls` command in macOS, or `dir` command in Windows. 58 | 59 | > If you are using a Unix-style command-line in Windows, like [cmder](http://cmder.net/), then you can use `ls` instead of `dir`. 60 | 61 | ## `code .` 62 | 63 | You can set up VSCode as a terminal command for quickly opening files and folders: 64 | 65 | 1. Open VSCode and select View > Command Palette, then type in "install command" and select **Shell Command: Install 'code' command in PATH** 66 | 2. Now, after restarting your Terminal application, you should be able to enter `code .` (open current folder) to open VSCode to that folder 67 | 68 | ## `open .` 69 | 70 | In macOS Terminal, you can enter `open .` to open the current working directory in Finder. 71 | 72 | ## Keyboard Shortcuts 73 | 74 | - *Tab Completion* — Many terminal applications (macOS Terminal and cmder.exe) will support Tab Completion. Start typing a folder name and hit the Tab key, and it will auto-complete to a matched folder name that already exists. Hit it twice to display all matches. 75 | - *Previous/Next Command* — You can use the Up and Down arrow keys to repeat previous commands 76 | - *Stop Program* — You can push `Ctrl + C` to kill a program, like the canvas-sketch development environment. 77 | - *New Terminal Tab* — In macOS Terminal, you can push `Cmd + T` to open a new tab from the same folder 78 | 79 | ## 80 | 81 | #### [← Back to Documentation](../README.md) -------------------------------------------------------------------------------- /docs/exercises.md: -------------------------------------------------------------------------------- 1 | #### :closed_book: [workshop-generative-art](../README.md) → Exercises 2 | 3 | --- 4 | 5 | # Exercises 6 | 7 | Here are some exercises to try during lunch & free coding periods. Like a Sol LeWitt artwork, you are given the instructions, and its your task to implement it with code and a generative system. 8 | 9 | - [Generative Poster Designs](#pie) 10 | - [Generative Sol LeWitt Inspired Wall Drawing](#wall) 11 | 12 | 13 | 14 | ## Generative Poster Designs 15 | 16 | This is a sketch trying to recreate [these poster designs](https://twitter.com/hyper_yolo/status/1116208688522383360) by Amie Chen ([@hyper_yolo](https://twitter.com/hyper_yolo/status/1116208688522383360)). 17 | 18 | Instructions: 19 | 20 | - Create an evenly spaced grid across the page 21 | - At each point in the grid, draw a 75%-filled pie chart shape at 0º, 90º, 180º or 270º rotation 22 | - Fill the background with a random pastel colour 23 | 24 | Results: 25 | 26 | ![Generative Pies](images/pies.png) 27 | 28 | Hints: 29 | 30 | - Try using `Random.pick` from [canvas-sketch-util/random](https://github.com/mattdesl/canvas-sketch-util/blob/master/docs/random.md) to select a random angle from a set of 4 31 | - Try creating the pie shape with `arc()` (which has `start` and `end` angle parameters) and `moveTo()` to finish the shape by placing the pen back in the center of the circle 32 | - You can calculate the radius by using some equation like so: 33 | `radius = width / (gridColumnCount - 1) * scaleFactor` 34 | 35 | You can find the solution in `src/2d/10-pie-charts.js`. 36 | 37 | ## Generative Sol LeWitt Inspired Wall Drawing 38 | 39 | This is a more advanced example for those looking to challenge themselves. The result is a unique generative system inspired by the ideas of Sol LeWitt and his instruction-based wall drawings. 40 | 41 | Instructions: 42 | 43 | - Using a 6x6 grid of evenly spaced points 44 | - Connect two random points on the grid; forming a trapezoid with two parallel sides extending down 45 | - Fill the trapezoid with a colour, then stroke with the background colour 46 | - Find another two random points and repeat; continuing until all grid points are exhausted 47 | - Layer the shapes by the average Y position of their two grid points 48 | 49 | Sketch: 50 | 51 | ![sketch](./images/wall-sketch.png) 52 | 53 | Example Outputs: 54 | 55 | ![wall-1](./images/wall-1.png) 56 | 57 | ![wall-2](./images/wall-2.png) 58 | 59 | You can find the solution in `src/2d/07-advanced-wall-drawing.js`. 60 | 61 | ## 62 | 63 | #### [← Back to Documentation](../README.md) -------------------------------------------------------------------------------- /docs/images/pies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/docs/images/pies.png -------------------------------------------------------------------------------- /docs/images/wall-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/docs/images/wall-1.png -------------------------------------------------------------------------------- /docs/images/wall-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/docs/images/wall-2.png -------------------------------------------------------------------------------- /docs/images/wall-sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/docs/images/wall-sketch.png -------------------------------------------------------------------------------- /docs/images/xyz-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/docs/images/xyz-1.png -------------------------------------------------------------------------------- /docs/images/xyz-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/docs/images/xyz-2.png -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | #### :closed_book: [workshop-generative-art](../README.md) → Installation Guide 2 | 3 | --- 4 | 5 | # Installation Guide 6 | 7 | This guide will run you through installing all the tools and prerequisites required for the workshop. 8 | 9 | # Contents 10 | 11 | - [Code Editor](#code-editor) 12 | 13 | - [Browser](#browser) 14 | 15 | - [Command-Line](#command-line) 16 | 17 | - [Node.js & npm](#nodejs--npm) 18 | 19 | - [`canvas-sketch`](#canvas-sketch) 20 | 21 | - [`canvas-sketch-util`](#canvas-sketch-util) 22 | 23 | - [ThreeJS](#threejs) 24 | 25 | - [Other Dependencies](#other-dependencies) 26 | 27 | # Code Editor 28 | 29 | If you don't have a JavaScript code editor, you can download [VSCode](https://code.visualstudio.com/) from its website. 30 | 31 | # Browser 32 | 33 | I recommend [Chrome](https://www.google.com/chrome/) for development, as it has great support for canvas rendering and exporting high-resolution images. 34 | 35 | All browsers come built-in with the `` API, so you don't need to install that yourself. 36 | 37 | # Command-Line 38 | 39 | In macOS, you can go to *Applications > Utilities > Terminal.app* to run Terminal and enter commands. 40 | 41 | In Windows, you can click *Start* and search for `cmd.exe`, or download [cmder.net](http://cmder.net/) for a more powerful experience. 42 | 43 | If you are new to the command-line, you can see some [Tips for using the Command-Line](./command-line.md). 44 | 45 | # Node.js & npm 46 | 47 | You can download and install the latest version of [Node.js](https://nodejs.org/en/) (version 8.x or higher) from its website. This will come included with a recent version of npm (5.x or higher). 48 | 49 | Once installed, you should be able to run `node --version` and `npm --version` from your command-line. 50 | 51 | # `canvas-sketch` 52 | 53 | We will be using [`canvas-sketch`](https://github.com/mattdesl/canvas-sketch/) and its command-line interface (CLI) during the workshop. 54 | 55 | To install the CLI with npm, use the `--global` or `-g` flag like so: 56 | 57 | ```sh 58 | npm install canvas-sketch-cli --global 59 | ``` 60 | 61 | > :bulb: Note the `-cli` suffix in the name; this tells npm to install the CLI tool, not the code library. 62 | 63 | # `canvas-sketch-util` 64 | 65 | As the workshop progresses, we will start to depend on third-party utilities for math, random number generation, and other features. The [canvas-sketch-util](https://github.com/mattdesl/canvas-sketch-util/) library includes many of these features, and you can install it with npm. 66 | 67 | Run the following from your project folder: 68 | 69 | ```sh 70 | npm install canvas-sketch-util 71 | ``` 72 | 73 | Now, your code can require it like so: 74 | 75 | ```js 76 | const { lerp } = require('canvas-sketch-util/math'); 77 | const random = require('canvas-sketch-util/random'); 78 | ``` 79 | 80 | # ThreeJS 81 | 82 | In the second part of the workshop, we will introduce [ThreeJS](https://threejs.org/), a 3D engine on top of WebGL and GLSL. 83 | 84 | To install it, run the following from your project folder: 85 | 86 | ```sh 87 | npm install three 88 | ``` 89 | 90 | Now, you can require it like so: 91 | 92 | ```js 93 | const THREE = require('three'); 94 | ``` 95 | 96 | # Other Dependencies 97 | 98 | If you find a third-praty module on [npm](https://www.npmjs.com/package/) that you like, you can install and require it in the same way as you did `canvas-sketch-util` and `three`. 99 | 100 | ## 101 | 102 | #### [← Back to Documentation](../README.md) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workshop-generative-art", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "keywords": [], 8 | "author": "Matt DesLauriers ", 9 | "dependencies": { 10 | "bezier-easing": "^2.1.0", 11 | "canvas-sketch": "0.0.27", 12 | "canvas-sketch-cli": "^1.1.7", 13 | "canvas-sketch-util": "^1.4.2", 14 | "convex-hull": "^1.0.3", 15 | "eases": "^1.0.8", 16 | "gl-matrix": "^2.8.1", 17 | "glsl-face-normal": "^1.0.2", 18 | "glsl-hsl2rgb": "^1.1.0", 19 | "glsl-noise": "0.0.0", 20 | "load-asset": "^1.2.0", 21 | "nice-color-palettes": "^2.0.0", 22 | "regl": "^1.3.7", 23 | "three": "^0.97.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/2d/00-pink-square.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | 3 | const settings = { 4 | dimensions: [ 2048, 2048 ] 5 | }; 6 | 7 | const sketch = () => { 8 | return ({ context, width, height }) => { 9 | context.fillStyle = 'pink'; 10 | context.fillRect(0, 0, width, height); 11 | }; 12 | }; 13 | 14 | canvasSketch(sketch, settings); 15 | -------------------------------------------------------------------------------- /src/2d/01-grid.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | 3 | const settings = { 4 | dimensions: [ 2048, 2048 ] 5 | }; 6 | 7 | const sketch = () => { 8 | const count = 6; 9 | 10 | const createGrid = () => { 11 | const points = []; 12 | for (let x = 0; x < count; x++) { 13 | for (let y = 0; y < count; y++) { 14 | const u = x / (count - 1); 15 | const v = y / (count - 1); 16 | points.push([ u, v ]); 17 | } 18 | } 19 | return points; 20 | }; 21 | 22 | const points = createGrid(); 23 | 24 | return ({ context, width, height }) => { 25 | context.fillStyle = 'white'; 26 | context.fillRect(0, 0, width, height); 27 | 28 | points.forEach(([ u, v ]) => { 29 | const x = u * width; 30 | const y = v * height; 31 | 32 | context.beginPath(); 33 | context.arc(x, y, 40, 0, Math.PI * 2); 34 | context.fillStyle = 'black'; 35 | context.fill(); 36 | }); 37 | }; 38 | }; 39 | 40 | canvasSketch(sketch, settings); 41 | -------------------------------------------------------------------------------- /src/2d/02-grid-margin.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const { lerp } = require('canvas-sketch-util/math'); 3 | 4 | const settings = { 5 | dimensions: [ 2048, 2048 ] 6 | }; 7 | 8 | const sketch = ({ update }) => { 9 | const count = 5; 10 | 11 | const createGrid = () => { 12 | const points = []; 13 | for (let x = 0; x < count; x++) { 14 | for (let y = 0; y < count; y++) { 15 | const u = x / (count - 1); 16 | const v = y / (count - 1); 17 | points.push([ u, v ]); 18 | } 19 | } 20 | return points; 21 | }; 22 | 23 | const points = createGrid(); 24 | 25 | return ({ context, width, height, params }) => { 26 | const margin = width * 0.175; 27 | 28 | context.fillStyle = 'white'; 29 | context.fillRect(0, 0, width, height); 30 | 31 | points.forEach(([ u, v ]) => { 32 | const x = lerp(margin, width - margin, u); 33 | const y = lerp(margin, height - margin, v); 34 | 35 | context.beginPath(); 36 | context.arc(x, y, 40, 0, Math.PI * 2); 37 | context.strokeStyle = 'black'; 38 | context.lineWidth = 20; 39 | context.stroke(); 40 | }); 41 | }; 42 | }; 43 | 44 | canvasSketch(sketch, settings); 45 | -------------------------------------------------------------------------------- /src/2d/03-grid-random.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const { lerp } = require('canvas-sketch-util/math'); 3 | 4 | const settings = { 5 | dimensions: [ 2048, 2048 ] 6 | }; 7 | 8 | const sketch = ({ width, height }) => { 9 | const count = 40; 10 | const margin = width * 0.15; 11 | const padding = 20; 12 | const tileSize = (width - margin * 2) / count - padding; 13 | 14 | const createGrid = () => { 15 | const points = []; 16 | for (let x = 0; x < count; x++) { 17 | for (let y = 0; y < count; y++) { 18 | const u = x / (count - 1); 19 | const v = y / (count - 1); 20 | points.push([ u, v ]); 21 | } 22 | } 23 | return points; 24 | }; 25 | 26 | // Get N% of full grid 27 | const points = createGrid().filter(() => Math.random() > 0.5); 28 | 29 | return ({ context, width, height }) => { 30 | context.fillStyle = '#cc8080'; 31 | context.fillRect(0, 0, width, height); 32 | 33 | points.forEach(([ u, v ]) => { 34 | const x = lerp(margin, width - margin, u); 35 | const y = lerp(margin, height - margin, v); 36 | 37 | context.beginPath(); 38 | context.arc(x, y, tileSize * 0.5, 0, Math.PI * 2); 39 | context.fillStyle = '#fff'; 40 | context.fill(); 41 | }); 42 | }; 43 | }; 44 | 45 | canvasSketch(sketch, settings); 46 | -------------------------------------------------------------------------------- /src/2d/04-grid-colors.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const { lerp } = require('canvas-sketch-util/math'); 3 | const palettes = require('nice-color-palettes/1000.json'); 4 | const random = require('canvas-sketch-util/random'); 5 | 6 | let palette = random.pick(palettes); 7 | 8 | palette = random.shuffle(palette); 9 | palette = palette.slice(0, random.rangeFloor(2, palette.length + 1)); 10 | 11 | const background = palette.shift(); 12 | 13 | const settings = { 14 | dimensions: [ 2048, 2048 ] 15 | }; 16 | 17 | const sketch = () => { 18 | const count = 30; 19 | 20 | const createGrid = () => { 21 | const points = []; 22 | for (let x = 0; x < count; x++) { 23 | for (let y = 0; y < count; y++) { 24 | const u = x / (count - 1); 25 | const v = y / (count - 1); 26 | const position = [ u, v ]; 27 | points.push({ 28 | color: random.pick(palette), 29 | radius: Math.abs(30 + 20 * random.gaussian()), 30 | position 31 | }); 32 | } 33 | } 34 | return points; 35 | }; 36 | 37 | let points = createGrid().filter(() => { 38 | return Math.random() > 0.75; 39 | }); 40 | 41 | points = random.shuffle(points); 42 | 43 | return ({ context, width, height }) => { 44 | const margin = width * 0.175; 45 | 46 | context.fillStyle = background; 47 | context.fillRect(0, 0, width, height); 48 | 49 | points.forEach(data => { 50 | const { 51 | position, 52 | radius, 53 | color 54 | } = data; 55 | const x = lerp(margin, width - margin, position[0]); 56 | const y = lerp(margin, height - margin, position[1]); 57 | 58 | context.beginPath(); 59 | context.arc(x, y, radius, 0, Math.PI * 2); 60 | context.fillStyle = color; 61 | context.fill(); 62 | }); 63 | }; 64 | }; 65 | 66 | canvasSketch(sketch, settings); 67 | -------------------------------------------------------------------------------- /src/2d/05-grid-konami.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const { lerp } = require('canvas-sketch-util/math'); 3 | const random = require('canvas-sketch-util/random'); 4 | 5 | const settings = { 6 | dimensions: [ 2048, 2048 ] 7 | }; 8 | 9 | const sketch = async () => { 10 | const count = 20; 11 | const characters = '←↑→↓AB'.split(''); 12 | const background = 'hsl(0, 0%, 96%)'; 13 | const palette = ['hsl(0, 0%, 10%)']; 14 | 15 | const createGrid = () => { 16 | const points = []; 17 | for (let x = 0; x < count; x++) { 18 | for (let y = 0; y < count; y++) { 19 | const u = x / (count - 1); 20 | const v = y / (count - 1); 21 | const position = [ u, v ]; 22 | const character = random.pick(characters); 23 | const r = /[AB]/i.test(character) ? 25 : 50; 24 | const e = /[AB]/i.test(character) ? 10 : 20; 25 | points.push({ 26 | color: random.pick(palette), 27 | radius: Math.abs(r + e * random.gaussian()), 28 | position, 29 | character 30 | }); 31 | } 32 | } 33 | return points; 34 | }; 35 | 36 | let points = createGrid().filter(() => random.chance(0.5)); 37 | 38 | // We can use Browser's "FontFace" API to load fonts from JavaScript 39 | // This will ensure the font is renderable before first drawing to Canvas 40 | const fontUrl = 'assets/fonts/SpaceGrotesk-Medium.woff'; 41 | const font = new window.FontFace( 42 | 'SpaceGrotesk-Medium', 43 | `url(${fontUrl})` 44 | ); 45 | 46 | // We use async/await ES6 syntax to wait for the font to load 47 | await font.load(); 48 | 49 | // Add the loaded font to the document 50 | document.fonts.add(font); 51 | 52 | // Now return a render function for the sketch 53 | return ({ context, width, height }) => { 54 | const margin = width * 0.175; 55 | 56 | context.fillStyle = background; 57 | context.fillRect(0, 0, width, height); 58 | 59 | points.forEach(data => { 60 | const { 61 | position, 62 | radius, 63 | color, 64 | character 65 | } = data; 66 | const x = lerp(margin, width - margin, position[0]); 67 | const y = lerp(margin, height - margin, position[1]); 68 | 69 | // Draw the character 70 | context.fillStyle = color; 71 | context.font = `${radius}px "SpaceGrotesk-Medium"`; 72 | context.textAlign = 'center'; 73 | context.textBaseline = 'middle'; 74 | context.fillText(character, x, y); 75 | }); 76 | }; 77 | }; 78 | 79 | canvasSketch(sketch, settings); 80 | -------------------------------------------------------------------------------- /src/2d/06-grid-painting.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const { lerp } = require('canvas-sketch-util/math'); 3 | const random = require('canvas-sketch-util/random'); 4 | const palettes = require('nice-color-palettes/1000.json'); 5 | 6 | const settings = { 7 | dimensions: [ 2048, 2048 ] 8 | }; 9 | 10 | const sketch = ({ width, height }) => { 11 | const count = 40; 12 | const margin = width * 0.15; 13 | const maxColors = random.rangeFloor(2, 6); 14 | const fontFamily = '"Andale Mono"'; 15 | const palette = random.shuffle(random.pick(palettes)).slice(0, maxColors); 16 | const background = 'hsl(0, 0%, 94%)'; 17 | const characters = '=.'.split(''); 18 | 19 | const createGrid = () => { 20 | const points = []; 21 | const frequency = random.range(0.75, 1.25); 22 | 23 | for (let x = 0; x < count; x++) { 24 | for (let y = 0; y < count; y++) { 25 | let u = x / (count - 1); 26 | let v = y / (count - 1); 27 | 28 | const [ dx, dy ] = random.insideSphere(0.05); 29 | u += dx; 30 | v += dy; 31 | 32 | const n = random.noise2D(u * frequency, v * frequency); 33 | const size = n * 0.5 + 0.5; 34 | const baseSize = width * 0.05; 35 | const sizeOffset = width * 0.05; 36 | 37 | points.push({ 38 | color: random.pick(palette), 39 | size: Math.abs(baseSize * size + random.gaussian() * sizeOffset), 40 | rotation: n * Math.PI * 0.5, 41 | character: random.pick(characters), 42 | position: [ u, v ] 43 | }); 44 | } 45 | } 46 | return points; 47 | }; 48 | 49 | const grid = createGrid(); 50 | 51 | return ({ context, width, height }) => { 52 | context.fillStyle = background; 53 | context.fillRect(0, 0, width, height); 54 | 55 | grid.forEach(({ position, rotation, size, color, character }) => { 56 | const [ u, v ] = position; 57 | const x = lerp(margin, width - margin, u); 58 | const y = lerp(margin, height - margin, v); 59 | context.fillStyle = context.strokeStyle = color; 60 | context.textAlign = 'center'; 61 | context.textBaseline = 'middle'; 62 | context.font = `${size}px ${fontFamily}`; 63 | 64 | context.save(); 65 | context.translate(x, y); 66 | context.rotate(rotation); 67 | context.globalAlpha = 0.85; 68 | context.fillText(character, 0, 0); 69 | context.restore(); 70 | }); 71 | }; 72 | }; 73 | 74 | canvasSketch(sketch, settings); 75 | -------------------------------------------------------------------------------- /src/2d/07-advanced-wall-drawing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An example of a Sol LeWitt inspired "Wall Drawing" using 3 | * a simple generative algorithm. 4 | * 5 | * The instructions for this mural: 6 | * 7 | * - Using a 6x6 grid of evenly spaced points 8 | * - Connect two random points on the grid; forming a trapezoid with two parallel sides extending down 9 | * - Fill the trapezoid with a colour, then stroke with the background colour 10 | * - Find another two random points and repeat; continuing until all grid points are exhausted 11 | * - Layer the shapes by the average Y position of their two grid points 12 | */ 13 | 14 | const canvasSketch = require('canvas-sketch'); 15 | const random = require('canvas-sketch-util/random'); 16 | const { lerp } = require('canvas-sketch-util/math'); 17 | const palettes = require('nice-color-palettes/1000.json'); 18 | 19 | const settings = { 20 | dimensions: [ 2048, 1024 ] 21 | }; 22 | 23 | const sketch = ({ width, height }) => { 24 | // Let's get a random palette of 1-5 colours 25 | const nColors = random.rangeFloor(1, 6); 26 | const palette = random.shuffle(random.pick(palettes)).slice(0, nColors); 27 | const background = 'white'; 28 | 29 | // Padding around edges 30 | const margin = width * 0.05; 31 | 32 | // Create a grid of points (in pixel space) within the margin bounds 33 | const createGrid = () => { 34 | const xCount = 6; 35 | const yCount = 6; 36 | const points = []; 37 | for (let x = 0; x < xCount; x++) { 38 | for (let y = 0; y < yCount; y++) { 39 | const u = x / (xCount - 1); 40 | const v = y / (yCount - 1); 41 | const px = lerp(margin, width - margin, u); 42 | const py = lerp(margin, height - margin, v); 43 | points.push([ px, py ]); 44 | } 45 | } 46 | return points; 47 | }; 48 | 49 | // Create the grid 50 | let grid = createGrid(); 51 | 52 | // Now create the shapes 53 | let shapes = []; 54 | 55 | // As long as we still have two grid points left 56 | while (grid.length > 2) { 57 | // Select two random points from the grid 58 | const pointsToRemove = random.shuffle(grid).slice(0, 2); 59 | // Not enough points left, just break out 60 | if (pointsToRemove.length < 2) { 61 | break; 62 | } 63 | 64 | // The color of this trapezoid 65 | const color = random.pick(palette); 66 | 67 | // Filter these points out of the grid 68 | grid = grid.filter(p => !pointsToRemove.includes(p)); 69 | 70 | // Now let's form the trapezoid from points A to B 71 | const [ a, b ] = pointsToRemove; 72 | 73 | shapes.push({ 74 | color, 75 | // The path goes from the bottom of the page, 76 | // up to the first point, 77 | // through the second point, 78 | // and then back down to the bottom of the page 79 | path: [ 80 | [ a[0], height - margin ], 81 | a, 82 | b, 83 | [ b[0], height - margin ] 84 | ], 85 | // The average Y position of both grid points 86 | // This will be used for layering 87 | y: (a[1] + b[1]) / 2 88 | }); 89 | } 90 | 91 | // Sort/layer the shapes according to their average Y position 92 | shapes.sort((a, b) => a.y - b.y); 93 | 94 | // Now render 95 | return ({ context, width, height }) => { 96 | // Make sure our alpha is back to 1.0 before 97 | // we draw our background color 98 | context.globalAlpha = 1; 99 | context.fillStyle = background; 100 | context.fillRect(0, 0, width, height); 101 | 102 | shapes.forEach(({ lineWidth, path, color }) => { 103 | context.beginPath(); 104 | path.forEach(([ x, y ]) => { 105 | context.lineTo(x, y); 106 | }); 107 | context.closePath(); 108 | 109 | // Draw the trapezoid with a specific colour 110 | context.lineWidth = 20; 111 | context.globalAlpha = 0.85; 112 | context.fillStyle = color; 113 | context.fill(); 114 | 115 | // Outline at full opacity 116 | context.lineJoin = context.lineCap = 'round'; 117 | context.strokeStyle = background; 118 | context.globalAlpha = 1; 119 | context.stroke(); 120 | }); 121 | }; 122 | }; 123 | 124 | canvasSketch(sketch, settings); 125 | -------------------------------------------------------------------------------- /src/2d/08-grid-animation.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/workshop-generative-art/f3be34f913357ee9fb76b4ad5771f523ad6cec36/src/2d/08-grid-animation.js -------------------------------------------------------------------------------- /src/2d/09-noise-lines.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const { lerp, linspace } = require('canvas-sketch-util/math'); 3 | const random = require('canvas-sketch-util/random'); 4 | 5 | const settings = { 6 | animate: true, 7 | duration: 4, 8 | dimensions: [ 2048, 2048 ] 9 | }; 10 | 11 | const sketch = () => { 12 | const lines = linspace(20, true).map(y => { 13 | return linspace(20, true).map(x => { 14 | return [ 15 | x, y 16 | ]; 17 | }); 18 | }); 19 | return ({ context, width, height, playhead }) => { 20 | context.fillStyle = 'black'; 21 | context.fillRect(0, 0, width, height); 22 | 23 | const margin = 0.1 * width; 24 | lines.forEach(line => { 25 | context.beginPath(); 26 | line.forEach(position => { 27 | let [ u, v ] = position; 28 | 29 | v += 0.1 * loopNoise(u, v, playhead); 30 | const x = lerp(margin, width - margin, u); 31 | const y = lerp(margin, width - margin, v); 32 | context.lineTo(x, y); 33 | }); 34 | context.lineWidth = 0.01 * width; 35 | context.strokeStyle = 'white'; 36 | context.stroke(); 37 | }) 38 | }; 39 | 40 | function loopNoise (x, y, t, scale = 1) { 41 | const duration = scale; 42 | const current = t * scale; 43 | return ((duration - current) * random.noise3D(x, y, current) + current * random.noise3D(x, y, current - duration)) / duration; 44 | } 45 | }; 46 | 47 | canvasSketch(sketch, settings); 48 | -------------------------------------------------------------------------------- /src/2d/10-pie-charts.js: -------------------------------------------------------------------------------- 1 | // A recreation of: 2 | // https://twitter.com/hyper_yolo/status/1116208688522383360 3 | 4 | const canvasSketch = require('canvas-sketch'); 5 | const Random = require('canvas-sketch-util/random'); 6 | const { lerp } = require('canvas-sketch-util/math'); 7 | const palettes = require('nice-color-palettes'); 8 | 9 | const settings = { 10 | scaleToView: true, 11 | dimensions: [ 2048, 2048 ] 12 | }; 13 | 14 | const sketch = ({ width, height }) => { 15 | const palette = Random.shuffle(Random.pick(palettes)); 16 | const background = palette.shift(); 17 | 18 | const count = Random.rangeFloor(4, 10); 19 | 20 | const createGrid = () => { 21 | const points = []; 22 | for (let x = 0; x < count; x++) { 23 | for (let y = 0; y < count; y++) { 24 | const u = x / (count - 1); 25 | const v = y / (count - 1); 26 | 27 | const corner = Random.pick([ 0, 0.5, 1, 1.5 ]); 28 | const arcStart = Math.PI * corner; 29 | const arcEnd = arcStart + Math.PI * 1.5; 30 | 31 | points.push({ 32 | position: [ u, v ], 33 | arcStart, 34 | arcEnd 35 | }); 36 | } 37 | } 38 | return points; 39 | }; 40 | 41 | const points = createGrid(); 42 | 43 | return ({ context, width, height }) => { 44 | context.fillStyle = background; 45 | context.fillRect(0, 0, width, height); 46 | 47 | points.forEach(point => { 48 | const { 49 | position, 50 | arcStart, 51 | arcEnd 52 | } = point; 53 | 54 | const [ u, v ] = position; 55 | const margin = width * 0.2; 56 | const x = lerp(margin, width - margin, u); 57 | const y = lerp(margin, height - margin, v); 58 | const dim = Math.min(width, height) - margin * 2; 59 | const radius = dim / (count - 1) * 0.35; 60 | context.beginPath(); 61 | context.arc(x, y, radius, arcStart, arcEnd, false); 62 | context.fillStyle = 'black'; 63 | context.lineTo(x, y); 64 | context.fill(); 65 | }) 66 | }; 67 | }; 68 | 69 | canvasSketch(sketch, settings); 70 | -------------------------------------------------------------------------------- /src/LICENSE.md: -------------------------------------------------------------------------------- 1 | The JavaScript files contained within this folder and its subfolders are licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/) (CC BY-NC-SA 4.0). 2 | 3 | This means you can share and remix the code, as long as you: 4 | 5 | - Attribute your adaptation back to this repository 6 | - Do not sell your adaptation or use it for commercial purposes 7 | - Share your adaptation under the same CC BY-NC-SA 4.0 license -------------------------------------------------------------------------------- /src/slides/computational-fur.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const random = require('canvas-sketch-util/random'); 3 | const { lerp } = require('canvas-sketch-util/math'); 4 | 5 | random.setSeed(random.getRandomSeed()); 6 | 7 | const settings = { 8 | seed: random.getSeed(), 9 | exportPixelRatio: 2, 10 | dimensions: [ 1440, 1440 ] 11 | }; 12 | 13 | console.log('Seed', settings.seed); 14 | 15 | const sketch = ({ width, height }) => { 16 | const lineCount = 250; 17 | const lineSegments = 400; 18 | const foreground = '#EEF0F0'; 19 | 20 | let lines = []; 21 | const margin = width * 0.15; 22 | 23 | for (let i = 0; i < lineCount; i++) { 24 | const A = i / (lineCount - 1); 25 | const line = []; 26 | 27 | const x = lerp(margin, width - margin, A); 28 | for (let j = 0; j < lineSegments; j++) { 29 | const B = j / (lineSegments - 1); 30 | const y = lerp(margin, height - margin, B); 31 | 32 | const frequency0 = 0.00105 + random.gaussian() * 0.00003; 33 | const z0 = noise(x * frequency0, y * frequency0, -1); 34 | const z1 = noise(x * frequency0, y * frequency0, +1); 35 | 36 | const warp = random.gaussian(20, 40); 37 | const fx = x + z0 * warp; 38 | const fy = y + z1 * warp; 39 | 40 | const point = [ fx, fy ]; 41 | line.push(point); 42 | } 43 | 44 | lines.push(line); 45 | } 46 | 47 | return ({ context, width, height }) => { 48 | context.fillStyle = '#181818'; 49 | context.globalAlpha = 1; 50 | context.globalCompositeOperation = 'source-over'; 51 | context.fillRect(0, 0, width, height); 52 | context.lineWidth = 1; 53 | 54 | lines.forEach(line => { 55 | context.beginPath(); 56 | line.forEach(([ x, y ]) => context.lineTo(x, y)); 57 | context.globalCompositeOperation = 'lighter'; 58 | context.strokeStyle = foreground; 59 | context.globalAlpha = 0.35; 60 | context.stroke(); 61 | }); 62 | }; 63 | 64 | function noise (nx, ny, z, freq = 0.75) { 65 | // This uses many layers of noise to create a more organic pattern 66 | nx *= freq; 67 | ny *= freq; 68 | let e = (1.00 * (random.noise3D(1 * nx, 1 * ny, z) * 0.5 + 0.5) + 69 | 0.50 * (random.noise3D(2 * nx, 2 * ny, z) * 0.5 + 0.5) + 70 | 0.25 * (random.noise3D(4 * nx, 4 * ny, z) * 0.5 + 0.5) + 71 | 0.13 * (random.noise3D(8 * nx, 8 * ny, z) * 0.5 + 0.5) + 72 | 0.06 * (random.noise3D(16 * nx, 16 * ny, z) * 0.5 + 0.5) + 73 | 0.03 * (random.noise3D(32 * nx, 32 * ny, z) * 0.5 + 0.5)); 74 | e /= (1.00 + 0.50 + 0.25 + 0.13 + 0.06 + 0.03); 75 | e = Math.pow(e, 2); 76 | e = Math.max(e, 0); 77 | e *= 2; 78 | return e * 2 - 1; 79 | } 80 | }; 81 | 82 | canvasSketch(sketch, settings); 83 | -------------------------------------------------------------------------------- /src/slides/slides-background-penplotter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A re-implementation of the slide background patterns, 3 | * but supporting AxiDraw V3 Mechanical Pen Plotter (i.e. export as SVG). 4 | */ 5 | 6 | const canvasSketch = require('canvas-sketch'); 7 | const { createRandom, getRandomSeed } = require('canvas-sketch-util/random'); 8 | const { lerp } = require('canvas-sketch-util/math'); 9 | const { renderPolylines } = require('canvas-sketch-util/penplot'); 10 | const { vec2 } = require('gl-matrix'); 11 | 12 | const settings = { 13 | dimensions: 'A4', 14 | units: 'cm' 15 | }; 16 | 17 | const sketch = ({ context, width, height, units, render }) => { 18 | // Some constant variables here 19 | const margin = 3.5; 20 | 21 | // Set up a function that will re-generate the state entirely 22 | let seed, lines; 23 | const reset = () => { 24 | seed = getRandomSeed(); 25 | const random = createRandom(seed); 26 | lines = generate(random); 27 | }; 28 | 29 | // On pressing the N key, we re-generate and re-render 30 | window.addEventListener('keydown', (ev) => { 31 | if (ev.key === 'n') { 32 | ev.preventDefault(); 33 | reset(); 34 | render(); 35 | } 36 | }); 37 | 38 | // Set the initial state 39 | reset(); 40 | 41 | // We enclose the renderPolylines within a function 42 | // so that it always picks up the current 'lines' array 43 | return () => renderPolylines(lines, { 44 | context, 45 | width, 46 | height, 47 | units 48 | }); 49 | 50 | function generate (random) { 51 | const length = random.range(0.25, 2); 52 | 53 | let lineCount = random.rangeFloor(4, 14); 54 | if (lineCount % 2 !== 0) lineCount++; 55 | 56 | const isEqualSegments = random.boolean(); 57 | let lineSegments = isEqualSegments 58 | ? lineCount 59 | : Math.floor(random.range(lineCount * 0.5, lineCount * 4)); 60 | if (lineSegments % 2 !== 0) lineSegments++; 61 | 62 | let points = []; 63 | 64 | for (let i = 0; i < lineCount; i++) { 65 | const A = i / (lineCount - 1); 66 | const x = lerp(margin, width - margin, A); 67 | for (let j = 0; j < lineSegments; j++) { 68 | const B = j / (lineSegments - 1); 69 | const y = lerp(margin, height - margin, B); 70 | 71 | const point = [ x, y ]; 72 | points.push(point); 73 | } 74 | } 75 | 76 | const tileSize = (width - margin * 2) / (lineCount - 1); 77 | const ix = random.rangeFloor(0, lineCount - 1); 78 | const center = [ 79 | margin + (ix * tileSize) + tileSize / 2, 80 | lerp(margin, height - margin, random.value()) 81 | ]; 82 | 83 | // Turn each point into a line segment 84 | return points.map(point => { 85 | let direction = vec2.sub([], point, center); 86 | vec2.normalize(direction, direction); 87 | 88 | const a = vec2.scaleAndAdd([], point, direction, length / 2); 89 | const b = vec2.scaleAndAdd([], point, direction, -length / 2); 90 | return [ a, b ]; 91 | }); 92 | } 93 | }; 94 | 95 | canvasSketch(sketch, settings); 96 | -------------------------------------------------------------------------------- /src/slides/slides-background.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates 100 uniqu slide images that can be used 3 | * as subtle backdrops for Keynote presentations. 4 | */ 5 | 6 | const canvasSketch = require('canvas-sketch'); 7 | const random = require('canvas-sketch-util/random'); 8 | const { lerp } = require('canvas-sketch-util/math'); 9 | const { vec2 } = require('gl-matrix'); 10 | 11 | const settings = { 12 | animate: true, 13 | fps: 1.8, 14 | playbackRate: 'throttle', 15 | totalFrames: 100, 16 | dimensions: [ 1440, 900 ] 17 | }; 18 | 19 | const sketch = ({ width, height }) => { 20 | return ({ context, width, height }) => { 21 | const margin = 150; 22 | 23 | let lineCount = random.rangeFloor(4, 14); 24 | if (lineCount % 2 !== 0) lineCount++; 25 | 26 | const isEqualSegments = random.boolean(); 27 | let lineSegments = isEqualSegments 28 | ? lineCount 29 | : Math.floor(random.range(lineCount * 0.5, lineCount * 4)); 30 | if (lineSegments % 2 !== 0) lineSegments++; 31 | 32 | const colors = [ '#000000', '#EEF0F0' ]; 33 | const [background, foreground] = colors; 34 | 35 | let points = []; 36 | 37 | for (let i = 0; i < lineCount; i++) { 38 | const A = i / (lineCount - 1); 39 | const x = lerp(margin, width - margin, A); 40 | for (let j = 0; j < lineSegments; j++) { 41 | const B = j / (lineSegments - 1); 42 | const y = lerp(margin, height - margin, B); 43 | 44 | const point = [ x, y ]; 45 | points.push(point); 46 | } 47 | } 48 | 49 | context.fillStyle = background; 50 | context.globalAlpha = 1; 51 | context.fillRect(0, 0, width, height); 52 | context.lineWidth = 2; 53 | 54 | const tileSize = (width - margin * 2) / (lineCount - 1); 55 | const ix = random.rangeFloor(0, lineCount - 1); 56 | const center = [ 57 | margin + (ix * tileSize) + tileSize / 2, 58 | lerp(margin, height - margin, random.value()) 59 | ]; 60 | 61 | const length = random.range(10, 100); 62 | 63 | // Turn each point into a line segment 64 | points.forEach(point => { 65 | let direction = vec2.sub([], point, center); 66 | vec2.normalize(direction, direction); 67 | 68 | const a = vec2.scaleAndAdd([], point, direction, length / 2); 69 | const b = vec2.scaleAndAdd([], point, direction, -length / 2); 70 | const line = [ a, b ]; 71 | 72 | context.beginPath(); 73 | line.forEach(([x, y]) => context.lineTo(x, y)); 74 | context.strokeStyle = foreground; 75 | context.globalAlpha = 1; 76 | context.stroke(); 77 | }); 78 | }; 79 | }; 80 | 81 | canvasSketch(sketch, settings); 82 | -------------------------------------------------------------------------------- /src/slides/sol-lewitt-wall-drawing-273.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A JavaScript implementation of Sol LeWitt's Wall Drawing #273 (1975) at SFMOMA. 3 | * 4 | * Instructions: 5 | * - A six-inch (15 cm) grid covering the walls. Lines from corners, sides, 6 | * and centre of the walls to random points on the grid. 7 | * - (7th wall) Red lines from the midpoints of four sides, blue lines from 8 | * four corners, yellow lines from the center. 9 | */ 10 | 11 | const canvasSketch = require('canvas-sketch'); 12 | const random = require('canvas-sketch-util/random'); 13 | const { lerp, lerpArray } = require('canvas-sketch-util/math'); 14 | 15 | // const palettes = require('nice-color-palettes'); 16 | 17 | const settings = { 18 | dimensions: 'A4', 19 | orientation: 'landscape', 20 | units: 'cm', 21 | pixelsPerInch: 300 22 | }; 23 | 24 | const sketch = ({ width, height }) => { 25 | const margin = 2; 26 | const gridMargin = margin; 27 | const gridXCount = 10; 28 | const gridYCount = gridXCount; 29 | const skipEdge = true; 30 | const count = 40; 31 | const randomColors = false; 32 | const splicing = true; 33 | 34 | // Use a palette inspired by Sol LeWitt 35 | const palette = [ '#ea3f3f', '#76b9ed', '#f2d843' ]; 36 | 37 | // Or, use a random palette 38 | // const palette = random.shuffle(random.pick(palettes)).slice(0, 3); 39 | 40 | // Make a grid of points 41 | const gridPoints = []; 42 | for (let x = 0; x < gridXCount; x++) { 43 | for (let y = 0; y < gridYCount; y++) { 44 | if (skipEdge) { 45 | if (x === 0 || x === gridXCount - 1 || y === 0 || y === gridYCount - 1) continue; 46 | } 47 | const u = gridXCount <= 1 ? 0.5 : x / (gridXCount - 1); 48 | const v = gridYCount <= 1 ? 0.5 : y / (gridYCount - 1); 49 | 50 | const px = lerp(gridMargin, width - gridMargin, u); 51 | const py = lerp(gridMargin, height - gridMargin, v); 52 | gridPoints.push([ px, py ]); 53 | } 54 | } 55 | 56 | // Make a list of the four "corner" points 57 | const corners = [ 58 | [ 0, 0 ], [ 1, 0 ], 59 | [ 1, 1 ], [ 0, 1 ] 60 | ].map(uv => { 61 | const [u, v] = uv; 62 | const px = lerp(margin, width - margin, u); 63 | const py = lerp(margin, height - margin, v); 64 | return [ px, py ]; 65 | }); 66 | 67 | const connect = (start, color) => { 68 | if (gridPoints.length === 0) return null; 69 | const gridIndex = random.rangeFloor(0, gridPoints.length); 70 | const end = gridPoints[gridIndex]; 71 | if (splicing) gridPoints.splice(gridIndex, 1); 72 | 73 | return { 74 | color: randomColors ? random.pick(palette) : color, 75 | path: [ start, end ] 76 | }; 77 | }; 78 | 79 | const fromMidpoint = () => { 80 | // Midpoint of line 81 | const t = 0.5; 82 | 83 | // Can instead be random along edge 84 | // const t = random.value(); 85 | 86 | const cornerIndex = random.rangeFloor(0, corners.length); 87 | const nextCornerIndex = (cornerIndex + 1) % corners.length; 88 | const edgeStart = corners[cornerIndex]; 89 | const edgeEnd = corners[nextCornerIndex]; 90 | return lerpArray(edgeStart, edgeEnd, t); 91 | }; 92 | 93 | const fromCorner = () => { 94 | const cornerIndex = random.rangeFloor(0, corners.length); 95 | return corners[cornerIndex % corners.length]; 96 | }; 97 | 98 | const fromCenter = () => { 99 | return [ width / 2, height / 2 ]; 100 | }; 101 | 102 | let items = []; 103 | 104 | // Gather all types.. 105 | const types = [ 106 | { count: count / 2, emitter: fromMidpoint, color: palette[0] }, 107 | { count: count / 2, emitter: fromCorner, color: palette[1] }, 108 | { count: count, emitter: fromCenter, color: palette[2] } 109 | ]; 110 | 111 | // Emit each line type 112 | types.forEach(type => { 113 | for (let i = 0; i < type.count; i++) { 114 | if (gridPoints.length === 0) return; 115 | const start = type.emitter(); 116 | items.push(connect(start, type.color)); 117 | } 118 | }); 119 | 120 | // Cleanup lines 121 | items = items.filter(Boolean); 122 | items = random.shuffle(items); 123 | 124 | // Draw lines 125 | return ({ context, width, height }) => { 126 | context.fillStyle = 'white'; 127 | context.fillRect(0, 0, width, height); 128 | 129 | items.forEach(item => { 130 | context.beginPath(); 131 | item.path.forEach(point => { 132 | context.lineTo(point[0], point[1]); 133 | }); 134 | context.fillStyle = context.strokeStyle = item.color; 135 | context.lineWidth = 0.075; 136 | context.globalAlpha = 1; 137 | context.lineJoin = 'round'; 138 | context.lineCap = 'round'; 139 | context.stroke(); 140 | }); 141 | }; 142 | }; 143 | 144 | canvasSketch(sketch, settings); 145 | -------------------------------------------------------------------------------- /src/slides/three-concepts-meshes.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const random = require('canvas-sketch-util/random'); 3 | const palettes = require('nice-color-palettes/1000.json').slice(250); 4 | 5 | // Ensure ThreeJS is in global scope for the 'examples/' 6 | global.THREE = require('three'); 7 | 8 | const isometric = true; 9 | const simplePalette = true; 10 | const palette = simplePalette ? [ '#efb3b3', '#a0d0e8' ] : random.shuffle(random.pick(palettes)).slice(0, 2); 11 | 12 | const settings = { 13 | dimensions: [1440, 900], 14 | exportPixelRatio: 2, 15 | scaleToView: true, 16 | animate: true, 17 | // Get a WebGL canvas rather than 2D 18 | context: 'webgl', 19 | // animate: true, 20 | fps: 24, 21 | duration: 8, 22 | // Turn on MSAA 23 | attributes: { antialias: true } 24 | }; 25 | 26 | const sketch = ({ context, update }) => { 27 | random.setSeed(6); 28 | 29 | // Create a renderer 30 | const renderer = new THREE.WebGLRenderer({ 31 | context 32 | }); 33 | 34 | // WebGL background color 35 | renderer.setClearColor('#000', 1); 36 | 37 | // Setup a camera 38 | const camera = isometric 39 | ? new THREE.OrthographicCamera() 40 | : new THREE.PerspectiveCamera(45, 1, 0.01, 100); 41 | 42 | // Setup your scene 43 | const scene = new THREE.Scene(); 44 | 45 | const count = 10; 46 | const geometry = new THREE.BoxGeometry(1, 1, 1); 47 | for (let x = 0; x < count; x++) { 48 | for (let y = 0; y < count; y++) { 49 | const U = x / (count - 1); 50 | const V = y / (count - 1); 51 | const spacing = 1.25; 52 | const u = (U * 2 - 1) * spacing; 53 | const v = (V * 2 - 1) * spacing; 54 | 55 | const material = new THREE.MeshStandardMaterial({ 56 | roughness: 0.75, 57 | metalness: 0.25, 58 | color: new THREE.Color(random.pick(palette)) 59 | }); 60 | const mesh = new THREE.Mesh(geometry, material); 61 | mesh.scale.set( 62 | random.gaussian(), 63 | random.gaussian() * random.gaussian() * 2, 64 | random.gaussian() 65 | ).multiplyScalar(0.25 * random.gaussian()); 66 | mesh.position.set( 67 | u, 68 | 0, 69 | v 70 | ); 71 | scene.add(mesh); 72 | } 73 | } 74 | 75 | // Specify an ambient/unlit colour 76 | // scene.add(new THREE.AmbientLight('#181818')); 77 | 78 | // Add some light 79 | const light = new THREE.DirectionalLight('white', 4); 80 | light.position.set(-1, 4, 1); 81 | light.lookAt(new THREE.Vector3()); 82 | scene.add(light); 83 | 84 | // draw each frame 85 | return { 86 | // Handle resize events here 87 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 88 | renderer.setPixelRatio(pixelRatio); 89 | renderer.setSize(viewportWidth, viewportHeight); 90 | if (isometric) { 91 | // Setup an isometric perspective 92 | const aspect = viewportWidth / viewportHeight; 93 | const zoom = 2.25; 94 | const offset = settings.animate ? 0.25 : 0.25; 95 | camera.left = -zoom * aspect; 96 | camera.right = zoom * aspect; 97 | camera.top = zoom + offset; 98 | camera.bottom = -zoom + offset; 99 | camera.near = -100; 100 | camera.far = 100; 101 | camera.position.set(zoom, zoom, zoom); 102 | camera.lookAt(new THREE.Vector3()); 103 | } else { 104 | camera.aspect = viewportWidth / viewportHeight; 105 | } 106 | camera.updateProjectionMatrix(); 107 | }, 108 | // And render events here 109 | render ({ playhead, frame, width, height }) { 110 | if (!isometric) { 111 | const orbit = Math.PI / 4 + playhead * Math.PI * 2; 112 | const radius = 4; 113 | const y = 4; 114 | const x = Math.cos(orbit) * radius; 115 | const z = Math.sin(orbit) * radius; 116 | camera.position.set(x, y, z); 117 | camera.lookAt(new THREE.Vector3()); 118 | } 119 | 120 | if (settings.animate) { 121 | scene.rotation.y = playhead * Math.PI * 2; 122 | } 123 | camera.setViewOffset(width, height, 0, 25, width, height); 124 | renderer.render(scene, camera); 125 | }, 126 | // Dispose of WebGL context (optional) 127 | unload () { 128 | renderer.dispose(); 129 | } 130 | }; 131 | }; 132 | 133 | canvasSketch(sketch, settings); 134 | -------------------------------------------------------------------------------- /src/slides/three-concepts-two-meshes.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const random = require('canvas-sketch-util/random'); 3 | 4 | // Ensure ThreeJS is in global scope for the 'examples/' 5 | global.THREE = require('three'); 6 | 7 | const settings = { 8 | dimensions: [1440, 900], 9 | exportPixelRatio: 2, 10 | scaleToView: true, 11 | // Get a WebGL canvas rather than 2D 12 | context: 'webgl', 13 | // Turn on MSAA 14 | attributes: { antialias: true } 15 | }; 16 | 17 | const sketch = ({ context, update }) => { 18 | random.setSeed(16); 19 | 20 | // Create a renderer 21 | const renderer = new THREE.WebGLRenderer({ 22 | context 23 | }); 24 | 25 | // WebGL background color 26 | renderer.setClearColor('#000', 1); 27 | 28 | // Setup a camera 29 | const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100); 30 | 31 | // Setup your scene 32 | const scene = new THREE.Scene(); 33 | 34 | const geometry = new THREE.BoxGeometry(1, 1, 1); 35 | 36 | const material1 = new THREE.MeshStandardMaterial({ 37 | roughness: 1, 38 | metalness: 0, 39 | color: new THREE.Color('hsl(0, 55%, 60%)') 40 | }); 41 | const mesh1 = new THREE.Mesh(geometry, material1); 42 | mesh1.position.set(1, 0, 0); 43 | scene.add(mesh1); 44 | 45 | const material2 = new THREE.MeshStandardMaterial({ 46 | roughness: 1, 47 | metalness: 0, 48 | color: new THREE.Color('hsl(200, 55%, 60%)') 49 | }); 50 | const mesh2 = new THREE.Mesh(geometry, material2); 51 | mesh2.position.set(-1, 0, 0); 52 | mesh2.scale.x = 0.5; 53 | mesh2.scale.z = 0.25; 54 | scene.add(mesh2); 55 | 56 | // Specify an ambient/unlit colour 57 | scene.add(new THREE.AmbientLight('hsl(0, 0%, 10%)')); 58 | 59 | // Add some light 60 | const light = new THREE.DirectionalLight('white', 1.25); 61 | light.position.set(3, 5, 4); 62 | light.lookAt(new THREE.Vector3()); 63 | scene.add(light); 64 | 65 | // draw each frame 66 | return { 67 | // Handle resize events here 68 | resize ({ pixelRatio, viewportWidth, viewportHeight, width, height }) { 69 | renderer.setPixelRatio(pixelRatio); 70 | renderer.setSize(viewportWidth, viewportHeight); 71 | camera.aspect = viewportWidth / viewportHeight; 72 | camera.setViewOffset( 73 | width, height, 74 | 0, 40, 75 | width, height 76 | ); 77 | camera.updateProjectionMatrix(); 78 | }, 79 | // And render events here 80 | render ({ playhead, frame }) { 81 | const orbit = Math.PI / 4 + playhead * Math.PI * 2; 82 | const y = 4; 83 | const radius = 4; 84 | const x = Math.cos(orbit) * radius; 85 | const z = Math.sin(orbit) * radius; 86 | camera.position.set(x, y, z); 87 | camera.lookAt(new THREE.Vector3()); 88 | camera.position.y -= 0.35; 89 | 90 | renderer.render(scene, camera); 91 | }, 92 | // Dispose of WebGL context (optional) 93 | unload () { 94 | renderer.dispose(); 95 | } 96 | }; 97 | }; 98 | 99 | canvasSketch(sketch, settings); 100 | -------------------------------------------------------------------------------- /src/slides/three-concepts.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | 3 | // Ensure ThreeJS is in global scope for the 'examples/' 4 | global.THREE = require('three'); 5 | const defaultSubdiv = 16; 6 | const materials = [ 7 | { 8 | material: new THREE.MeshBasicMaterial({ 9 | color: 'white', 10 | wireframe: true 11 | }) 12 | }, 13 | { 14 | material: new THREE.MeshBasicMaterial({ 15 | color: 'white' 16 | }) 17 | }, 18 | { 19 | material: new THREE.MeshNormalMaterial({ 20 | flatShading: true 21 | }) 22 | }, 23 | { 24 | material: new THREE.MeshStandardMaterial({ 25 | color: 'white', 26 | roughness: 1, 27 | metalness: 0.0, 28 | flatShading: true 29 | }) 30 | }, 31 | { 32 | material: new THREE.MeshStandardMaterial({ 33 | color: 'white', 34 | roughness: 1, 35 | metalness: 0.0, 36 | flatShading: false 37 | }) 38 | }, 39 | { 40 | material: new THREE.MeshNormalMaterial({ 41 | flatShading: true 42 | }), 43 | geometry: new THREE.TorusGeometry(0.75, 0.25, defaultSubdiv, defaultSubdiv * 2) 44 | }, 45 | { 46 | material: new THREE.MeshNormalMaterial({ 47 | flatShading: true 48 | }), 49 | geometry: new THREE.BoxGeometry(1, 1, 1) 50 | } 51 | ]; 52 | 53 | const settings = { 54 | dimensions: [1440, 900], 55 | exportPixelRatio: 2, 56 | scaleToView: true, 57 | // Make the loop animated 58 | animate: true, 59 | totalFrames: materials.length + 1, 60 | fps: 1, 61 | playbackRate: 'throttle', 62 | // Get a WebGL canvas rather than 2D 63 | context: 'webgl', 64 | // Turn on MSAA 65 | attributes: { antialias: true } 66 | }; 67 | 68 | const sketch = ({ context, update }) => { 69 | // Create a renderer 70 | const renderer = new THREE.WebGLRenderer({ 71 | context 72 | }); 73 | 74 | // WebGL background color 75 | renderer.setClearColor('black', 1); 76 | 77 | // Setup a camera 78 | const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100); 79 | camera.position.set(2, 2, -4); 80 | camera.lookAt(new THREE.Vector3()); 81 | 82 | // Setup your scene 83 | const scene = new THREE.Scene(); 84 | 85 | const mesh = new THREE.Mesh(); 86 | scene.add(mesh); 87 | 88 | // Specify an ambient/unlit colour 89 | scene.add(new THREE.AmbientLight('#000')); 90 | 91 | // Add some light 92 | const light = new THREE.PointLight('#fff', 1, 15.5); 93 | light.position.set(2, 2, -4).multiplyScalar(1.5); 94 | scene.add(light); 95 | 96 | // draw each frame 97 | return { 98 | // Handle resize events here 99 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 100 | renderer.setPixelRatio(pixelRatio); 101 | renderer.setSize(viewportWidth, viewportHeight); 102 | camera.aspect = viewportWidth / viewportHeight; 103 | camera.updateProjectionMatrix(); 104 | }, 105 | // And render events here 106 | render ({ time, frame }) { 107 | const { material, subdiv = defaultSubdiv, geometry } = materials[frame % materials.length]; 108 | mesh.geometry.dispose(); 109 | mesh.geometry = geometry || new THREE.SphereGeometry(1, subdiv * 2, subdiv); 110 | mesh.material = material; 111 | renderer.render(scene, camera); 112 | }, 113 | // Dispose of WebGL context (optional) 114 | unload () { 115 | renderer.dispose(); 116 | } 117 | }; 118 | }; 119 | 120 | canvasSketch(sketch, settings); 121 | -------------------------------------------------------------------------------- /src/webgl/00-cube.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three'); 2 | 3 | const canvasSketch = require('canvas-sketch'); 4 | 5 | const settings = { 6 | animate: true, 7 | dimensions: [ 1024, 1280 ], 8 | // Get a WebGL canvas rather than 2D 9 | context: 'webgl', 10 | // Turn on MSAA 11 | attributes: { antialias: true } 12 | }; 13 | 14 | const sketch = ({ context, width, height }) => { 15 | // Create a renderer 16 | const renderer = new THREE.WebGLRenderer({ 17 | context 18 | }); 19 | 20 | // WebGL background color 21 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 22 | 23 | // Setup a camera, we will update its settings on resize 24 | const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 1000); 25 | camera.position.set(2, 2, 2); 26 | camera.lookAt(new THREE.Vector3()); 27 | 28 | // Setup your scene 29 | const scene = new THREE.Scene(); 30 | 31 | // Re-use the same Geometry across all our cubes 32 | const geometry = new THREE.BoxGeometry(1, 1, 1); 33 | 34 | // Basic "unlit" material with no depth 35 | const material = new THREE.MeshNormalMaterial(); 36 | 37 | // Create the mesh 38 | const mesh = new THREE.Mesh(geometry, material); 39 | 40 | // Smaller cube 41 | mesh.scale.setScalar(0.5); 42 | 43 | // Then add the group to the scene 44 | scene.add(mesh); 45 | 46 | // draw each frame 47 | return { 48 | // Handle resize events here 49 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 50 | renderer.setPixelRatio(pixelRatio); 51 | renderer.setSize(viewportWidth, viewportHeight); 52 | 53 | camera.aspect = viewportWidth / viewportHeight; 54 | 55 | // Update camera properties 56 | camera.updateProjectionMatrix(); 57 | }, 58 | // And render events here 59 | render ({ time }) { 60 | // Rotate mesh 61 | mesh.rotation.y = time * 0.25; 62 | // Draw scene with our camera 63 | renderer.render(scene, camera); 64 | }, 65 | // Dispose of WebGL context (optional) 66 | unload () { 67 | renderer.dispose(); 68 | } 69 | }; 70 | }; 71 | 72 | canvasSketch(sketch, settings); 73 | -------------------------------------------------------------------------------- /src/webgl/01-isometric.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three'); 2 | 3 | const canvasSketch = require('canvas-sketch'); 4 | const random = require('canvas-sketch-util/random'); 5 | 6 | const settings = { 7 | animate: true, 8 | dimensions: [ 1024, 1280 ], 9 | // Get a WebGL canvas rather than 2D 10 | context: 'webgl', 11 | // Turn on MSAA 12 | attributes: { antialias: true } 13 | }; 14 | 15 | const sketch = ({ context, width, height }) => { 16 | // Create a renderer 17 | const renderer = new THREE.WebGLRenderer({ 18 | context 19 | }); 20 | 21 | // WebGL background color 22 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 23 | 24 | // Setup a camera, we will update its settings on resize 25 | const camera = new THREE.OrthographicCamera(); 26 | 27 | // Setup your scene 28 | const scene = new THREE.Scene(); 29 | 30 | // Re-use the same Geometry across all our cubes 31 | const geometry = new THREE.BoxGeometry(1, 1, 1); 32 | 33 | // Basic "unlit" material with no depth 34 | const material = new THREE.MeshNormalMaterial(); 35 | 36 | // Create the mesh 37 | const mesh = new THREE.Mesh(geometry, material); 38 | 39 | // Smaller cube 40 | mesh.scale.setScalar(0.5); 41 | 42 | // Then add the group to the scene 43 | scene.add(mesh); 44 | 45 | // draw each frame 46 | return { 47 | // Handle resize events here 48 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 49 | renderer.setPixelRatio(pixelRatio); 50 | renderer.setSize(viewportWidth, viewportHeight); 51 | 52 | // Setup an isometric perspective 53 | const aspect = viewportWidth / viewportHeight; 54 | const zoom = 1.85; 55 | camera.left = -zoom * aspect; 56 | camera.right = zoom * aspect; 57 | camera.top = zoom; 58 | camera.bottom = -zoom; 59 | camera.near = -100; 60 | camera.far = 100; 61 | camera.position.set(zoom, zoom, zoom); 62 | camera.lookAt(new THREE.Vector3()); 63 | 64 | // Update camera properties 65 | camera.updateProjectionMatrix(); 66 | }, 67 | // And render events here 68 | render ({ time }) { 69 | // Rotate mesh 70 | mesh.rotation.y = time * 0.25; 71 | // Draw scene with our camera 72 | renderer.render(scene, camera); 73 | }, 74 | // Dispose of WebGL context (optional) 75 | unload () { 76 | renderer.dispose(); 77 | } 78 | }; 79 | }; 80 | 81 | canvasSketch(sketch, settings); 82 | -------------------------------------------------------------------------------- /src/webgl/02-cubes.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three'); 2 | 3 | const canvasSketch = require('canvas-sketch'); 4 | const random = require('canvas-sketch-util/random'); 5 | const palettes = require('nice-color-palettes'); 6 | 7 | const settings = { 8 | animate: true, 9 | dimensions: [ 1024, 1280 ], 10 | // Get a WebGL canvas rather than 2D 11 | context: 'webgl', 12 | // Turn on MSAA 13 | attributes: { antialias: true } 14 | }; 15 | 16 | const sketch = ({ context, width, height }) => { 17 | // Create a renderer 18 | const renderer = new THREE.WebGLRenderer({ 19 | context 20 | }); 21 | 22 | // WebGL background color 23 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 24 | 25 | // Setup a camera, we will update its settings on resize 26 | const camera = new THREE.OrthographicCamera(); 27 | 28 | // Setup your scene 29 | const scene = new THREE.Scene(); 30 | 31 | // Get a palette for our scene 32 | const palette = random.pick(palettes); 33 | 34 | // Randomize mesh attributes 35 | const randomizeMesh = (mesh) => { 36 | // Choose a random point in a 3D volume between -1..1 37 | const point = new THREE.Vector3( 38 | random.value() * 2 - 1, 39 | random.value() * 2 - 1, 40 | random.value() * 2 - 1 41 | ); 42 | mesh.position.copy(point); 43 | mesh.originalPosition = mesh.position.clone(); 44 | 45 | // Choose a color for the mesh material 46 | mesh.material.color.set(random.pick(palette)); 47 | 48 | // Randomly scale each axis 49 | mesh.scale.set( 50 | random.gaussian(), 51 | random.gaussian(), 52 | random.gaussian() 53 | ); 54 | 55 | // Do more random scaling on each axis 56 | if (random.chance(0.5)) mesh.scale.x *= random.gaussian(); 57 | if (random.chance(0.5)) mesh.scale.y *= random.gaussian(); 58 | if (random.chance(0.5)) mesh.scale.z *= random.gaussian(); 59 | 60 | // Further scale each object 61 | mesh.scale.multiplyScalar(random.gaussian() * 0.25); 62 | }; 63 | 64 | // A group that will hold all of our cubes 65 | const container = new THREE.Group(); 66 | 67 | // Re-use the same Geometry across all our cubes 68 | const geometry = new THREE.BoxGeometry(1, 1, 1); 69 | 70 | // The # of cubes to create 71 | const chunks = 50; 72 | 73 | // Create each cube and return a THREE.Mesh 74 | const meshes = Array.from(new Array(chunks)).map(() => { 75 | // Basic "unlit" material with no depth 76 | const material = new THREE.MeshBasicMaterial({ 77 | // Avoid popping 78 | depthTest: false, 79 | color: random.pick(palette) 80 | }); 81 | 82 | // Create the mesh 83 | const mesh = new THREE.Mesh(geometry, material); 84 | 85 | // Randomize it 86 | randomizeMesh(mesh); 87 | 88 | return mesh; 89 | }); 90 | 91 | // Add meshes to the group 92 | meshes.forEach(m => container.add(m)); 93 | 94 | // Then add the group to the scene 95 | scene.add(container); 96 | 97 | // draw each frame 98 | return { 99 | // Handle resize events here 100 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 101 | renderer.setPixelRatio(pixelRatio); 102 | renderer.setSize(viewportWidth, viewportHeight); 103 | 104 | // Setup an isometric perspective 105 | const aspect = viewportWidth / viewportHeight; 106 | const zoom = 1.85; 107 | camera.left = -zoom * aspect; 108 | camera.right = zoom * aspect; 109 | camera.top = zoom; 110 | camera.bottom = -zoom; 111 | camera.near = -100; 112 | camera.far = 100; 113 | camera.position.set(zoom, zoom, zoom); 114 | camera.lookAt(new THREE.Vector3()); 115 | 116 | // Update camera properties 117 | camera.updateProjectionMatrix(); 118 | }, 119 | // And render events here 120 | render ({ time, deltaTime, width, height }) { 121 | // Animate each mesh with noise 122 | meshes.forEach(mesh => { 123 | const f = 0.5; 124 | mesh.position.x = mesh.originalPosition.x + 0.25 * random.noise3D( 125 | mesh.originalPosition.x * f, 126 | mesh.originalPosition.y * f, 127 | mesh.originalPosition.z * f, 128 | time * 0.25 129 | ); 130 | }); 131 | 132 | // Draw scene with our camera 133 | renderer.render(scene, camera); 134 | }, 135 | // Dispose of WebGL context (optional) 136 | unload () { 137 | renderer.dispose(); 138 | } 139 | }; 140 | }; 141 | 142 | canvasSketch(sketch, settings); 143 | -------------------------------------------------------------------------------- /src/webgl/03-cubes-lighting.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three'); 2 | 3 | const canvasSketch = require('canvas-sketch'); 4 | const random = require('canvas-sketch-util/random'); 5 | const palettes = require('nice-color-palettes'); 6 | 7 | const settings = { 8 | animate: true, 9 | dimensions: [ 1024, 1280 ], 10 | // Get a WebGL canvas rather than 2D 11 | context: 'webgl', 12 | // Turn on MSAA 13 | attributes: { antialias: true } 14 | }; 15 | 16 | const sketch = ({ context, width, height }) => { 17 | // Create a renderer 18 | const renderer = new THREE.WebGLRenderer({ 19 | context 20 | }); 21 | 22 | // WebGL background color 23 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 24 | 25 | // Setup a camera, we will update its settings on resize 26 | const camera = new THREE.OrthographicCamera(); 27 | 28 | // Setup your scene 29 | const scene = new THREE.Scene(); 30 | 31 | // Get a palette for our scene 32 | const palette = random.pick(palettes); 33 | 34 | // Snap 0..1 point to a -1..1 grid 35 | const grid = (n, gridSize) => { 36 | const max = gridSize - 1; 37 | const snapped = Math.round(n * max) / max; 38 | return snapped * 2 - 1; 39 | }; 40 | 41 | // Randomize mesh attributes 42 | const randomizeMesh = (mesh) => { 43 | const gridSize = random.rangeFloor(3, 11); 44 | // Choose a random grid point in a 3D volume between -1..1 45 | const point = new THREE.Vector3( 46 | grid(random.value(), gridSize), 47 | grid(random.value(), gridSize), 48 | grid(random.value(), gridSize) 49 | ); 50 | 51 | // Stretch it vertically 52 | point.y *= 2; 53 | // Scale all the points closer together 54 | point.multiplyScalar(0.5); 55 | 56 | // Save position 57 | mesh.position.copy(point); 58 | mesh.originalPosition = mesh.position.clone(); 59 | 60 | // Choose a color for the mesh material 61 | mesh.material.color.set(random.pick(palette)); 62 | 63 | // Randomly scale each axis 64 | mesh.scale.set( 65 | random.gaussian(), 66 | random.gaussian(), 67 | random.gaussian() 68 | ); 69 | 70 | // Do more random scaling on each axis 71 | if (random.chance(0.5)) mesh.scale.x *= random.gaussian(); 72 | if (random.chance(0.5)) mesh.scale.y *= random.gaussian(); 73 | if (random.chance(0.5)) mesh.scale.z *= random.gaussian(); 74 | 75 | // Further scale each object 76 | mesh.scale.multiplyScalar(random.gaussian() * 0.25); 77 | }; 78 | 79 | // A group that will hold all of our cubes 80 | const container = new THREE.Group(); 81 | 82 | // Re-use the same Geometry across all our cubes 83 | const geometry = new THREE.BoxGeometry(1, 1, 1); 84 | 85 | // The # of cubes to create 86 | const chunks = 50; 87 | 88 | // Create each cube and return a THREE.Mesh 89 | const meshes = Array.from(new Array(chunks)).map(() => { 90 | // Basic "unlit" material with no depth 91 | const material = new THREE.MeshStandardMaterial({ 92 | metalness: 0, 93 | roughness: 1, 94 | color: random.pick(palette) 95 | }); 96 | 97 | // Create the mesh 98 | const mesh = new THREE.Mesh(geometry, material); 99 | 100 | // Randomize it 101 | randomizeMesh(mesh); 102 | 103 | return mesh; 104 | }); 105 | 106 | // Add meshes to the group 107 | meshes.forEach(m => container.add(m)); 108 | 109 | // Then add the group to the scene 110 | scene.add(container); 111 | 112 | // Add a harsh light to the scene 113 | const light = new THREE.DirectionalLight('white', 1); 114 | light.position.set(0, 0, 2); 115 | scene.add(light); 116 | 117 | // draw each frame 118 | return { 119 | // Handle resize events here 120 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 121 | renderer.setPixelRatio(pixelRatio); 122 | renderer.setSize(viewportWidth, viewportHeight); 123 | 124 | // Setup an isometric perspective 125 | const aspect = viewportWidth / viewportHeight; 126 | const zoom = 1.85; 127 | camera.left = -zoom * aspect; 128 | camera.right = zoom * aspect; 129 | camera.top = zoom; 130 | camera.bottom = -zoom; 131 | camera.near = -100; 132 | camera.far = 100; 133 | camera.position.set(zoom, zoom, zoom); 134 | camera.lookAt(new THREE.Vector3()); 135 | 136 | // Update camera properties 137 | camera.updateProjectionMatrix(); 138 | }, 139 | // And render events here 140 | render ({ time }) { 141 | // Animate each mesh with noise 142 | meshes.forEach(mesh => { 143 | const f = 0.5; 144 | mesh.position.x = mesh.originalPosition.x + 0.25 * random.noise3D( 145 | mesh.originalPosition.x * f, 146 | mesh.originalPosition.y * f, 147 | mesh.originalPosition.z * f, 148 | time * 0.25 149 | ); 150 | }); 151 | 152 | // Draw scene with our camera 153 | renderer.render(scene, camera); 154 | }, 155 | // Dispose of WebGL context (optional) 156 | unload () { 157 | renderer.dispose(); 158 | } 159 | }; 160 | }; 161 | 162 | canvasSketch(sketch, settings); 163 | -------------------------------------------------------------------------------- /src/webgl/04-cubes-animation.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three'); 2 | 3 | const canvasSketch = require('canvas-sketch'); 4 | const random = require('canvas-sketch-util/random'); 5 | const palettes = require('nice-color-palettes'); 6 | 7 | const settings = { 8 | animate: true, 9 | dimensions: [ 1024, 1280 ], 10 | // Get a WebGL canvas rather than 2D 11 | context: 'webgl', 12 | // Turn on MSAA 13 | attributes: { antialias: true } 14 | }; 15 | 16 | const sketch = ({ context, width, height }) => { 17 | // Create a renderer 18 | const renderer = new THREE.WebGLRenderer({ 19 | context 20 | }); 21 | 22 | // WebGL background color 23 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 24 | 25 | // Setup a camera, we will update its settings on resize 26 | const camera = new THREE.OrthographicCamera(); 27 | 28 | // Setup your scene 29 | const scene = new THREE.Scene(); 30 | 31 | // Get a palette for our scene 32 | const palette = random.pick(palettes); 33 | 34 | // Snap 0..1 point to a -1..1 grid 35 | const grid = (n, gridSize) => { 36 | const max = gridSize - 1; 37 | const snapped = Math.round(n * max) / max; 38 | return snapped * 2 - 1; 39 | }; 40 | 41 | // Randomize mesh attributes 42 | const randomizeMesh = (mesh) => { 43 | const gridSize = random.rangeFloor(3, 11); 44 | // Choose a random grid point in a 3D volume between -1..1 45 | const point = new THREE.Vector3( 46 | grid(random.value(), gridSize), 47 | grid(random.value(), gridSize), 48 | grid(random.value(), gridSize) 49 | ); 50 | 51 | // Stretch it vertically 52 | point.y *= 1.5; 53 | // Scale all the points closer together 54 | point.multiplyScalar(0.5); 55 | point.y -= 0.65; 56 | 57 | // Save position 58 | mesh.position.copy(point); 59 | mesh.originalPosition = mesh.position.clone(); 60 | 61 | // Choose a color for the mesh material 62 | mesh.material.color.set(random.pick(palette)); 63 | 64 | // Randomly scale each axis 65 | mesh.scale.set( 66 | random.gaussian(), 67 | random.gaussian(), 68 | random.gaussian() 69 | ); 70 | 71 | // Do more random scaling on each axis 72 | if (random.chance(0.5)) mesh.scale.x *= random.gaussian(); 73 | if (random.chance(0.5)) mesh.scale.y *= random.gaussian(); 74 | if (random.chance(0.5)) mesh.scale.z *= random.gaussian(); 75 | 76 | // Further scale each object 77 | mesh.scale.multiplyScalar(random.gaussian() * 0.25); 78 | 79 | // Store the scale 80 | mesh.originalScale = mesh.scale.clone(); 81 | 82 | // Set some time properties on each mesh 83 | mesh.time = 0; 84 | mesh.duration = random.range(1, 4); 85 | }; 86 | 87 | // A group that will hold all of our cubes 88 | const container = new THREE.Group(); 89 | 90 | // Re-use the same Geometry across all our cubes 91 | const geometry = new THREE.BoxGeometry(1, 1, 1); 92 | 93 | // The # of cubes to create 94 | const chunks = 50; 95 | 96 | // Create each cube and return a THREE.Mesh 97 | const meshes = Array.from(new Array(chunks)).map(() => { 98 | // Basic "unlit" material with no depth 99 | const material = new THREE.MeshStandardMaterial({ 100 | metalness: 0, 101 | roughness: 1, 102 | color: random.pick(palette) 103 | }); 104 | 105 | // Create the mesh 106 | const mesh = new THREE.Mesh(geometry, material); 107 | 108 | // Randomize it 109 | randomizeMesh(mesh); 110 | 111 | // Set an initially random time 112 | mesh.time = random.range(0, mesh.duration); 113 | 114 | return mesh; 115 | }); 116 | 117 | // Add meshes to the group 118 | meshes.forEach(m => container.add(m)); 119 | 120 | // Then add the group to the scene 121 | scene.add(container); 122 | 123 | // Add a harsh light to the scene 124 | const light = new THREE.DirectionalLight('white', 1); 125 | light.position.set(0, 0, 2); 126 | scene.add(light); 127 | 128 | // draw each frame 129 | return { 130 | // Handle resize events here 131 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 132 | renderer.setPixelRatio(pixelRatio); 133 | renderer.setSize(viewportWidth, viewportHeight); 134 | 135 | // Setup an isometric perspective 136 | const aspect = viewportWidth / viewportHeight; 137 | const zoom = 1.85; 138 | camera.left = -zoom * aspect; 139 | camera.right = zoom * aspect; 140 | camera.top = zoom; 141 | camera.bottom = -zoom; 142 | camera.near = -100; 143 | camera.far = 100; 144 | camera.position.set(zoom, zoom, zoom); 145 | camera.lookAt(new THREE.Vector3()); 146 | 147 | // Update camera properties 148 | camera.updateProjectionMatrix(); 149 | }, 150 | // And render events here 151 | render ({ time, deltaTime }) { 152 | // Animate each mesh with noise 153 | meshes.forEach(mesh => { 154 | // Each mesh has its own time that increases each frame 155 | mesh.time += deltaTime; 156 | 157 | // If it hits the end of its life, reset it 158 | if (mesh.time > mesh.duration) { 159 | randomizeMesh(mesh); 160 | } 161 | 162 | // Scale meshes in and out 163 | mesh.scale.copy(mesh.originalScale); 164 | mesh.scale.multiplyScalar(Math.sin(mesh.time / mesh.duration * Math.PI)); 165 | 166 | // Move meshes up 167 | mesh.position.y += deltaTime * 0.5; 168 | 169 | // Add slight movement 170 | const f = 0.5; 171 | mesh.scale.y = mesh.originalScale.y + 0.25 * random.noise3D( 172 | mesh.originalPosition.x * f, 173 | mesh.originalPosition.y * f, 174 | mesh.originalPosition.z * f, 175 | time * 0.25 176 | ); 177 | }); 178 | 179 | // Draw scene with our camera 180 | renderer.render(scene, camera); 181 | }, 182 | // Dispose of WebGL context (optional) 183 | unload () { 184 | renderer.dispose(); 185 | } 186 | }; 187 | }; 188 | 189 | canvasSketch(sketch, settings); 190 | -------------------------------------------------------------------------------- /src/webgl/05-seamless-loop.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | // const eases = require('eases/expo-in-out'); 3 | const BezierEasing = require('bezier-easing'); 4 | 5 | // Ensure ThreeJS is in global scope for the 'examples/' 6 | global.THREE = require('three'); 7 | 8 | // Include any additional ThreeJS examples below 9 | require('three/examples/js/controls/OrbitControls'); 10 | 11 | const settings = { 12 | dimensions: [ 512, 512 ], 13 | duration: 4, 14 | fps: 24, 15 | playbackRate: 'throttle', 16 | // Make the loop animated 17 | animate: true, 18 | // Get a WebGL canvas rather than 2D 19 | context: 'webgl', 20 | // Turn on MSAA 21 | attributes: { antialias: true } 22 | }; 23 | 24 | const sketch = ({ context }) => { 25 | // Create a renderer 26 | const renderer = new THREE.WebGLRenderer({ 27 | context 28 | }); 29 | 30 | // WebGL background color 31 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 32 | 33 | // Setup a camera 34 | const camera = new THREE.OrthographicCamera(); 35 | 36 | // Setup your scene 37 | const scene = new THREE.Scene(); 38 | 39 | const mesh = new THREE.Mesh( 40 | new THREE.BoxGeometry(1, 1, 1), 41 | new THREE.MeshPhysicalMaterial({ 42 | color: 'red', 43 | metalness: 0, 44 | roughness: 1, 45 | flatShading: true 46 | }) 47 | ); 48 | scene.add(mesh); 49 | 50 | // Add some light 51 | const light = new THREE.DirectionalLight('white', 1); 52 | light.position.set(1, 2, 1); 53 | scene.add(light); 54 | 55 | const ease = BezierEasing(0.74, -0.01, 0.21, 0.99); 56 | 57 | // draw each frame 58 | return { 59 | // Handle resize events here 60 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 61 | renderer.setPixelRatio(pixelRatio); 62 | renderer.setSize(viewportWidth, viewportHeight); 63 | 64 | const aspect = viewportWidth / viewportHeight; 65 | 66 | // Ortho zoom 67 | const zoom = 1.5; 68 | 69 | // Bounds 70 | camera.left = -zoom * aspect; 71 | camera.right = zoom * aspect; 72 | camera.top = zoom; 73 | camera.bottom = -zoom; 74 | 75 | // Near/Far 76 | camera.near = -100; 77 | camera.far = 100; 78 | 79 | // Set position & look at world center 80 | camera.position.set(zoom, zoom, zoom); 81 | camera.lookAt(new THREE.Vector3()); 82 | 83 | // Update the camera 84 | camera.updateProjectionMatrix(); 85 | }, 86 | // Update & render your scene here 87 | render ({ playhead }) { 88 | scene.rotation.y = ease(playhead) * Math.PI * 2; 89 | renderer.render(scene, camera); 90 | }, 91 | // Dispose of events & renderer for cleaner hot-reloading 92 | unload () { 93 | renderer.dispose(); 94 | } 95 | }; 96 | }; 97 | 98 | canvasSketch(sketch, settings); 99 | -------------------------------------------------------------------------------- /src/webgl/06-shader.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const createShader = require('canvas-sketch-util/shader'); 3 | const glsl = require('glslify'); 4 | 5 | // Setup our sketch 6 | const settings = { 7 | dimensions: [ 1440, 900 ], 8 | exportPixelRatio: 2, 9 | context: 'webgl', 10 | animate: true 11 | }; 12 | 13 | // Your glsl code 14 | const frag = glsl(` 15 | precision highp float; 16 | 17 | uniform float time; 18 | uniform float aspect; 19 | varying vec2 vUv; 20 | 21 | void main () { 22 | // Get a vector from current UV to (0.5, 0.5) 23 | vec2 center = vUv - 0.5; 24 | 25 | // Fix it for current aspect ratio 26 | center.x *= aspect; 27 | 28 | // Get length of the vector (i.e. radius of polar coordinate) 29 | float dist = length(center); 30 | 31 | // Create a "mask" circle 32 | float mask = smoothstep(0.2025, 0.2, dist); 33 | 34 | vec3 color = 0.5 + 0.5 * cos(time + vUv.xyx + vec3(0.0, 2.0, 4.0)); 35 | gl_FragColor = vec4(color, mask); 36 | } 37 | `); 38 | 39 | // Your sketch, which simply returns the shader 40 | const sketch = ({ gl }) => { 41 | // Create the shader and return it 42 | return createShader({ 43 | // Pass along WebGL context 44 | gl, 45 | // Specify fragment and/or vertex shader strings 46 | frag, 47 | clearColor: 'hsl(0, 0%, 95%)', 48 | // Specify additional uniforms to pass down to the shaders 49 | uniforms: { 50 | // Expose props from canvas-sketch 51 | time: ({ time }) => time, 52 | aspect: ({ width, height }) => width / height 53 | } 54 | }); 55 | }; 56 | 57 | canvasSketch(sketch, settings); 58 | -------------------------------------------------------------------------------- /src/webgl/07-shader-noise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An attempt to recreate the effect by DIA Studio Primary project 3 | * as a GLSL shader. 4 | * 5 | * https://www.behance.net/gallery/33134879/Primary 6 | */ 7 | 8 | const canvasSketch = require('canvas-sketch'); 9 | const createShader = require('canvas-sketch-util/shader'); 10 | const glsl = require('glslify'); 11 | 12 | // Setup our sketch 13 | const settings = { 14 | context: 'webgl', 15 | dimensions: [ 1024, 1024 ], 16 | animate: true 17 | }; 18 | 19 | // Your glsl code 20 | const frag = glsl(/* glsl */` 21 | precision highp float; 22 | 23 | #pragma glslify: noise = require('glsl-noise/simplex/3d'); 24 | #pragma glslify: hsl2rgb = require('glsl-hsl2rgb'); 25 | 26 | uniform float time; 27 | uniform float aspect; 28 | varying vec2 vUv; 29 | 30 | float circle (vec2 point) { 31 | vec2 pos = vUv - point; 32 | pos.x *= aspect; 33 | return length(pos); 34 | } 35 | 36 | void main () { 37 | float distFromCenter = circle(vec2(0.5)); 38 | 39 | float mask = smoothstep(0.25, 0.2475, distFromCenter); 40 | 41 | vec2 q = vUv; 42 | q.x *= aspect; 43 | 44 | float d = 0.0; 45 | d += (noise(vec3(q * 1.0, time * 0.5)) * 0.5 + 0.5) * 0.5; 46 | d += (noise(vec3(q * 0.25 + 0.5, time * 0.25)) * 0.5 + 0.5) * 0.5; 47 | d = clamp(d, 0.0, 1.0); 48 | 49 | vec3 color = hsl2rgb( 50 | mod(time * 0.05, 1.0) + (0.5 + d * 0.5), 51 | 0.5, 52 | 0.5 + d * 0.25 53 | ); 54 | 55 | vec3 fragColor = color; 56 | 57 | gl_FragColor = vec4(fragColor, mask); 58 | } 59 | `); 60 | 61 | // Your sketch, which simply returns the shader 62 | const sketch = async ({ gl }) => { 63 | // Create the shader and return it 64 | return createShader({ 65 | clearColor: 'hsl(0, 0%, 95%)', 66 | // Pass along WebGL context 67 | gl, 68 | // Specify fragment and/or vertex shader strings 69 | frag, 70 | // Specify additional uniforms to pass down to the shaders 71 | uniforms: { 72 | // Expose props from canvas-sketch 73 | time: ({ time }) => time, 74 | aspect: ({ width, height }) => width / height 75 | } 76 | }); 77 | }; 78 | 79 | canvasSketch(sketch, settings); 80 | -------------------------------------------------------------------------------- /src/webgl/08-vertex-shader.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const convexHull = require('convex-hull'); 3 | const random = require('canvas-sketch-util/random'); 4 | const { linspace } = require('canvas-sketch-util/math'); 5 | const glslify = require('glslify'); 6 | 7 | // Ensure ThreeJS is in global scope for the 'examples/' 8 | global.THREE = require('three'); 9 | 10 | // Include any additional ThreeJS examples below 11 | require('three/examples/js/controls/OrbitControls'); 12 | 13 | const settings = { 14 | dimensions: [ 512, 512 ], 15 | // Make the loop animated 16 | animate: true, 17 | // Get a WebGL canvas rather than 2D 18 | context: 'webgl', 19 | // Turn on MSAA 20 | attributes: { antialias: true } 21 | }; 22 | 23 | const sketch = ({ context }) => { 24 | // Create a renderer 25 | const renderer = new THREE.WebGLRenderer({ 26 | context 27 | }); 28 | 29 | // WebGL background color 30 | renderer.setClearColor('hsl(0, 0%, 0%)', 1); 31 | 32 | // Setup a camera 33 | const camera = new THREE.OrthographicCamera(); 34 | 35 | // Setup your scene 36 | const scene = new THREE.Scene(); 37 | 38 | const vertexShader = glslify(` 39 | varying vec2 vUv; 40 | 41 | uniform float time; 42 | 43 | #pragma glslify: noise = require('glsl-noise/simplex/4d'); 44 | 45 | void main () { 46 | vUv = uv; 47 | 48 | vec3 transformed = position.xyz; 49 | 50 | float offset = 0.0; 51 | offset += 0.5 * (noise(vec4(normal.xyz * 1.0, time * 0.25)) * 0.5 + 0.5); 52 | 53 | transformed += normal * offset; 54 | 55 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 56 | } 57 | `); 58 | 59 | const fragmentShader = glslify(` 60 | varying vec2 vUv; 61 | uniform float time; 62 | 63 | #pragma glslify: hsl2rgb = require('glsl-hsl2rgb'); 64 | 65 | void main () { 66 | vec3 color = hsl2rgb(mod(vUv.y * 0.1 + time * 0.1, 1.0), 0.5, 0.5); 67 | gl_FragColor = vec4(color, 1.0); 68 | if (!gl_FrontFacing) { 69 | gl_FragColor = vec4(color * 0.25, 1.0); 70 | } 71 | } 72 | `); 73 | 74 | const geometry = new THREE.DodecahedronGeometry(1, 0); 75 | geometry.computeFlatVertexNormals(); 76 | 77 | const mesh = new THREE.Mesh( 78 | geometry, 79 | new THREE.ShaderMaterial({ 80 | flatShading: true, 81 | side: THREE.DoubleSide, 82 | vertexShader, 83 | fragmentShader, 84 | uniforms: { 85 | time: { value: 0 } 86 | } 87 | }) 88 | ); 89 | scene.add(mesh); 90 | 91 | // draw each frame 92 | return { 93 | // Handle resize events here 94 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 95 | renderer.setPixelRatio(pixelRatio); 96 | renderer.setSize(viewportWidth, viewportHeight); 97 | 98 | const aspect = viewportWidth / viewportHeight; 99 | 100 | // Ortho zoom 101 | const zoom = 2.0; 102 | 103 | // Bounds 104 | camera.left = -zoom * aspect; 105 | camera.right = zoom * aspect; 106 | camera.top = zoom; 107 | camera.bottom = -zoom; 108 | 109 | // Near/Far 110 | camera.near = -100; 111 | camera.far = 100; 112 | 113 | // Set position & look at world center 114 | camera.position.set(zoom, zoom, zoom); 115 | camera.lookAt(new THREE.Vector3()); 116 | 117 | // Update the camera 118 | camera.updateProjectionMatrix(); 119 | }, 120 | // Update & render your scene here 121 | render ({ time }) { 122 | mesh.rotation.z = 0.1 * time; 123 | mesh.material.uniforms.time.value = time; 124 | renderer.render(scene, camera); 125 | }, 126 | // Dispose of events & renderer for cleaner hot-reloading 127 | unload () { 128 | renderer.dispose(); 129 | } 130 | }; 131 | }; 132 | 133 | canvasSketch(sketch, settings); 134 | -------------------------------------------------------------------------------- /src/webgl/09-blob.js: -------------------------------------------------------------------------------- 1 | const canvasSketch = require('canvas-sketch'); 2 | const convexHull = require('convex-hull'); 3 | const random = require('canvas-sketch-util/random'); 4 | const { linspace } = require('canvas-sketch-util/math'); 5 | const glslify = require('glslify'); 6 | 7 | // Ensure ThreeJS is in global scope for the 'examples/' 8 | global.THREE = require('three'); 9 | 10 | // Include any additional ThreeJS examples below 11 | require('three/examples/js/controls/OrbitControls'); 12 | 13 | const settings = { 14 | // Make the loop animated 15 | animate: true, 16 | // Get a WebGL canvas rather than 2D 17 | context: 'webgl', 18 | // Turn on MSAA 19 | attributes: { antialias: true } 20 | }; 21 | 22 | const sketch = ({ context }) => { 23 | // Create a renderer 24 | const renderer = new THREE.WebGLRenderer({ 25 | context 26 | }); 27 | 28 | // WebGL background color 29 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 30 | 31 | // Setup a camera 32 | const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100); 33 | camera.position.set(2, 2, -4); 34 | camera.lookAt(new THREE.Vector3()); 35 | 36 | // Setup camera controller 37 | const controls = new THREE.OrbitControls(camera); 38 | 39 | // Setup your scene 40 | const scene = new THREE.Scene(); 41 | 42 | const vertexShader = glslify(` 43 | varying vec2 vUv; 44 | 45 | uniform float time; 46 | 47 | #pragma glslify: noise = require('glsl-noise/simplex/4d'); 48 | 49 | void main () { 50 | vUv = uv; 51 | 52 | vec3 transformed = position.xyz; 53 | 54 | float offset = 0.0; 55 | offset += 0.5 * noise(vec4(position.xyz * 0.5, time * 0.25)); 56 | offset += 0.25 * noise(vec4(position.xyz * 1.5, time * 0.25)); 57 | 58 | transformed += normal * offset; 59 | 60 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 61 | } 62 | `); 63 | 64 | const fragmentShader = glslify(` 65 | varying vec2 vUv; 66 | uniform float time; 67 | 68 | #pragma glslify: hsl2rgb = require('glsl-hsl2rgb'); 69 | 70 | void main () { 71 | float hue = mix(0.2, 0.5, sin(vUv.x * 3.14)); 72 | vec3 color = hsl2rgb(vec3(hue, 0.5, vUv.y)); 73 | gl_FragColor = vec4(color, 1.0); 74 | } 75 | `); 76 | 77 | const geometry = new THREE.SphereGeometry(1, 64, 64); 78 | 79 | const mesh = new THREE.Mesh( 80 | geometry, 81 | new THREE.ShaderMaterial({ 82 | flatShading: true, 83 | // wireframe: true, 84 | side: THREE.DoubleSide, 85 | vertexShader, 86 | fragmentShader, 87 | uniforms: { 88 | time: { value: 0 } 89 | } 90 | }) 91 | ); 92 | scene.add(mesh); 93 | 94 | // draw each frame 95 | return { 96 | // Handle resize events here 97 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 98 | renderer.setPixelRatio(pixelRatio); 99 | renderer.setSize(viewportWidth, viewportHeight); 100 | camera.aspect = viewportWidth / viewportHeight; 101 | camera.updateProjectionMatrix(); 102 | }, 103 | // Update & render your scene here 104 | render ({ time }) { 105 | mesh.rotation.y = time; 106 | mesh.material.uniforms.time.value = time; 107 | controls.update(); 108 | renderer.render(scene, camera); 109 | }, 110 | // Dispose of events & renderer for cleaner hot-reloading 111 | unload () { 112 | controls.dispose(); 113 | renderer.dispose(); 114 | } 115 | }; 116 | }; 117 | 118 | canvasSketch(sketch, settings); 119 | --------------------------------------------------------------------------------