├── .gitignore ├── LICENSE.md ├── README.md ├── docs ├── cloning.md ├── command-line.md ├── images │ ├── demo1.png │ ├── demo2.png │ ├── demo6.png │ ├── xyz-1.png │ └── xyz-2.png └── snippets.md ├── package-lock.json ├── package.json └── src ├── extras ├── custom-attribute.js └── noise-sphere.js ├── gego ├── 0-sphere.js ├── 1-random.js ├── 2-advanced.js └── 3-shader-animation.js └── turrell ├── 0-shader-cube-stack.js ├── 1-shader-cube-city.js └── 2-shader-interactive.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | demo/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017 Matt DesLauriers 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # draw_code.001 — ThreeJS, WebGL & GLSL 2 | 3 | This repository includes code & resources for students attending the *ThreeJS, WebGL & GLSL* workshops. 4 | 5 | ###### ✨ [ [Interactive Demo](https://drawcode-001-demo.surge.sh/) ] 6 | 7 | 8 | [](https://drawcode-001-demo.surge.sh/) 9 | 10 | # Contents 11 | 12 | - 📖 [Digital Book](#digital-book) 13 | 14 | - 🔧 [Tools & Prerequisites](#tools--prerequisites) 15 | 16 | - 🎨 [Installing `canvas-sketch` CLI](#installing-canvas-sketch-cli) 17 | 18 | - ✂️️ [Code Snippets](#code-snippets) 19 | 20 | - 💻 [Command-Line Tips & Suggestions](#command-line-tips--suggestions) 21 | 22 | - 🔥 [Modules for Creative Coding](#modules) 23 | 24 | - ⚡️ [Cloning & Running Examples](#cloning--running-examples) 25 | 26 | - ✨ [Further Reading](#further-reading) 27 | 28 | # Digital Book 29 | 30 | You can browse the interactive "slide book" here: 31 | 32 | - [https://drawcode-001.surge.sh/](https://drawcode-001.surge.sh/) 33 | - [ [mirror](https://001--angry-saha-ab0472.netlify.com/) ] 34 | 35 | # Tools & Prerequisites 36 | 37 | Here is a list of tools, software and libraries that will be used during the workshop. 38 | 39 | | Tool | Documentation | Description | 40 | |---|---|---| 41 | | *Code Editor* | | A JavaScript code editor, [VSCode](https://code.visualstudio.com/) is recommended 42 | | *Browser* | | A modern browser, [Chrome](https://www.google.com/chrome/) is recommended 43 | | *Canvas API* | [[docs](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)] | The HTML5 `` API, built into all browsers 44 | | *Command-Line* | [[guide](./docs/command-line.md)] | A command-line application like Terminal (macOS) or [cmder](http://cmder.net/) (Windows) 45 | | [Node.js](https://nodejs.org/en/) (v8+) | [[docs](https://nodejs.org/dist/latest-v8.x/docs/api/)] | Used for running command-line JavaScript tools 46 | | [npm](https://npmjs.com/) (v5+) | [[docs](https://nodejs.org/dist/latest-v8.x/docs/api/)] | Used to install third-party dependencies and tools 47 | | [`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 48 | | [`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 49 | | [ThreeJS](https://threejs.org/) | [[docs](https://threejs.org/docs/)] | A 3D rendering engine for WebGL 50 | 51 | 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`: 52 | 53 | ```sh 54 | npm --version 55 | node --version 56 | ``` 57 | 58 | ## Installing `canvas-sketch` CLI 59 | 60 | We will be using [`canvas-sketch`](https://github.com/mattdesl/canvas-sketch/) and its command-line interface (CLI) during the workshop. 61 | 62 | To install the CLI with npm, use the `--global` or `-g` flag like so: 63 | 64 | ```sh 65 | npm install canvas-sketch-cli --global 66 | ``` 67 | 68 | > :bulb: Note the `-cli` suffix in the name; this tells npm to install the CLI tool, not the code library. 69 | 70 | # Code Snippets 71 | 72 | I've also included a small "recipes" document that you can use as a reference if you are forgetting some of the patterns and recipes discussed during the workshop. 73 | 74 | - [Code Snippets](./docs/snippets.md) 75 | 76 | # Command-Line Tips & Suggestions 77 | 78 | If you are new to the command-line, you can read more details here: 79 | 80 | - [Command-Line Tips & Suggestions](./docs/command-line.md) 81 | 82 | # Cloning & Running Examples 83 | 84 | 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: 85 | 86 | - [Cloning & Running Examples](./docs/cloning.md) 87 | 88 | # Modules 89 | 90 | This workshop encourages students to make use of [npm](https://www.npmjs.com) modules to build complex and interesting artworks. 91 | 92 | If you find a module you want to use, like [riso-colors](https://www.npmjs.com/package/riso-colors), you can install it from your project folder like so: 93 | 94 | ```sh 95 | npm install riso-colors 96 | ``` 97 | 98 | Below are some of the modules used in the workshop: 99 | 100 | - [riso-colors](https://www.npmjs.com/package/riso-colors) - Risograph color palettes 101 | - [paper-colors](https://www.npmjs.com/package/paper-colors) - paper color palettes 102 | - [three-tube-wireframe](https://www.npmjs.com/package/three-tube-wireframe) - build volumetric wireframes from a THREE.Geometry 103 | - [anime.js](https://www.npmjs.com/package/animejs) - animation library 104 | - [simple-input-events](https://www.npmjs.com/package/simple-input-events) - utility for mobile & desktop touch/mouse events 105 | - [three-geometry-data](https://www.npmjs.com/package/three-geometry-data) - get nested vertex & normal data from a THREE.Geometry 106 | - [three-quaternion-from-normal](https://www.npmjs.com/package/three-quaternion-from-normal) - to orient a mesh toward a normal vector 107 | 108 | Here's a list of some other modules you might like to use in generative art: 109 | 110 | - [load-asset](https://www.npmjs.com/package/load-asset) - a utility to load images and other assets with async/await 111 | - [nice-color-palettes](https://www.npmjs.com/package/nice-color-palettes) - a collection of 1000 beautiful color palettes 112 | - [chromotome](https://www.npmjs.com/package/chromotome) - dozens of hand-picked color palettes 113 | - [poisson-disk-sampling](https://www.npmjs.com/package/poisson-disk-sampling) - can be used for 2D and 3D object placements 114 | - [convex-hull](https://www.npmjs.com/package/convex-hull) - 2D and 3D convex hull generation 115 | - [delaunay-triangulate](https://www.npmjs.com/package/delaunay-triangulate) - 2D and 3D triangulation 116 | - [voronoi-diagram](https://www.npmjs.com/package/voronoi-diagram) - for 2D and 3D voronoi diagrams 117 | - [svg-mesh-3d](https://github.com/mattdesl/svg-mesh-3d) - convert SVG path string to a 3D mesh 118 | - [eases](https://www.npmjs.com/package/eases) - a set of common easing functions 119 | - [glsl-noise](https://www.npmjs.com/package/glsl-noise) - noise functions as a GLSL module (used with glslify) 120 | - [glsl-hsl2rgb](https://www.npmjs.com/package/glsl-hsl2rgb) - HSL to RGB function as a GLSL module (used with glslify) 121 | 122 | # Further Reading 123 | 124 | More links to generative art & creative coding: 125 | 126 | - [Vanilla Canvas2D Demo](https://codepen.io/mattdesl/pen/BMGZJZ) 127 | 128 | - Generative Art 129 | 130 | - [Generative Artistry](https://generativeartistry.com/) 131 | 132 | - [Anders Hoff](https://inconvergent.net/#writing) — Writing on Generative Art 133 | 134 | - [Tyler Hobbs](http://www.tylerlhobbs.com/writings) — Writing on Generative Art 135 | 136 | - [My Blog](https://mattdesl.svbtle.com/) — Writing on Creative Coding & Generative Art 137 | 138 | - GLSL & Shaders 139 | 140 | - [The Book of Shaders](https://thebookofshaders.com/) 141 | 142 | - [Lesson: GLSL Shader Basics](https://github.com/Jam3/jam3-lesson-webgl-shader-intro) 143 | 144 | - [Lesson: Custom Shaders in ThreeJS](https://github.com/Jam3/jam3-lesson-webgl-shader-threejs) 145 | 146 | - Math 147 | 148 | - [Linear Interpolation](https://mattdesl.svbtle.com/linear-interpolation) — intro to `lerp` 149 | 150 | - [math-as-code](https://github.com/Jam3/math-as-code) — A cheat sheet for mathematical notation in code form 151 | 152 | - More Resources 153 | 154 | - [awesome-creative-coding](https://github.com/terkelg/awesome-creative-coding) — a large list of resources 155 | 156 | - [graphics-resources](https://github.com/mattdesl/graphics-resources) — a large list of papers & study material 157 | 158 | - Tools 159 | 160 | - [giftool.surge.sh](https://giftool.surge.sh/) — A simple tool for creating looping GIF animations from a folder of PNG frames 161 | 162 | - [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 163 | 164 | - [ThreeJS Editor](https://threejs.org/editor/) — An online scene editor for ThreeJS 165 | 166 | - Communities 167 | 168 | - [creative-dev Slack team](https://creative-dev.herokuapp.com/) 169 | 170 | - [#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. 171 | 172 | - Riso Printers in Toronto 173 | 174 | - [Vide Press](https://videpress.ca/) 175 | 176 | - [Colour Code Printing](http://colourcodeprinting.com/) 177 | 178 | # License 179 | 180 | This repository has a dual license. 181 | 182 | The textual documentation and markdown files are all licensed as MIT. 183 | 184 | The images and 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. 185 | -------------------------------------------------------------------------------- /docs/cloning.md: -------------------------------------------------------------------------------- 1 | #### :closed_book: [draw_code.001](../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/drawcode-workshops/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/drawcode-workshops/001.git draw_code-001 29 | ``` 30 | 31 | This will create a new folder called `draw_code-001` 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 draw_code-001 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: [draw_code.001](../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/images/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drawcode-workshops/001/81ad9956c1cdc17c5a434ea69e2ab3bdcde7b976/docs/images/demo1.png -------------------------------------------------------------------------------- /docs/images/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drawcode-workshops/001/81ad9956c1cdc17c5a434ea69e2ab3bdcde7b976/docs/images/demo2.png -------------------------------------------------------------------------------- /docs/images/demo6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drawcode-workshops/001/81ad9956c1cdc17c5a434ea69e2ab3bdcde7b976/docs/images/demo6.png -------------------------------------------------------------------------------- /docs/images/xyz-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drawcode-workshops/001/81ad9956c1cdc17c5a434ea69e2ab3bdcde7b976/docs/images/xyz-1.png -------------------------------------------------------------------------------- /docs/images/xyz-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drawcode-workshops/001/81ad9956c1cdc17c5a434ea69e2ab3bdcde7b976/docs/images/xyz-2.png -------------------------------------------------------------------------------- /docs/snippets.md: -------------------------------------------------------------------------------- 1 | #### :closed_book: [draw_code.001](../README.md) → Snippets 2 | 3 | --- 4 | 5 | # Snippets 6 | 7 | Here you will find some 'recipes' and patterns that we'll be using during the workshop. 8 | 9 | ## Isometric ThreeJS Camera 10 | 11 | In your setup, replace the perspective camera with: 12 | 13 | ```js 14 | const camera = new THREE.OrthographicCamera(); 15 | ``` 16 | 17 | In the `resize` function, replace the perspective camera configuration with: 18 | 19 | ```js 20 | const aspect = viewportWidth / viewportHeight; 21 | 22 | // Ortho zoom 23 | const zoom = 1.0; 24 | 25 | // Bounds 26 | camera.left = -zoom * aspect; 27 | camera.right = zoom * aspect; 28 | camera.top = zoom; 29 | camera.bottom = -zoom; 30 | 31 | // Near/Far 32 | camera.near = -100; 33 | camera.far = 100; 34 | 35 | // Set position & look at world center 36 | camera.position.set(zoom, zoom, zoom); 37 | camera.lookAt(new THREE.Vector3()); 38 | 39 | // Update the camera 40 | camera.updateProjectionMatrix(); 41 | ``` 42 | 43 | ## 3D Coordinate System 44 | 45 | Here's a small reference you can use to remember XYZ axes in ThreeJS. 46 | 47 | 48 | 49 | ## Raycast Mouse With Infinite Ground 50 | 51 | You can use the following to find the 3D position under the mouse by raycasting to a virtual 'ground plane' along the XZ plane. 52 | 53 | ```js 54 | const raycaster = new THREE.Raycaster(); 55 | const ground = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); 56 | 57 | // Disbale 'touch-action' for better mobile support 58 | document.body.style.touchAction = 'none'; 59 | 60 | // Listen for pointer events on the body 61 | document.body.addEventListener('pointermove', ev => { 62 | const mouse = new THREE.Vector2( 63 | ev.clientX / window.innerWidth * 2 - 1, 64 | -ev.clientY / window.innerHeight * 2 + 1 65 | ); 66 | raycaster.setFromCamera(mouse, camera); 67 | const target = new THREE.Vector3(); 68 | const hit = raycaster.ray.intersectPlane(ground, target); 69 | if (hit) { 70 | console.log('Hit ground at', target); 71 | } 72 | }); 73 | ``` 74 | 75 | ## `simple-input-events` for unified mouse / touch 76 | 77 | If you want, you can use the `simple-input-events` utility to receive mouse and touch input like so: 78 | 79 | ```js 80 | const createInputEvents = require('simple-input-events'); 81 | 82 | const sketch = (props) => { 83 | const input = createInputEvents({ 84 | // input events happen on canvas 85 | target: props.canvas, 86 | // block page swipe events on mobile 87 | preventDefault: true 88 | }).on('move', ({ position }) => { 89 | // get a uv from 0..1 90 | const u = position[0] / props.styleWidth; 91 | const v = position[1] / props.styleHeight; 92 | 93 | // get the X and Y in working units 94 | // e.g. works in 'cm' or 'in' as well 95 | const x = props.width * u; 96 | const y = props.height * v; 97 | 98 | // ... do something with position ... 99 | }); 100 | 101 | return { 102 | render () { 103 | // ... draw your scene 104 | }, 105 | unload () { 106 | // disable mouse events 107 | input.disable(); 108 | } 109 | } 110 | }; 111 | ``` 112 | 113 | ## Noise from 2D Coordinates 114 | 115 | If you have 2D coordinates between `N0..N1`, you can get back a *simplex noise* signal from those coordinates that smoothly varies between `-1...1`. 116 | 117 | ```js 118 | const Random = require('canvas-sketch-util/random'); 119 | 120 | const frequency = 1; 121 | const amplitude = 1; 122 | 123 | const n = amplitude * Random.noise2D(x * frequency, y * frequency); 124 | ``` 125 | 126 | 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. 127 | 128 | > *Tip:* It's a good idea to pass normalized values in the range `-1..1` into your noise functions. 129 | 130 | ## `0..1` to `-1...1` 131 | 132 | If *t* is between 0 and 1 (inclusive) and you want to map it to -1 to 1 (inclusive), you can use this: 133 | 134 | ```js 135 | const n = t * 2 - 1; 136 | ``` 137 | 138 | ## `-1..1` to `0...1` 139 | 140 | If *t* is between -1 and 1 (inclusive) and you want to map it to 0 to 1 (inclusive), you can use this: 141 | 142 | ```js 143 | const n = t * 0.5 + 0.5; 144 | ``` 145 | 146 | ## Looping Motion in `-1..1` Range 147 | 148 | To create a looping motion from `-1..1` you can use `Math.sin()`, like so: 149 | 150 | ```js 151 | const motionSpeed = 0.5; 152 | const v = Math.sin(time * motionSpeed); 153 | ``` 154 | 155 | You can map this value into `0..1` space and/or interpolate it to another range. 156 | 157 | ## Ping-Pong Motion in `0..1` Range 158 | 159 | 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. 160 | 161 | ```js 162 | const v = Math.sin(playhead * Math.PI); 163 | ``` 164 | 165 | You can invert this with `1.0 - v` if you need it to vary from 1, to 0, and then back to 1. 166 | 167 | ## Orient Mesh from Point A to B 168 | 169 | Let's say you have a mesh that you'd like to orient so that it faces the same direction as a unit vector, AKA a *normal*. You can do the following: 170 | 171 | ```js 172 | const quaternionFromNormal = require('three-quaternion-from-normal'); 173 | 174 | // Say we want mesh to point from A to B point 175 | const A = new THREE.Vector3(1, 0, 0); 176 | const B = new THREE.Vector3(2, 5, -1); 177 | 178 | // Get normal A->B 179 | const normal = B.clone().sub(A).normalize(); 180 | 181 | // Get orientation 182 | const quaternion = quaternionFromNormal(normal); 183 | 184 | // Apply orientation to mesh 185 | mesh.quaternion.copy(quaternion); 186 | ``` 187 | 188 | This uses the [three-quaternion-from-normal](https://www.npmjs.com/package/three-quaternion-from-normal) module. 189 | 190 | ## 191 | 192 | #### [← Back to Documentation](../README.md) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "001", 3 | "version": "1.0.0", 4 | "description": "draw_code.001 — ThreeJS, WebGL & GLSL", 5 | "license": "MIT", 6 | "author": { 7 | "name": "draw_code", 8 | "email": "hello@drawcode.info", 9 | "url": "https://github.com/drawcode-workshops" 10 | }, 11 | "dependencies": { 12 | "animejs": "^3.1.0", 13 | "canvas-sketch": "^0.6.1", 14 | "canvas-sketch-util": "^1.8.0", 15 | "chromotome": "^1.11.0", 16 | "convex-hull": "^1.0.3", 17 | "glsl-dither": "^1.0.1", 18 | "glsl-noise": "0.0.0", 19 | "nice-color-palettes": "^3.0.0", 20 | "paper-colors": "^1.0.0", 21 | "riso-colors": "^1.0.1", 22 | "simple-input-events": "^1.0.0", 23 | "three": "^0.107.0", 24 | "three-geometry-data": "^1.0.1", 25 | "three-quaternion-from-normal": "^1.0.0", 26 | "three-tube-wireframe": "^1.2.0", 27 | "unindex-mesh": "^2.0.0" 28 | }, 29 | "devDependencies": { 30 | "canvas-sketch-cli": "^1.10.1", 31 | "surge": "^0.21.3" 32 | }, 33 | "scripts": { 34 | "deploy": "npm run build-demo && npm run deploy-demo", 35 | "build-demo": "canvas-sketch src/turrell/2-shader-interactive.js --build --dir=demo --inline --name=index", 36 | "deploy-demo": "surge -p demo/ -d drawcode-001-demo.surge.sh" 37 | }, 38 | "keywords": [], 39 | "repository": { 40 | "type": "git", 41 | "url": "git://github.com/drawcode-workshops/001.git" 42 | }, 43 | "homepage": "https://github.com/drawcode-workshops/001", 44 | "bugs": { 45 | "url": "https://github.com/drawcode-workshops/001/issues" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/extras/custom-attribute.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | require('three/examples/js/math/ConvexHull'); 7 | require('three/examples/js/geometries/ConvexGeometry'); 8 | 9 | const glslify = require('glslify'); 10 | const canvasSketch = require('canvas-sketch'); 11 | const { linspace } = require('canvas-sketch-util/math'); 12 | const Random = require('canvas-sketch-util/random'); 13 | const risoColors = require('riso-colors').map(c => c.hex); 14 | const paperColors = require('paper-colors').map(c => c.hex); 15 | 16 | const settings = { 17 | // Make the loop animated 18 | animate: true, 19 | dimensions: [ 1024, 1024 ], 20 | // Get a WebGL canvas rather than 2D 21 | context: 'webgl', 22 | // Turn on MSAA 23 | attributes: { antialias: true } 24 | }; 25 | 26 | const sketch = ({ context }) => { 27 | // Create a renderer 28 | const renderer = new THREE.WebGLRenderer({ 29 | context 30 | }); 31 | 32 | // WebGL background color 33 | renderer.setClearColor(Random.pick(paperColors), 1); 34 | 35 | // Setup a camera 36 | const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100); 37 | camera.position.set(1.5, 1.5, -1.5); 38 | camera.lookAt(new THREE.Vector3()); 39 | 40 | // Setup camera controller 41 | const controls = new THREE.OrbitControls(camera, context.canvas); 42 | 43 | // Setup your scene 44 | const scene = new THREE.Scene(); 45 | 46 | // Get N random points in a sphere 47 | let points = linspace(Random.rangeFloor(15, 30)).map(() => { 48 | const point = Random.insideSphere(1); 49 | return new THREE.Vector3().fromArray(point); 50 | }); 51 | 52 | const geometry = new THREE.ConvexBufferGeometry(points); 53 | geometry.center(); 54 | 55 | const colors = []; 56 | const vertexCount = geometry.getAttribute('position').count; 57 | for (let i = 0; i < vertexCount; i++) { 58 | const color = new THREE.Color(Random.pick(risoColors)); 59 | for (let t = 0; t < 3; t++) { 60 | colors.push(color.r, color.g, color.b); 61 | } 62 | } 63 | 64 | const color = new THREE.BufferAttribute(new Float32Array(colors), 3); 65 | geometry.addAttribute('color', color); 66 | 67 | const material = new THREE.ShaderMaterial({ 68 | uniforms: { 69 | time: { value: 0 } 70 | }, 71 | side: THREE.DoubleSide, 72 | vertexShader: glslify(` 73 | attribute vec3 color; 74 | uniform float time; 75 | varying vec3 vColor; 76 | varying vec2 vUv; 77 | void main () { 78 | vec3 transformed = position.xyz; 79 | vColor = color; 80 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 81 | } 82 | `), 83 | fragmentShader: ` 84 | varying vec3 vColor; 85 | void main () { 86 | gl_FragColor = vec4(vColor, 1.0); 87 | } 88 | ` 89 | }); 90 | const mesh = new THREE.Mesh(geometry, material); 91 | scene.add(mesh); 92 | 93 | // draw each frame 94 | return { 95 | // Handle resize events here 96 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 97 | renderer.setPixelRatio(pixelRatio); 98 | renderer.setSize(viewportWidth, viewportHeight); 99 | camera.aspect = viewportWidth / viewportHeight; 100 | camera.updateProjectionMatrix(); 101 | }, 102 | // Update & render your scene here 103 | render ({ time }) { 104 | mesh.material.uniforms.time.value = time; 105 | controls.update(); 106 | renderer.render(scene, camera); 107 | }, 108 | // Dispose of events & renderer for cleaner hot-reloading 109 | unload () { 110 | controls.dispose(); 111 | renderer.dispose(); 112 | } 113 | }; 114 | }; 115 | 116 | canvasSketch(sketch, settings); -------------------------------------------------------------------------------- /src/extras/noise-sphere.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const glslify = require('glslify'); 8 | const canvasSketch = require('canvas-sketch'); 9 | 10 | const settings = { 11 | // Make the loop animated 12 | animate: true, 13 | // Get a WebGL canvas rather than 2D 14 | context: 'webgl', 15 | // Turn on MSAA 16 | attributes: { antialias: true } 17 | }; 18 | 19 | const sketch = ({ context }) => { 20 | // Create a renderer 21 | const renderer = new THREE.WebGLRenderer({ 22 | context 23 | }); 24 | 25 | // WebGL background color 26 | renderer.setClearColor('pink', 1); 27 | 28 | // Setup a camera 29 | const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100); 30 | camera.position.set(4, 4, -4); 31 | camera.lookAt(new THREE.Vector3()); 32 | 33 | // Setup camera controller 34 | const controls = new THREE.OrbitControls(camera, context.canvas); 35 | 36 | // Setup your scene 37 | const scene = new THREE.Scene(); 38 | 39 | const geometry = new THREE.SphereGeometry(1, 32, 32); 40 | const material = new THREE.ShaderMaterial({ 41 | uniforms: { 42 | time: { value: 0 }, 43 | color: { value: new THREE.Color('tomato') } 44 | }, 45 | wireframe: true, 46 | vertexShader: glslify(` 47 | #pragma glslify: noise = require('glsl-noise/simplex/4d'); 48 | uniform float time; 49 | void main () { 50 | vec3 transformed = position.xyz; 51 | 52 | float n = noise(vec4(position.xyz, time)) * 0.5 + 0.5; 53 | transformed += n * normal; 54 | 55 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 56 | } 57 | `), 58 | fragmentShader: ` 59 | uniform vec3 color; 60 | void main () { 61 | gl_FragColor = vec4(color, 1.0); 62 | } 63 | ` 64 | }); 65 | const mesh = new THREE.Mesh(geometry, material); 66 | scene.add(mesh); 67 | 68 | // draw each frame 69 | return { 70 | // Handle resize events here 71 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 72 | renderer.setPixelRatio(pixelRatio); 73 | renderer.setSize(viewportWidth, viewportHeight); 74 | camera.aspect = viewportWidth / viewportHeight; 75 | camera.updateProjectionMatrix(); 76 | }, 77 | // Update & render your scene here 78 | render ({ time }) { 79 | mesh.material.uniforms.time.value = time; 80 | controls.update(); 81 | renderer.render(scene, camera); 82 | }, 83 | // Dispose of events & renderer for cleaner hot-reloading 84 | unload () { 85 | controls.dispose(); 86 | renderer.dispose(); 87 | } 88 | }; 89 | }; 90 | 91 | canvasSketch(sketch, settings); 92 | -------------------------------------------------------------------------------- /src/gego/0-sphere.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const canvasSketch = require('canvas-sketch'); 8 | 9 | const settings = { 10 | dimensions: [ 1024, 1024 ], 11 | // Set a total duration of the loop 12 | duration: 5, 13 | // Make the loop animated 14 | animate: true, 15 | // Get a WebGL canvas rather than 2D 16 | context: 'webgl', 17 | // Turn on MSAA 18 | attributes: { antialias: true } 19 | }; 20 | 21 | const sketch = ({ context }) => { 22 | // Create a renderer 23 | const renderer = new THREE.WebGLRenderer({ 24 | context 25 | }); 26 | 27 | // WebGL background color 28 | renderer.setClearColor('hsl(0, 0%, 95%)', 1); 29 | 30 | // Setup a camera 31 | const camera = new THREE.PerspectiveCamera(60, 1, 0.01, 100); 32 | camera.position.set(1, 1, -3); 33 | camera.lookAt(new THREE.Vector3()); 34 | 35 | // Setup camera controller 36 | const controls = new THREE.OrbitControls(camera, context.canvas); 37 | 38 | // Setup your scene 39 | const scene = new THREE.Scene(); 40 | 41 | // Choose a geometry type 42 | const geometry = new THREE.IcosahedronGeometry(1, 1); 43 | // const geometry = new THREE.PlaneGeometry(2, 2, 8, 8); 44 | // const geometry = new THREE.SphereGeometry(1, 16, 8); 45 | 46 | const material = new THREE.MeshBasicMaterial({ 47 | color: 'black', 48 | wireframe: true 49 | }); 50 | const mesh = new THREE.Mesh(geometry, material); 51 | scene.add(mesh); 52 | 53 | const joinGeometry = new THREE.TorusGeometry(1.5, 0.25, 8, 8); 54 | 55 | const joins = geometry.vertices.map(position => { 56 | const joinMaterial = new THREE.MeshBasicMaterial({ 57 | color: 'black' 58 | }); 59 | 60 | const join = new THREE.Mesh(joinGeometry, joinMaterial); 61 | join.position.copy(position); 62 | join.scale.setScalar(0.025); 63 | join.lookAt(new THREE.Vector3()); 64 | scene.add(join); 65 | return join; 66 | }); 67 | 68 | // draw each frame 69 | return { 70 | // Handle resize events here 71 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 72 | renderer.setPixelRatio(pixelRatio); 73 | renderer.setSize(viewportWidth, viewportHeight); 74 | camera.aspect = viewportWidth / viewportHeight; 75 | camera.updateProjectionMatrix(); 76 | }, 77 | // Update & render your scene here 78 | render ({ playhead }) { 79 | scene.rotation.y = playhead * Math.PI * 1; 80 | controls.update(); 81 | renderer.render(scene, camera); 82 | }, 83 | // Dispose of events & renderer for cleaner hot-reloading 84 | unload () { 85 | controls.dispose(); 86 | renderer.dispose(); 87 | } 88 | }; 89 | }; 90 | 91 | canvasSketch(sketch, settings); 92 | -------------------------------------------------------------------------------- /src/gego/1-random.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const canvasSketch = require('canvas-sketch'); 8 | const risoColors = require('riso-colors'); 9 | const paperColors = require('paper-colors'); 10 | const Random = require('canvas-sketch-util/random'); 11 | const createTubeWireframe = require('three-tube-wireframe'); 12 | 13 | const settings = { 14 | dimensions: [ 1024, 1024 ], 15 | // Get a WebGL canvas rather than 2D 16 | context: 'webgl', 17 | // Turn on MSAA 18 | attributes: { antialias: true } 19 | }; 20 | 21 | const sketch = ({ context }) => { 22 | // Create a renderer 23 | const renderer = new THREE.WebGLRenderer({ 24 | context 25 | }); 26 | 27 | // WebGL background color 28 | renderer.setClearColor(Random.pick(paperColors).hex, 1); 29 | 30 | // Setup a camera 31 | const camera = new THREE.PerspectiveCamera(50, 1, 0.01, 100); 32 | camera.position.set(2, 2, -2); 33 | camera.lookAt(new THREE.Vector3()); 34 | 35 | // Setup your scene 36 | const scene = new THREE.Scene(); 37 | 38 | const color = Random.pick(risoColors).hex; 39 | 40 | // Choose a geometry type 41 | const geometry = new THREE.PlaneGeometry(2, 2, 15, 15); 42 | // const geometry = new THREE.CylinderGeometry(0.5, 0.5, 2, 4, 8); 43 | 44 | geometry.vertices.forEach(vertex => { 45 | const f = 0.75; 46 | const amp = 0.5; 47 | 48 | // One option is just to push out along Z axis using 2D noise 49 | const n = Random.noise2D(vertex.x * f, vertex.y * f); 50 | vertex.z += n * amp; 51 | }); 52 | 53 | const material = new THREE.MeshBasicMaterial({ 54 | color 55 | }); 56 | 57 | const wireGeometry = createTubeWireframe(geometry, { 58 | thickness: 0.005, 59 | radiusSegments: 4, 60 | mode: 'cross-hatch' 61 | }); 62 | const mesh = new THREE.Mesh(wireGeometry, material); 63 | scene.add(mesh); 64 | 65 | const joinGeometry = new THREE.TorusGeometry(1.0, 0.25, 5, 4); 66 | 67 | const joins = geometry.vertices.map((position, i) => { 68 | const joinMaterial = new THREE.MeshBasicMaterial({ 69 | color 70 | }); 71 | 72 | const join = new THREE.Mesh(joinGeometry, joinMaterial); 73 | join.position.copy(position); 74 | join.scale.setScalar(0.025); 75 | 76 | // make join look at centre 77 | join.lookAt(new THREE.Vector3()); 78 | 79 | scene.add(join); 80 | return join; 81 | }); 82 | 83 | // draw each frame 84 | return { 85 | // Handle resize events here 86 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 87 | renderer.setPixelRatio(pixelRatio); 88 | renderer.setSize(viewportWidth, viewportHeight); 89 | camera.aspect = viewportWidth / viewportHeight; 90 | camera.updateProjectionMatrix(); 91 | }, 92 | // Update & render your scene here 93 | render ({ playhead }) { 94 | renderer.render(scene, camera); 95 | }, 96 | // Dispose of events & renderer for cleaner hot-reloading 97 | unload () { 98 | renderer.dispose(); 99 | } 100 | }; 101 | }; 102 | 103 | canvasSketch(sketch, settings); 104 | -------------------------------------------------------------------------------- /src/gego/2-advanced.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const canvasSketch = require('canvas-sketch'); 8 | const risoColors = require('riso-colors'); 9 | const paperColors = require('paper-colors'); 10 | const Random = require('canvas-sketch-util/random'); 11 | const createTubeWireframe = require('three-tube-wireframe'); 12 | 13 | const settings = { 14 | dimensions: [ 1024, 1024 ], 15 | // Get a WebGL canvas rather than 2D 16 | context: 'webgl', 17 | // Turn on MSAA 18 | attributes: { antialias: true } 19 | }; 20 | 21 | const sketch = ({ context }) => { 22 | // Create a renderer 23 | const renderer = new THREE.WebGLRenderer({ 24 | context 25 | }); 26 | 27 | // WebGL background color 28 | renderer.setClearColor(Random.pick(paperColors).hex, 1); 29 | 30 | // Setup a camera 31 | const camera = new THREE.PerspectiveCamera(50, 1, 0.01, 100); 32 | camera.position.set(2, 2, -2); 33 | camera.lookAt(new THREE.Vector3()); 34 | 35 | // Setup your scene 36 | const scene = new THREE.Scene(); 37 | 38 | const color = Random.pick(risoColors).hex; 39 | 40 | // Choose a geometry type 41 | const geometry = new THREE.PlaneGeometry(2, 2, 15, 15); 42 | // const geometry = new THREE.CylinderGeometry(0.5, 0.5, 2, 4, 8); 43 | 44 | geometry.vertices.forEach(vertex => { 45 | const f = 0.75; 46 | const amp = 0.5; 47 | 48 | // One option is just to push out along Z axis using 2D noise 49 | // const n = Random.noise2D(vertex.x * f, vertex.y * f); 50 | // vertex.z += n * amp; 51 | 52 | // Another option is to rotate by 3D noise 53 | const n = Random.noise3D(vertex.x * f, vertex.y * f, vertex.z * f); 54 | vertex.applyEuler(new THREE.Euler(0, n * amp * Math.PI / 2, 0)); 55 | }); 56 | 57 | // Make ThreeJS update its vertex normals 58 | geometry.computeVertexNormals(); 59 | 60 | const material = new THREE.MeshBasicMaterial({ 61 | color 62 | }); 63 | 64 | const wireGeometry = createTubeWireframe(geometry, { 65 | thickness: 0.0045, 66 | radiusSegments: 4, 67 | filter: () => Random.gaussian() > -0.5, 68 | mode: 'quad' 69 | }); 70 | const mesh = new THREE.Mesh(wireGeometry, material); 71 | scene.add(mesh); 72 | 73 | const joinGeometry = new THREE.TorusGeometry(0.75, 0.2, 8, 4); 74 | 75 | geometry.vertices.forEach(position => { 76 | const joinMaterial = new THREE.MeshBasicMaterial({ 77 | color 78 | }); 79 | 80 | const join = new THREE.Mesh(joinGeometry, joinMaterial); 81 | join.position.copy(position); 82 | join.scale.setScalar(0.025); 83 | 84 | // it looks interesting to have random orientations for each 85 | join.quaternion.fromArray(Random.quaternion()); 86 | 87 | scene.add(join); 88 | }); 89 | 90 | // draw each frame 91 | return { 92 | // Handle resize events here 93 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 94 | renderer.setPixelRatio(pixelRatio); 95 | renderer.setSize(viewportWidth, viewportHeight); 96 | camera.aspect = viewportWidth / viewportHeight; 97 | camera.updateProjectionMatrix(); 98 | }, 99 | // Update & render your scene here 100 | render ({ playhead }) { 101 | renderer.render(scene, camera); 102 | }, 103 | // Dispose of events & renderer for cleaner hot-reloading 104 | unload () { 105 | renderer.dispose(); 106 | } 107 | }; 108 | }; 109 | 110 | canvasSketch(sketch, settings); 111 | -------------------------------------------------------------------------------- /src/gego/3-shader-animation.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const canvasSketch = require('canvas-sketch'); 8 | const risoColors = require('riso-colors'); 9 | const paperColors = require('paper-colors'); 10 | const Random = require('canvas-sketch-util/random'); 11 | const createTubeWireframe = require('three-tube-wireframe'); 12 | const glslify = require('glslify'); 13 | 14 | const settings = { 15 | // Make the loop animated 16 | animate: true, 17 | // Get a WebGL canvas rather than 2D 18 | context: 'webgl', 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(Random.pick(paperColors).hex, 1); 30 | 31 | // Setup a camera 32 | const camera = new THREE.PerspectiveCamera(60, 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, context.canvas); 38 | 39 | // Setup your scene 40 | const scene = new THREE.Scene(); 41 | 42 | const color = Random.pick(risoColors).hex; 43 | 44 | // const geometry = new THREE.SphereGeometry(1, 16, 8); 45 | const geometry = new THREE.PlaneGeometry(4, 4, 15, 15); 46 | const wireGeometry = createTubeWireframe(geometry, { 47 | thickness: 0.01, 48 | buffer: true, 49 | mode: 'quad', 50 | radiusSegments: 3, 51 | lengthSegments: 3, 52 | }); 53 | const material = new THREE.ShaderMaterial({ 54 | uniforms: { 55 | color: { value: new THREE.Color(color) }, 56 | time: { value: 0 }, 57 | opacity: { value: 1 } 58 | }, 59 | transparent: true, 60 | vertexShader: glslify(` 61 | #pragma glslify: noise = require('glsl-noise/simplex/4d'); 62 | attribute vec3 basePosition; 63 | uniform float time; 64 | void main () { 65 | vec3 transformed = position.xyz; 66 | 67 | float n = noise(vec4(basePosition.xyz * 0.5, time * 0.5)); 68 | transformed.z += n; 69 | 70 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 71 | } 72 | `), 73 | fragmentShader: glslify(` 74 | uniform vec3 color; 75 | uniform float opacity; 76 | void main () { 77 | gl_FragColor = vec4(color, opacity); 78 | } 79 | `) 80 | }); 81 | const mesh = new THREE.Mesh(wireGeometry, material); 82 | scene.add(mesh); 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 | camera.aspect = viewportWidth / viewportHeight; 91 | camera.updateProjectionMatrix(); 92 | }, 93 | // Update & render your scene here 94 | render ({ time }) { 95 | material.uniforms.time.value = time; 96 | controls.update(); 97 | renderer.render(scene, camera); 98 | }, 99 | // Dispose of events & renderer for cleaner hot-reloading 100 | unload () { 101 | controls.dispose(); 102 | renderer.dispose(); 103 | } 104 | }; 105 | }; 106 | 107 | canvasSketch(sketch, settings); 108 | -------------------------------------------------------------------------------- /src/turrell/0-shader-cube-stack.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const canvasSketch = require('canvas-sketch'); 8 | const Random = require('canvas-sketch-util/random'); 9 | const risoColors = require('riso-colors').map(c => c.hex); 10 | const paperColors = require('paper-colors').map(c => c.hex); 11 | 12 | const settings = { 13 | // Get a WebGL canvas rather than 2D 14 | context: 'webgl', 15 | dimensions: [ 2048, 2048 ], 16 | // Turn on MSAA 17 | attributes: { antialias: true } 18 | }; 19 | 20 | const sketch = (props) => { 21 | const { context } = props; 22 | // Create a renderer 23 | const renderer = new THREE.WebGLRenderer({ 24 | context 25 | }); 26 | 27 | // WebGL background color 28 | const background = Random.pick(paperColors); 29 | const colors = Random.shuffle(risoColors); 30 | let colorIndex = 0; 31 | 32 | renderer.setClearColor(background, 1); 33 | 34 | // Setup a camera 35 | const camera = new THREE.OrthographicCamera(); 36 | 37 | // Setup your scene 38 | const scene = new THREE.Scene(); 39 | 40 | // Create a new box 41 | const geometry = new THREE.BoxGeometry(1, 1, 1); 42 | 43 | // Remove two of the triangles from the geometry (the top faces) 44 | geometry.faces.splice(4, 2); 45 | 46 | // A function to create a new shader material with 47 | // a random color & gradient 48 | const createMaterial = () => { 49 | const color = colors[colorIndex++ % colors.length]; 50 | 51 | const material = new THREE.ShaderMaterial({ 52 | // Make sure inner side is visible as well 53 | side: THREE.DoubleSide, 54 | uniforms: { 55 | power: { value: Random.range(1, 20) }, 56 | color: { value: new THREE.Color(color) }, 57 | background: { value: new THREE.Color(background) } 58 | }, 59 | // Pass coordinate down to fragment shader 60 | vertexShader: ` 61 | varying vec2 vUv; 62 | void main () { 63 | vec3 transformed = position.xyz; 64 | vUv = uv; 65 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 66 | } 67 | `, 68 | // Receive coordinate and create a gradient 69 | fragmentShader: ` 70 | varying vec2 vUv; 71 | uniform vec3 color; 72 | uniform vec3 background; 73 | uniform float power; 74 | 75 | void main () { 76 | float d = pow(vUv.y, power * vUv.y); 77 | vec3 outColor = mix(background, color, d); 78 | gl_FragColor = vec4(outColor, 1.0); 79 | } 80 | ` 81 | }); 82 | return material; 83 | }; 84 | 85 | // When true, the outermost cube will be the background color 86 | const masking = false; 87 | 88 | // Draw N meshes 89 | const maxMeshes = 20; 90 | for (let i = 0; i < maxMeshes; i++) { 91 | const material = createMaterial(); 92 | const mesh = new THREE.Mesh(geometry, material); 93 | // Value that goes from ~0...1 94 | let v = (i + 1) / maxMeshes; 95 | // Use pow() to make it exponential, not just linear 96 | v = Math.pow(v, 5); 97 | // scale each mesh 98 | mesh.scale.setScalar(v); 99 | scene.add(mesh); 100 | if (masking) { 101 | mesh.position.y -= 0.5; 102 | if (i === maxMeshes - 1) { 103 | mesh.material.uniforms.color.value.set(background); 104 | } 105 | } 106 | } 107 | 108 | // draw each frame 109 | return { 110 | // Handle resize events here 111 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 112 | renderer.setPixelRatio(pixelRatio); 113 | renderer.setSize(viewportWidth, viewportHeight); 114 | 115 | const aspect = viewportWidth / viewportHeight; 116 | 117 | // Ortho zoom 118 | const zoom = 1; 119 | 120 | // Bounds 121 | camera.left = -zoom * aspect; 122 | camera.right = zoom * aspect; 123 | camera.top = zoom; 124 | camera.bottom = -zoom; 125 | 126 | // Near/Far 127 | camera.near = -100; 128 | camera.far = 100; 129 | 130 | // Set position & look at world center 131 | camera.position.set(zoom, zoom, zoom); 132 | camera.lookAt(new THREE.Vector3()); 133 | 134 | // Update the camera 135 | camera.updateProjectionMatrix(); 136 | }, 137 | // Update & render your scene here 138 | render ({ time }) { 139 | renderer.render(scene, camera); 140 | }, 141 | // Dispose of events & renderer for cleaner hot-reloading 142 | unload () { 143 | renderer.dispose(); 144 | } 145 | }; 146 | }; 147 | 148 | canvasSketch(sketch, settings); -------------------------------------------------------------------------------- /src/turrell/1-shader-cube-city.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const canvasSketch = require('canvas-sketch'); 8 | const Random = require('canvas-sketch-util/random'); 9 | const risoColors = require('riso-colors').map(c => c.hex); 10 | const paperColors = require('paper-colors').map(c => c.hex); 11 | 12 | const settings = { 13 | // Get a WebGL canvas rather than 2D 14 | context: 'webgl', 15 | dimensions: [ 2048, 2048 ], 16 | // Turn on MSAA 17 | attributes: { antialias: true } 18 | }; 19 | 20 | const sketch = (props) => { 21 | const { context } = props; 22 | // Create a renderer 23 | const renderer = new THREE.WebGLRenderer({ 24 | context 25 | }); 26 | 27 | // WebGL background color 28 | const background = Random.pick(paperColors); 29 | 30 | renderer.setClearColor(background, 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 | // Create a new box 39 | const geometry = new THREE.BoxGeometry(1, 1, 1); 40 | geometry.translate(0, 0.5, 0); 41 | 42 | // Choose a random color for all cubes 43 | const color = Random.pick(risoColors); 44 | 45 | // A function to create a new shader material with 46 | // a random color & gradient 47 | const createMaterial = () => { 48 | const material = new THREE.ShaderMaterial({ 49 | uniforms: { 50 | power: { value: Random.range(1, 20) }, 51 | color: { value: new THREE.Color(color) }, 52 | background: { value: new THREE.Color(background) } 53 | }, 54 | // Pass coordinate down to fragment shader 55 | vertexShader: ` 56 | varying vec2 vUv; 57 | void main () { 58 | vec3 transformed = position.xyz; 59 | vUv = uv; 60 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 61 | } 62 | `, 63 | // Receive coordinate and create a gradient 64 | fragmentShader: ` 65 | varying vec2 vUv; 66 | uniform vec3 color; 67 | uniform vec3 background; 68 | uniform float power; 69 | 70 | void main () { 71 | float d = pow(vUv.y, power * vUv.y); 72 | vec3 outColor = mix(background, color, d); 73 | gl_FragColor = vec4(outColor, 1.0); 74 | } 75 | ` 76 | }); 77 | 78 | // Use a mutli-face material for the 6 sided cube 79 | // Top and bottom sides will draw the background color 80 | const emptyMaterial = new THREE.MeshBasicMaterial({ 81 | color: background 82 | }); 83 | 84 | return [ 85 | material, 86 | material, 87 | emptyMaterial, 88 | emptyMaterial, 89 | material, 90 | material 91 | ]; 92 | }; 93 | 94 | // Create a random mesh at the given position 95 | const createMesh = (position) => { 96 | const mesh = new THREE.Mesh(geometry, createMaterial()); 97 | 98 | // copy the position 99 | mesh.position.copy(position); 100 | 101 | // scale it randomly 102 | mesh.scale.set( 103 | Math.abs(Random.gaussian() * Random.gaussian()), 104 | Math.abs(Random.gaussian() * Random.gaussian()), 105 | Math.abs(Random.gaussian() * Random.gaussian()) 106 | ); 107 | 108 | // Center things a little bit better 109 | mesh.position.y += -1; 110 | 111 | scene.add(mesh); 112 | return mesh; 113 | }; 114 | 115 | for (let i = 0; i < 100; i++) { 116 | const position = new THREE.Vector3( 117 | Random.gaussian() * Random.gaussian(), 118 | 0, 119 | Random.gaussian() * Random.gaussian() 120 | ); 121 | createMesh(position); 122 | } 123 | 124 | // draw each frame 125 | return { 126 | // Handle resize events here 127 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 128 | renderer.setPixelRatio(pixelRatio); 129 | renderer.setSize(viewportWidth, viewportHeight); 130 | 131 | const aspect = viewportWidth / viewportHeight; 132 | 133 | // Ortho zoom 134 | const zoom = 4; 135 | 136 | // Bounds 137 | camera.left = -zoom * aspect; 138 | camera.right = zoom * aspect; 139 | camera.top = zoom; 140 | camera.bottom = -zoom; 141 | 142 | // Near/Far 143 | camera.near = -100; 144 | camera.far = 100; 145 | 146 | // Set position & look at world center 147 | camera.position.set(zoom, zoom, zoom); 148 | camera.lookAt(new THREE.Vector3()); 149 | 150 | // Update the camera 151 | camera.updateProjectionMatrix(); 152 | }, 153 | // Update & render your scene here 154 | render ({ time }) { 155 | renderer.render(scene, camera); 156 | }, 157 | // Dispose of events & renderer for cleaner hot-reloading 158 | unload () { 159 | renderer.dispose(); 160 | } 161 | }; 162 | }; 163 | 164 | canvasSketch(sketch, settings); -------------------------------------------------------------------------------- /src/turrell/2-shader-interactive.js: -------------------------------------------------------------------------------- 1 | // Ensure ThreeJS is in global scope for the 'examples/' 2 | global.THREE = require('three'); 3 | 4 | // Include any additional ThreeJS examples below 5 | require('three/examples/js/controls/OrbitControls'); 6 | 7 | const canvasSketch = require('canvas-sketch'); 8 | const Random = require('canvas-sketch-util/random'); 9 | const risoColors = require('riso-colors').map(c => c.hex); 10 | const paperColors = require('paper-colors').map(c => c.hex); 11 | const anime = require('animejs'); 12 | const createInputEvents = require('simple-input-events'); 13 | 14 | const settings = { 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 = (props) => { 24 | const { context } = props; 25 | // Create a renderer 26 | const renderer = new THREE.WebGLRenderer({ 27 | context 28 | }); 29 | 30 | // WebGL background color 31 | let background = Random.pick(paperColors); 32 | renderer.setClearColor(background, 1); 33 | 34 | // Setup a camera 35 | const camera = new THREE.OrthographicCamera(); 36 | 37 | // Setup your scene 38 | const scene = new THREE.Scene(); 39 | 40 | // Create a new box 41 | const geometry = new THREE.BoxGeometry(1, 1, 1); 42 | geometry.translate(0, 0.5, 0); 43 | 44 | // A function to create a new shader material with 45 | // a random color & gradient 46 | const createMaterial = () => { 47 | const material = new THREE.ShaderMaterial({ 48 | uniforms: { 49 | power: { value: Random.range(1, 10) }, 50 | color: { value: new THREE.Color(Random.pick(risoColors)) }, 51 | background: { value: new THREE.Color(background) } 52 | }, 53 | // Pass coordinate down to fragment shader 54 | vertexShader: ` 55 | varying vec2 vUv; 56 | void main () { 57 | vec3 transformed = position.xyz; 58 | vUv = uv; 59 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0); 60 | } 61 | `, 62 | // Receive coordinate and create a gradient 63 | fragmentShader: ` 64 | varying vec2 vUv; 65 | uniform vec3 color; 66 | uniform vec3 background; 67 | uniform float power; 68 | uniform float time; 69 | 70 | void main () { 71 | float d = pow(vUv.y, power * vUv.y); 72 | vec3 outColor = mix(background, color , d); 73 | gl_FragColor = vec4(outColor, 1.0); 74 | } 75 | ` 76 | }); 77 | 78 | // Use a mutli-face material for the 6 sided cube 79 | // Top and bottom sides will draw the background color 80 | const emptyMaterial = new THREE.MeshBasicMaterial({ 81 | color: background 82 | }); 83 | 84 | return [ 85 | material, 86 | material, 87 | emptyMaterial, 88 | emptyMaterial, 89 | material, 90 | material 91 | ]; 92 | }; 93 | 94 | // Animates a mesh by setting its Y scale to very small 95 | // then animating it to an ideal size, and then back to small 96 | const animate = async (mesh) => { 97 | const targetScale = mesh.scale.y; 98 | 99 | // Set initial size 100 | // Need to use non-zero to avoid ThreeJS console warnings 101 | const minScale = 0.0001; 102 | mesh.scale.y = minScale; 103 | 104 | // animate to an initial value 105 | await anime({ 106 | targets: mesh.scale, 107 | y: targetScale, 108 | duration: 1000, 109 | easing: 'easeOutExpo' 110 | }).finished; 111 | 112 | // animate back to almost zero 113 | await anime({ 114 | targets: mesh.scale, 115 | y: minScale, 116 | duration: 1000, 117 | easing: 'easeInExpo' 118 | }).finished; 119 | 120 | // IMPORTANT! We need to remove the mesh 121 | // from the scene otherwise the app will eventually 122 | // start to slow down and run out of memory 123 | scene.remove(mesh); 124 | }; 125 | 126 | // Create a random mesh at the given position 127 | const createMesh = (position) => { 128 | const mesh = new THREE.Mesh(geometry, createMaterial(1000)); 129 | 130 | // copy the position 131 | mesh.position.copy(position); 132 | 133 | // scale it randomly 134 | mesh.scale.set( 135 | Math.abs(Random.gaussian() * Random.gaussian()), 136 | Math.abs(Random.gaussian() * Random.gaussian()), 137 | Math.abs(Random.gaussian() * Random.gaussian()) 138 | ); 139 | 140 | scene.add(mesh); 141 | return mesh; 142 | }; 143 | 144 | const raycaster = new THREE.Raycaster(); 145 | const ground = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); 146 | 147 | // Listen for pointer events on the body 148 | // We can use simple-input-events for mobile + desktop 149 | const input = createInputEvents({ 150 | target: props.canvas, 151 | preventDefault: true 152 | }).on('move', ({ position }) => { 153 | const mouse = new THREE.Vector2( 154 | position[0] / props.styleWidth * 2 - 1, 155 | -position[1] / props.styleHeight * 2 + 1 156 | ); 157 | raycaster.setFromCamera(mouse, camera); 158 | const target = new THREE.Vector3(); 159 | const hit = raycaster.ray.intersectPlane(ground, target); 160 | if (hit) { 161 | // If we hit the ground, create a new mesh 162 | const mesh = createMesh(target); 163 | animate(mesh); 164 | } 165 | }); 166 | 167 | // Randomize colors on click 168 | window.addEventListener('click', ev => { 169 | // Get a new background color 170 | background = Random.pick(paperColors); 171 | renderer.setClearColor(background, 1); 172 | 173 | // update all meshes with materials 174 | scene.traverse(child => { 175 | if (child.material) { 176 | child.material = createMaterial(); 177 | } 178 | }); 179 | }); 180 | 181 | context.canvas.style.cursor = 'pointer'; 182 | 183 | // draw each frame 184 | return { 185 | // Handle resize events here 186 | resize ({ pixelRatio, viewportWidth, viewportHeight }) { 187 | renderer.setPixelRatio(pixelRatio); 188 | renderer.setSize(viewportWidth, viewportHeight); 189 | 190 | const aspect = viewportWidth / viewportHeight; 191 | 192 | // Ortho zoom 193 | const zoom = 4; 194 | 195 | // Bounds 196 | camera.left = -zoom * aspect; 197 | camera.right = zoom * aspect; 198 | camera.top = zoom; 199 | camera.bottom = -zoom; 200 | 201 | // Near/Far 202 | camera.near = -100; 203 | camera.far = 100; 204 | 205 | // Set position & look at world center 206 | camera.position.set(zoom, zoom, zoom); 207 | camera.lookAt(new THREE.Vector3()); 208 | 209 | // Update the camera 210 | camera.updateProjectionMatrix(); 211 | }, 212 | // Update & render your scene here 213 | render ({ time }) { 214 | renderer.render(scene, camera); 215 | }, 216 | // Dispose of events & renderer for cleaner hot-reloading 217 | unload () { 218 | input.disable(); 219 | renderer.dispose(); 220 | } 221 | }; 222 | }; 223 | 224 | canvasSketch(sketch, settings); --------------------------------------------------------------------------------