├── .gitattributes ├── LICENSE ├── README.md ├── assets ├── Craft_Light.jpg ├── Craft_Rough.jpg ├── LeePerry.obj ├── Parchment.jpg ├── Watercolor_ColdPress.jpg ├── negx.jpg ├── negy.jpg ├── negz.jpg ├── noise1.png ├── noise2.png ├── park_negx.jpg ├── park_negy.jpg ├── park_negz.jpg ├── park_posx.jpg ├── park_posy.jpg ├── park_posz.jpg ├── pisa_negx.png ├── pisa_negy.png ├── pisa_negz.png ├── pisa_posx.png ├── pisa_posy.png ├── pisa_posz.png ├── posx.jpg ├── posy.jpg ├── posz.jpg └── suzanne.obj ├── cross-hatch-i ├── crossHatchMaterial copy.js ├── crossHatchMaterial.js ├── index.html └── main.js ├── cross-hatch-ii ├── crossHatchMaterial.js ├── index.html └── main.js ├── cross-hatch-iii ├── crossHatchMaterial.js ├── index.html └── main.js ├── cross-hatch-iv ├── crossHatchMaterial.js ├── index.html └── main.js ├── cross-hatch-v ├── crossHatchMaterial.js ├── index.html └── main.js ├── cross-hatch-vi ├── crossHatchMaterial.js ├── index.html └── main.js ├── cross-hatch-vii ├── crossHatchMaterial.js ├── index.html └── main.js ├── cross-hatch-viii ├── index.html ├── lineMaterial.js └── main.js ├── index.html ├── js ├── FBO.js ├── ShaderPass.js ├── ShaderPingPongPass.js ├── envMap.js ├── paper.js ├── renderer.js ├── scene.js ├── sceneBackdrop.js ├── sceneBlob.js ├── sceneLeePerry.js ├── sceneMetaballs.js ├── sceneSpheres.js ├── sceneSuzanne.js └── sceneTorus.js ├── lines-i ├── index.html ├── main.js └── screenSpaceSketchMaterial.js ├── lines-ii ├── index.html ├── lineMaterial.js └── main.js ├── lines-iii ├── index.html ├── lineMaterial.js └── main.js ├── lines-iv ├── index.html ├── lineMaterial.js └── main.js ├── lines-v ├── index.html ├── lineMaterial.js └── main.js ├── lines-vi ├── index.html ├── lineMaterial.js └── main.js ├── patch-i ├── index.html ├── lineMaterial.js └── main.js ├── patch-ii ├── index.html ├── lineMaterial.js └── main.js ├── post-blueprint ├── CoordsMaterial.js ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cartoon-i ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cartoon-ii ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cartoon-iii ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cartoon-iv ├── ColorMaterial.js ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cartoon-v ├── ColorMaterial.js ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cartoon-vi ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cmyk-halftone-i ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cross-hatch-i ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cross-hatch-ii ├── Material.js ├── index.html ├── main.js └── post.js ├── post-cross-hatch-iii ├── Material.js ├── index.html ├── main.js └── post.js ├── post-hope ├── Material.js ├── index.html ├── main.js └── post.js ├── post-lines-i ├── Material.js ├── index.html ├── main.js └── post.js ├── post-patch-i ├── CoordsMaterial.js ├── Material.js ├── index.html ├── main.js └── post.js ├── post-scribble-i ├── Material.js ├── index.html ├── main.js └── post.js ├── post-technical ├── CoordsMaterial.js ├── Material.js ├── index.html ├── main.js └── post.js ├── scribble-i ├── index.html ├── lineMaterial.js └── main.js ├── scribble-ii ├── index.html ├── lineMaterial.js └── main.js ├── scribble-iii ├── index.html ├── lineMaterial.js └── main.js ├── shaders ├── aastep.js ├── blend-darken.js ├── blend-screen.js ├── fast-separable-gaussian-blur.js ├── lines.js ├── luma.js ├── ortho-vs.js ├── sobel.js └── worley-noise.js ├── snapshots ├── cross-hatch-i.jpg ├── cross-hatch-ii.jpg ├── cross-hatch-iii.jpg ├── cross-hatch-iv.jpg ├── cross-hatch-v.jpg ├── cross-hatch-vi.jpg ├── cross-hatch-vii.jpg ├── cross-hatch-viii.jpg ├── gallery.jpg ├── lines-i.jpg ├── lines-ii.jpg ├── lines-iii.jpg ├── lines-iv.jpg ├── lines-v.jpg ├── lines-vi.jpg ├── patch-i.jpg ├── patch-ii.jpg ├── post-blueprint.jpg ├── post-cartoon-i.jpg ├── post-cartoon-ii.jpg ├── post-cartoon-iii.jpg ├── post-cartoon-iv.jpg ├── post-cartoon-v.jpg ├── post-cartoon-vi.jpg ├── post-cross-hatch-i.jpg ├── post-cross-hatch-ii.jpg ├── post-cross-hatch-iii.jpg ├── post-halftone-i.jpg ├── post-hope.jpg ├── post-lines-i.jpg ├── post-patch-i.jpg ├── post-scribble-i.jpg ├── post-technical.jpg ├── scribble-i.jpg ├── scribble-ii.jpg └── scribble-iii.jpg └── third_party ├── FBOHelper.js ├── Maf.js ├── MarchingCubes.js ├── OBJLoader.js ├── OrbitControls.js ├── SubdivisionModifier.js ├── dat.gui.module.js ├── perlin.js └── three.module.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 thespite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Experiments for digital #inktober 2020 2 | 3 | Explorations on cross-hatching, engraving, and similar non-photorealistic rendering. 4 | 5 | Build with WebGL and three.js. GLSL syntax is WebGL2, so won't work on Safari (try Safari Technology Preview). 6 | Not created with performance in mind, some might be too slow for mobile! 7 | 8 | Link: https://spite.github.io/sketch/ 9 | 10 | [![Index page](https://raw.githubusercontent.com/spite/sketch/master/snapshots/gallery.jpg)](https://spite.github.io/sketch/) 11 | 12 | Paper textures from https://maxpacks.com/paperpack 13 | 14 | Lee Perry head from [I-R Entertainment Ltd.](https://ir-ltd.net/) 15 | 16 | Suzanne from [NaN/Blende)r Foundation](https://www.blender.org/) 17 | -------------------------------------------------------------------------------- /assets/Craft_Light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/Craft_Light.jpg -------------------------------------------------------------------------------- /assets/Craft_Rough.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/Craft_Rough.jpg -------------------------------------------------------------------------------- /assets/Parchment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/Parchment.jpg -------------------------------------------------------------------------------- /assets/Watercolor_ColdPress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/Watercolor_ColdPress.jpg -------------------------------------------------------------------------------- /assets/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/negx.jpg -------------------------------------------------------------------------------- /assets/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/negy.jpg -------------------------------------------------------------------------------- /assets/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/negz.jpg -------------------------------------------------------------------------------- /assets/noise1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/noise1.png -------------------------------------------------------------------------------- /assets/noise2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/noise2.png -------------------------------------------------------------------------------- /assets/park_negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/park_negx.jpg -------------------------------------------------------------------------------- /assets/park_negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/park_negy.jpg -------------------------------------------------------------------------------- /assets/park_negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/park_negz.jpg -------------------------------------------------------------------------------- /assets/park_posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/park_posx.jpg -------------------------------------------------------------------------------- /assets/park_posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/park_posy.jpg -------------------------------------------------------------------------------- /assets/park_posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/park_posz.jpg -------------------------------------------------------------------------------- /assets/pisa_negx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/pisa_negx.png -------------------------------------------------------------------------------- /assets/pisa_negy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/pisa_negy.png -------------------------------------------------------------------------------- /assets/pisa_negz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/pisa_negz.png -------------------------------------------------------------------------------- /assets/pisa_posx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/pisa_posx.png -------------------------------------------------------------------------------- /assets/pisa_posy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/pisa_posy.png -------------------------------------------------------------------------------- /assets/pisa_posz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/pisa_posz.png -------------------------------------------------------------------------------- /assets/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/posx.jpg -------------------------------------------------------------------------------- /assets/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/posy.jpg -------------------------------------------------------------------------------- /assets/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/assets/posz.jpg -------------------------------------------------------------------------------- /cross-hatch-i/crossHatchMaterial copy.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | TextureLoader, 4 | Vector2, 5 | Vector3, 6 | } from "../third_party/three.module.js"; 7 | import { lines } from "../shaders/lines.js"; 8 | 9 | class CrossHatchMaterial extends MeshStandardMaterial { 10 | constructor(options) { 11 | super(options); 12 | const self = this; 13 | 14 | this.onBeforeCompile = (shader, renderer) => { 15 | const loader = new TextureLoader(); 16 | const texture = loader.load("../assets/Watercolor_ColdPress.jpg"); 17 | shader.uniforms.resolution = { value: new Vector2(1, 1) }; 18 | shader.uniforms.paperTexture = { value: texture }; 19 | shader.uniforms.range = { value: new Vector2(0.25, 0.75) }; 20 | shader.uniforms.range2 = { value: new Vector2(0.5, 0.5) }; 21 | shader.uniforms.scale = { value: 1 }; 22 | shader.uniforms.radius = { value: 1 }; 23 | self.uniforms = shader.uniforms; 24 | shader.vertexShader = shader.vertexShader.replace( 25 | `#include `, 26 | `#include 27 | out vec2 vCoords;` 28 | ); 29 | shader.vertexShader = shader.vertexShader.replace( 30 | `#include `, 31 | `#include 32 | vCoords = uv;` 33 | ); 34 | 35 | shader.fragmentShader = shader.fragmentShader.replace( 36 | `#include `, 37 | `#include 38 | uniform vec2 resolution; 39 | uniform sampler2D paperTexture; 40 | uniform vec2 range; 41 | uniform vec2 range2; 42 | uniform float scale; 43 | uniform float radius; 44 | in vec2 vCoords; 45 | #define TAU 6.28318530718 46 | ${lines} 47 | ` 48 | ); 49 | shader.fragmentShader = shader.fragmentShader.replace( 50 | "#include ", 51 | `#include 52 | float l = luma(gl_FragColor.rgb); 53 | // float darkColor = l; 54 | // float lightColor = 1. - smoothstep(0., .1, l-.5); 55 | // float darkLines = lines(darkColor, 100.*vCoords, resolution, range, range2, scale, radius); 56 | // float lightLines = lines(lightColor, 100.*vCoords, resolution, range, range2, scale, radius); 57 | ivec2 size = textureSize(paperTexture, 0); 58 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 59 | float levels = 5.; 60 | float freq = float(int(l * levels))/levels; 61 | float line = 1.; 62 | float f = 100.; 63 | float dd = .01; 64 | float m = .01; 65 | float x0 = .5; 66 | vec2 coords = vCoords; 67 | vec2 fw = fwidth(coords); 68 | float r = 50. * abs(fw.y+fw.x); 69 | coords *= vec2(1500., 100.); 70 | coords -= vec2(.5); 71 | float a = .123123;//TAU / 8.; 72 | float s = sin(a); 73 | float c = cos(a); 74 | mat2 rot = mat2(c, -s, s, c); 75 | coords = rot * coords; 76 | if(freq<4./5.){ 77 | float x = .5 + .5 * sin(coords.y * 1.); 78 | line *= .75 + .25 * smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 79 | } 80 | if(freq<3./5.){ 81 | float x = .5 + .5 * sin(coords.y * 2.); 82 | line *= .5 + .5 * smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 83 | } 84 | if(freq<2./5.){ 85 | float x = .5 + .5 * sin(coords.y * 4.); 86 | line *= .25 + .75 * smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 87 | 88 | } 89 | if(freq<1./5.){ 90 | float x = .5 + .5 * sin(coords.y * 8.); 91 | line *= smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 92 | } 93 | 94 | coords = vCoords; 95 | coords *= vec2(1500., 100.); 96 | coords -= vec2(.5); 97 | a = -.49345;//TAU / 8.; 98 | s = sin(a); 99 | c = cos(a); 100 | rot = mat2(c, -s, s, c); 101 | coords = rot * coords; 102 | float line2 = 1.; 103 | if(freq<4./5.){ 104 | float x = .5 + .5 * sin(coords.x * 1.); 105 | line2 *= .75 + .25 * smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 106 | } 107 | if(freq<3./5.){ 108 | float x = .5 + .5 * sin(coords.x * 2.); 109 | line2 *= .5 + .5 * smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 110 | } 111 | if(freq<2./5.){ 112 | float x = .5 + .5 * sin(coords.x * 4.); 113 | line2 *= .25 + .75 * smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 114 | 115 | } 116 | if(freq<1./5.){ 117 | float x = .5 + .5 * sin(coords.x * 8.); 118 | line2 *= smoothstep(.0, .01 , abs(x-x0)-r/2. ) ; 119 | } 120 | 121 | gl_FragColor.rgb = paper.rgb * (line); 122 | ` 123 | ); 124 | }; 125 | } 126 | } 127 | 128 | export { CrossHatchMaterial }; 129 | -------------------------------------------------------------------------------- /cross-hatch-i/crossHatchMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | Color, 3 | MeshStandardMaterial, 4 | RepeatWrapping, 5 | TextureLoader, 6 | Vector2, 7 | } from "../third_party/three.module.js"; 8 | 9 | class CrossHatchMaterial extends MeshStandardMaterial { 10 | constructor(options) { 11 | super(options); 12 | 13 | const loader = new TextureLoader(); 14 | const noiseTexture = loader.load("../assets/noise1.png"); 15 | noiseTexture.wrapS = noiseTexture.wrapT = RepeatWrapping; 16 | 17 | this.params = { 18 | roughness: 0.2, 19 | metalness: 0.1, 20 | inkColor: 0x160D04, 21 | threshold: .5, 22 | min: .4, 23 | max: 1, 24 | e: .2 25 | }; 26 | 27 | this.uniforms = { 28 | inkColor: {value: new Color(this.params.inkColor)}, 29 | resolution: { value: new Vector2(1, 1) }, 30 | paperTexture: { value: null }, 31 | noiseTexture: { value: noiseTexture }, 32 | threshold: { value: this.params.threshold }, 33 | range: { value: new Vector2(this.params.min, this.params.max)}, 34 | e: {value: this.params.e} 35 | } 36 | 37 | this.onBeforeCompile = (shader, renderer) => { 38 | const texture = loader.load("../assets/Craft_Light.jpg"); 39 | for (const uniformName of Object.keys(this.uniforms)) { 40 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 41 | } 42 | 43 | shader.vertexShader = shader.vertexShader.replace( 44 | `#include `, 45 | `#include 46 | out vec2 vCoords; 47 | out vec4 vWorldPosition;` 48 | ); 49 | shader.vertexShader = shader.vertexShader.replace( 50 | `#include `, 51 | `#include 52 | vCoords = uv; 53 | vWorldPosition = modelMatrix * vec4(position, 1.);` 54 | ); 55 | 56 | shader.fragmentShader = shader.fragmentShader.replace( 57 | `#include `, 58 | `#include 59 | uniform vec2 resolution; 60 | uniform sampler2D paperTexture; 61 | uniform sampler2D noiseTexture; 62 | uniform vec3 inkColor; 63 | uniform float threshold; 64 | uniform vec2 range; 65 | uniform float e; 66 | in vec2 vCoords; 67 | in vec4 vWorldPosition; 68 | #define TAU 6.28318530718 69 | 70 | // adapted from https://www.shadertoy.com/view/4lfXDM 71 | 72 | float noise( in vec2 x ){return texture(noiseTexture, x*.01).x;} 73 | float texh(in vec2 p, in float str) 74 | { 75 | p*= .7; 76 | float rz= 1.; 77 | for (int i=0;i<10;i++) 78 | { 79 | float g = texture(noiseTexture,vec2(0.025,.5)*p).x; 80 | g = smoothstep(0.-str*0.1,2.3-str*0.1,g); 81 | rz = min(1.-g,rz); 82 | p.xy = p.yx; 83 | p += .07; 84 | p *= 1.2; 85 | if (float(i) > str)break; 86 | } 87 | return rz * 1.05; 88 | } 89 | 90 | float texcube(in vec3 p, in vec3 n, in float str) { 91 | vec3 v = vec3(texh(p.yz,str), texh(p.zx,str), texh(p.xy,str)); 92 | return dot(v, n*n); 93 | } 94 | 95 | float luma(vec3 color) { 96 | return dot(color, vec3(0.299, 0.587, 0.114)); 97 | } 98 | float luma(vec4 color) { 99 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 100 | } 101 | ` 102 | ); 103 | shader.fragmentShader = shader.fragmentShader.replace( 104 | "#include ", 105 | `#include 106 | float l = 1. - luma(gl_FragColor.rgb); 107 | l = smoothstep(range.x, range.y, l); 108 | float l2 = l;//.5 * smoothstep(0., 1., luma(gl_FragColor.rgb)-threshold); 109 | ivec2 size = textureSize(paperTexture, 0); 110 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 111 | float line = texcube(vWorldPosition.xyz, vNormal, l*10.); 112 | float line2 = clamp(0.,1.,texcube(vWorldPosition.xyz, vNormal, l2*10.)-threshold); 113 | line = smoothstep(.5-e, .5+e, line); 114 | line2 = smoothstep(.5-e, .5+e, line2); 115 | gl_FragColor.rgb = mix( inkColor.rgb, paper.rgb, .25 + .75 * line); 116 | gl_FragColor.rgb += vec3(4.*line2); 117 | ` 118 | ); 119 | }; 120 | } 121 | } 122 | 123 | function generateParams(gui, material) { 124 | const params = material.params; 125 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 126 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 127 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 128 | gui.add(params, "min", 0, 1,.01).onChange((v) => (material.uniforms.range.value.x = v)); 129 | gui.add(params, "max", 0, 1,.01).onChange((v) => (material.uniforms.range.value.y = v)); 130 | gui.add(params, "threshold", 0, 1,.01).onChange((v) => (material.uniforms.threshold.value = v)); 131 | gui.add(params, "e", 0, 1,.01).onChange((v) => (material.uniforms.e.value = v)); 132 | } 133 | export { CrossHatchMaterial, generateParams }; 134 | -------------------------------------------------------------------------------- /cross-hatch-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-i/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { CrossHatchMaterial, generateParams } from "./crossHatchMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new CrossHatchMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /cross-hatch-ii/crossHatchMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | RepeatWrapping, 4 | TextureLoader, 5 | Vector2, 6 | Color, 7 | } from "../third_party/three.module.js"; 8 | 9 | class CrossHatchMaterial extends MeshStandardMaterial { 10 | constructor(options) { 11 | super(options); 12 | 13 | const loader = new TextureLoader(); 14 | const noiseTexture = loader.load("../assets/noise1.png"); 15 | noiseTexture.wrapS = noiseTexture.wrapT = RepeatWrapping; 16 | 17 | this.params = { 18 | roughness: 0.2, 19 | metalness: 0.1, 20 | inkColor: 0x160D04, 21 | threshold: .5, 22 | min: .4, 23 | max: 1, 24 | e: .2 25 | }; 26 | 27 | this.uniforms = { 28 | inkColor: {value: new Color(this.params.inkColor)}, 29 | resolution: { value: new Vector2(1, 1) }, 30 | paperTexture: { value: null }, 31 | noiseTexture: { value: noiseTexture }, 32 | threshold: { value: this.params.threshold }, 33 | range: { value: new Vector2(this.params.min, this.params.max)}, 34 | e: {value: this.params.e} 35 | } 36 | 37 | this.onBeforeCompile = (shader, renderer) => { 38 | for (const uniformName of Object.keys(this.uniforms)) { 39 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 40 | } 41 | 42 | shader.vertexShader = shader.vertexShader.replace( 43 | `#include `, 44 | `#include 45 | out vec2 vCoords; 46 | out vec4 vWorldPosition;` 47 | ); 48 | shader.vertexShader = shader.vertexShader.replace( 49 | `#include `, 50 | `#include 51 | vCoords = uv; 52 | vWorldPosition = modelMatrix * vec4(position, 1.);` 53 | ); 54 | 55 | shader.fragmentShader = shader.fragmentShader.replace( 56 | `#include `, 57 | `#include 58 | uniform vec2 resolution; 59 | uniform sampler2D paperTexture; 60 | uniform sampler2D noiseTexture; 61 | uniform vec3 inkColor; 62 | uniform float threshold; 63 | uniform vec2 range; 64 | uniform float e; 65 | in vec2 vCoords; 66 | in vec4 vWorldPosition; 67 | #define TAU 6.28318530718 68 | 69 | // adapted from https://www.shadertoy.com/view/4lfXDM 70 | float noise( in vec2 x ){return texture(noiseTexture, x*.01).x;} 71 | float texh(in vec2 p, in float str) 72 | { 73 | p*= .7; 74 | float rz= 1.; 75 | for (int i=0;i<10;i++) 76 | { 77 | float g = texture(noiseTexture,vec2(0.025,.5)*p).x; 78 | g = smoothstep(0.-str*0.1,2.3-str*0.1,g); 79 | rz = min(1.-g,rz); 80 | p.xy = p.yx; 81 | p += .07; 82 | p *= 1.2; 83 | if (float(i) > str)break; 84 | } 85 | return rz * 1.05; 86 | } 87 | 88 | float texcube(in vec3 p, in vec3 n, in float str) { 89 | return texh(p.xy, str); 90 | vec3 v = vec3(texh(p.yz,str), texh(p.zx,str), texh(p.xy,str)); 91 | return dot(v, n*n); 92 | } 93 | 94 | float luma(vec3 color) { 95 | return dot(color, vec3(0.299, 0.587, 0.114)); 96 | } 97 | float luma(vec4 color) { 98 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 99 | } 100 | ` 101 | ); 102 | shader.fragmentShader = shader.fragmentShader.replace( 103 | "#include ", 104 | `#include 105 | float l = 1. - luma(gl_FragColor.rgb); 106 | l = smoothstep(range.x, range.y, l); 107 | float l2 = l;//.5 * smoothstep(0., 1., luma(gl_FragColor.rgb)-threshold); 108 | ivec2 size = textureSize(paperTexture, 0); 109 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 110 | vec3 coords = .01 * gl_FragCoord.xyz; 111 | float line = texcube(coords, vNormal, l*10.); 112 | float line2 = clamp(0.,1.,texcube(coords, vNormal, l2*10.)-threshold); 113 | line = smoothstep(.5-e, .5+e, line); 114 | //line = .15 + .85 * line; 115 | line2 = smoothstep(.5-e, .5+e, line2); 116 | gl_FragColor.rgb = mix( inkColor, paper.rgb, .25 + .75 * line); 117 | gl_FragColor.rgb += vec3(4.*line2); 118 | ` 119 | ); 120 | }; 121 | } 122 | } 123 | 124 | function generateParams(gui, material) { 125 | const params = material.params; 126 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 127 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 128 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 129 | gui.add(params, "min", 0, 1,.01).onChange((v) => (material.uniforms.range.value.x = v)); 130 | gui.add(params, "max", 0, 1,.01).onChange((v) => (material.uniforms.range.value.y = v)); 131 | gui.add(params, "threshold", 0, 1,.01).onChange((v) => (material.uniforms.threshold.value = v)); 132 | gui.add(params, "e", 0, 1,.01).onChange((v) => (material.uniforms.e.value = v)); 133 | } 134 | 135 | export { CrossHatchMaterial, generateParams }; 136 | -------------------------------------------------------------------------------- /cross-hatch-ii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-ii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { CrossHatchMaterial, generateParams } from "./crossHatchMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new CrossHatchMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /cross-hatch-iii/crossHatchMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | class CrossHatchMaterial extends MeshStandardMaterial { 8 | constructor(options) { 9 | super(options); 10 | 11 | this.params = { 12 | roughness: 0.2, 13 | metalness: 0.1, 14 | inkColor: 0x126A87, 15 | scale: 20, 16 | }; 17 | 18 | this.uniforms = { 19 | inkColor: {value: new Color(this.params.inkColor)}, 20 | resolution: { value: new Vector2(1, 1) }, 21 | paperTexture: { value: null }, 22 | scale: {value:this.params.scale} 23 | } 24 | 25 | this.onBeforeCompile = (shader, renderer) => { 26 | for (const uniformName of Object.keys(this.uniforms)) { 27 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 28 | } 29 | 30 | shader.vertexShader = shader.vertexShader.replace( 31 | `#include `, 32 | `#include 33 | out vec2 vCoords; 34 | out vec4 vWorldPosition;` 35 | ); 36 | shader.vertexShader = shader.vertexShader.replace( 37 | `#include `, 38 | `#include 39 | vCoords = uv; 40 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 41 | ); 42 | 43 | shader.fragmentShader = shader.fragmentShader.replace( 44 | `#include `, 45 | `#include 46 | uniform vec2 resolution; 47 | uniform sampler2D paperTexture; 48 | uniform vec3 inkColor; 49 | uniform float scale; 50 | in vec2 vCoords; 51 | in vec4 vWorldPosition; 52 | #define TAU 6.28318530718 53 | 54 | vec2 noise(vec2 x){ 55 | return fract(cos(dot(x,vec2(134.,1.61034)))*vec2(416418.0,1265.7486)); 56 | } 57 | 58 | // adapted from https://www.shadertoy.com/view/wsdyRf 59 | 60 | float calc(in vec2 uv, float f) { 61 | vec2 uv00 = vec2(0,0)+floor(uv); 62 | vec2 uv01 = vec2(0,1)+floor(uv); 63 | vec2 uv10 = vec2(1,0)+floor(uv); 64 | vec2 uv11 = vec2(1,1)+floor(uv); 65 | 66 | vec3 col = vec3(0); 67 | vec2 n00 = noise(uv00); 68 | vec2 n01 = noise(uv01); 69 | vec2 n10 = noise(uv10); 70 | vec2 n11 = noise(uv11); 71 | uv00 = ceil(uv00) + n00-.5; 72 | uv01 = ceil(uv01) + n01-.5; 73 | uv10 = ceil(uv10) + n10-.5; 74 | uv11 = ceil(uv11) + n11-.5; 75 | 76 | vec2 uv0 = mix(uv00,uv01, float(distance(uv00,uv)>distance(uv01,uv))); 77 | vec2 uv1 = mix(uv10,uv11, float(distance(uv10,uv)>distance(uv11,uv))); 78 | vec2 uvC = mix(uv0,uv1, float(distance(uv0,uv) >distance(uv1,uv))); 79 | vec2 uvL = uv-uvC; 80 | vec2 vn = noise(uvC)-.5; 81 | float g = dot(uvL,normalize(vn)); 82 | //float size = 1.;//1.+.5*sin(iTime); 83 | //float so = .1;//.4+1./(16.+size); 84 | return .5 - (sin(15.*g));//smoothstep(-so+.25,so+.25,sin((6.+size*12.)*g)); 85 | } 86 | 87 | float texcube(in vec3 p, in vec3 n, in float f) { 88 | vec3 v = vec3(calc(p.yz, f), calc(p.zx, f), calc(p.xy, f)); 89 | return dot(v, n*n); 90 | } 91 | 92 | float luma(vec3 color) { 93 | return dot(color, vec3(0.299, 0.587, 0.114)); 94 | } 95 | float luma(vec4 color) { 96 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 97 | } 98 | 99 | float blendColorBurn(float base, float blend) { 100 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 101 | } 102 | 103 | vec3 blendColorBurn(vec3 base, vec3 blend) { 104 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 105 | } 106 | 107 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 108 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 109 | } 110 | 111 | float aastep(float threshold, float value) { 112 | #ifdef GL_OES_standard_derivatives 113 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 114 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 115 | #else 116 | return step(threshold, value); 117 | #endif 118 | } 119 | ` 120 | ); 121 | shader.fragmentShader = shader.fragmentShader.replace( 122 | "#include ", 123 | `#include 124 | float l = luma(gl_FragColor.rgb); 125 | ivec2 size = textureSize(paperTexture, 0); 126 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 127 | vec3 coords = scale * vWorldPosition.xyz / vWorldPosition.w; 128 | float line = texcube(coords, vNormal, 1.); 129 | float r = aastep( l, line); 130 | gl_FragColor.rgb = blendColorBurn(paper.rgb, inkColor, r); 131 | ` 132 | ); 133 | }; 134 | } 135 | } 136 | 137 | function generateParams(gui, material) { 138 | const params = material.params; 139 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 140 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 141 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 142 | gui.add(params, "scale", 10, 30,.01).onChange((v) => (material.uniforms.scale.value = v)); 143 | } 144 | 145 | export { CrossHatchMaterial ,generateParams }; 146 | -------------------------------------------------------------------------------- /cross-hatch-iii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-iii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { CrossHatchMaterial, generateParams } from "./crossHatchMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new CrossHatchMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /cross-hatch-iv/crossHatchMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | import { lines } from "../shaders/lines.js"; 7 | 8 | class CrossHatchMaterial extends MeshStandardMaterial { 9 | constructor(options) { 10 | super(options); 11 | 12 | this.params = { 13 | roughness: 0.2, 14 | metalness: 0.1, 15 | inkColor: 0xF313F2, 16 | scale: 200, 17 | }; 18 | 19 | this.uniforms = { 20 | inkColor: {value: new Color(this.params.inkColor)}, 21 | resolution: { value: new Vector2(1, 1) }, 22 | paperTexture: { value: null }, 23 | scale: {value:this.params.scale} 24 | } 25 | 26 | this.onBeforeCompile = (shader, renderer) => { 27 | for (const uniformName of Object.keys(this.uniforms)) { 28 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 29 | } 30 | 31 | shader.vertexShader = shader.vertexShader.replace( 32 | `#include `, 33 | `#include 34 | out vec2 vCoords; 35 | out vec4 vWorldPosition;` 36 | ); 37 | shader.vertexShader = shader.vertexShader.replace( 38 | `#include `, 39 | `#include 40 | vCoords = uv; 41 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 42 | ); 43 | 44 | shader.fragmentShader = shader.fragmentShader.replace( 45 | `#include `, 46 | `#include 47 | uniform vec2 resolution; 48 | uniform sampler2D paperTexture; 49 | uniform vec3 inkColor; 50 | uniform float scale; 51 | in vec2 vCoords; 52 | in vec4 vWorldPosition; 53 | #define TAU 6.28318530718 54 | 55 | vec2 noise(vec2 x){ 56 | return fract(cos(dot(x,vec2(134.,1.61034)))*vec2(416418.0,1265.7486)); 57 | } 58 | 59 | float calc(vec2 v, float l) { 60 | float a = atan(v.y, v.x); 61 | float r = .5 + .5 * sin(length(v)+a); 62 | return r - l; 63 | } 64 | 65 | float texcube(in vec3 p, in vec3 n, in float f) { 66 | vec3 v = vec3(calc(p.yz,f), calc(p.xz,f), calc(p.xy,f)); 67 | return dot(v, n*n); 68 | } 69 | 70 | float luma(vec3 color) { 71 | return dot(color, vec3(0.299, 0.587, 0.114)); 72 | } 73 | float luma(vec4 color) { 74 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 75 | } 76 | 77 | float blendColorBurn(float base, float blend) { 78 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 79 | } 80 | 81 | vec3 blendColorBurn(vec3 base, vec3 blend) { 82 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 83 | } 84 | 85 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 86 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 87 | } 88 | 89 | float aastep(float threshold, float value) { 90 | #ifdef GL_OES_standard_derivatives 91 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 92 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 93 | #else 94 | return step(threshold, value); 95 | #endif 96 | } 97 | ` 98 | ); 99 | shader.fragmentShader = shader.fragmentShader.replace( 100 | "#include ", 101 | `#include 102 | float l = luma(gl_FragColor.rgb); 103 | ivec2 size = textureSize(paperTexture, 0); 104 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 105 | vec3 coords = scale * vWorldPosition.xyz / vWorldPosition.w; 106 | float line = texcube(coords, vNormal, .5*l); 107 | float r = aastep(l, line); 108 | gl_FragColor.rgb = blendColorBurn(paper.rgb, inkColor, r); 109 | ` 110 | ); 111 | }; 112 | } 113 | } 114 | 115 | function generateParams(gui, material) { 116 | const params = material.params; 117 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 118 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 119 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 120 | gui.add(params, "scale", 10, 400,.1).onChange((v) => (material.uniforms.scale.value = v)); 121 | } 122 | 123 | export { CrossHatchMaterial ,generateParams }; 124 | -------------------------------------------------------------------------------- /cross-hatch-iv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-iv/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { CrossHatchMaterial, generateParams } from "./crossHatchMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new CrossHatchMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /cross-hatch-v/crossHatchMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | RepeatWrapping, 4 | TextureLoader, 5 | Vector2, 6 | Color, 7 | } from "../third_party/three.module.js"; 8 | import { lines } from "../shaders/lines.js"; 9 | 10 | class CrossHatchMaterial extends MeshStandardMaterial { 11 | constructor(options) { 12 | super(options); 13 | 14 | const loader = new TextureLoader(); 15 | const noiseTexture = loader.load("../assets/noise1.png"); 16 | noiseTexture.wrapS = noiseTexture.wrapT = RepeatWrapping; 17 | 18 | this.params = { 19 | roughness: 0.2, 20 | metalness: 0.1, 21 | inkColor: 0xAF601A, 22 | e: .01 23 | }; 24 | 25 | this.uniforms = { 26 | inkColor: {value: new Color(this.params.inkColor)}, 27 | resolution: { value: new Vector2(1, 1) }, 28 | paperTexture: { value: null }, 29 | noiseTexture: { value: noiseTexture }, 30 | e: {value: this.params.e} 31 | } 32 | 33 | this.onBeforeCompile = (shader, renderer) => { 34 | for (const uniformName of Object.keys(this.uniforms)) { 35 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 36 | } 37 | 38 | shader.vertexShader = shader.vertexShader.replace( 39 | `#include `, 40 | `#include 41 | out vec2 vCoords; 42 | out vec4 vWorldPosition;` 43 | ); 44 | shader.vertexShader = shader.vertexShader.replace( 45 | `#include `, 46 | `#include 47 | vCoords = uv; 48 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 49 | ); 50 | 51 | shader.fragmentShader = shader.fragmentShader.replace( 52 | `#include `, 53 | `#include 54 | uniform vec2 resolution; 55 | uniform sampler2D paperTexture; 56 | uniform sampler2D noiseTexture; 57 | uniform vec3 inkColor; 58 | uniform float scale; 59 | uniform float e; 60 | in vec2 vCoords; 61 | in vec4 vWorldPosition; 62 | #define TAU 6.28318530718 63 | 64 | // adapted from https://www.shadertoy.com/view/4lfXDM 65 | float noise( in vec2 x ){return texture(noiseTexture, x*.01).x;} 66 | float texh(in vec2 p, in float str) { 67 | float rz= 1.; 68 | for (int i=0;i<10;i++) 69 | { 70 | float g = texture(noiseTexture,vec2(0.025,.5)*p).x; 71 | g = smoothstep(0.-str*0.1,2.3-str*0.1,g); 72 | rz = min(1.-g,rz); 73 | p.xy = p.yx; 74 | p += .7; 75 | p *= 1.52; 76 | if (float(i) > str)break; 77 | } 78 | return rz * 1.05; 79 | } 80 | 81 | float texcube(in vec3 p, in vec3 n, in float str, float a) { 82 | float s = sin(a); 83 | float c = cos(a); 84 | mat2 rot = mat2(c, -s, s, c); 85 | vec3 v = vec3(texh(rot*p.yz,str), texh(rot*p.zx,str), texh(rot*p.xy,str)); 86 | return dot(v, n*n); 87 | } 88 | 89 | float luma(vec3 color) { 90 | return dot(color, vec3(0.299, 0.587, 0.114)); 91 | } 92 | float luma(vec4 color) { 93 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 94 | } 95 | 96 | float blendDarken(float base, float blend) { 97 | return min(blend,base); 98 | } 99 | 100 | vec3 blendDarken(vec3 base, vec3 blend) { 101 | return vec3(blendDarken(base.r,blend.r),blendDarken(base.g,blend.g),blendDarken(base.b,blend.b)); 102 | } 103 | 104 | vec3 blendDarken(vec3 base, vec3 blend, float opacity) { 105 | return (blendDarken(base, blend) * opacity + base * (1.0 - opacity)); 106 | } 107 | 108 | float aastep(float threshold, float value) { 109 | #ifdef GL_OES_standard_derivatives 110 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 111 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 112 | #else 113 | return step(threshold, value); 114 | #endif 115 | } 116 | ` 117 | ); 118 | shader.fragmentShader = shader.fragmentShader.replace( 119 | "#include ", 120 | `#include 121 | float l = 1.-luma(gl_FragColor.rgb); 122 | float darks = 1.-2.*luma(gl_FragColor.rgb); 123 | ivec2 size = textureSize(paperTexture, 0); 124 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 125 | vec3 coords = 1. * vWorldPosition.xyz; 126 | vec3 eye = normalize(-cameraPosition.xyz); 127 | vec3 ref = reflect(vNormal, eye); 128 | float line = texcube(coords+0.*ref, vNormal, l*5., TAU/8.); 129 | float lineDark = texcube(coords+0.*ref, vNormal, darks*5., TAU/16.); 130 | float r = 1. - smoothstep(l-e, l+e, line); 131 | float rDark = 1. - smoothstep(l-e, l+e, lineDark); 132 | gl_FragColor.rgb = blendDarken(paper.rgb, inkColor, .5 * r); 133 | gl_FragColor.rgb = blendDarken(gl_FragColor.rgb, inkColor, .5 * rDark); 134 | ` 135 | ); 136 | }; 137 | } 138 | } 139 | 140 | function generateParams(gui, material) { 141 | const params = material.params; 142 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 143 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 144 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 145 | gui.add(params, "e", 0, 1,.01).onChange((v) => (material.uniforms.e.value = v)); 146 | } 147 | 148 | export { CrossHatchMaterial, generateParams }; 149 | -------------------------------------------------------------------------------- /cross-hatch-v/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-v/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { CrossHatchMaterial, generateParams } from "./crossHatchMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new CrossHatchMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Parchment"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /cross-hatch-vi/crossHatchMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | RepeatWrapping, 4 | TextureLoader, 5 | Vector2, 6 | Color, 7 | } from "../third_party/three.module.js"; 8 | 9 | class CrossHatchMaterial extends MeshStandardMaterial { 10 | constructor(options) { 11 | super(options); 12 | 13 | const loader = new TextureLoader(); 14 | const noiseTexture = loader.load("../assets/noise1.png"); 15 | noiseTexture.wrapS = noiseTexture.wrapT = RepeatWrapping; 16 | 17 | this.params = { 18 | roughness: 0.2, 19 | metalness: 0.1, 20 | inkColor: 0x2036ff, 21 | scale: 50, 22 | width: 2, 23 | }; 24 | 25 | this.uniforms = { 26 | inkColor: { value: new Color(this.params.inkColor) }, 27 | resolution: { value: new Vector2(1, 1) }, 28 | paperTexture: { value: null }, 29 | noiseTexture: { value: noiseTexture }, 30 | width: { value: this.params.width }, 31 | scale: { value: this.params.scale }, 32 | }; 33 | 34 | this.onBeforeCompile = (shader, renderer) => { 35 | for (const uniformName of Object.keys(this.uniforms)) { 36 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 37 | } 38 | 39 | shader.vertexShader = shader.vertexShader.replace( 40 | `#include `, 41 | `#include 42 | out vec2 vCoords; 43 | out vec4 vWorldPosition;` 44 | ); 45 | shader.vertexShader = shader.vertexShader.replace( 46 | `#include `, 47 | `#include 48 | vCoords = uv; 49 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 50 | ); 51 | 52 | shader.fragmentShader = shader.fragmentShader.replace( 53 | `#include `, 54 | `#include 55 | uniform vec2 resolution; 56 | uniform sampler2D paperTexture; 57 | uniform sampler2D noiseTexture; 58 | uniform float scale; 59 | uniform float width; 60 | uniform vec3 inkColor; 61 | in vec2 vCoords; 62 | in vec4 vWorldPosition; 63 | #define TAU 6.28318530718 64 | 65 | float noise( in vec2 x ){return texture(noiseTexture, x*.01).x;} 66 | 67 | float texh(in vec2 p, in float lum) { 68 | float e = width * length(vec2(dFdx(p.x), dFdy(p.y))); 69 | if (lum < 1.00) { 70 | float v = abs(mod(p.x + p.y, 10.0)); 71 | if (v < e) { 72 | return 0.; 73 | } 74 | } 75 | 76 | if (lum < 0.8) { 77 | float v = abs(mod(p.x - p.y, 10.0)); 78 | if (v < e) { 79 | return 0.; 80 | } 81 | } 82 | 83 | if (lum < 0.6) { 84 | float v = abs(mod(p.x + p.y - 5.0, 10.0)); 85 | if (v < e) { 86 | return 0.; 87 | } 88 | } 89 | 90 | if (lum < 0.4) { 91 | float v = abs(mod(p.x - p.y - 5.0, 10.0)); 92 | if (v < e) { 93 | return 0.; 94 | } 95 | } 96 | 97 | if (lum < 0.2) { 98 | float v = abs(mod(p.x + p.y - 7.5, 10.0)); 99 | if (v < e) { 100 | return 0.; 101 | } 102 | } 103 | 104 | return 1.; 105 | } 106 | 107 | float texcube(in vec3 p, in vec3 n, in float l, float a) { 108 | //return texh(p.xy, l); 109 | float s = sin(a); 110 | float c = cos(a); 111 | mat2 rot = mat2(c, -s, s, c); 112 | vec3 v = vec3(texh(rot*p.yz,l), texh(rot*p.zx,l), texh(rot*p.xy,l)); 113 | //return v.z; 114 | return dot(v, n*n); 115 | } 116 | 117 | float luma(vec3 color) { 118 | return dot(color, vec3(0.299, 0.587, 0.114)); 119 | } 120 | float luma(vec4 color) { 121 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 122 | } 123 | 124 | float blendDarken(float base, float blend) { 125 | return min(blend,base); 126 | } 127 | 128 | vec3 blendDarken(vec3 base, vec3 blend) { 129 | return vec3(blendDarken(base.r,blend.r),blendDarken(base.g,blend.g),blendDarken(base.b,blend.b)); 130 | } 131 | 132 | vec3 blendDarken(vec3 base, vec3 blend, float opacity) { 133 | return (blendDarken(base, blend) * opacity + base * (1.0 - opacity)); 134 | } 135 | 136 | float aastep(float threshold, float value) { 137 | #ifdef GL_OES_standard_derivatives 138 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 139 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 140 | #else 141 | return step(threshold, value); 142 | #endif 143 | } 144 | ` 145 | ); 146 | shader.fragmentShader = shader.fragmentShader.replace( 147 | "#include ", 148 | `#include 149 | float l = 2. * luma(gl_FragColor.rgb); 150 | ivec2 size = textureSize(paperTexture, 0); 151 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 152 | vec3 coords = scale*vWorldPosition.xyz;//*vec3(resolution, 1.); 153 | float line = texcube(coords.xyz, vNormal, l, TAU/16.); 154 | float r = line;//1. - smoothstep(l-e, l+e, line); 155 | gl_FragColor.rgb = blendDarken(paper.rgb, inkColor, 1.-r); 156 | //gl_FragColor.rgb = vec3(line); 157 | ` 158 | ); 159 | }; 160 | } 161 | } 162 | 163 | function generateParams(gui, material) { 164 | const params = material.params; 165 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 166 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 167 | gui 168 | .addColor(params, "inkColor") 169 | .onChange((v) => material.uniforms.inkColor.value.set(v)); 170 | gui 171 | .add(params, "scale", 10, 100, 0.1) 172 | .onChange((v) => (material.uniforms.scale.value = v)); 173 | gui 174 | .add(params, "width", 0, 10, 0.01) 175 | .onChange((v) => (material.uniforms.width.value = v)); 176 | } 177 | 178 | export { CrossHatchMaterial, generateParams }; 179 | -------------------------------------------------------------------------------- /cross-hatch-vi/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-vi/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { CrossHatchMaterial, generateParams } from "./crossHatchMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new CrossHatchMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Parchment"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /cross-hatch-vii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-vii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { CrossHatchMaterial, generateParams } from "./crossHatchMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new CrossHatchMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /cross-hatch-viii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cross-hatch-viii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.4, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Watercolor cold press"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /js/FBO.js: -------------------------------------------------------------------------------- 1 | import { 2 | WebGLRenderTarget, 3 | ClampToEdgeWrapping, 4 | LinearFilter, 5 | RGBAFormat, 6 | UnsignedByteType, 7 | } from "../third_party/three.module.js"; 8 | 9 | function getFBO(w, h, options = {}) { 10 | const fbo = new WebGLRenderTarget(w, h, { 11 | wrapS: options.wrapS || ClampToEdgeWrapping, 12 | wrapT: options.wrapT || ClampToEdgeWrapping, 13 | minFilter: options.minFilter || LinearFilter, 14 | magFilter: options.magFilter || LinearFilter, 15 | format: options.format || RGBAFormat, 16 | type: options.type || UnsignedByteType, 17 | stencilBuffer: options.stencilBuffer || false, 18 | depthBuffer: options.depthBuffer || true, 19 | }); 20 | return fbo; 21 | } 22 | 23 | export { getFBO }; 24 | -------------------------------------------------------------------------------- /js/ShaderPass.js: -------------------------------------------------------------------------------- 1 | import { 2 | OrthographicCamera, 3 | Scene, 4 | Mesh, 5 | PlaneBufferGeometry, 6 | } from "../third_party/three.module.js"; 7 | import { getFBO } from "../js/FBO.js"; 8 | 9 | class ShaderPass { 10 | constructor(renderer, shader, options = {}) { 11 | this.renderer = renderer; 12 | this.shader = shader; 13 | this.orthoScene = new Scene(); 14 | this.fbo = getFBO(1, 1, options); 15 | this.orthoCamera = new OrthographicCamera( 16 | 1 / -2, 17 | 1 / 2, 18 | 1 / 2, 19 | 1 / -2, 20 | 0.00001, 21 | 1000 22 | ); 23 | this.orthoQuad = new Mesh(new PlaneBufferGeometry(1, 1), this.shader); 24 | this.orthoQuad.scale.set(1, 1, 1); 25 | this.orthoScene.add(this.orthoQuad); 26 | this.texture = this.fbo.texture; 27 | } 28 | 29 | render(final) { 30 | if (!final) { 31 | this.renderer.setRenderTarget(this.fbo); 32 | } 33 | this.renderer.render(this.orthoScene, this.orthoCamera); 34 | this.renderer.setRenderTarget(null); 35 | } 36 | 37 | setSize(width, height) { 38 | this.fbo.setSize(width, height); 39 | this.orthoQuad.scale.set(width, height, 1); 40 | this.orthoCamera.left = -width / 2; 41 | this.orthoCamera.right = width / 2; 42 | this.orthoCamera.top = height / 2; 43 | this.orthoCamera.bottom = -height / 2; 44 | this.orthoCamera.updateProjectionMatrix(); 45 | } 46 | } 47 | 48 | export { ShaderPass }; 49 | -------------------------------------------------------------------------------- /js/ShaderPingPongPass.js: -------------------------------------------------------------------------------- 1 | import { 2 | OrthographicCamera, 3 | Scene, 4 | Mesh, 5 | PlaneBufferGeometry, 6 | } from "../third_party/three.module.js"; 7 | import { getFBO } from "../js/FBO.js"; 8 | 9 | class ShaderPingPongPass { 10 | constructor(renderer, shader, options = {}) { 11 | this.renderer = renderer; 12 | this.shader = shader; 13 | this.orthoScene = new Scene(); 14 | this.fbo = getFBO(1, 1, options); 15 | this.fbos = [this.fbo, this.fbo.clone()]; 16 | this.currentFBO = 0; 17 | this.orthoCamera = new OrthographicCamera( 18 | 1 / -2, 19 | 1 / 2, 20 | 1 / 2, 21 | 1 / -2, 22 | 0.00001, 23 | 1000 24 | ); 25 | this.orthoQuad = new Mesh(new PlaneBufferGeometry(1, 1), this.shader); 26 | this.orthoQuad.scale.set(1, 1, 1); 27 | this.orthoScene.add(this.orthoQuad); 28 | this.texture = this.fbo.texture; 29 | } 30 | 31 | render(final) { 32 | if (!final) { 33 | this.renderer.setRenderTarget(this.fbos[1 - this.currentFBO]); 34 | } 35 | this.renderer.render(this.orthoScene, this.orthoCamera); 36 | this.renderer.setRenderTarget(null); 37 | this.currentFBO = 1 - this.currentFBO; 38 | } 39 | 40 | setSize(width, height) { 41 | this.orthoQuad.scale.set(width, height, 1); 42 | 43 | this.fbos[0].setSize(width, height); 44 | this.fbos[1].setSize(width, height); 45 | 46 | this.orthoQuad.scale.set(width, height, 1); 47 | 48 | this.orthoCamera.left = -width / 2; 49 | this.orthoCamera.right = width / 2; 50 | this.orthoCamera.top = height / 2; 51 | this.orthoCamera.bottom = -height / 2; 52 | this.orthoCamera.updateProjectionMatrix(); 53 | } 54 | } 55 | 56 | export { ShaderPingPongPass }; 57 | -------------------------------------------------------------------------------- /js/envMap.js: -------------------------------------------------------------------------------- 1 | // Manages cube maps. 2 | 3 | import { 4 | sRGBEncoding, 5 | CubeTextureLoader, 6 | } from "../third_party/three.module.js"; 7 | 8 | const cubeTexLoader = new CubeTextureLoader(); 9 | cubeTexLoader.setPath("../assets/"); 10 | 11 | const environments = { 12 | bridge: { file: "", extension: "jpg", texture: null }, 13 | park: { file: "park_", extension: "jpg", texture: null }, 14 | pisa: { file: "pisa_", extension: "png", texture: null }, 15 | }; 16 | 17 | function getTexture(name) { 18 | if (!environments[name].texture) { 19 | const f = environments[name].file; 20 | const ext = environments[name].extension; 21 | environments[name].texture = cubeTexLoader.load([ 22 | `${f}posx.${ext}`, 23 | `${f}negx.${ext}`, 24 | `${f}posy.${ext}`, 25 | `${f}negy.${ext}`, 26 | `${f}posz.${ext}`, 27 | `${f}negz.${ext}`, 28 | ]); 29 | environments[name].texture.encoding = sRGBEncoding; 30 | } 31 | return environments[name].texture; 32 | } 33 | 34 | const params = { 35 | environment: "bridge", 36 | }; 37 | function generateParams(gui, material) { 38 | return gui 39 | .add(params, "environment", Object.keys(environments)) 40 | .onChange((v) => { 41 | material.envMap = getTexture(v); 42 | }); 43 | } 44 | 45 | export { generateParams }; 46 | -------------------------------------------------------------------------------- /js/paper.js: -------------------------------------------------------------------------------- 1 | import { RepeatWrapping, TextureLoader } from "../third_party/three.module.js"; 2 | 3 | const loader = new TextureLoader(); 4 | 5 | const papers = { 6 | "Craft light": { file: "Craft_Light.jpg", texture: null, promise: null }, 7 | "Craft rough": { file: "Craft_Rough.jpg", texture: null, promise: null }, 8 | "Watercolor cold press": { 9 | file: "Watercolor_ColdPress.jpg", 10 | texture: null, 11 | promise: null, 12 | }, 13 | Parchment: { file: "Parchment.jpg", texture: null, promise: null }, 14 | }; 15 | 16 | async function getTexture(name) { 17 | if (papers[name].texture) { 18 | return papers[name].texture; 19 | } 20 | if (!papers[name].promise) { 21 | papers[name].promise = new Promise((resolve, reject) => { 22 | loader.load(`../assets/${papers[name].file}`, (res) => { 23 | res.wrapS = res.wrapT = RepeatWrapping; 24 | papers[name].texture = res; 25 | resolve(); 26 | }); 27 | }); 28 | } 29 | await papers[name].promise; 30 | return papers[name].texture; 31 | } 32 | 33 | const params = { 34 | paper: "Craft light", 35 | }; 36 | function generateParams(gui, material) { 37 | return gui.add(params, "paper", Object.keys(papers)).onChange(async (v) => { 38 | material.uniforms.paperTexture.value = await getTexture(v); 39 | }); 40 | } 41 | export { generateParams }; 42 | -------------------------------------------------------------------------------- /js/renderer.js: -------------------------------------------------------------------------------- 1 | import { 2 | WebGLRenderer, 3 | PerspectiveCamera, 4 | Scene, 5 | PCFSoftShadowMap, 6 | sRGBEncoding, 7 | } from "../third_party/three.module.js"; 8 | import { OrbitControls } from "../third_party/OrbitControls.js"; 9 | 10 | const canvas = document.querySelector("canvas"); 11 | 12 | const renderer = new WebGLRenderer({ 13 | antialias: true, 14 | alpha: true, 15 | canvas, 16 | preserveDrawingBuffer: false, 17 | powerPreference: "high-performance", 18 | }); 19 | renderer.setPixelRatio(window.devicePixelRatio); 20 | renderer.setClearColor(0xffffff, 1); 21 | 22 | renderer.shadowMap.enabled = true; 23 | renderer.shadowMap.type = PCFSoftShadowMap; 24 | //renderer.outputEncoding = sRGBEncoding; 25 | //renderer.gammaFactor = 2.2; 26 | 27 | const scene = new Scene(); 28 | const camera = new PerspectiveCamera(60, 1, 0.1, 100); 29 | camera.position.set(5, 5, 5); 30 | 31 | const controls = new OrbitControls(camera, renderer.domElement); 32 | controls.screenSpacePanning = true; 33 | 34 | const resizeFns = []; 35 | 36 | function onResize(fn) { 37 | resizeFns.push(fn); 38 | } 39 | 40 | function resize() { 41 | const width = window.innerWidth; 42 | const height = window.innerHeight; 43 | const dPR = window.devicePixelRatio; 44 | camera.aspect = width / height; 45 | camera.updateProjectionMatrix(); 46 | renderer.setSize(width, height); 47 | for (const fn of resizeFns) { 48 | fn(); 49 | } 50 | } 51 | 52 | window.addEventListener("resize", resize); 53 | 54 | export { renderer, scene, camera, resize, onResize }; 55 | -------------------------------------------------------------------------------- /js/scene.js: -------------------------------------------------------------------------------- 1 | import { obj as blob } from "../js/sceneBlob.js"; 2 | import { obj as torus } from "../js/sceneTorus.js"; 3 | import { obj as backdrop } from "../js/sceneBackdrop.js"; 4 | import { obj as spheres } from "../js/sceneSpheres.js"; 5 | import { obj as suzanne } from "../js/sceneSuzanne.js"; 6 | //import { obj as zardoz } from "../js/sceneZardoz.js"; 7 | import { obj as leePerry } from "../js/sceneLeePerry.js"; 8 | import { obj as metaballs } from "../js/sceneMetaballs.js"; 9 | 10 | import { 11 | DirectionalLight, 12 | HemisphereLight, 13 | AmbientLight, 14 | PointLight, 15 | } from "../third_party/three.module.js"; 16 | 17 | function initLights(scene) { 18 | const light = new DirectionalLight(0xffffff, 0.5); 19 | light.position.set(0, 1, 3); 20 | light.castShadow = true; 21 | light.shadow.bias = -0.0001; 22 | light.shadow.mapSize.set(4096, 4096); 23 | scene.add(light); 24 | 25 | const light2 = new DirectionalLight(0xffffff, 0.5); 26 | light2.position.set(-3, -3, -3); 27 | light2.castShadow = true; 28 | light2.shadow.bias = -0.0001; 29 | light2.shadow.mapSize.set(4096, 4096); 30 | scene.add(light2); 31 | 32 | const hemiLight = new HemisphereLight(0xbbbbbb, 0x080808, 1); 33 | scene.add(hemiLight); 34 | 35 | const ambientLight = new AmbientLight(0x202020); 36 | //scene.add(ambientLight); 37 | 38 | const spotLight = new PointLight(0xa183ff, 1); 39 | spotLight.castShadow = true; 40 | spotLight.distance = 8; 41 | spotLight.decay = 2; 42 | spotLight.power = 40; 43 | //scene.add(spotLight); 44 | } 45 | 46 | const scenes = { 47 | backdrop: { obj: backdrop, init: false }, 48 | suzanne: { obj: suzanne, init: false }, 49 | // zardoz: { obj: zardoz, init: false }, 50 | leePerry: { obj: leePerry, init: false }, 51 | torus: { obj: torus, init: false }, 52 | blob: { obj: blob, init: false }, 53 | spheres: { obj: spheres, init: false }, 54 | metaballs: { obj: metaballs, init: false }, 55 | }; 56 | 57 | async function initScene(scene, material, gui) { 58 | initLights(scene); 59 | 60 | const params = {}; 61 | const controllers = {}; 62 | const folder = gui.addFolder("Scene"); 63 | folder.open(); 64 | for (const key of Object.keys(scenes)) { 65 | params[key] = false; 66 | const controller = folder.add(params, key).onChange(async (v) => { 67 | if (!scenes[key].init) { 68 | await scenes[key].obj.init(material); 69 | scene.add(scenes[key].obj.group); 70 | scenes[key].init = true; 71 | } 72 | scenes[key].obj.group.visible = v; 73 | }); 74 | controllers[key] = controller; 75 | scenes[key].obj.params(folder); 76 | } 77 | 78 | controllers["backdrop"].setValue(true); 79 | 80 | return controllers; 81 | } 82 | 83 | function update() { 84 | for (const key of Object.keys(scenes)) { 85 | if (scenes[key].obj) { 86 | scenes[key].obj.update(); 87 | } 88 | } 89 | } 90 | 91 | export { initScene, update }; 92 | -------------------------------------------------------------------------------- /js/sceneBackdrop.js: -------------------------------------------------------------------------------- 1 | import { 2 | Group, 3 | Mesh, 4 | IcosahedronBufferGeometry, 5 | } from "../third_party/three.module.js"; 6 | 7 | const group = new Group(); 8 | let backdrop; 9 | let material; 10 | 11 | async function generate() { 12 | if (backdrop) { 13 | group.remove(backdrop); 14 | } 15 | backdrop = new Mesh(new IcosahedronBufferGeometry(20, 4), material); 16 | group.add(backdrop); 17 | } 18 | 19 | const obj = { 20 | init: async (m) => { 21 | material = m; 22 | await generate(); 23 | }, 24 | update: () => {}, 25 | group, 26 | generate, 27 | params: (gui) => {}, 28 | }; 29 | 30 | export { obj }; 31 | -------------------------------------------------------------------------------- /js/sceneBlob.js: -------------------------------------------------------------------------------- 1 | import { 2 | Group, 3 | Mesh, 4 | BufferGeometry, 5 | Vector3, 6 | IcosahedronGeometry, 7 | } from "../third_party/three.module.js"; 8 | import perlin from "../third_party/perlin.js"; 9 | 10 | let blob; 11 | const group = new Group(); 12 | let material; 13 | const geo = new IcosahedronGeometry(1, 5); 14 | 15 | const params = { 16 | scale: 1, 17 | noise: 1, 18 | }; 19 | 20 | async function generate() { 21 | if (blob) { 22 | group.remove(blob); 23 | } 24 | 25 | const v = new Vector3(); 26 | const vertices = geo.vertices; 27 | for (let j = 0; j < vertices.length; j++) { 28 | v.copy(vertices[j]); 29 | v.normalize(); 30 | const n = 31 | 1 + 32 | params.scale + 33 | params.scale * 34 | perlin.simplex3( 35 | params.noise * v.x, 36 | params.noise * v.y, 37 | params.noise * v.z 38 | ); 39 | v.multiplyScalar(n); 40 | vertices[j].copy(v); 41 | } 42 | geo.computeVertexNormals(); 43 | geo.computeFaceNormals(); 44 | 45 | blob = new Mesh(new BufferGeometry().fromGeometry(geo), material); 46 | 47 | blob.castShadow = blob.receiveShadow = true; 48 | group.add(blob); 49 | } 50 | 51 | const obj = { 52 | init: async (m, q, r) => { 53 | material = m; 54 | params.q = q || params.q; 55 | params.r = r || params.r; 56 | await generate(); 57 | }, 58 | update: () => {}, 59 | group, 60 | generate: () => generate(material), 61 | params: (gui) => { 62 | gui.add(params, "scale", 0.1, 2, 0.1).onChange(generate); 63 | gui.add(params, "noise", 0.1, 2, 0.1).onChange(generate); 64 | }, 65 | }; 66 | 67 | export { obj }; 68 | -------------------------------------------------------------------------------- /js/sceneLeePerry.js: -------------------------------------------------------------------------------- 1 | import { OBJLoader } from "../third_party/OBJLoader.js"; 2 | import { Matrix4, Group, Mesh } from "../third_party/three.module.js"; 3 | 4 | let leePerry; 5 | const group = new Group(); 6 | let material; 7 | 8 | const params = {}; 9 | 10 | async function loadModel(file) { 11 | return new Promise((resolve, reject) => { 12 | const loader = new OBJLoader(); 13 | loader.load(file, resolve, null, reject); 14 | }); 15 | } 16 | 17 | async function loadLeePerry() { 18 | const model = await loadModel("../assets/LeePerry.obj"); 19 | const geo = model.children[0].geometry; 20 | geo.center(); 21 | const scale = 0.1; 22 | geo.applyMatrix4(new Matrix4().makeScale(scale, scale, scale)); 23 | return geo; 24 | } 25 | 26 | async function generate() { 27 | if (leePerry) { 28 | group.remove(leePerry); 29 | } 30 | const geo = await loadLeePerry(); 31 | geo.center(); 32 | const scale = 0.5; 33 | geo.applyMatrix4(new Matrix4().makeScale(scale, scale, scale)); 34 | // geo.computeVertexNormals(); 35 | // geo.computeFaceNormals(); 36 | leePerry = new Mesh(geo, material); 37 | leePerry.castShadow = leePerry.receiveShadow = true; 38 | group.add(leePerry); 39 | } 40 | 41 | const obj = { 42 | init: async (m, q, r) => { 43 | material = m; 44 | await generate(); 45 | }, 46 | update: () => {}, 47 | group, 48 | generate: () => generate(material), 49 | params: (gui) => {}, 50 | }; 51 | 52 | export { obj }; 53 | -------------------------------------------------------------------------------- /js/sceneMetaballs.js: -------------------------------------------------------------------------------- 1 | import { 2 | Group, 3 | Mesh, 4 | IcosahedronBufferGeometry, 5 | } from "../third_party/three.module.js"; 6 | import { MarchingCubes } from "../third_party/MarchingCubes.js"; 7 | 8 | const group = new Group(); 9 | let material; 10 | 11 | const params = {}; 12 | const resolution = 64; 13 | 14 | function update(object, time, numblobs) { 15 | if (!object) { 16 | return; 17 | } 18 | 19 | object.reset(); 20 | 21 | const subtract = 12; 22 | const strength = 1.2 / ((Math.sqrt(numblobs) - 1) / 4 + 1); 23 | 24 | for (let i = 0; i < numblobs; i++) { 25 | const ballx = 26 | Math.sin(i + 1.26 * time * (1.03 + 0.5 * Math.cos(0.21 * i))) * 0.27 + 27 | 0.5; 28 | const bally = 29 | Math.abs(Math.cos(i + 1.12 * time * Math.cos(1.22 + 0.1424 * i))) * 0.77; // dip into the floor 30 | const ballz = 31 | Math.cos(i + 1.32 * time * 0.1 * Math.sin(0.92 + 0.53 * i)) * 0.27 + 0.5; 32 | 33 | object.addBall(ballx, bally, ballz, strength, subtract); 34 | } 35 | } 36 | 37 | let effect; 38 | 39 | async function generate() { 40 | effect = new MarchingCubes(resolution, material, true, true); 41 | effect.position.set(0, 0, 0); 42 | effect.scale.set(4, 4, 4); 43 | 44 | effect.castShadow = effect.receiveShadow = true; 45 | 46 | effect.enableUvs = false; 47 | effect.enableColors = false; 48 | 49 | group.add(effect); 50 | } 51 | 52 | const obj = { 53 | init: async (m) => { 54 | material = m; 55 | await generate(); 56 | }, 57 | update: () => { 58 | update(effect, 0.0005 * performance.now(), 10); 59 | }, 60 | group, 61 | generate: () => generate(material), 62 | params: (gui) => {}, 63 | }; 64 | 65 | export { obj }; 66 | -------------------------------------------------------------------------------- /js/sceneSpheres.js: -------------------------------------------------------------------------------- 1 | import { 2 | Group, 3 | Mesh, 4 | IcosahedronBufferGeometry, 5 | } from "../third_party/three.module.js"; 6 | 7 | const group = new Group(); 8 | let material; 9 | const sphereGeometry = new IcosahedronBufferGeometry(1, 4); 10 | 11 | const params = {}; 12 | 13 | async function generate() { 14 | const r = 10; 15 | for (let j = 0; j < 20; j++) { 16 | const sphere = new Mesh(sphereGeometry, material); 17 | sphere.castShadow = sphere.receiveShadow = true; 18 | sphere.scale.setScalar(0.75 + Math.random() * 0.5); 19 | const x = Math.random() * 2 * r - r; 20 | const y = Math.random() * 2 * r - r; 21 | const z = Math.random() * 2 * r - r; 22 | sphere.position.set(x, y, z); 23 | group.add(sphere); 24 | } 25 | } 26 | 27 | const obj = { 28 | init: async (m) => { 29 | material = m; 30 | await generate(); 31 | }, 32 | update: () => {}, 33 | group, 34 | generate: () => generate(material), 35 | params: (gui) => {}, 36 | }; 37 | 38 | export { obj }; 39 | -------------------------------------------------------------------------------- /js/sceneSuzanne.js: -------------------------------------------------------------------------------- 1 | import { OBJLoader } from "../third_party/OBJLoader.js"; 2 | import { SubdivisionModifier } from "../third_party/SubdivisionModifier.js"; 3 | import { 4 | BufferGeometry, 5 | Matrix4, 6 | Group, 7 | Mesh, 8 | BufferAttribute, 9 | } from "../third_party/three.module.js"; 10 | 11 | let suzanne; 12 | const group = new Group(); 13 | let material; 14 | 15 | const params = {}; 16 | 17 | function mergeMesh(mesh) { 18 | let count = 0; 19 | mesh.traverse((m) => { 20 | if (m instanceof Mesh) { 21 | count += m.geometry.attributes.position.count; 22 | } 23 | }); 24 | let geo = new BufferGeometry(); 25 | const positions = new Float32Array(count * 3); 26 | count = 0; 27 | mesh.traverse((m) => { 28 | if (m instanceof Mesh) { 29 | const mat = new Matrix4().makeTranslation( 30 | m.position.x, 31 | m.position.y, 32 | m.position.z 33 | ); 34 | m.geometry.applyMatrix4(mat); 35 | const pos = m.geometry.attributes.position; 36 | for (let j = 0; j < pos.count; j++) { 37 | positions[(count + j) * 3] = pos.array[j * 3]; 38 | positions[(count + j) * 3 + 1] = pos.array[j * 3 + 1]; 39 | positions[(count + j) * 3 + 2] = pos.array[j * 3 + 2]; 40 | } 41 | count += pos.count; 42 | } 43 | }); 44 | geo.setAttribute("position", new BufferAttribute(positions, 3)); 45 | return geo; 46 | } 47 | 48 | async function loadModel(file) { 49 | return new Promise((resolve, reject) => { 50 | const loader = new OBJLoader(); 51 | loader.load(file, resolve, null, reject); 52 | }); 53 | } 54 | 55 | async function loadSuzanne() { 56 | const model = await loadModel("../assets/suzanne.obj"); 57 | const geo = mergeMesh(model); 58 | const modified = new SubdivisionModifier(3); 59 | const geo2 = new BufferGeometry().fromGeometry(modified.modify(geo)); 60 | geo2.center(); 61 | const scale = 3; 62 | geo2.applyMatrix4(new Matrix4().makeScale(scale, scale, scale)); 63 | return geo2; 64 | } 65 | 66 | async function generate() { 67 | if (suzanne) { 68 | group.remove(suzanne); 69 | } 70 | const geo = await loadSuzanne(); 71 | suzanne = new Mesh(geo, material); 72 | suzanne.castShadow = suzanne.receiveShadow = true; 73 | group.add(suzanne); 74 | } 75 | 76 | const obj = { 77 | init: async (m, q, r) => { 78 | material = m; 79 | await generate(); 80 | }, 81 | update: () => {}, 82 | group, 83 | generate: () => generate(material), 84 | params: (gui) => {}, 85 | }; 86 | 87 | export { obj }; 88 | -------------------------------------------------------------------------------- /js/sceneTorus.js: -------------------------------------------------------------------------------- 1 | import { 2 | Group, 3 | Mesh, 4 | TorusKnotBufferGeometry, 5 | } from "../third_party/three.module.js"; 6 | 7 | let torus; 8 | const group = new Group(); 9 | let material; 10 | 11 | const params = { 12 | q: 3, 13 | r: 2, 14 | radius: 2, 15 | radius2: 0.5, 16 | }; 17 | 18 | async function generate() { 19 | if (torus) { 20 | group.remove(torus); 21 | } 22 | torus = new Mesh( 23 | new TorusKnotBufferGeometry( 24 | params.radius, 25 | params.radius2, 26 | 400, 27 | 50, 28 | params.q, 29 | params.r 30 | ), 31 | material 32 | ); 33 | torus.castShadow = torus.receiveShadow = true; 34 | group.add(torus); 35 | } 36 | 37 | const obj = { 38 | init: async (m) => { 39 | material = m; 40 | await generate(); 41 | }, 42 | update: () => {}, 43 | group, 44 | generate, 45 | params: (gui) => { 46 | gui.add(params, "q", 1, 10, 1).onChange(generate); 47 | gui.add(params, "r", 1, 10, 1).onChange(generate); 48 | gui.add(params, "radius", 1, 3).onChange(generate); 49 | gui.add(params, "radius2", 0.1, 1).onChange(generate); 50 | }, 51 | }; 52 | 53 | export { obj }; 54 | -------------------------------------------------------------------------------- /lines-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lines-i/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { 3 | ScreenSpaceSketchMaterial, 4 | generateParams, 5 | } from "./screenSpaceSketchMaterial.js"; 6 | import * as dat from "../third_party/dat.gui.module.js"; 7 | import { initScene, update } from "../js/scene.js"; 8 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 9 | import { generateParams as generatePaperParams } from "../js/paper.js"; 10 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 11 | 12 | const gui = new dat.GUI(); 13 | const materialFolder = gui.addFolder("Material"); 14 | materialFolder.open(); 15 | 16 | const material = new ScreenSpaceSketchMaterial({ 17 | color: 0x808080, 18 | roughness: 0.4, 19 | metalness: 0.1, 20 | side: DoubleSide, 21 | }); 22 | generateParams(materialFolder, material); 23 | const paperController = generatePaperParams(materialFolder, material); 24 | paperController.setValue("Watercolor cold press"); 25 | const envMapController = generateEnvParams(materialFolder, material); 26 | envMapController.setValue("bridge"); 27 | 28 | const tmp = new Vector2(); 29 | function render() { 30 | update(); 31 | renderer.getSize(tmp); 32 | tmp.multiplyScalar(window.devicePixelRatio); 33 | material.uniforms.resolution.value.copy(tmp); 34 | renderer.render(scene, camera); 35 | renderer.setAnimationLoop(render); 36 | } 37 | 38 | async function init() { 39 | const controllers = await initScene(scene, material, gui); 40 | controllers.torus.setValue(true); 41 | controllers.spheres.setValue(true); 42 | resize(); 43 | render(); 44 | } 45 | 46 | init(); 47 | -------------------------------------------------------------------------------- /lines-i/screenSpaceSketchMaterial.js: -------------------------------------------------------------------------------- 1 | import { MeshStandardMaterial, Vector2 } from "../third_party/three.module.js"; 2 | import { lines } from "../shaders/lines.js"; 3 | 4 | class ScreenSpaceSketchMaterial extends MeshStandardMaterial { 5 | constructor(options) { 6 | super(options); 7 | 8 | this.params = { 9 | roughness: 0.4, 10 | metalness: 0.1, 11 | min: 0.25, 12 | max: 0.75, 13 | min2: 0.5, 14 | max2: 0.5, 15 | scale: 1, 16 | radius: 1, 17 | }; 18 | 19 | this.uniforms = { 20 | resolution: { value: new Vector2(1, 1) }, 21 | paperTexture: { value: null }, 22 | range: { value: new Vector2(this.params.min, this.params.max) }, 23 | range2: { value: new Vector2(this.params.min2, this.params.max2) }, 24 | scale: { value: this.params.scale }, 25 | radius: { value: this.params.radius }, 26 | }; 27 | 28 | this.onBeforeCompile = (shader, renderer) => { 29 | for (const uniformName of Object.keys(this.uniforms)) { 30 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 31 | } 32 | 33 | shader.fragmentShader = shader.fragmentShader.replace( 34 | `#include `, 35 | `#include 36 | uniform vec2 resolution; 37 | uniform sampler2D paperTexture; 38 | uniform vec2 range; 39 | uniform vec2 range2; 40 | uniform float scale; 41 | uniform float radius; 42 | ${lines}` 43 | ); 44 | shader.fragmentShader = shader.fragmentShader.replace( 45 | "#include ", 46 | `#include 47 | float l = luma(gl_FragColor.rgb); 48 | float darkColor = l; 49 | float lightColor = 1. - smoothstep(0., .1, l-.5); 50 | float darkLines = lines(darkColor, gl_FragCoord.xy, resolution, range, range2, scale, radius); 51 | float lightLines = lines(lightColor, gl_FragCoord.xy, resolution, range, range2, scale, radius); 52 | ivec2 size = textureSize(paperTexture, 0); 53 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 54 | gl_FragColor.rgb = paper.rgb * vec3(.25 + .75 * darkLines) + 1. * (1. - lightLines );` 55 | ); 56 | }; 57 | } 58 | } 59 | 60 | function generateParams(gui, material) { 61 | const params = material.params; 62 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 63 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 64 | gui 65 | .add(params, "min", 0, 2) 66 | .onChange((v) => (material.uniforms.range.value.x = v)); 67 | gui 68 | .add(params, "max", 0, 2) 69 | .onChange((v) => (material.uniforms.range.value.y = v)); 70 | gui 71 | .add(params, "min2", 0, 2) 72 | .onChange((v) => (material.uniforms.range2.value.y = v)); 73 | gui 74 | .add(params, "max2", 0, 2) 75 | .onChange((v) => (material.uniforms.range2.value.y = v)); 76 | gui 77 | .add(params, "scale", 0, 10) 78 | .onChange((v) => (material.uniforms.scale.value = v)); 79 | gui 80 | .add(params, "radius", 1, 10) 81 | .onChange((v) => (material.uniforms.radius.value = v)); 82 | } 83 | 84 | export { ScreenSpaceSketchMaterial, generateParams }; 85 | -------------------------------------------------------------------------------- /lines-ii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lines-ii/lineMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | Color, 3 | MeshStandardMaterial, 4 | Vector2, 5 | } from "../third_party/three.module.js"; 6 | 7 | class LineMaterial extends MeshStandardMaterial { 8 | constructor(options) { 9 | super(options); 10 | 11 | this.params = { 12 | roughness: 0.4, 13 | metalness: 0.1, 14 | scale:50, 15 | inkColor: 0xff0000, 16 | }; 17 | 18 | this.uniforms = { 19 | resolution: { value: new Vector2(1, 1) }, 20 | paperTexture: { value: null }, 21 | scale: { value: this.params.scale }, 22 | inkColor: { value: new Color(this.params.inkColor) }, 23 | }; 24 | 25 | this.onBeforeCompile = (shader, renderer) => { 26 | for (const uniformName of Object.keys(this.uniforms)) { 27 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 28 | } 29 | 30 | shader.vertexShader = shader.vertexShader.replace( 31 | `#include `, 32 | `#include 33 | out vec2 vCoords; 34 | out vec4 vWorldPosition;` 35 | ); 36 | shader.vertexShader = shader.vertexShader.replace( 37 | `#include `, 38 | `#include 39 | vCoords = uv; 40 | vWorldPosition = modelMatrix * vec4(position, 1.);` 41 | ); 42 | 43 | shader.fragmentShader = shader.fragmentShader.replace( 44 | `#include `, 45 | `#include 46 | uniform vec2 resolution; 47 | uniform sampler2D paperTexture; 48 | uniform sampler2D noiseTexture; 49 | uniform vec3 inkColor; 50 | uniform float scale; 51 | in vec2 vCoords; 52 | in vec4 vWorldPosition; 53 | #define TAU 6.28318530718 54 | 55 | // procedural noise from IQ 56 | vec2 hash( vec2 p ) 57 | { 58 | p = vec2( dot(p,vec2(127.1,311.7)), 59 | dot(p,vec2(269.5,183.3)) ); 60 | return -1.0 + 2.0*fract(sin(p)*43758.5453123); 61 | } 62 | 63 | float noise( in vec2 p ) 64 | { 65 | const float K1 = 0.366025404; // (sqrt(3)-1)/2; 66 | const float K2 = 0.211324865; // (3-sqrt(3))/6; 67 | 68 | vec2 i = floor( p + (p.x+p.y)*K1 ); 69 | 70 | vec2 a = p - i + (i.x+i.y)*K2; 71 | vec2 o = (a.x>a.y) ? vec2(1.0,0.0) : vec2(0.0,1.0); 72 | vec2 b = a - o + K2; 73 | vec2 c = a - 1.0 + 2.0*K2; 74 | 75 | vec3 h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 ); 76 | 77 | vec3 n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0))); 78 | 79 | return dot( n, vec3(70.0) ); 80 | } 81 | 82 | ///////// 83 | 84 | float aastep(float threshold, float value) { 85 | #ifdef GL_OES_standard_derivatives 86 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 87 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 88 | #else 89 | return step(threshold, value); 90 | #endif 91 | } 92 | 93 | float hetched(vec2 p, vec2 q) 94 | { 95 | return (1.45*abs(p.y) + .3 * noise(q)); 96 | } 97 | 98 | vec3 texcube(in vec3 p, in vec3 n, in vec3 q) { 99 | vec3 v = vec3(hetched(p.yz,q.xy), hetched(p.zx,q.xy), hetched(p.xy,q.xy)); 100 | return v; 101 | } 102 | 103 | float luma(vec3 color) { 104 | return dot(color, vec3(0.299, 0.587, 0.114)); 105 | } 106 | float luma(vec4 color) { 107 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 108 | } 109 | float blendColorBurn(float base, float blend) { 110 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 111 | } 112 | 113 | vec3 blendColorBurn(vec3 base, vec3 blend) { 114 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 115 | } 116 | 117 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 118 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 119 | } 120 | ` 121 | ); 122 | shader.fragmentShader = shader.fragmentShader.replace( 123 | "#include ", 124 | `#include 125 | float l = 1.-luma(gl_FragColor.rgb); 126 | ivec2 size = textureSize(paperTexture, 0); 127 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 128 | vec3 coords = scale * vCoords.xyy; 129 | vec3 qr = coords.xyz; 130 | vec3 line = texcube(2.0*fract(qr) - 1.0, vec3(1.), 1. * coords)*(1.-l); 131 | float r = aastep(.5*l, line.x); 132 | gl_FragColor.rgb = blendColorBurn(paper.rgb, inkColor, 1.-r); 133 | ` 134 | ); 135 | }; 136 | } 137 | } 138 | 139 | function generateParams(gui, material) { 140 | const params = material.params; 141 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 142 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 143 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 144 | gui.add(params, "scale", 10, 100,.01).onChange((v) => (material.uniforms.scale.value = v)); 145 | } 146 | 147 | export { LineMaterial, generateParams }; 148 | -------------------------------------------------------------------------------- /lines-ii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.4, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft rough"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /lines-iii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lines-iii/lineMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | import { lines } from "../shaders/lines.js"; 7 | 8 | class LineMaterial extends MeshStandardMaterial { 9 | constructor(options) { 10 | super(options); 11 | 12 | this.params = { 13 | roughness: 0.4, 14 | metalness: 0.1, 15 | scale: 20, 16 | inkColor: 0xff0000, 17 | }; 18 | 19 | this.uniforms = { 20 | resolution: { value: new Vector2(1, 1) }, 21 | paperTexture: { value: null }, 22 | scale: { value: this.params.scale }, 23 | inkColor: { value: new Color(this.params.inkColor) }, 24 | }; 25 | 26 | this.onBeforeCompile = (shader, renderer) => { 27 | for (const uniformName of Object.keys(this.uniforms)) { 28 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 29 | } 30 | 31 | shader.vertexShader = shader.vertexShader.replace( 32 | `#include `, 33 | `#include 34 | out vec2 vCoords; 35 | out vec4 vWorldPosition;` 36 | ); 37 | shader.vertexShader = shader.vertexShader.replace( 38 | `#include `, 39 | `#include 40 | vCoords = uv; 41 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 42 | ); 43 | 44 | shader.fragmentShader = shader.fragmentShader.replace( 45 | `#include `, 46 | `#include 47 | uniform vec2 resolution; 48 | uniform sampler2D paperTexture; 49 | uniform vec3 inkColor; 50 | uniform float scale; 51 | in vec2 vCoords; 52 | in vec4 vWorldPosition; 53 | #define TAU 6.28318530718 54 | 55 | // procedural noise from IQ 56 | vec2 hash( vec2 p ) 57 | { 58 | p = vec2( dot(p,vec2(127.1,311.7)), 59 | dot(p,vec2(269.5,183.3)) ); 60 | return -1.0 + 2.0*fract(sin(p)*43758.5453123); 61 | } 62 | 63 | float noise( in vec2 p ) 64 | { 65 | const float K1 = 0.366025404; // (sqrt(3)-1)/2; 66 | const float K2 = 0.211324865; // (3-sqrt(3))/6; 67 | 68 | vec2 i = floor( p + (p.x+p.y)*K1 ); 69 | 70 | vec2 a = p - i + (i.x+i.y)*K2; 71 | vec2 o = (a.x>a.y) ? vec2(1.0,0.0) : vec2(0.0,1.0); 72 | vec2 b = a - o + K2; 73 | vec2 c = a - 1.0 + 2.0*K2; 74 | 75 | vec3 h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 ); 76 | 77 | vec3 n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0))); 78 | 79 | return dot( n, vec3(70.0) ); 80 | } 81 | 82 | ///////// 83 | 84 | float aastep(float threshold, float value) { 85 | #ifdef GL_OES_standard_derivatives 86 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 87 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 88 | #else 89 | return step(threshold, value); 90 | #endif 91 | } 92 | 93 | float hetched(vec2 p, vec2 q) 94 | { 95 | return (1.45*abs(p.y) + .3 * noise(q)); 96 | } 97 | 98 | float texcube(in vec3 p, in vec3 n, in vec3 q) { 99 | vec3 v = vec3(hetched(p.yz,q.xy), hetched(p.zx,q.xy), hetched(p.xy,q.xy)); 100 | return v.z; 101 | return dot(v, n*n); 102 | } 103 | 104 | float luma(vec3 color) { 105 | return dot(color, vec3(0.299, 0.587, 0.114)); 106 | } 107 | float luma(vec4 color) { 108 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 109 | } 110 | float blendColorBurn(float base, float blend) { 111 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 112 | } 113 | 114 | vec3 blendColorBurn(vec3 base, vec3 blend) { 115 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 116 | } 117 | 118 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 119 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 120 | } 121 | ` 122 | ); 123 | shader.fragmentShader = shader.fragmentShader.replace( 124 | "#include ", 125 | `#include 126 | float l = luma(gl_FragColor.rgb); 127 | ivec2 size = textureSize(paperTexture, 0); 128 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 129 | vec3 coords = scale * vWorldPosition.xyz/ vWorldPosition.w; 130 | vec3 qr = coords.xyz; 131 | float line = texcube(2.0*fract(qr) - 1.0, vNormal, .1 * coords)*l; 132 | float r = aastep(.5*(1.-l), line); 133 | gl_FragColor.rgb = blendColorBurn(paper.rgb, inkColor, 1.-r); 134 | 135 | ` 136 | ); 137 | }; 138 | } 139 | } 140 | 141 | function generateParams(gui, material) { 142 | const params = material.params; 143 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 144 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 145 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 146 | gui.add(params, "scale", 10, 40,.01).onChange((v) => (material.uniforms.scale.value = v)); 147 | } 148 | 149 | export { LineMaterial, generateParams }; 150 | -------------------------------------------------------------------------------- /lines-iii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.4, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft rough"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /lines-iv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lines-iv/lineMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | import { lines } from "../shaders/lines.js"; 7 | 8 | class LineMaterial extends MeshStandardMaterial { 9 | constructor(options) { 10 | super(options); 11 | 12 | this.params = { 13 | roughness: 0.4, 14 | metalness: 0.1, 15 | scale: .01, 16 | inkColor: 0x87B41F, 17 | }; 18 | 19 | this.uniforms = { 20 | resolution: { value: new Vector2(1, 1) }, 21 | paperTexture: { value: null }, 22 | scale: { value: this.params.scale }, 23 | inkColor: { value: new Color(this.params.inkColor) }, 24 | }; 25 | 26 | this.onBeforeCompile = (shader, renderer) => { 27 | for (const uniformName of Object.keys(this.uniforms)) { 28 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 29 | } 30 | 31 | shader.vertexShader = shader.vertexShader.replace( 32 | `#include `, 33 | `#include 34 | out vec2 vCoords; 35 | out vec4 vWorldPosition;` 36 | ); 37 | shader.vertexShader = shader.vertexShader.replace( 38 | `#include `, 39 | `#include 40 | vCoords = uv; 41 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 42 | ); 43 | 44 | shader.fragmentShader = shader.fragmentShader.replace( 45 | `#include `, 46 | `#include 47 | uniform vec2 resolution; 48 | uniform sampler2D paperTexture; 49 | uniform float scale; 50 | uniform vec3 inkColor; 51 | in vec2 vCoords; 52 | in vec4 vWorldPosition; 53 | #define TAU 6.28318530718 54 | 55 | // procedural noise from IQ 56 | vec2 hash( vec2 p ) 57 | { 58 | p = vec2( dot(p,vec2(127.1,311.7)), 59 | dot(p,vec2(269.5,183.3)) ); 60 | return -1.0 + 2.0*fract(sin(p)*43758.5453123); 61 | } 62 | 63 | float noise( in vec2 p ) 64 | { 65 | const float K1 = 0.366025404; // (sqrt(3)-1)/2; 66 | const float K2 = 0.211324865; // (3-sqrt(3))/6; 67 | 68 | vec2 i = floor( p + (p.x+p.y)*K1 ); 69 | 70 | vec2 a = p - i + (i.x+i.y)*K2; 71 | vec2 o = (a.x>a.y) ? vec2(1.0,0.0) : vec2(0.0,1.0); 72 | vec2 b = a - o + K2; 73 | vec2 c = a - 1.0 + 2.0*K2; 74 | 75 | vec3 h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 ); 76 | 77 | vec3 n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0))); 78 | 79 | return dot( n, vec3(70.0) ); 80 | } 81 | 82 | ///////// 83 | 84 | float aastep(float threshold, float value) { 85 | #ifdef GL_OES_standard_derivatives 86 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 87 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 88 | #else 89 | return step(threshold, value); 90 | #endif 91 | } 92 | 93 | float hetched(vec2 p, vec2 q) 94 | { 95 | return (1.45*abs(p.y) + .3 * noise(q)); 96 | } 97 | 98 | float texcube(in vec3 p, in vec3 n, in vec3 q) { 99 | vec3 v = vec3(hetched(p.yz,q.xy), hetched(p.zx,q.xy), hetched(p.xy,q.xy)); 100 | return dot(v, n*n); 101 | } 102 | 103 | float luma(vec3 color) { 104 | return dot(color, vec3(0.299, 0.587, 0.114)); 105 | } 106 | float luma(vec4 color) { 107 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 108 | } 109 | float blendColorBurn(float base, float blend) { 110 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 111 | } 112 | 113 | vec3 blendColorBurn(vec3 base, vec3 blend) { 114 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 115 | } 116 | 117 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 118 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 119 | } 120 | ` 121 | ); 122 | shader.fragmentShader = shader.fragmentShader.replace( 123 | "#include ", 124 | `#include 125 | float l = luma(gl_FragColor.rgb); 126 | ivec2 size = textureSize(paperTexture, 0); 127 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 128 | vec3 coords = scale * gl_FragCoord.xyz/ gl_FragCoord.w; 129 | vec3 qr = coords.xyz; 130 | float line = texcube(2.0*fract(qr) - 1.0, vNormal, .1 * coords)*l; 131 | float r = aastep(-.25+1.-l, line); 132 | gl_FragColor.rgb = blendColorBurn(paper.rgb, inkColor, 1.-r); 133 | 134 | ` 135 | ); 136 | }; 137 | } 138 | } 139 | 140 | function generateParams(gui, material) { 141 | const params = material.params; 142 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 143 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 144 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 145 | gui.add(params, "scale", .001, .02,.001).onChange((v) => (material.uniforms.scale.value = v)); 146 | } 147 | 148 | export { LineMaterial, generateParams }; 149 | -------------------------------------------------------------------------------- /lines-iv/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.4, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft rough"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /lines-v/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lines-v/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.4, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft rough"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /lines-vi/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lines-vi/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | // from https://twitter.com/oceanquigley/status/1322991432160866304 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new LineMaterial({ 16 | color: 0x808080, 17 | roughness: 0.4, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const paperController = generatePaperParams(materialFolder, material); 23 | paperController.setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | const tmp = new Vector2(); 28 | function render() { 29 | renderer.getSize(tmp); 30 | tmp.multiplyScalar(window.devicePixelRatio); 31 | material.uniforms.resolution.value.copy(tmp); 32 | renderer.render(scene, camera); 33 | renderer.setAnimationLoop(render); 34 | } 35 | 36 | async function init() { 37 | const controllers = await initScene(scene, material, gui); 38 | controllers.torus.setValue(true); 39 | controllers.spheres.setValue(true); 40 | resize(); 41 | render(); 42 | } 43 | 44 | init(); 45 | -------------------------------------------------------------------------------- /patch-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /patch-i/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /patch-ii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /patch-ii/lineMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | RepeatWrapping, 4 | TextureLoader, 5 | Vector2, 6 | Color, 7 | } from "../third_party/three.module.js"; 8 | 9 | class LineMaterial extends MeshStandardMaterial { 10 | constructor(options) { 11 | super(options); 12 | 13 | const loader = new TextureLoader(); 14 | const texture = loader.load("../assets/Craft_Rough.jpg"); 15 | const noiseTexture = loader.load("../assets/noise2.png"); 16 | noiseTexture.wrapS = noiseTexture.wrapT = RepeatWrapping; 17 | 18 | this.params = { 19 | roughness: 0.4, 20 | metalness: 0.1, 21 | scale: 200, 22 | inkColor: 0xD94A4A, 23 | e: .4 24 | }; 25 | 26 | this.uniforms = { 27 | resolution: { value: new Vector2(1, 1) }, 28 | paperTexture: { value: texture }, 29 | noiseTexture: { value: noiseTexture }, 30 | inkColor: { value: new Color(this.params.inkColor)}, 31 | scale: { value: this.params.scale }, 32 | }; 33 | 34 | this.onBeforeCompile = (shader, renderer) => { 35 | for (const uniformName of Object.keys(this.uniforms)) { 36 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 37 | } 38 | 39 | shader.vertexShader = shader.vertexShader.replace( 40 | `#include `, 41 | `#include 42 | out vec2 vCoords; 43 | out vec4 vWorldPosition;` 44 | ); 45 | shader.vertexShader = shader.vertexShader.replace( 46 | `#include `, 47 | `#include 48 | vCoords = uv; 49 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 50 | ); 51 | 52 | shader.fragmentShader = shader.fragmentShader.replace( 53 | `#include `, 54 | `#include 55 | uniform vec2 resolution; 56 | uniform sampler2D paperTexture; 57 | uniform sampler2D noiseTexture; 58 | uniform vec3 inkColor; 59 | uniform float scale; 60 | uniform float e; 61 | in vec2 vCoords; 62 | in vec4 vWorldPosition; 63 | #define TAU 6.28318530718 64 | 65 | // adapted from https://www.shadertoy.com/view/4ssyz4 66 | 67 | float aastep(float threshold, float value) { 68 | #ifdef GL_OES_standard_derivatives 69 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 70 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 71 | #else 72 | return step(threshold, value); 73 | #endif 74 | } 75 | 76 | vec4 getRand(vec2 pos) 77 | { 78 | ivec2 size = textureSize(noiseTexture, 0); 79 | return texture(noiseTexture,pos/vec2(float(size.x), float(size.y))); 80 | } 81 | 82 | float quantize(float v, int num) 83 | { 84 | return floor(v*float(num)+.5)/float(num); 85 | } 86 | 87 | 88 | float htPattern(vec2 pos, float v) 89 | { 90 | int lnum = 10;//100 - int(round(v * 5.)/5.)*10; 91 | float p; 92 | float b0= v; 93 | float bq=quantize(b0,lnum); 94 | float b=bq*float(lnum); 95 | float db=b0*float(lnum)-b; 96 | float d=1.; 97 | d*=1.*(1.-(b+.3*float(lnum))/1.3/float(lnum)); 98 | float ang=-(float(lnum-1)-b-.5+.0)/float(lnum)/**3.121*/*PI; 99 | vec2 dir = vec2(cos(ang),sin(ang)); 100 | vec2 dir2 = vec2(cos(ang*3.),sin(ang*3.)); 101 | float l=length(pos+getRand(pos).xy*0.-resolution.xy*.5-dir2*resolution.y*.4)*d; 102 | p = 1.-1.7*exp(-cos(l)*cos(l)*1./d/d); 103 | return p; 104 | } 105 | 106 | float texcube(in vec3 p, in vec3 n, in float l) { 107 | vec3 v = vec3(htPattern(p.yz,l),htPattern(p.zx,l), htPattern(p.xy,l)); 108 | return dot(v, n*n); 109 | } 110 | 111 | float luma(vec3 color) { 112 | return dot(color, vec3(0.299, 0.587, 0.114)); 113 | } 114 | float luma(vec4 color) { 115 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 116 | } 117 | float blendColorBurn(float base, float blend) { 118 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 119 | } 120 | 121 | vec3 blendColorBurn(vec3 base, vec3 blend) { 122 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 123 | } 124 | 125 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 126 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 127 | } 128 | ` 129 | ); 130 | shader.fragmentShader = shader.fragmentShader.replace( 131 | "#include ", 132 | `#include 133 | float l = 1.2 * luma(gl_FragColor.rgb); 134 | ivec2 size = textureSize(paperTexture, 0); 135 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 136 | vec3 coords = scale * vWorldPosition.xyz/ vWorldPosition.w; 137 | float line = texcube(coords, vNormal, l); 138 | float r = aastep(-.25+1.-l, line); 139 | gl_FragColor.rgb = blendColorBurn(paper.rgb, inkColor, 1.-r); 140 | 141 | ` 142 | ); 143 | }; 144 | } 145 | } 146 | 147 | function generateParams(gui, material) { 148 | const params = material.params; 149 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 150 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 151 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 152 | gui.add(params, "scale", 50, 400, .1).onChange((v) => (material.uniforms.scale.value = v)); 153 | } 154 | 155 | export { LineMaterial, generateParams }; 156 | 157 | -------------------------------------------------------------------------------- /patch-ii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /post-blueprint/CoordsMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshBasicMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | class Material extends MeshBasicMaterial { 8 | constructor(options) { 9 | super(options); 10 | 11 | this.params = { 12 | angleGrid: 0, 13 | scale: 40, 14 | }; 15 | 16 | this.uniforms = { 17 | angleGrid: { value: this.params.angleGrid }, 18 | scale: { value: this.params.scale }, 19 | }; 20 | 21 | this.onBeforeCompile = (shader, renderer) => { 22 | for (const uniformName of Object.keys(this.uniforms)) { 23 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 24 | } 25 | 26 | shader.vertexShader = shader.vertexShader.replace( 27 | `#include `, 28 | `#include 29 | out vec2 vCoords; 30 | out vec3 vPosition; 31 | out vec4 vWorldPosition;` 32 | ); 33 | shader.vertexShader = shader.vertexShader.replace( 34 | `#include `, 35 | `#include 36 | vCoords = uv; 37 | vPosition = position; 38 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 39 | ); 40 | 41 | shader.fragmentShader = shader.fragmentShader.replace( 42 | `#include `, 43 | `#include 44 | uniform float angleGrid; 45 | uniform float scale; 46 | 47 | in vec2 vCoords; 48 | in vec3 vPosition; 49 | in vec4 vWorldPosition; 50 | #define TAU 6.28318530718 51 | 52 | mat2 rot(in float a) { 53 | float s = sin(a); 54 | float c = cos(a); 55 | mat2 rot = mat2(c, -s, s, c); 56 | return rot; 57 | } 58 | ` 59 | ); 60 | shader.fragmentShader = shader.fragmentShader.replace( 61 | "#include ", 62 | `#include 63 | 64 | vec2 uv = rot(angleGrid) * vWorldPosition.xy; 65 | float stripeX = .5 + .5 * sin(uv.x * scale); 66 | float stripeY = .5 + .5 * sin(uv.y * scale); 67 | float stripe = max(stripeX, stripeY); 68 | float e = .05 * length(vec2(dFdx(uv.x), dFdy(uv.y))) * scale; 69 | gl_FragColor.a = .25; 70 | if(stripe < 1.-e) { 71 | gl_FragColor.a = 1.; 72 | } 73 | gl_FragColor.rgb = vWorldPosition.xyz / vWorldPosition.w; 74 | ` 75 | ); 76 | }; 77 | } 78 | } 79 | 80 | function generateParams(gui, material) { 81 | const params = material.params; 82 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 83 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 84 | } 85 | 86 | export { Material, generateParams }; 87 | -------------------------------------------------------------------------------- /post-blueprint/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-blueprint/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | renderer.setClearColor(0xffffff, 0); 10 | 11 | const post = new Post(renderer); 12 | 13 | const gui = new dat.GUI(); 14 | const materialFolder = gui.addFolder("Material"); 15 | materialFolder.open(); 16 | 17 | const material = new Material({ 18 | color: 0x808080, 19 | roughness: 0.2, 20 | metalness: 0.1, 21 | side: DoubleSide, 22 | }); 23 | generateParams(materialFolder, material); 24 | const postController = post.generateParams(materialFolder); 25 | postController["paper"].setValue("Parchment"); 26 | const envMapController = generateEnvParams(materialFolder, material); 27 | envMapController.setValue("bridge"); 28 | 29 | function render() { 30 | update(); 31 | post.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.backdrop.setValue(false); 38 | controllers.torus.setValue(true); 39 | controllers.spheres.setValue(true); 40 | resize(); 41 | render(); 42 | } 43 | 44 | onResize(() => { 45 | const width = window.innerWidth; 46 | const height = window.innerHeight; 47 | const dPR = window.devicePixelRatio; 48 | post.setSize(width * dPR, height * dPR); 49 | }); 50 | 51 | init(); 52 | -------------------------------------------------------------------------------- /post-cartoon-i/Material.js: -------------------------------------------------------------------------------- 1 | import { MeshStandardMaterial } from "../third_party/three.module.js"; 2 | 3 | const Material = MeshStandardMaterial; 4 | 5 | function generateParams(gui, material) { 6 | gui 7 | .add(material, "roughness", 0, 1) 8 | .onChange((v) => (material.roughness = v)); 9 | gui 10 | .add(material, "metalness", 0, 1) 11 | .onChange((v) => (material.metalness = v)); 12 | } 13 | 14 | export { Material, generateParams }; 15 | -------------------------------------------------------------------------------- /post-cartoon-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cartoon-i/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cartoon-ii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cartoon-ii/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cartoon-iii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cartoon-iii/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cartoon-iv/Material.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | const Material = MeshStandardMaterial; 8 | 9 | function generateParams(gui, material) { 10 | const params = material; 11 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 12 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 13 | } 14 | 15 | export { Material, generateParams }; 16 | -------------------------------------------------------------------------------- /post-cartoon-iv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cartoon-iv/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cartoon-v/Material.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | const Material = MeshStandardMaterial; 8 | 9 | function generateParams(gui, material) { 10 | const params = material; 11 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 12 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 13 | } 14 | 15 | export { Material, generateParams }; 16 | -------------------------------------------------------------------------------- /post-cartoon-v/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cartoon-v/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cartoon-vi/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cartoon-vi/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cmyk-halftone-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cmyk-halftone-i/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cross-hatch-i/Material.js: -------------------------------------------------------------------------------- 1 | import { MeshStandardMaterial } from "../third_party/three.module.js"; 2 | 3 | const Material = MeshStandardMaterial; 4 | 5 | function generateParams(gui, material) { 6 | gui 7 | .add(material, "roughness", 0, 1) 8 | .onChange((v) => (material.roughness = v)); 9 | gui 10 | .add(material, "metalness", 0, 1) 11 | .onChange((v) => (material.metalness = v)); 12 | } 13 | 14 | export { Material, generateParams }; 15 | -------------------------------------------------------------------------------- /post-cross-hatch-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cross-hatch-i/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cross-hatch-i/post.js: -------------------------------------------------------------------------------- 1 | import { 2 | Color, 3 | DoubleSide, 4 | MeshNormalMaterial, 5 | RawShaderMaterial, 6 | } from "../third_party/three.module.js"; 7 | import { ShaderPass } from "../js/ShaderPass.js"; 8 | import { getFBO } from "../js/FBO.js"; 9 | import { shader as orthoVs } from "../shaders/ortho-vs.js"; 10 | import { shader as sobel } from "../shaders/sobel.js"; 11 | import { shader as aastep } from "../shaders/aastep.js"; 12 | import { shader as luma } from "../shaders/luma.js"; 13 | import { generateParams as generatePaperParams } from "../js/paper.js"; 14 | import { shader as darken } from "../shaders/blend-darken.js"; 15 | 16 | const normalMat = new MeshNormalMaterial({ side: DoubleSide }); 17 | 18 | const fragmentShader = `#version 300 es 19 | precision highp float; 20 | 21 | uniform sampler2D colorTexture; 22 | uniform sampler2D normalTexture; 23 | uniform sampler2D paperTexture; 24 | uniform vec3 inkColor; 25 | uniform float scale; 26 | uniform float thickness; 27 | 28 | out vec4 fragColor; 29 | 30 | in vec2 vUv; 31 | 32 | ${sobel} 33 | 34 | ${luma} 35 | 36 | float texh(in vec2 p, in float lum) { 37 | float e = thickness * length(vec2(dFdx(p.x), dFdy(p.y))); 38 | if (lum < 1.00) { 39 | float v = abs(mod(p.x + p.y, 10.0)); 40 | if (v < e) { 41 | return 0.; 42 | } 43 | } 44 | 45 | if (lum < 0.8) { 46 | float v = abs(mod(p.x - p.y, 10.0)); 47 | if (v < e) { 48 | return 0.; 49 | } 50 | } 51 | 52 | if (lum < 0.6) { 53 | float v = abs(mod(p.x + p.y - 5.0, 10.0)); 54 | if (v < e) { 55 | return 0.; 56 | } 57 | } 58 | 59 | if (lum < 0.4) { 60 | float v = abs(mod(p.x - p.y - 5.0, 10.0)); 61 | if (v < e) { 62 | return 0.; 63 | } 64 | } 65 | 66 | if (lum < 0.2) { 67 | float v = abs(mod(p.x + p.y - 7.5, 10.0)); 68 | if (v < e) { 69 | return 0.; 70 | } 71 | } 72 | 73 | return 1.; 74 | } 75 | 76 | ${aastep} 77 | 78 | ${darken} 79 | 80 | void main() { 81 | vec2 size = vec2(textureSize(colorTexture, 0)); 82 | float e = .01; 83 | vec4 color = texture(colorTexture, vUv); 84 | float l = 2. * luma(color.rgb); 85 | float normalEdge = 1.- length(sobel(normalTexture, vUv, size, thickness)); 86 | normalEdge = aastep(.5, normalEdge); 87 | // float colorEdge = 1.- length(sobel(colorTexture, vUv, size, 1.)); 88 | // colorEdge = aastep(.5, colorEdge); 89 | // colorEdge += .5; 90 | vec4 paper = texture(paperTexture, .00025 * vUv*size); 91 | float r = texh(scale*vUv*size, l) * normalEdge; 92 | fragColor.rgb = blendDarken(paper.rgb, inkColor/255., 1.-r); 93 | fragColor.a = 1.; 94 | } 95 | `; 96 | 97 | class Post { 98 | constructor(renderer) { 99 | this.renderer = renderer; 100 | this.colorFBO = getFBO(1, 1); 101 | this.normalFBO = getFBO(1, 1); 102 | this.params = { 103 | scale: 0.3, 104 | thickness: 2.5, 105 | inkColor: new Color(255, 0, 0), 106 | }; 107 | const shader = new RawShaderMaterial({ 108 | uniforms: { 109 | paperTexture: { value: null }, 110 | colorTexture: { value: this.colorFBO.texture }, 111 | normalTexture: { value: this.normalFBO.texture }, 112 | inkColor: { value: this.params.inkColor }, 113 | scale: { value: this.params.scale }, 114 | thickness: { value: this.params.thickness }, 115 | }, 116 | vertexShader: orthoVs, 117 | fragmentShader, 118 | }); 119 | this.renderPass = new ShaderPass(renderer, shader); 120 | } 121 | 122 | setSize(w, h) { 123 | this.normalFBO.setSize(w, h); 124 | this.colorFBO.setSize(w, h); 125 | this.renderPass.setSize(w, h); 126 | } 127 | 128 | render(scene, camera) { 129 | this.renderer.setRenderTarget(this.colorFBO); 130 | this.renderer.render(scene, camera); 131 | this.renderer.setRenderTarget(null); 132 | scene.overrideMaterial = normalMat; 133 | this.renderer.setRenderTarget(this.normalFBO); 134 | this.renderer.render(scene, camera); 135 | this.renderer.setRenderTarget(null); 136 | scene.overrideMaterial = null; 137 | this.renderPass.render(true); 138 | } 139 | 140 | generateParams(gui) { 141 | const controllers = {}; 142 | controllers["scale"] = gui 143 | .add(this.params, "scale", 0.1, 2) 144 | .onChange(async (v) => { 145 | this.renderPass.shader.uniforms.scale.value = v; 146 | }); 147 | controllers["thickness"] = gui 148 | .add(this.params, "thickness", 0.1, 10) 149 | .onChange(async (v) => { 150 | this.renderPass.shader.uniforms.thickness.value = v; 151 | }); 152 | controllers["inkColor"] = gui 153 | .addColor(this.params, "inkColor") 154 | .onChange(async (v) => { 155 | this.renderPass.shader.uniforms.inkColor.value.copy(v); 156 | }); 157 | controllers["paper"] = generatePaperParams(gui, this.renderPass.shader); 158 | return controllers; 159 | } 160 | } 161 | 162 | export { Post }; 163 | -------------------------------------------------------------------------------- /post-cross-hatch-ii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cross-hatch-ii/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-cross-hatch-iii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-cross-hatch-iii/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-hope/Material.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | const Material = MeshStandardMaterial; 8 | 9 | function generateParams(gui, material) { 10 | const params = material; 11 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 12 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 13 | } 14 | 15 | export { Material, generateParams }; 16 | -------------------------------------------------------------------------------- /post-hope/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-hope/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-lines-i/Material.js: -------------------------------------------------------------------------------- 1 | import { MeshStandardMaterial } from "../third_party/three.module.js"; 2 | 3 | const Material = MeshStandardMaterial; 4 | 5 | function generateParams(gui, material) { 6 | gui 7 | .add(material, "roughness", 0, 1) 8 | .onChange((v) => (material.roughness = v)); 9 | gui 10 | .add(material, "metalness", 0, 1) 11 | .onChange((v) => (material.metalness = v)); 12 | } 13 | 14 | export { Material, generateParams }; 15 | -------------------------------------------------------------------------------- /post-lines-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-lines-i/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | post.render(scene, camera); 29 | renderer.setAnimationLoop(render); 30 | } 31 | 32 | async function init() { 33 | const controllers = await initScene(scene, material, gui); 34 | controllers.torus.setValue(true); 35 | controllers.spheres.setValue(true); 36 | resize(); 37 | render(); 38 | } 39 | 40 | onResize(() => { 41 | const width = window.innerWidth; 42 | const height = window.innerHeight; 43 | const dPR = window.devicePixelRatio; 44 | post.setSize(width * dPR, height * dPR); 45 | }); 46 | 47 | init(); 48 | -------------------------------------------------------------------------------- /post-patch-i/CoordsMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshBasicMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | class Material extends MeshBasicMaterial { 8 | constructor(options) { 9 | super(options); 10 | 11 | this.params = { 12 | roughness: 0.4, 13 | metalness: 0.1, 14 | scale: 100, 15 | inkColor: 0x1831d3, 16 | min: 0.1, 17 | max: 0.9, 18 | e: 0.1, 19 | }; 20 | 21 | this.uniforms = { 22 | resolution: { value: new Vector2(1, 1) }, 23 | paperTexture: { value: null }, 24 | scale: { value: this.params.scale }, 25 | inkColor: { value: new Color(this.params.inkColor) }, 26 | range: { value: new Vector2(this.params.min, this.params.max) }, 27 | e: { value: this.params.e }, 28 | }; 29 | 30 | this.onBeforeCompile = (shader, renderer) => { 31 | for (const uniformName of Object.keys(this.uniforms)) { 32 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 33 | } 34 | 35 | shader.vertexShader = shader.vertexShader.replace( 36 | `#include `, 37 | `#include 38 | out vec2 vCoords; 39 | out vec3 vPosition; 40 | out vec4 vWorldPosition;` 41 | ); 42 | shader.vertexShader = shader.vertexShader.replace( 43 | `#include `, 44 | `#include 45 | vCoords = uv; 46 | vPosition = position; 47 | vWorldPosition = modelViewMatrix * vec4(position, 1.);// + vec4(normal.xyz, 0.);` 48 | ); 49 | 50 | shader.fragmentShader = shader.fragmentShader.replace( 51 | `#include `, 52 | `#include 53 | uniform vec2 resolution; 54 | uniform sampler2D paperTexture; 55 | uniform vec3 inkColor; 56 | uniform vec2 range; 57 | uniform float scale; 58 | uniform float e; 59 | in vec2 vCoords; 60 | in vec3 vPosition; 61 | in vec4 vWorldPosition; 62 | #define TAU 6.28318530718 63 | ` 64 | ); 65 | shader.fragmentShader = shader.fragmentShader.replace( 66 | "#include ", 67 | `#include 68 | gl_FragColor.rgb = vWorldPosition.xyz; 69 | ` 70 | ); 71 | }; 72 | } 73 | } 74 | 75 | function generateParams(gui, material) { 76 | const params = material.params; 77 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 78 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 79 | } 80 | 81 | export { Material, generateParams }; 82 | -------------------------------------------------------------------------------- /post-patch-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-patch-i/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-scribble-i/Material.js: -------------------------------------------------------------------------------- 1 | import { MeshStandardMaterial } from "../third_party/three.module.js"; 2 | 3 | const Material = MeshStandardMaterial; 4 | 5 | function generateParams(gui, material) { 6 | gui 7 | .add(material, "roughness", 0, 1) 8 | .onChange((v) => (material.roughness = v)); 9 | gui 10 | .add(material, "metalness", 0, 1) 11 | .onChange((v) => (material.metalness = v)); 12 | } 13 | 14 | export { Material, generateParams }; 15 | -------------------------------------------------------------------------------- /post-scribble-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-scribble-i/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.torus.setValue(true); 36 | controllers.spheres.setValue(true); 37 | resize(); 38 | render(); 39 | } 40 | 41 | onResize(() => { 42 | const width = window.innerWidth; 43 | const height = window.innerHeight; 44 | const dPR = window.devicePixelRatio; 45 | post.setSize(width * dPR, height * dPR); 46 | }); 47 | 48 | init(); 49 | -------------------------------------------------------------------------------- /post-technical/CoordsMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshBasicMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | class Material extends MeshBasicMaterial { 8 | constructor(options) { 9 | super(options); 10 | 11 | this.params = { 12 | angleGrid: 0, 13 | scale: 40, 14 | }; 15 | 16 | this.uniforms = { 17 | angleGrid: { value: this.params.angleGrid }, 18 | scale: { value: this.params.scale }, 19 | }; 20 | 21 | this.onBeforeCompile = (shader, renderer) => { 22 | for (const uniformName of Object.keys(this.uniforms)) { 23 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 24 | } 25 | 26 | shader.vertexShader = shader.vertexShader.replace( 27 | `#include `, 28 | `#include 29 | out vec2 vCoords; 30 | out vec3 vPosition; 31 | out vec4 vWorldPosition;` 32 | ); 33 | shader.vertexShader = shader.vertexShader.replace( 34 | `#include `, 35 | `#include 36 | vCoords = uv; 37 | vPosition = position; 38 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 39 | ); 40 | 41 | shader.fragmentShader = shader.fragmentShader.replace( 42 | `#include `, 43 | `#include 44 | uniform float angleGrid; 45 | uniform float scale; 46 | 47 | in vec2 vCoords; 48 | in vec3 vPosition; 49 | in vec4 vWorldPosition; 50 | #define TAU 6.28318530718 51 | 52 | mat2 rot(in float a) { 53 | float s = sin(a); 54 | float c = cos(a); 55 | mat2 rot = mat2(c, -s, s, c); 56 | return rot; 57 | } 58 | ` 59 | ); 60 | shader.fragmentShader = shader.fragmentShader.replace( 61 | "#include ", 62 | `#include 63 | 64 | vec2 uv = rot(angleGrid) * vWorldPosition.xy; 65 | float stripeX = .5 + .5 * sin(uv.x * scale); 66 | float stripeY = .5 + .5 * sin(uv.y * scale); 67 | float stripe = max(stripeX, stripeY); 68 | float e = 2. * length(vec2(dFdx(uv.x), dFdy(uv.y))) * 0.70710678118654757; 69 | gl_FragColor.a = .25; 70 | if(stripe < 1.-e) { 71 | gl_FragColor.a = 1.; 72 | } 73 | gl_FragColor.rgb = vWorldPosition.xyz / vWorldPosition.w; 74 | ` 75 | ); 76 | }; 77 | } 78 | } 79 | 80 | function generateParams(gui, material) { 81 | const params = material.params; 82 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 83 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 84 | } 85 | 86 | export { Material, generateParams }; 87 | -------------------------------------------------------------------------------- /post-technical/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /post-technical/main.js: -------------------------------------------------------------------------------- 1 | import { DoubleSide } from "../third_party/three.module.js"; 2 | import { Material, generateParams } from "./Material.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize, onResize } from "../js/renderer.js"; 6 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 7 | import { Post } from "./post.js"; 8 | 9 | const post = new Post(renderer); 10 | 11 | const gui = new dat.GUI(); 12 | const materialFolder = gui.addFolder("Material"); 13 | materialFolder.open(); 14 | 15 | const material = new Material({ 16 | color: 0x808080, 17 | roughness: 0.2, 18 | metalness: 0.1, 19 | side: DoubleSide, 20 | }); 21 | generateParams(materialFolder, material); 22 | const postController = post.generateParams(materialFolder); 23 | postController["paper"].setValue("Parchment"); 24 | const envMapController = generateEnvParams(materialFolder, material); 25 | envMapController.setValue("bridge"); 26 | 27 | function render() { 28 | update(); 29 | post.render(scene, camera); 30 | renderer.setAnimationLoop(render); 31 | } 32 | 33 | async function init() { 34 | const controllers = await initScene(scene, material, gui); 35 | controllers.backdrop.setValue(false); 36 | controllers.torus.setValue(true); 37 | controllers.spheres.setValue(true); 38 | resize(); 39 | render(); 40 | } 41 | 42 | onResize(() => { 43 | const width = window.innerWidth; 44 | const height = window.innerHeight; 45 | const dPR = window.devicePixelRatio; 46 | post.setSize(width * dPR, height * dPR); 47 | }); 48 | 49 | init(); 50 | -------------------------------------------------------------------------------- /scribble-i/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /scribble-i/lineMaterial.js: -------------------------------------------------------------------------------- 1 | import { 2 | MeshStandardMaterial, 3 | Vector2, 4 | Color, 5 | } from "../third_party/three.module.js"; 6 | 7 | class LineMaterial extends MeshStandardMaterial { 8 | constructor(options) { 9 | super(options); 10 | 11 | this.params = { 12 | roughness: 0.4, 13 | metalness: 0.1, 14 | scale: .1, 15 | inkColor: 0x14690A, 16 | factor: 1.1, 17 | e: .4 18 | }; 19 | 20 | this.uniforms = { 21 | resolution: { value: new Vector2(1, 1) }, 22 | paperTexture: { value: null }, 23 | scale: { value: this.params.scale }, 24 | inkColor: { value: new Color(this.params.inkColor) }, 25 | factor: { value: this.params.factor}, 26 | e: { value: this.params.e} 27 | }; 28 | 29 | this.onBeforeCompile = (shader, renderer) => { 30 | for (const uniformName of Object.keys(this.uniforms)) { 31 | shader.uniforms[uniformName] = this.uniforms[uniformName]; 32 | } 33 | 34 | shader.vertexShader = shader.vertexShader.replace( 35 | `#include `, 36 | `#include 37 | out vec2 vCoords; 38 | out vec3 vPosition; 39 | out vec4 vWorldPosition;` 40 | ); 41 | shader.vertexShader = shader.vertexShader.replace( 42 | `#include `, 43 | `#include 44 | vCoords = uv; 45 | vPosition = position; 46 | vWorldPosition = modelViewMatrix * vec4(position, 1.);` 47 | ); 48 | 49 | shader.fragmentShader = shader.fragmentShader.replace( 50 | `#include `, 51 | `#include 52 | uniform vec2 resolution; 53 | uniform sampler2D paperTexture; 54 | uniform vec3 inkColor; 55 | uniform float factor; 56 | uniform float scale; 57 | uniform float e; 58 | in vec2 vCoords; 59 | in vec3 vPosition; 60 | in vec4 vWorldPosition; 61 | #define TAU 6.28318530718 62 | 63 | mat2 m = mat2( vec2 (2.1, 1.0), vec2(-1.0, 2.1) ); 64 | 65 | float noise_f(vec2 p) { 66 | return sin(1.66*p.x)*sin(1.66*p.y); 67 | } 68 | 69 | float warp(in vec2 p) { 70 | float f = 0.0; 71 | float amp = 1.0; 72 | float kAmp = 0.5; 73 | float freq = 0.63; 74 | float kFreq = 1.53; 75 | 76 | f += amp*noise_f(p); p = m*p*freq; amp *=kAmp; freq *=kFreq; 77 | f += amp*noise_f(p); p = m*p*freq; amp *=kAmp; freq *=kFreq; 78 | f += amp*noise_f(p); p = m*p*freq; amp *=kAmp; freq *=kFreq; 79 | f += amp*noise_f(p); 80 | 81 | f = fract(f); 82 | 83 | return f; 84 | } 85 | 86 | float aastep(float threshold, float value) { 87 | #ifdef GL_OES_standard_derivatives 88 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 89 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 90 | #else 91 | return step(threshold, value); 92 | #endif 93 | } 94 | 95 | #define tileSize 0.05 96 | #define tileCenter vec2(tileSize / 2.0, tileSize / 2.0) 97 | 98 | float scribble(in vec2 uv, in float q) { 99 | 100 | vec2 tile = vec2(floor(uv.x / tileSize), floor(uv.y / tileSize)); 101 | uv -= tile * tileSize; 102 | float dist = length(tileCenter - uv) + warp(uv); 103 | float circle = .5 + .5 * cos(dist * 1000. + 100. * warp(uv.yx)); 104 | return aastep(e, circle+q); 105 | } 106 | 107 | ///////// 108 | 109 | float texcube(in vec3 p, in vec3 n, in float q) { 110 | //p = .05 * vec3(warp(p.xy), warp(p.xy), warp(p.xy)); 111 | vec3 v = vec3(scribble(p.yz,q), scribble(p.zx,q), scribble(p.xy,q)); 112 | //p = 1. * vec3(warp(p.xy), warp(p.xy), warp(p.xy)); 113 | p *= factor; 114 | vec3 v2 = vec3(scribble(p.yz,q), scribble(p.zx,q), scribble(p.xy,q)); 115 | v *= v2; 116 | return dot(v, n*n); 117 | } 118 | 119 | float luma(vec3 color) { 120 | return dot(color, vec3(0.299, 0.587, 0.114)); 121 | } 122 | float luma(vec4 color) { 123 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 124 | } 125 | float blendColorBurn(float base, float blend) { 126 | return (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0); 127 | } 128 | 129 | vec3 blendColorBurn(vec3 base, vec3 blend) { 130 | return vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b)); 131 | } 132 | 133 | vec3 blendColorBurn(vec3 base, vec3 blend, float opacity) { 134 | return (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity)); 135 | } 136 | ` 137 | ); 138 | shader.fragmentShader = shader.fragmentShader.replace( 139 | "#include ", 140 | `#include 141 | float l = luma(gl_FragColor.rgb); 142 | ivec2 size = textureSize(paperTexture, 0); 143 | vec4 paper = texture(paperTexture, gl_FragCoord.xy / vec2(float(size.x), float(size.y))); 144 | 145 | vec3 coords = scale * vWorldPosition.xyz / vWorldPosition.w; 146 | float line = texcube(coords, vNormal, l); 147 | 148 | float r = aastep(1.-l, line); 149 | 150 | gl_FragColor.rgb = blendColorBurn(paper.rgb, inkColor, 1.-r); 151 | //gl_FragColor.rgb = vec3(r); 152 | ` 153 | ); 154 | }; 155 | } 156 | } 157 | 158 | function generateParams(gui, material) { 159 | const params = material.params; 160 | gui.add(params, "roughness", 0, 1).onChange((v) => (material.roughness = v)); 161 | gui.add(params, "metalness", 0, 1).onChange((v) => (material.metalness = v)); 162 | gui.addColor(params, "inkColor").onChange((v) => (material.uniforms.inkColor.value.set(v))); 163 | gui.add(params, "scale", .05, .2 ,.001).onChange((v) => (material.uniforms.scale.value = v)); 164 | gui.add(params, "factor", .5, 1.5,.001).onChange((v) => (material.uniforms.factor.value = v)); 165 | gui.add(params, "e", 0, 1,.001).onChange((v) => (material.uniforms.e.value = v)); 166 | } 167 | 168 | export { LineMaterial, generateParams }; 169 | -------------------------------------------------------------------------------- /scribble-i/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /scribble-ii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /scribble-ii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Craft light"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /scribble-iii/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sketch 6 | 10 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /scribble-iii/main.js: -------------------------------------------------------------------------------- 1 | import { Vector2, DoubleSide } from "../third_party/three.module.js"; 2 | import { LineMaterial, generateParams } from "./lineMaterial.js"; 3 | import * as dat from "../third_party/dat.gui.module.js"; 4 | import { initScene, update } from "../js/scene.js"; 5 | import { renderer, scene, camera, resize } from "../js/renderer.js"; 6 | import { generateParams as generatePaperParams } from "../js/paper.js"; 7 | import { generateParams as generateEnvParams } from "../js/envMap.js"; 8 | 9 | const gui = new dat.GUI(); 10 | const materialFolder = gui.addFolder("Material"); 11 | materialFolder.open(); 12 | 13 | const material = new LineMaterial({ 14 | color: 0x808080, 15 | roughness: 0.2, 16 | metalness: 0.1, 17 | side: DoubleSide, 18 | }); 19 | generateParams(materialFolder, material); 20 | const paperController = generatePaperParams(materialFolder, material); 21 | paperController.setValue("Parchment"); 22 | const envMapController = generateEnvParams(materialFolder, material); 23 | envMapController.setValue("bridge"); 24 | 25 | const tmp = new Vector2(); 26 | function render() { 27 | update(); 28 | renderer.getSize(tmp); 29 | tmp.multiplyScalar(window.devicePixelRatio); 30 | material.uniforms.resolution.value.copy(tmp); 31 | renderer.render(scene, camera); 32 | renderer.setAnimationLoop(render); 33 | } 34 | 35 | async function init() { 36 | const controllers = await initScene(scene, material, gui); 37 | controllers.torus.setValue(true); 38 | controllers.spheres.setValue(true); 39 | resize(); 40 | render(); 41 | } 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /shaders/aastep.js: -------------------------------------------------------------------------------- 1 | const shader = ` 2 | float aastep(float threshold, float value) { 3 | #ifdef GL_OES_standard_derivatives 4 | float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; 5 | return smoothstep(threshold-afwidth, threshold+afwidth, value); 6 | #else 7 | return step(threshold, value); 8 | #endif 9 | }`; 10 | 11 | export { shader }; 12 | -------------------------------------------------------------------------------- /shaders/blend-darken.js: -------------------------------------------------------------------------------- 1 | const shader = ` 2 | float blendDarken(float base, float blend) { 3 | return min(blend,base); 4 | } 5 | 6 | vec3 blendDarken(vec3 base, vec3 blend) { 7 | return vec3(blendDarken(base.r,blend.r),blendDarken(base.g,blend.g),blendDarken(base.b,blend.b)); 8 | } 9 | 10 | vec3 blendDarken(vec3 base, vec3 blend, float opacity) { 11 | return (blendDarken(base, blend) * opacity + base * (1.0 - opacity)); 12 | }`; 13 | 14 | export { shader }; 15 | -------------------------------------------------------------------------------- /shaders/blend-screen.js: -------------------------------------------------------------------------------- 1 | const shader = ` 2 | float blendScreen(float base, float blend) { 3 | return 1.0-((1.0-base)*(1.0-blend)); 4 | } 5 | 6 | vec3 blendScreen(vec3 base, vec3 blend) { 7 | return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); 8 | } 9 | 10 | vec3 blendScreen(vec3 base, vec3 blend, float opacity) { 11 | return (blendScreen(base, blend) * opacity + base * (1.0 - opacity)); 12 | }`; 13 | 14 | export { shader }; 15 | -------------------------------------------------------------------------------- /shaders/fast-separable-gaussian-blur.js: -------------------------------------------------------------------------------- 1 | const blur5 = ` 2 | vec4 blur5(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 3 | vec4 color = vec4(0.0); 4 | vec2 off1 = vec2(1.3333333333333333) * direction; 5 | color += texture(image, uv) * 0.29411764705882354; 6 | color += texture(image, uv + (off1 / resolution)) * 0.35294117647058826; 7 | color += texture(image, uv - (off1 / resolution)) * 0.35294117647058826; 8 | return color; 9 | }`; 10 | 11 | const blur9 = ` 12 | vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 13 | vec4 color = vec4(0.0); 14 | vec2 off1 = vec2(1.3846153846) * direction; 15 | vec2 off2 = vec2(3.2307692308) * direction; 16 | color += texture(image, uv) * 0.2270270270; 17 | color += texture(image, uv + (off1 / resolution)) * 0.3162162162; 18 | color += texture(image, uv - (off1 / resolution)) * 0.3162162162; 19 | color += texture(image, uv + (off2 / resolution)) * 0.0702702703; 20 | color += texture(image, uv - (off2 / resolution)) * 0.0702702703; 21 | return color; 22 | }`; 23 | 24 | const blur13 = ` 25 | vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 26 | vec4 color = vec4(0.0); 27 | vec2 off1 = vec2(1.411764705882353) * direction; 28 | vec2 off2 = vec2(3.2941176470588234) * direction; 29 | vec2 off3 = vec2(5.176470588235294) * direction; 30 | color += texture(image, uv) * 0.1964825501511404; 31 | color += texture(image, uv + (off1 / resolution)) * 0.2969069646728344; 32 | color += texture(image, uv - (off1 / resolution)) * 0.2969069646728344; 33 | color += texture(image, uv + (off2 / resolution)) * 0.09447039785044732; 34 | color += texture(image, uv - (off2 / resolution)) * 0.09447039785044732; 35 | color += texture(image, uv + (off3 / resolution)) * 0.010381362401148057; 36 | color += texture(image, uv - (off3 / resolution)) * 0.010381362401148057; 37 | return color; 38 | } 39 | `; 40 | 41 | export { blur5, blur9, blur13 }; 42 | -------------------------------------------------------------------------------- /shaders/lines.js: -------------------------------------------------------------------------------- 1 | const lines = ` 2 | float luma(vec3 color) { 3 | return dot(color, vec3(0.299, 0.587, 0.114)); 4 | } 5 | 6 | float luma(vec4 color) { 7 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 8 | } 9 | 10 | float lines( in float l, in vec2 fragCoord, in vec2 resolution, in vec2 range, in vec2 range2, float scale, float radius){ 11 | vec2 center = vec2(resolution.x/2., resolution.y/2.); 12 | vec2 uv = fragCoord.xy; 13 | 14 | vec2 d = uv - center; 15 | float r = length(d)/1000.; 16 | float a = atan(d.y,d.x) + scale*(radius-r)/radius; 17 | vec2 uvt = center+r*vec2(cos(a),sin(a)); 18 | 19 | vec2 uv2 = fragCoord.xy / resolution.xy; 20 | float c = range2.x + range2.y * sin(uvt.x*1000.); 21 | float f = smoothstep(range.x*c, range.y*c, l ); 22 | f = smoothstep( 0., .5, f ); 23 | 24 | return f; 25 | }`; 26 | 27 | export { lines }; 28 | -------------------------------------------------------------------------------- /shaders/luma.js: -------------------------------------------------------------------------------- 1 | const shader = ` 2 | float luma(vec3 color) { 3 | return dot(color, vec3(0.299, 0.587, 0.114)); 4 | } 5 | float luma(vec4 color) { 6 | return dot(color.rgb, vec3(0.299, 0.587, 0.114)); 7 | }`; 8 | 9 | export { shader }; 10 | -------------------------------------------------------------------------------- /shaders/ortho-vs.js: -------------------------------------------------------------------------------- 1 | const shader = `#version 300 es 2 | precision highp float; 3 | 4 | in vec3 position; 5 | in vec2 uv; 6 | 7 | uniform vec2 resolution; 8 | uniform mat4 modelViewMatrix; 9 | uniform mat4 projectionMatrix; 10 | 11 | out vec2 vUv; 12 | 13 | void main() { 14 | vUv = uv; 15 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1. ); 16 | } 17 | `; 18 | 19 | export { shader }; 20 | -------------------------------------------------------------------------------- /shaders/sobel.js: -------------------------------------------------------------------------------- 1 | const shader = ` 2 | 3 | vec4 sobel(in sampler2D src, in vec2 uv, in vec2 resolution, in float width){ 4 | float x = width / resolution.x; 5 | float y = width / resolution.y; 6 | vec4 horizEdge = vec4( 0.0 ); 7 | horizEdge -= texture(src, vec2( uv.x - x, uv.y - y ) ) * 1.0; 8 | horizEdge -= texture(src, vec2( uv.x - x, uv.y ) ) * 2.0; 9 | horizEdge -= texture(src, vec2( uv.x - x, uv.y + y ) ) * 1.0; 10 | horizEdge += texture(src, vec2( uv.x + x, uv.y - y ) ) * 1.0; 11 | horizEdge += texture(src, vec2( uv.x + x, uv.y ) ) * 2.0; 12 | horizEdge += texture(src, vec2( uv.x + x, uv.y + y ) ) * 1.0; 13 | vec4 vertEdge = vec4( 0.0 ); 14 | vertEdge -= texture(src, vec2( uv.x - x, uv.y - y ) ) * 1.0; 15 | vertEdge -= texture(src, vec2( uv.x , uv.y - y ) ) * 2.0; 16 | vertEdge -= texture(src, vec2( uv.x + x, uv.y - y ) ) * 1.0; 17 | vertEdge += texture(src, vec2( uv.x - x, uv.y + y ) ) * 1.0; 18 | vertEdge += texture(src, vec2( uv.x , uv.y + y ) ) * 2.0; 19 | vertEdge += texture(src, vec2( uv.x + x, uv.y + y ) ) * 1.0; 20 | vec4 edge = sqrt((horizEdge * horizEdge) + (vertEdge * vertEdge)); 21 | return edge; 22 | }`; 23 | 24 | export { shader }; 25 | -------------------------------------------------------------------------------- /shaders/worley-noise.js: -------------------------------------------------------------------------------- 1 | const worleyNoise = ` 2 | #define UI0 1597334673U 3 | #define UI1 3812015801U 4 | #define UI2 uvec2(UI0, UI1) 5 | #define UI3 uvec3(UI0, UI1, 2798796415U) 6 | #define UIF (1.0 / float(0xffffffffU)) 7 | 8 | vec3 hash33(vec3 p) 9 | { 10 | uvec3 q = uvec3(ivec3(p)) * UI3; 11 | q = (q.x ^ q.y ^ q.z)*UI3; 12 | return -1. + 2. * vec3(q) * UIF; 13 | } 14 | 15 | float remap(float x, float a, float b, float c, float d) 16 | { 17 | return (((x - a) / (b - a)) * (d - c)) + c; 18 | } 19 | 20 | // Gradient noise by iq (modified to be tileable) 21 | float gradientNoise(vec3 x, float freq) 22 | { 23 | // grid 24 | vec3 p = floor(x); 25 | vec3 w = fract(x); 26 | 27 | // quintic interpolant 28 | vec3 u = w * w * w * (w * (w * 6. - 15.) + 10.); 29 | 30 | 31 | // gradients 32 | vec3 ga = hash33(mod(p + vec3(0., 0., 0.), freq)); 33 | vec3 gb = hash33(mod(p + vec3(1., 0., 0.), freq)); 34 | vec3 gc = hash33(mod(p + vec3(0., 1., 0.), freq)); 35 | vec3 gd = hash33(mod(p + vec3(1., 1., 0.), freq)); 36 | vec3 ge = hash33(mod(p + vec3(0., 0., 1.), freq)); 37 | vec3 gf = hash33(mod(p + vec3(1., 0., 1.), freq)); 38 | vec3 gg = hash33(mod(p + vec3(0., 1., 1.), freq)); 39 | vec3 gh = hash33(mod(p + vec3(1., 1., 1.), freq)); 40 | 41 | // projections 42 | float va = dot(ga, w - vec3(0., 0., 0.)); 43 | float vb = dot(gb, w - vec3(1., 0., 0.)); 44 | float vc = dot(gc, w - vec3(0., 1., 0.)); 45 | float vd = dot(gd, w - vec3(1., 1., 0.)); 46 | float ve = dot(ge, w - vec3(0., 0., 1.)); 47 | float vf = dot(gf, w - vec3(1., 0., 1.)); 48 | float vg = dot(gg, w - vec3(0., 1., 1.)); 49 | float vh = dot(gh, w - vec3(1., 1., 1.)); 50 | 51 | // interpolation 52 | return va + 53 | u.x * (vb - va) + 54 | u.y * (vc - va) + 55 | u.z * (ve - va) + 56 | u.x * u.y * (va - vb - vc + vd) + 57 | u.y * u.z * (va - vc - ve + vg) + 58 | u.z * u.x * (va - vb - ve + vf) + 59 | u.x * u.y * u.z * (-va + vb + vc - vd + ve - vf - vg + vh); 60 | } 61 | 62 | // Tileable 3D worley noise 63 | float worleyNoise(vec3 uv, float freq) 64 | { 65 | vec3 id = floor(uv); 66 | vec3 p = fract(uv); 67 | 68 | float minDist = 10000.; 69 | for (float x = -1.; x <= 1.; ++x) 70 | { 71 | for(float y = -1.; y <= 1.; ++y) 72 | { 73 | for(float z = -1.; z <= 1.; ++z) 74 | { 75 | vec3 offset = vec3(x, y, z); 76 | vec3 h = hash33(mod(id + offset, vec3(freq))) * .5 + .5; 77 | h += offset; 78 | vec3 d = p - h; 79 | minDist = min(minDist, dot(d, d)); 80 | } 81 | } 82 | } 83 | 84 | // inverted worley noise 85 | return 1. - minDist; 86 | } 87 | 88 | // Fbm for Perlin noise based on iq's blog 89 | float perlinfbm(vec3 p, float freq, int octaves) 90 | { 91 | float G = exp2(-.85); 92 | float amp = 1.; 93 | float noise = 0.; 94 | for (int i = 0; i < octaves; ++i) 95 | { 96 | noise += amp * gradientNoise(p * freq, freq); 97 | freq *= 2.; 98 | amp *= G; 99 | } 100 | 101 | return noise; 102 | } 103 | 104 | // Tileable Worley fbm inspired by Andrew Schneider's Real-Time Volumetric Cloudscapes 105 | // chapter in GPU Pro 7. 106 | float worleyFbm(vec3 p, float freq) 107 | { 108 | return worleyNoise(p*freq, freq) * .625 + 109 | worleyNoise(p*freq*2., freq*2.) * .25 + 110 | worleyNoise(p*freq*4., freq*4.) * .125; 111 | } 112 | 113 | float edge(vec3 p, float freq) { 114 | float d= 0.; 115 | float up = worleyFbm(p - vec3(0.,d,0.), freq); 116 | float down = worleyFbm(p- vec3(0.,d,0.), freq); 117 | float right = worleyFbm(p- vec3(d,0.,0.), freq); 118 | float left = worleyFbm(p+ vec3(d,0.,0.), freq); 119 | return (up+down+right+left)/4.; 120 | } 121 | `; 122 | 123 | export { worleyNoise }; 124 | -------------------------------------------------------------------------------- /snapshots/cross-hatch-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-i.jpg -------------------------------------------------------------------------------- /snapshots/cross-hatch-ii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-ii.jpg -------------------------------------------------------------------------------- /snapshots/cross-hatch-iii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-iii.jpg -------------------------------------------------------------------------------- /snapshots/cross-hatch-iv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-iv.jpg -------------------------------------------------------------------------------- /snapshots/cross-hatch-v.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-v.jpg -------------------------------------------------------------------------------- /snapshots/cross-hatch-vi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-vi.jpg -------------------------------------------------------------------------------- /snapshots/cross-hatch-vii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-vii.jpg -------------------------------------------------------------------------------- /snapshots/cross-hatch-viii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/cross-hatch-viii.jpg -------------------------------------------------------------------------------- /snapshots/gallery.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/gallery.jpg -------------------------------------------------------------------------------- /snapshots/lines-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/lines-i.jpg -------------------------------------------------------------------------------- /snapshots/lines-ii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/lines-ii.jpg -------------------------------------------------------------------------------- /snapshots/lines-iii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/lines-iii.jpg -------------------------------------------------------------------------------- /snapshots/lines-iv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/lines-iv.jpg -------------------------------------------------------------------------------- /snapshots/lines-v.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/lines-v.jpg -------------------------------------------------------------------------------- /snapshots/lines-vi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/lines-vi.jpg -------------------------------------------------------------------------------- /snapshots/patch-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/patch-i.jpg -------------------------------------------------------------------------------- /snapshots/patch-ii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/patch-ii.jpg -------------------------------------------------------------------------------- /snapshots/post-blueprint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-blueprint.jpg -------------------------------------------------------------------------------- /snapshots/post-cartoon-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cartoon-i.jpg -------------------------------------------------------------------------------- /snapshots/post-cartoon-ii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cartoon-ii.jpg -------------------------------------------------------------------------------- /snapshots/post-cartoon-iii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cartoon-iii.jpg -------------------------------------------------------------------------------- /snapshots/post-cartoon-iv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cartoon-iv.jpg -------------------------------------------------------------------------------- /snapshots/post-cartoon-v.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cartoon-v.jpg -------------------------------------------------------------------------------- /snapshots/post-cartoon-vi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cartoon-vi.jpg -------------------------------------------------------------------------------- /snapshots/post-cross-hatch-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cross-hatch-i.jpg -------------------------------------------------------------------------------- /snapshots/post-cross-hatch-ii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cross-hatch-ii.jpg -------------------------------------------------------------------------------- /snapshots/post-cross-hatch-iii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-cross-hatch-iii.jpg -------------------------------------------------------------------------------- /snapshots/post-halftone-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-halftone-i.jpg -------------------------------------------------------------------------------- /snapshots/post-hope.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-hope.jpg -------------------------------------------------------------------------------- /snapshots/post-lines-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-lines-i.jpg -------------------------------------------------------------------------------- /snapshots/post-patch-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-patch-i.jpg -------------------------------------------------------------------------------- /snapshots/post-scribble-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-scribble-i.jpg -------------------------------------------------------------------------------- /snapshots/post-technical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/post-technical.jpg -------------------------------------------------------------------------------- /snapshots/scribble-i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/scribble-i.jpg -------------------------------------------------------------------------------- /snapshots/scribble-ii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/scribble-ii.jpg -------------------------------------------------------------------------------- /snapshots/scribble-iii.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/sketch/0b654afc9d02786c51d547858077c86db91656bd/snapshots/scribble-iii.jpg --------------------------------------------------------------------------------