├── .eslintignore ├── .gitignore ├── .gitmodules ├── .nvmrc ├── 7.svg ├── 7.svg.js ├── README.md ├── aexion.glsl ├── ao.glsl ├── app.js ├── audio └── swell.js ├── background.glsl ├── band-filter.glsl ├── bin └── gallery.js ├── bloom.glsl ├── bright.glsl ├── camera-tweens.js ├── capturer ├── capturer-interface.js └── ccapture.js ├── ccapture.all.min.js ├── cellular-tile.glsl ├── color-map ├── hsb2rgb.glsl └── rainbow.glsl ├── cross-hatching.glsl ├── dampening.js ├── dat.gui.min.js ├── de-map.glsl ├── de ├── chen.glsl ├── lorenz.glsl └── lui-chen.glsl ├── debug-color-clip.glsl ├── default-scene-renderer.js ├── dispersion-ray-direction.glsl ├── dispersion ├── hue-to-ior-exponential.glsl ├── hue-to-ior-polynomial.glsl └── hue-to-ior-sigmoid.glsl ├── dodec.glsl ├── dodecahedron.glsl ├── effects ├── god-rays-poisson.glsl └── god-rays.glsl ├── env.jpg ├── final-pass.glsl ├── foldInv.glsl ├── foldNd.glsl ├── folds.glsl ├── folds ├── dodecahedron-fold.glsl ├── half-tetrahedral.glsl ├── mandelbox-fold.glsl ├── octahedron-fold.glsl └── tetrahedral.glsl ├── frag.glsl ├── gallery ├── index.html └── introspective-reflective │ ├── bundle.js │ ├── dat.gui.min.js │ ├── index.html │ └── node_modules │ └── webvr-polyfill │ └── build │ └── webvr-polyfill.js ├── get-normal.glsl ├── glsl-dispersion.glsl ├── gradient.glsl ├── hg_sdf ├── p-mod-interval1.glsl ├── p-mod-polar-c.glsl ├── p-mod1.glsl ├── p-mod2.glsl ├── p-mod3.glsl └── vmax.glsl ├── hidden.svg.js ├── index.html ├── index.js ├── info.json ├── inner-glow.glsl ├── iridescent.glsl ├── lighting └── simple-shadow.glsl ├── loop-noise.glsl ├── lorem.svg.js ├── luminance.js ├── mandelbox.glsl ├── matCap.glsl ├── melt.svg.js ├── menger-sphere.glsl ├── mobius.svg.js ├── model ├── cluster.glsl ├── dodecahedral.glsl ├── generalized-polyhedra-include.glsl ├── gyroid-trianglewave.glsl ├── gyroid.glsl ├── icosahedral.glsl ├── octahedral.glsl ├── quartz.glsl ├── sdf-fbm.glsl ├── tetrahedron.glsl └── tri-prism.glsl ├── modulo ├── neighbor-grid.glsl ├── p-mod4.glsl └── subdivide.glsl ├── octahedron.glsl ├── outline3.svg.js ├── package-lock.json ├── package.json ├── patterns └── herring-bone.glsl ├── pie-slice.glsl ├── presets.json ├── ray-apply-proj-matrix.glsl ├── reflection.glsl ├── relink-pkgs.sh ├── repeat.svg.js ├── rgb2hsv.glsl ├── rotated-map.glsl ├── rotation-matrix2.glsl ├── rotation-matrix3.glsl ├── rotation-matrix4.glsl ├── screenshot.jpg ├── shaders ├── base-svg-sdf.glsl ├── bloom.shader.js ├── bright.shader.js ├── final-pass.shader.js ├── frag.shader.js ├── svg.glsl └── svgSDF.js ├── simple-index.js ├── soft-shadows.glsl ├── sphere-fold.glsl ├── split.glsl ├── stone.jpg ├── svg-to-glsl.js ├── time.glsl ├── twist.glsl ├── utils.js ├── vert.glsl ├── voronoi.glsl ├── word.svg.js ├── year-6.svg └── year-6.svg.js /.eslintignore: -------------------------------------------------------------------------------- 1 | gallery/**/*.js 2 | *.min.js 3 | ccapture.js/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional eslint cache 42 | .eslintcache 43 | 44 | # Optional REPL history 45 | .node_repl_history 46 | 47 | # Output of 'npm pack' 48 | *.tgz 49 | 50 | # Created by https://www.gitignore.io/api/vim 51 | 52 | ### Vim ### 53 | # swap 54 | [._]*.s[a-w][a-z] 55 | [._]s[a-w][a-z] 56 | # session 57 | Session.vim 58 | # temporary 59 | .netrwhist 60 | *~ 61 | # auto-generated tag files 62 | tags 63 | setup-environment.sh 64 | presets/ 65 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ccapture.js"] 2 | path = ccapture.js 3 | url = git@github.com:spite/ccapture.js.git 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.12.2 2 | -------------------------------------------------------------------------------- /7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /7.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 9 | 10 | ` 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Screenshot](./screenshot.jpg) 2 | 3 | # Ray Marching Experiments 4 | 5 | Here lies my experiments in ray marching. Be warned. Their beauty is only skin 6 | deep. While fair on the exterior, the code that lies beneath is full of maggots. 7 | I intentionally leave it this way to focus on experimental aspect while coding. 8 | 9 | Ray marching is the process of moving along a ray coming out of the camera 10 | towards a scene for every pixel on the screen. You moving along this ray based 11 | on the minimum distance to any surface in the scene as described by a distance 12 | field. Since the distance from the distance field is the minimum distance to any 13 | object, you know you can move along your ray that distance without hitting an 14 | object. So you proceed to do this with each step until you reach a point where 15 | you are within a small enough distance from an object that you have effectively 16 | hit the surface. 17 | 18 | ## Install 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | ## Run 25 | 26 | **Note:** Currently I am using unreleased modules of my own creation 27 | for doing shader work with [WebVR](https://webvr.info/) & [Google 28 | Cardboard](https://vr.google.com/cardboard/). Examples should 29 | work but you will not be able to run the code yourself. [Tweet me 30 | @lejeunerenard](https://twitter.com/lejeunerenard) if you want these modules 31 | completed. 32 | 33 | ```bash 34 | npm start 35 | ``` 36 | 37 | ## Resources: 38 | 39 | - [Raymarching Distance Fields by Simon 40 | Hugo](http://9bitscience.blogspot.com/2013/07/raymarching-distance-fields_14.html) 41 | 42 | ## Technologies Used 43 | 44 | - [stack.gl](http://stack.gl) 45 | - [glslify](https://github.com/stackgl/glslify) 46 | -------------------------------------------------------------------------------- /aexion.glsl: -------------------------------------------------------------------------------- 1 | #ifndef Iterations 2 | #define Iterations 15 3 | #endif 4 | 5 | const vec3 un = vec3(1., -1., 0.); 6 | 7 | vec2 aexion( inout vec3 p ) { 8 | float trap = maxDistance; 9 | 10 | const float delta = 4.; 11 | vec4 CT = vec4( 12 | abs(dot(p, un.xxx) - delta), 13 | abs(dot(p, un.yyx) - delta), 14 | abs(dot(p, un.yxy) - delta), 15 | abs(dot(p, un.xyy) - delta) 16 | ); 17 | vec4 V = vec4(0.); 18 | float V2 = 0.; 19 | float dr = 2.; 20 | 21 | for (int i = 0; i < Iterations; i++) { 22 | V = clamp(V, -1., 1.) * 2. - V; 23 | V2 = dot(V,V); 24 | 25 | float c = clamp(max(.25 / V2, .25), 0., 1.) * 4.; 26 | V *= c; 27 | dr /= c; 28 | 29 | V = V * 2. + CT; 30 | dr *= .5; 31 | 32 | trap = min(trap, length(p)); 33 | 34 | if (V2 > 3600.) break; 35 | } 36 | 37 | return vec2(sqrt(V2) * dr, trap); 38 | } 39 | 40 | #pragma glslify: export(aexion) 41 | -------------------------------------------------------------------------------- /ao.glsl: -------------------------------------------------------------------------------- 1 | float calcAO( in vec3 pos, in vec3 nor, in float t ) { 2 | float occ = 0.0; 3 | float sca = 1.0; 4 | for( int i=0; i<4; i++ ) { 5 | float hr = 0.01 + 0.12*float(i)/4.0; 6 | vec3 aopos = nor * hr + pos; 7 | vec3 dd = map(aopos, t); 8 | occ += -(dd.x-hr)*sca; 9 | sca *= 0.95; 10 | } 11 | return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ); 12 | } 13 | 14 | float calcAO( in vec3 pos, in vec3 nor ) { 15 | return calcAO(pos, nor, 0.); 16 | } 17 | 18 | #pragma glslify: export(calcAO) 19 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import createShader from 'gl-shader' 2 | import createTexture from 'gl-texture2d' 3 | import createFBO from 'gl-fbo' 4 | 5 | import ndarray from 'ndarray' 6 | import TWEEN from 'tween.js' 7 | import fill from 'ndarray-fill' 8 | import makeContext from 'gl-context' 9 | import { rot4 } from './utils' 10 | import drawTriangle from 'a-big-triangle' 11 | 12 | import defined from 'defined' 13 | import assert from 'assert' 14 | import { vec3, mat4 } from 'gl-matrix' 15 | import { getLuminance, getColorWFixedLuminance } from './luminance' 16 | 17 | // import convert from './svg-to-glsl.js' 18 | 19 | const dpr = 1.0 / window.devicePixelRatio 20 | 21 | const TWO_PI = 2 * Math.PI 22 | // const PHI = (1 + Math.sqrt(5)) / 2 23 | 24 | const MANDELBOX = false 25 | const BLOOM = false 26 | const BLOOM_PASSES = 4 27 | const BLOOM_RADIUS = BLOOM_PASSES - 1 28 | const BLOOM_WET = 2.0 29 | const BLOOM_MIN_BRIGHTNESS = 0.85 30 | 31 | // Initialize shell 32 | export default class App { 33 | constructor (options = {}) { 34 | let canvas = document.createElement('canvas') 35 | document.body.appendChild(canvas) 36 | canvas.style.display = 'none' 37 | 38 | let gl = makeContext(canvas, { preserveDrawingBuffer: true }) 39 | 40 | // enable extensions 41 | var ext = gl.getExtension('OES_standard_derivatives') 42 | if (!ext) { 43 | throw new Error('derivatives not supported') 44 | } 45 | 46 | this.LOOKAT = true 47 | this.SHOW_SVG_SDF = false 48 | 49 | this.presets = {} 50 | const preset = { 51 | offset: { 52 | x: 0.492, 53 | y: 0.218, 54 | z: 0.197 55 | }, 56 | d: 0.52, 57 | scale: 1.3325, 58 | rot2angle: [0.076, 0.716, 0.451], 59 | cameraAngles: [0.05, 0.085, 0.289] 60 | } 61 | 62 | this.d = preset.d 63 | this.cameraRo = vec3.fromValues(-1.65, 1.65, 1.65) 64 | this.offsetC = [0.339, -0.592, 0.228, 0.008] 65 | 66 | this.colors1 = [168, 141, 198] 67 | this.colors2 = [75, 24, 17] 68 | // this.getEqualLuminance(this.colors1, this.colors2, 0) 69 | 70 | // Ray Marching Parameters 71 | this.epsilon = preset.epsilon || 0.000001 72 | 73 | // Fractal parameters 74 | this.offset = (preset.offset) 75 | ? vec3.fromValues(preset.offset.x, preset.offset.y, preset.offset.z) 76 | : vec3.fromValues(0, 0, 0) 77 | this.scale = preset.scale 78 | this.rot2angle = preset.rot2angle || [0, 0, 0] 79 | this.cameraAngles = preset.cameraAngles || [0, 0, 0] 80 | 81 | this.angle1C = -0.2654 82 | this.angle2C = 0.449 83 | this.angle3C = 0.89 84 | 85 | // this.setupAnimation(preset) 86 | 87 | this.glInit(gl) 88 | 89 | // Audio 90 | this.audioFFT = 32 91 | this.audioTexArray = new Uint8Array(1 * this.audioFFT) 92 | this.audioNday = ndarray(this.audioTexArray, [this.audioFFT, 1]) 93 | this.audioTex = createTexture(gl, this.audioNday) 94 | this.pulseGoal = 0 95 | 96 | // -- Shapes -- 97 | this.shapeOptions = { 98 | cube: 0, 99 | sphere: 1, 100 | cigar: 2, 101 | pyramid: 3, 102 | torus: 4, 103 | octahedron: 5, 104 | none: -1 105 | } 106 | this.shapeMode = 'cube' 107 | this.shapeScale = [1, 1, 1] 108 | this._shape2DSDFTextures = {} 109 | 110 | // Capturing state 111 | this.capturing = defined(options.capturing, false) 112 | 113 | this.loaded = Promise.resolve() 114 | // .then(() => { 115 | // const { glsl, metadata } = convert(require('./7.svg.js')) 116 | // const fbo = this.generateSVGTexture(glsl, metadata.viewBox) 117 | // this.add2DSDFTexture('year-7', fbo.color[0]) 118 | // }) 119 | .then(() => { 120 | this.setupAudio() 121 | }) 122 | 123 | // Scene Rendering 124 | this.sceneRenderer = options.sceneRenderer 125 | 126 | this.totalTime = 0 127 | 128 | Object.assign(this, { 129 | canvas, 130 | gl 131 | }) 132 | } 133 | 134 | getEqualLuminance (reference, second, position) { 135 | second[position] = null 136 | second[position] = getColorWFixedLuminance( 137 | second[0], second[1], second[2], 138 | getLuminance(reference)) 139 | } 140 | 141 | getDimensions () { 142 | let width = this.width || window.innerWidth 143 | let height = this.height || window.innerHeight 144 | return [dpr * width, dpr * height] 145 | } 146 | 147 | setupFBOs (gl) { 148 | let dim = this.getDimensions() 149 | this.state = [ 150 | createFBO(gl, dim, { depth: false }), 151 | createFBO(gl, dim, { depth: false }), 152 | createFBO(gl, dim, { depth: false }), 153 | createFBO(gl, dim, { depth: false }), 154 | createFBO(gl, dim, { depth: false }) ] 155 | 156 | this.state[0].color.magFilter = gl.LINEAR 157 | this.state[0].color.minFilter = gl.LINEAR 158 | this.state[1].color.magFilter = gl.LINEAR 159 | this.state[1].color.minFilter = gl.LINEAR 160 | this.state[2].color.magFilter = gl.LINEAR 161 | this.state[2].color.minFilter = gl.LINEAR 162 | this.state[3].color.magFilter = gl.LINEAR 163 | this.state[3].color.minFilter = gl.LINEAR 164 | this.state[4].color.magFilter = gl.LINEAR 165 | this.state[4].color.minFilter = gl.LINEAR 166 | } 167 | 168 | renderSVGSDF (svgToSDF, viewBox = { x1: 0, y1: 0, x2: 100, y2: 100 }, fbo, gl = this.gl) { 169 | const t = 0 170 | const MAX_TEXTURE_SIZE = gl.getParameter(gl.MAX_TEXTURE_SIZE) 171 | let dim = [MAX_TEXTURE_SIZE / 2, MAX_TEXTURE_SIZE / 2] 172 | 173 | let svgSDF 174 | // By default create a FBO to return 175 | if (!fbo) { 176 | try { 177 | svgSDF = createFBO(gl, dim, { preferFloat: true, depth: false }) 178 | } catch (e) { 179 | svgSDF = createFBO(gl, dim, { depth: false }) 180 | } 181 | } else { 182 | svgSDF = fbo 183 | } 184 | svgSDF.color[0].wrap = [gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE] 185 | svgSDF.color[0].magFilter = gl.LINEAR 186 | svgSDF.color[0].minFilter = gl.LINEAR 187 | svgSDF.color[0].mipSamples = 12 188 | 189 | svgToSDF.bind() 190 | svgToSDF.uniforms.resolution = dim 191 | svgToSDF.uniforms.scale = Math.min(dim[0], dim[1]) / Math.max(viewBox.x2 - viewBox.x1, viewBox.y2 - viewBox.y1) 192 | svgToSDF.uniforms.epsilon = this.epsilon 193 | 194 | let updates = this.getCamera(t) 195 | svgToSDF.uniforms.cameraRo = this.SHOW_SVG_SDF ? updates[0] : [0, 0, 1.25] 196 | svgToSDF.uniforms.cameraMatrix = (updates[1]) 197 | 198 | if (this.SHOW_SVG_SDF) { 199 | gl.bindFramebuffer(gl.FRAMEBUFFER, null) 200 | } else { 201 | svgSDF.bind() 202 | } 203 | 204 | drawTriangle(gl) 205 | 206 | return svgSDF 207 | } 208 | 209 | setupAnimation (preset) { 210 | let self = this 211 | // Epsilon Animation 212 | let eps1 = new TWEEN.Tween(this) 213 | eps1 214 | .to({ epsilon: 0.001 }, 10 * 1000) 215 | .easing(TWEEN.Easing.Quadratic.InOut) 216 | // eps1.start(0) 217 | 218 | // Camera location animation 219 | let ob = { 220 | x: self.cameraRo[0], 221 | y: self.cameraRo[1], 222 | z: self.cameraRo[2] 223 | } 224 | function updatePos () { 225 | self.cameraRo[0] = this.x 226 | self.cameraRo[1] = this.y 227 | self.cameraRo[2] = this.z 228 | } 229 | 230 | let cameraPosTween = new TWEEN.Tween(ob) 231 | cameraPosTween 232 | .to({ x: self.cameraRo[0], y: -0.1, z: self.cameraRo[2] }, 5 * 1000) 233 | .easing(TWEEN.Easing.Quadratic.Out) 234 | .onUpdate(updatePos) 235 | 236 | let cameraPosTween2 = new TWEEN.Tween(ob) 237 | cameraPosTween2 238 | .to({ x: self.cameraRo[0], y: self.cameraRo[1], z: self.cameraRo[2] }, 5 * 1000) 239 | .easing(TWEEN.Easing.Quadratic.Out) 240 | .onUpdate(updatePos) 241 | 242 | cameraPosTween.chain(cameraPosTween2) 243 | cameraPosTween2.chain(cameraPosTween) 244 | cameraPosTween.start(0) 245 | 246 | // Camera rotation 247 | function updateRot () { 248 | self.cameraAngles[0] = this[0] 249 | self.cameraAngles[1] = this[1] 250 | self.cameraAngles[2] = this[2] 251 | } 252 | 253 | let rotObj = [...this.cameraAngles] 254 | let camRotTween1 = new TWEEN.Tween(rotObj) 255 | camRotTween1 256 | .to([-0.328, this.cameraAngles[1], this.cameraAngles[2]], 4 * 1000) 257 | .onUpdate(updateRot) 258 | .easing(TWEEN.Easing.Linear.None) 259 | let camRotTween2 = new TWEEN.Tween(rotObj) 260 | camRotTween2 261 | .to([...this.cameraAngles], 4 * 1000) 262 | .onUpdate(updateRot) 263 | .easing(TWEEN.Easing.Linear.None) 264 | 265 | camRotTween1.chain(camRotTween2) 266 | camRotTween2.chain(camRotTween1) 267 | // camRotTween1.start(0) 268 | 269 | // Animation Fractal 270 | let rotTween1 = new TWEEN.Tween(this.rot2angle) 271 | rotTween1 272 | .delay(1 * 1000) 273 | .to([2.144, this.rot2angle[1], this.rot2angle[2]], 6 * 1000) 274 | .easing(TWEEN.Easing.Quadratic.InOut) 275 | // let rotTween2 = new TWEEN.Tween(this.rot2angle) 276 | // rotTween2 277 | // .delay(1 * 1000) 278 | // .to([0.125, this.rot2angle[1], 0.985], 14 * 1000) 279 | // .easing(TWEEN.Easing.Quadratic.InOut) 280 | let rotTween3 = new TWEEN.Tween(this.rot2angle) 281 | rotTween3 282 | .delay(2 * 1000) 283 | .to([...this.rot2angle], 6 * 1000) 284 | .easing(TWEEN.Easing.Quadratic.InOut) 285 | 286 | rotTween1.chain(rotTween3) 287 | // rotTween2.chain(rotTween3) 288 | rotTween3.chain(rotTween1) 289 | 290 | // rotTween1.start(0) 291 | 292 | // Scale Tween 293 | let scaleTween1 = new TWEEN.Tween(this) 294 | scaleTween1 295 | .delay(0 * 1000) 296 | .to({ scale: 1.4911 }, 5 * 1000) 297 | .easing(TWEEN.Easing.Quadratic.InOut) 298 | let scaleTween2 = new TWEEN.Tween(this) 299 | scaleTween2 300 | .delay(0 * 1000) 301 | .to({ scale: this.scale }, 5 * 1000) 302 | .easing(TWEEN.Easing.Quadratic.InOut) 303 | 304 | scaleTween1.chain(scaleTween2) 305 | scaleTween2.chain(scaleTween1) 306 | // scaleTween1.start(0) 307 | 308 | // Offset Tween 309 | let offsetTween1 = new TWEEN.Tween(this.offset) 310 | offsetTween1 311 | .to([ this.offset[0], 0.539, this.offset[2] ], 10 * 1000) 312 | .easing(TWEEN.Easing.Quadratic.InOut) 313 | let offsetTween2 = new TWEEN.Tween(this.offset) 314 | offsetTween2 315 | .to([ this.offset[0], this.offset[1], this.offset[2] ], 10 * 1000) 316 | .easing(TWEEN.Easing.Quadratic.InOut) 317 | 318 | offsetTween1.chain(offsetTween2) 319 | offsetTween2.chain(offsetTween1) 320 | 321 | // offsetTween1.start(0) 322 | 323 | // Angle1C Tween 324 | let angle1CTween1 = new TWEEN.Tween(this) 325 | angle1CTween1 326 | .to({ angle1C: 0.5166 }, 20 * 1000) 327 | .easing(TWEEN.Easing.Linear.None) 328 | // angle1CTween1.start(0) 329 | 330 | // // Angle2C Tween 331 | // let angle2CTween1 = new TWEEN.Tween(this) 332 | // angle2CTween1 333 | // .to({ angle2C: TWO_PI * 2 }, 15 * 1000) 334 | // .easing(TWEEN.Easing.Quadratic.InOut) 335 | 336 | // angle2CTween1.start(0) 337 | 338 | // Angle3C Tween 339 | // let angle3CTween1 = new TWEEN.Tween(this) 340 | // angle3CTween1 341 | // .to({ angle3C: 1.25 }, 20 * 1000) 342 | // .easing(TWEEN.Easing.Quadratic.InOut) 343 | 344 | // angle3CTween1.start(0) 345 | } 346 | 347 | setupAudio () { 348 | return new Promise((resolve, reject) => { 349 | const audioCtx = new (window.AudioContext || window.webkitAudioContext)() 350 | 351 | const output = audioCtx.createGain() 352 | output.gain.setValueAtTime(0.2, audioCtx.currentTime) 353 | output.connect(audioCtx.destination) 354 | 355 | this.analyser = audioCtx.createAnalyser() 356 | this.analyser.fftSize = this.audioFFT 357 | }) 358 | } 359 | 360 | enableEvents () { 361 | if (this.capturing) return 362 | this.resizeBound = this.resizeBound || this.resize.bind(this) 363 | window.addEventListener('resize', this.resizeBound, true) 364 | window.addEventListener('vrdisplaypresentchange', this.resizeBound, true) 365 | } 366 | 367 | disposeEvents () { 368 | window.removeEventListener('resize', this.resizeBound, true) 369 | window.removeEventListener('vrdisplaypresentchange', this.resizeBound, true) 370 | } 371 | 372 | setupShader (property, shader, gl) { 373 | let showError = (e) => { 374 | let pre = document.createElement('pre') 375 | pre.classList.add('glsl-error') 376 | 377 | let code = document.createElement('code') 378 | code.innerHTML = e.message 379 | pre.appendChild(code) 380 | document.body.appendChild(pre) 381 | } 382 | 383 | try { 384 | this[property] = createShader(gl, shader.vertex, shader.fragment) 385 | shader.on('change', () => { 386 | // Remove existing errors 387 | const errors = document.body.querySelectorAll('.glsl-error') 388 | for (const error of errors) { 389 | error.parentNode.removeChild(error) 390 | } 391 | 392 | try { 393 | this[property] = createShader(gl, shader.vertex, shader.fragment) 394 | } catch (e) { 395 | if (e.name === 'GLError') { 396 | showError(e) 397 | } else { 398 | throw e 399 | } 400 | } 401 | }) 402 | } catch (e) { 403 | if (e.name === 'GLError') { 404 | showError(e) 405 | } else { 406 | throw e 407 | } 408 | } 409 | } 410 | 411 | glInit (gl) { 412 | // Turn off depth test 413 | gl.disable(gl.DEPTH_TEST) 414 | 415 | // Create fragment shader 416 | this.setupShader('shader', require('./shaders/frag.shader'), gl) 417 | 418 | this.setupShader('bright', require('./shaders/bright.shader'), gl) 419 | this.setupShader('bloom', require('./shaders/bloom.shader'), gl) 420 | this.setupShader('finalPass', require('./shaders/final-pass.shader'), gl) 421 | 422 | this.generateSVGTexture('', undefined, gl) 423 | gl.bindFramebuffer(gl.FRAMEBUFFER, null) // Undo whatever generateSVGTexture bound 424 | 425 | let blankSDF = ndarray([], [2, 2]) 426 | // Initialize at max distance 427 | fill(blankSDF, (i) => 1.0) 428 | this.defaultSDFTexture = createTexture(gl, blankSDF) 429 | 430 | this.currentState = 1 431 | this.setupFBOs(gl) 432 | 433 | this.shader.attributes.position.location = 0 434 | } 435 | 436 | kifsM (t = 0, scale = this.scale, offset = this.offset) { 437 | this.shader.uniforms.scale = scale 438 | this.shader.uniforms.offset = offset 439 | 440 | // Scale and Offset 441 | let _kifsM 442 | 443 | if (MANDELBOX) { 444 | _kifsM = mat4.fromValues( 445 | 1, 0, 0, -offset[0], 446 | 0, 1, 0, -offset[1], 447 | 0, 0, 1, -offset[2], 448 | 0, 0, 0, 1) 449 | } else { 450 | _kifsM = mat4.fromValues( 451 | scale, 0, 0, -offset[0] * (scale - 1), 452 | 0, scale, 0, -offset[1] * (scale - 1), 453 | 0, 0, scale, -offset[2] * (scale - 1), 454 | 0, 0, 0, 1) 455 | } 456 | 457 | const angleX = this.rot2angle[0] 458 | this.shader.uniforms.rot = angleX 459 | const axisX = vec3.fromValues(1, 0, 0) 460 | mat4.multiply(_kifsM, rot4(axisX, angleX), _kifsM) 461 | 462 | // Y-centric 463 | const angleY = this.rot2angle[1] 464 | const axisY = vec3.fromValues(0, 1, 0) 465 | mat4.multiply(_kifsM, rot4(axisY, angleY), _kifsM) 466 | 467 | // Z-centric 468 | const angleZ = this.rot2angle[2] 469 | const axisZ = vec3.fromValues(0, 0, 1) 470 | mat4.multiply(_kifsM, rot4(axisZ, angleZ), _kifsM) 471 | 472 | return _kifsM 473 | } 474 | 475 | generateSVGTexture (svgPaths, viewBox = { x1: 0, y1: 0, x2: 100, y2: 100 }, gl = this.gl) { 476 | const svgShader = require('./shaders/svgSDF')(svgPaths) 477 | this.svgToSDF = createShader(gl, svgShader.vertex, svgShader.fragment) 478 | this.svgViewBox = viewBox 479 | return this.renderSVGSDF(this.svgToSDF, this.svgViewBox, null, gl) 480 | } 481 | 482 | add2DSDFTexture (name, texture) { 483 | this.shapeOptions[name] = name 484 | this._shape2DSDFTextures[name] = { 485 | tex: texture, 486 | filename: name, 487 | _lastFilename: name, 488 | isVideo: false, 489 | asset: null 490 | } 491 | } 492 | 493 | resize (e) { 494 | let canvas = this.canvas 495 | let dim = this.getDimensions() 496 | canvas.width = dim[0] 497 | canvas.height = dim[1] 498 | canvas.style.width = dim[0] + 'px' 499 | canvas.style.height = dim[1] + 'px' 500 | 501 | this.state[0].shape = dim 502 | this.state[1].shape = dim 503 | this.state[2].shape = dim 504 | this.state[3].shape = dim 505 | this.state[4].shape = dim 506 | } 507 | 508 | tick (t) { 509 | this.shader.bind() 510 | 511 | this.update(t) 512 | this.render(t) 513 | if (this.SHOW_SVG_SDF) { 514 | if (this.debugSVGFBO) { 515 | this.renderSVGSDF(this.svgToSDF, this.svgViewBox, this.debugSVGFBO) 516 | } else { 517 | this.debugSVGFBO = this.renderSVGSDF(this.svgToSDF, this.svgViewBox) 518 | } 519 | } 520 | } 521 | 522 | getCamera (t) { 523 | t = this.getTime(t) 524 | let cameraMatrix = mat4.create() 525 | 526 | const origin = vec3.fromValues(0, 0, 0) 527 | const unitX = vec3.fromValues(1, 0, 0) 528 | const unitY = vec3.fromValues(0, 1, 0) 529 | const unitZ = vec3.fromValues(0, 0, 1) 530 | 531 | // Camera Rotation 532 | const cameraRo = vec3.clone(this.cameraRo) 533 | // vec3.rotateY(cameraRo, cameraRo, origin, TWO_PI * t / this.totalTime) 534 | 535 | // LookAt 536 | if (this.LOOKAT) { 537 | mat4.lookAt(cameraMatrix, cameraRo, origin, unitY) 538 | } else { 539 | const angleX = this.cameraAngles[0] 540 | const axisX = unitX 541 | mat4.multiply(cameraMatrix, rot4(axisX, angleX), cameraMatrix) 542 | 543 | // Y-centric 544 | const angleY = this.cameraAngles[1] 545 | const axisY = unitY 546 | mat4.multiply(cameraMatrix, rot4(axisY, angleY), cameraMatrix) 547 | 548 | // Z-centric 549 | const angleZ = this.cameraAngles[2] 550 | const axisZ = unitZ 551 | mat4.multiply(cameraMatrix, rot4(axisZ, angleZ), cameraMatrix) 552 | } 553 | 554 | this.cameraMatrix = cameraMatrix 555 | return [cameraRo, cameraMatrix] 556 | } 557 | 558 | update (t) { 559 | t = (window.time !== undefined) ? window.time : t 560 | 561 | TWEEN.update(t) 562 | 563 | this.shader.uniforms.epsilon = this.epsilon 564 | 565 | // Shape 566 | const SDF_TEX_LOC = 2 567 | const isSVG = true 568 | const svgName = 'year-7' 569 | let sdfTexture = this._shape2DSDFTextures[svgName] 570 | if (isSVG && sdfTexture) { 571 | this.shader.uniforms.sdf2DTexture = sdfTexture.tex.bind(SDF_TEX_LOC) 572 | } else { 573 | this.shader.uniforms.sdf2DTexture = this.defaultSDFTexture.bind(SDF_TEX_LOC) 574 | } 575 | 576 | let updates = this.getCamera(t) 577 | this.shader.uniforms.cameraRo = updates[0] 578 | this.shader.uniforms.cameraMatrix = (updates[1]) 579 | 580 | this.shader.uniforms.kifsM = this.kifsM(t, this.scale, this.offset) 581 | this.shader.uniforms.offsetC = this.offsetC 582 | 583 | this.shader.uniforms.angle1C = this.angle1C 584 | this.shader.uniforms.angle2C = this.angle2C 585 | this.shader.uniforms.angle3C = this.angle3C 586 | 587 | this.shader.uniforms.colors1 = [this.colors1[0] / 255, this.colors1[1] / 255, this.colors1[2] / 255] 588 | // Update colors2 based on colors1 luminance 589 | // this.getEqualLuminance(this.colors1, this.colors2, 0) 590 | this.shader.uniforms.colors2 = [this.colors2[0] / 255, this.colors2[1] / 255, this.colors2[2] / 255] 591 | 592 | this.shader.uniforms.d = this.d 593 | } 594 | 595 | bloomBlur (gl, t) { 596 | let dim = this.getDimensions() 597 | 598 | // Brightness pass 599 | let base = this.state[0].color[0] 600 | this.state[1].bind() 601 | this.bright.bind() 602 | this.bright.uniforms.minBright = BLOOM_MIN_BRIGHTNESS 603 | this.bright.uniforms.buffer = base.bind(0) 604 | this.bright.uniforms.resolution = dim 605 | drawTriangle(gl) 606 | 607 | for (let i = 0; i < BLOOM_PASSES; i++) { 608 | // Horizontal Blur 609 | let brightLayer = this.state[1].color[0] 610 | this.state[2].bind() 611 | 612 | this.bloom.bind() 613 | this.bloom.uniforms.buffer = brightLayer.bind(1) 614 | this.bloom.uniforms.resolution = dim 615 | this.bloom.uniforms.direction = [BLOOM_RADIUS - i, 0] 616 | this.bloom.uniforms.time = this.getTime(t) 617 | drawTriangle(gl) 618 | 619 | // Vertical Blur 620 | let prev = this.state[2].color[0] 621 | this.state[1].bind() 622 | 623 | this.bloom.uniforms.buffer = prev.bind(2) 624 | this.bloom.uniforms.resolution = dim 625 | this.bloom.uniforms.direction = [0, BLOOM_RADIUS - i] 626 | this.bloom.uniforms.time = this.getTime(t) 627 | drawTriangle(gl) 628 | } 629 | } 630 | 631 | finalPassRender (gl, t) { 632 | let dim = this.getDimensions() 633 | 634 | let base = this.state[0].color[0] 635 | 636 | // Additive blending 637 | this.currentState = (this.currentState) ? 0 : 1 638 | this.state[3 + this.currentState].bind() 639 | this.finalPass.bind() 640 | this.finalPass.uniforms.base = base.bind(3) 641 | this.finalPass.uniforms.buffer = this.state[1].color[0].bind(4) 642 | this.finalPass.uniforms.prevBuffer = this.state[3 + ((this.currentState + 1) % 2)].color[0].bind(5) 643 | this.finalPass.uniforms.resolution = dim 644 | this.finalPass.uniforms.time = this.getTime(t) 645 | this.finalPass.uniforms.wet = BLOOM_WET 646 | this.finalPass.uniforms.colors1 = [this.colors1[0] / 255, this.colors1[1] / 255, this.colors1[2] / 255] 647 | this.finalPass.uniforms.colors2 = [this.colors2[0] / 255, this.colors2[1] / 255, this.colors2[2] / 255] 648 | drawTriangle(gl) 649 | 650 | // Render again as framebuffer 651 | gl.bindFramebuffer(gl.FRAMEBUFFER, null) 652 | drawTriangle(gl) 653 | } 654 | 655 | getTime (t) { 656 | return window.time || t / 1000 657 | } 658 | 659 | render (t) { 660 | let { shader, gl } = this 661 | 662 | // Always render to FBO as finalPassRender will render to final frame buffer 663 | this.state[0].bind() 664 | 665 | shader.uniforms.time = this.getTime(t) 666 | shader.uniforms.BLOOM = BLOOM 667 | this.sceneRenderer(shader, t) 668 | 669 | if (BLOOM) { 670 | this.bloomBlur(gl, t) 671 | } 672 | 673 | this.finalPassRender(gl, t) 674 | } 675 | 676 | run () { 677 | assert(this.sceneRenderer, 'A sceneRenderer is required') 678 | 679 | this.canvas.style.display = null 680 | this.resize() 681 | 682 | this.enableEvents() 683 | } 684 | 685 | stop () { 686 | this.canvas.style.display = 'none' 687 | this.disposeEvents() 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /audio/swell.js: -------------------------------------------------------------------------------- 1 | import Octavian from 'octavian' 2 | 3 | function createSwell (audioCtx, note, start, length) { 4 | start += audioCtx.currentTime 5 | 6 | const baseNote = new Octavian.Note(note) 7 | const offNote = baseNote.perfectFourth() 8 | 9 | const baseGain = audioCtx.createGain() 10 | baseGain.gain.setValueAtTime(1, audioCtx.currentTime) 11 | 12 | const osc1 = audioCtx.createOscillator() 13 | osc1.type = 'sawtooth' 14 | osc1.frequency.setValueAtTime(baseNote.frequency, audioCtx.currentTime) 15 | 16 | const filter1 = audioCtx.createBiquadFilter() 17 | filter1.type = 'lowpass' 18 | filter1.frequency.setValueAtTime(0, start) 19 | filter1.frequency.linearRampToValueAtTime(1000, start + length / 2) 20 | filter1.frequency.linearRampToValueAtTime(0, start + length) 21 | filter1.detune.setValueAtTime(0, start) 22 | filter1.detune.linearRampToValueAtTime(400, start + length / 2) 23 | filter1.detune.linearRampToValueAtTime(0, start + length) 24 | 25 | osc1.connect(filter1) 26 | filter1.connect(baseGain) 27 | 28 | const osc2 = audioCtx.createOscillator() 29 | osc2.type = 'sawtooth' 30 | osc2.frequency.setValueAtTime(offNote.frequency - 1.5, audioCtx.currentTime) 31 | 32 | const filter2 = audioCtx.createBiquadFilter() 33 | filter2.type = 'lowpass' 34 | filter2.frequency.setValueAtTime(0, start) 35 | filter2.frequency.linearRampToValueAtTime(880, start + length / 2) 36 | filter2.frequency.linearRampToValueAtTime(0, start + length) 37 | 38 | osc2.connect(filter2) 39 | filter2.connect(baseGain) 40 | 41 | osc1.start(start) 42 | osc2.start(start) 43 | 44 | return baseGain 45 | } 46 | 47 | module.exports = createSwell 48 | -------------------------------------------------------------------------------- /background.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: pModPolarBack = require(./hg_sdf/p-mod-polar-c.glsl) 2 | #pragma glslify: pMod2 = require(./hg_sdf/p-mod2.glsl) 3 | 4 | #ifndef range 5 | #define range(start, stop, t) saturate((t - start) / (stop - start)) 6 | #endif 7 | 8 | vec3 gammaEnc (in vec3 color) { 9 | const vec3 enc = vec3(0.454545); 10 | return pow(color, enc); 11 | } 12 | 13 | vec3 gammaDecode (in vec3 color) { 14 | const vec3 dec = vec3(2.2); 15 | return pow(color, dec); 16 | } 17 | 18 | vec3 getBackground (in vec2 uv, in float universe) { 19 | // Convert from [-1,1] -> [0, 1] 20 | vec2 coord = 0.5 * (uv.xy + vec2(1.0)); 21 | 22 | // --- Gradient index --- 23 | // float bgIndex = saturate(0.5 * (1. * uv.y + 1.0)); 24 | float bgIndex = saturate(length(uv)); 25 | // bgIndex = pow(bgIndex, 4.); 26 | // bgIndex += 0.1 * dot(sin(6. * uv), vec2(1)); 27 | bgIndex = 1. - bgIndex; // Flip 28 | 29 | // Metadata 30 | 31 | // --- Set colors / gradient --- 32 | // Gradients 33 | // vec3 color = mix(vec3(0.0), vec3(0.05, 0.025, 0.05), bgIndex); 34 | // vec3 color = mix(vec3(0.03, 0.04, 0.04), vec3(0.125, 0.1, 0.1), bgIndex); 35 | // vec3 color = mix(vec3(0.80, 0.75, 0.70), vec3(0.95, 0.95, 1), bgIndex); 36 | // vec3 color = mix(vec3(0.2), vec3(0.5), bgIndex); 37 | 38 | // const vec3 bgColor = #F2900A; 39 | // vec3 color = mix(0.8 * bgColor, bgColor, bgIndex); 40 | // color = mix(color, vec3(1), 0.6); 41 | 42 | // // Rainbow dark background color 43 | // float rainbowI = 0.5 * (atan(uv.y, uv.x) / PI + 1.); 44 | // rainbowI += 0.125 * sin(TWO_PI * rainbowI + sin(1. * TWO_PI * rainbowI)); 45 | // rainbowI += norT; 46 | // float rainbowValue = 1.; 47 | // vec3 rainbow = 0.5 + 0.5 * cos(TWO_PI * (rainbowI + vec3(0, 0.33, 0.67))); 48 | // vec3 color = mix(rainbowValue * rainbow, vec3(0.0), bgIndex); 49 | 50 | // Hex color 51 | // vec3 color = mix(#34A0E0, #60E083, saturate(1. * bgIndex)); 52 | // vec3 color = mix(0.25 * #91acff, 0.5 * #a19cff, saturate(1. * bgIndex)); 53 | // color *= 1.5; 54 | // vec3 color = mix(vec3(0.8, 0.9165, 1), vec3(0.5), 0.00); 55 | // color = mix(color, 0.7 * vec3(0, 1,0.8), bgIndex); 56 | 57 | // Solid colors 58 | // vec3 color = vec3(0.5); 59 | vec3 color = vec3(1); 60 | // vec3 color = vec3(1, 0, 0); 61 | // vec3 color = vec3(0); 62 | // vec3 color = mix(#5927F8, vec3(1), 0.20); 63 | // vec3 color = #FBFBEF; 64 | 65 | // -- Patterns -- 66 | 67 | // vec2 nQ = uv; 68 | // float warpScale = 2.; 69 | // nQ += warpScale * 0.10000 * cos( 2. * nQ.yx ); 70 | // nQ += warpScale * 0.05000 * cos( 4. * nQ.yx ); 71 | // nQ += warpScale * 0.02500 * cos( 8. * nQ.yx ); 72 | 73 | // float n = dot(cos(23.78 * nQ), sin(71.29 * nQ)); 74 | // n = dot(cos(vec2(0.2, 23.78) * nQ + n), sin(vec2(11.29, 0.7) * nQ)); 75 | // n = dot(uv, vec2(0.1)) + 0.2 * n + 8. * dot(uv, uv * cos(vec2(1,3) * uv)); 76 | 77 | // vec3 color = 0.5 + 0.5 * cos(TWO_PI * (n + vec3(uv, 1) + vec3(0, 0.33, 0.67))); 78 | 79 | // // Grid 80 | // const float size = 0.007; 81 | // float sizeI = smoothstep(-1.0, 0.25, uv.y); 82 | // uv *= rotMat2Back(0.35 * PI); 83 | // vec2 c = pMod2(uv, vec2(size)); 84 | // vec2 absQ = abs(uv); 85 | // float n = max(absQ.x, absQ.y) - (0.35 + 0.115 * sizeI) * size; 86 | // n = 1. - step(0.0, n); 87 | // vec3 color = 0.8 * vec3(n); 88 | 89 | // // Stripes 90 | // vec2 axis = vec2(1); 91 | // float dI = dot(uv, axis); 92 | // float period = 30.; 93 | // float n = sin(period * TWO_PI * dI); 94 | // // float cutoff = 0.8 * smoothstep(-0.5, 0.5, uv.y); 95 | // float cutoff = 0.0; 96 | // n = 1. - smoothstep(cutoff, cutoff + edge, n); 97 | // vec3 color = mix(vec3(0.0), vec3(1), n); 98 | 99 | // // Dots 100 | // float size = 0.0265; 101 | // vec2 c = pMod2(uv, vec2(size)); 102 | // float dotN = length(uv) - 0.3 * size; 103 | // // n = sin(54. * TWO_PI * dI); 104 | // // float dotCutoff = 0.8 * smoothstep(-0.5, 0.5, uv.y); 105 | // float dotCutoff = 0.; 106 | // dotN = smoothstep(dotCutoff, dotCutoff + edge, dotN); 107 | // vec3 color = vec3(1.00 * dotN); 108 | // // color = mix(mix(primeColor, vec3(1), dotN), color, m); 109 | 110 | // Manipulations 111 | // color = mix(color, #FFC070, saturate(smoothstep(0.0, 0.5, uv.y))); 112 | // color *= 1.35; // Brighten 113 | 114 | // color = pow(color, vec3(2.2)); 115 | 116 | // // Gradient tint 117 | // vec3 gradientColor = 0.5 + 0.5 * cos(TWO_PI * (vec3(uv, cos(dot(uv, vec2(1)))) + vec3(0, 0.33, 0.67))); 118 | // color *= mix(gradientColor, vec3(1), (0.70 + 0.2 * length(uv))); 119 | 120 | // color *= mix(vec3(0.95), vec3(1, 0.8, 0.8), saturate(1. - 0.95 * (uv.y + 1.0))); 121 | // color *= 1.2; 122 | // color = mix(color, vec3(0.5), 0.10); // desaturate 123 | // color = mix(color, vec3(1), mix(0.25, 0.80, 1. - bgIndex)); // lighten 124 | // color = mix(color, vec3(0), 0.3); // Darken 125 | // color *= 0.91; 126 | // color *= 0.9 * mix(vec3(1, 1.2, 1.2), vec3(1.1, 1.0, 1.05), 1. - bgIndex); // Tint 127 | // color *= vec3(0.9, 0.9, 1.0); // Tint 128 | 129 | return color; 130 | } 131 | 132 | vec3 getBackground (in vec2 uv) { 133 | return getBackground(uv, 0.); 134 | } 135 | 136 | vec3 background = vec3(0.); 137 | -------------------------------------------------------------------------------- /band-filter.glsl: -------------------------------------------------------------------------------- 1 | float bandFilter (in float t, in float start, in float end) { 2 | float mid = (end - start) * 0.5; 3 | mid = min(mid, 0.001); 4 | return smoothstep(start, start + mid, t) - smoothstep(end - mid, end, t); 5 | } 6 | #pragma glslify: export(bandFilter) 7 | -------------------------------------------------------------------------------- /bin/gallery.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer') 2 | const mkdirp = require('mkdirp') 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const galleryDir = 'gallery' 7 | const questions = [ 8 | { 9 | type: 'input', 10 | name: 'name', 11 | default: () => { 12 | let today = new Date() 13 | return today.toISOString().replace(/T.*/, '') 14 | }, 15 | message: 'Gallery Item Name' 16 | } 17 | ] 18 | inquirer.prompt(questions).then((answers) => { 19 | let galleryItemDir = answers.name 20 | galleryItemDir = galleryItemDir.replace(/ /, '-').toLowerCase() 21 | galleryItemDir = path.join(galleryDir, galleryItemDir) 22 | 23 | mkdirp(galleryItemDir, (err) => { 24 | if (err && err.code !== 'EEXIST') { 25 | console.error(err) 26 | return 27 | } 28 | 29 | mkdirp(path.join(galleryItemDir, './node_modules/webvr-polyfill/build/'), (err) => { 30 | if (err && err.code !== 'EEXIST') { 31 | console.error(err) 32 | return 33 | } 34 | 35 | ['bundle.js', 'dat.gui.min.js', './node_modules/webvr-polyfill/build/webvr-polyfill.js', 'index.html'].forEach((file) => { 36 | fs.createReadStream(file) 37 | .pipe(fs.createWriteStream(path.join(galleryItemDir, file))) 38 | }) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /bloom.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 resolution; 4 | uniform vec2 direction; 5 | uniform sampler2D buffer; 6 | // #define VARIABLE_BLOOM 1 7 | 8 | #pragma glslify: blur = require(glsl-fast-gaussian-blur/5) 9 | #ifdef VARIABLE_BLOOM 10 | uniform float time; 11 | #pragma glslify: blur9 = require(glsl-fast-gaussian-blur/9) 12 | #pragma glslify: blur5 = require(glsl-fast-gaussian-blur/5) 13 | #pragma glslify: cnoise2 = require(glsl-noise/classic/2d) 14 | #pragma glslify: snoise2 = require(glsl-noise/simplex/2d) 15 | #endif 16 | 17 | void main() { 18 | vec2 uv = vec2(gl_FragCoord.xy / resolution.xy); 19 | #ifdef VARIABLE_BLOOM 20 | vec4 blurColor = vec4(0); 21 | 22 | float n = cnoise2(323.0 * uv + 10.0 * vec2( 23 | cnoise2(uv + vec2(time, 1.3 * time)), 24 | cnoise2(2.0 * uv + 2024.30 + vec2(time, 1.3 * time)))); 25 | blurColor = blur(buffer, uv, resolution.xy, direction); 26 | blurColor = mix(blurColor, blur9(buffer, uv, resolution.xy, direction), smoothstep(0.5, 1.0, n)); 27 | blurColor = mix(blurColor, blur5(buffer, uv, resolution.xy, direction), smoothstep(0.0, 0.5, n)); 28 | 29 | gl_FragColor = blurColor; 30 | #else 31 | gl_FragColor = blur(buffer, uv, resolution.xy, direction); 32 | #endif 33 | } 34 | -------------------------------------------------------------------------------- /bright.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 resolution; 4 | uniform sampler2D buffer; 5 | uniform float minBright; 6 | 7 | void main() { 8 | vec2 uv = vec2(gl_FragCoord.xy / resolution.xy); 9 | 10 | vec4 color = texture2D(buffer, uv); 11 | vec3 colorLinear = pow(color.rgb, vec3(2.2)); 12 | 13 | 14 | // TODO Finish debugging bloom filter intensity 15 | 16 | // Check whether fragment output is higher than threshold, if so output as brightness color 17 | // float brightnessStandard = dot(colorLinear.rgb, vec3(0.2126, 0.7152, 0.0722)); 18 | // ITU BT.601 19 | // source: http://stackoverflow.com/a/596243/630490 20 | float brightnessPerceived = dot(colorLinear.rgb, vec3(0.299, 0.587, 0.114)); 21 | 22 | // float selectiveBrightness = 1.414214 - length(colorLinear.rgb - #FF0000); 23 | 24 | float brightness = brightnessPerceived; 25 | 26 | if (brightness >= minBright) { 27 | gl_FragColor = vec4(color.rgb, 1.0); 28 | } else { 29 | gl_FragColor = vec4(vec3(0.), 1.); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /camera-tweens.js: -------------------------------------------------------------------------------- 1 | import TWEEN from 'tween.js' 2 | 3 | export function cameraOrbit (ro, radius, from, to, period) { 4 | let tweened = from.slice() 5 | let tween = new TWEEN.Tween(tweened) 6 | tween 7 | .to(to, period) 8 | .onUpdate(function () { 9 | ro[0] = radius * Math.sin(this[0]) 10 | ro[1] = radius * Math.cos(this[1]) 11 | }) 12 | .easing(TWEEN.Easing.Quadratic.InOut) 13 | .onComplete(function (object) { 14 | this[0] = from[0] 15 | this[1] = from[1] 16 | }) 17 | return tween 18 | } 19 | -------------------------------------------------------------------------------- /capturer/capturer-interface.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | 3 | class Capturer { 4 | constructor (opt = {}) { 5 | assert.strictEqual(typeof opt, 'object', 'Options must be an object') 6 | assert.ok('filename' in opt, 'Options must define a "filename"') 7 | assert.ok('framerate' in opt, 'Options must define a "framerate"') 8 | assert.ok('quality' in opt, 'Options must define a "quality"') 9 | assert.ok('startTime' in opt, 'Options must define a "startTime"') 10 | assert.ok('timeLength' in opt, 'Options must define a "timeLength"') 11 | 12 | Object.assign(this, opt) 13 | } 14 | 15 | start () { 16 | assert.fail('start() was not defined for renderer') 17 | } 18 | 19 | async capture (canvas) { 20 | assert.fail('capture() was not defined for renderer') 21 | } 22 | 23 | async save () { 24 | assert.fail('save() was not defined for renderer') 25 | } 26 | } 27 | 28 | module.exports = Capturer 29 | -------------------------------------------------------------------------------- /capturer/ccapture.js: -------------------------------------------------------------------------------- 1 | const Capturer = require('./capturer-interface') 2 | const CCapture = require('../ccapture.js/src/CCapture') 3 | 4 | class CCaptureCapturer extends Capturer { 5 | constructor (opt = {}) { 6 | super(opt) 7 | 8 | this.capturer = new CCapture({ 9 | format: opt.format || 'jpg', 10 | framerate: this.framerate, 11 | name: this.filename, 12 | autoSaveTime: opt.autoSaveTime || 5, 13 | quality: this.quality, 14 | startTime: this.startTime, 15 | timeLimit: this.timeLength, 16 | verbose: true 17 | }) 18 | } 19 | 20 | start () { 21 | this.capturer.start() 22 | } 23 | 24 | async capture (canvas) { 25 | this.capturer.capture(canvas) 26 | } 27 | 28 | async save () { 29 | console.log('ccapture.js save') 30 | } 31 | } 32 | 33 | module.exports = CCaptureCapturer 34 | -------------------------------------------------------------------------------- /ccapture.all.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function download(t,e,i){function n(t){var e=t.split(/[:;,]/),i=e[1],n="base64"==e[2]?atob:decodeURIComponent,r=n(e.pop()),o=r.length,a=0,s=new Uint8Array(o);for(a;a>8,this.data[this.pos++]=t},t.prototype.writeDoubleBE=function(t){for(var e=new Uint8Array(new Float64Array([t]).buffer),i=e.length-1;i>=0;i--)this.writeByte(e[i])},t.prototype.writeFloatBE=function(t){for(var e=new Uint8Array(new Float32Array([t]).buffer),i=e.length-1;i>=0;i--)this.writeByte(e[i])},t.prototype.writeString=function(t){for(var e=0;e>8),this.writeU8(t);break;case 3:this.writeU8(32|t>>16),this.writeU8(t>>8),this.writeU8(t);break;case 4:this.writeU8(16|t>>24),this.writeU8(t>>16),this.writeU8(t>>8),this.writeU8(t);break;case 5:this.writeU8(8|t/4294967296&7),this.writeU8(t>>24),this.writeU8(t>>16),this.writeU8(t>>8),this.writeU8(t);break;default:throw new RuntimeException("Bad EBML VINT size "+e)}},t.prototype.measureEBMLVarInt=function(t){if(t<127)return 1;if(t<16383)return 2;if(t<2097151)return 3;if(t<268435455)return 4;if(t<34359738367)return 5;throw new RuntimeException("EBML VINT size not supported "+t)},t.prototype.writeEBMLVarInt=function(t){this.writeEBMLVarIntWidth(t,this.measureEBMLVarInt(t))},t.prototype.writeUnsignedIntBE=function(t,e){switch(void 0===e&&(e=this.measureUnsignedInt(t)),e){case 5:this.writeU8(Math.floor(t/4294967296));case 4:this.writeU8(t>>24);case 3:this.writeU8(t>>16);case 2:this.writeU8(t>>8);case 1:this.writeU8(t);break;default:throw new RuntimeException("Bad UINT size "+e)}},t.prototype.measureUnsignedInt=function(t){return t<256?1:t<65536?2:t<1<<24?3:t<4294967296?4:5},t.prototype.getAsDataArray=function(){if(this.posthis.length)throw"Seeking beyond the end of file is not allowed";this.pos=t},this.write=function(e){var i={offset:this.pos,data:e,length:r(e)},f=i.offset>=this.length;this.pos+=i.length,this.length=Math.max(this.length,this.pos),a=a.then(function(){if(h)return new Promise(function(e,r){n(i.data).then(function(n){var r=0,o=Buffer.from(n.buffer),a=function(n,o,s){r+=o,r>=s.length?e():t.write(h,s,r,s.length-r,i.offset+r,a)};t.write(h,o,0,o.length,i.offset,a)})});if(s)return new Promise(function(t,e){s.onwriteend=t,s.seek(i.offset),s.write(new Blob([i.data]))});if(!f)for(var e=0;e=r.offset+r.length)){if(i.offsetr.offset+r.length)throw new Error("Overwrite crosses blob boundaries");return i.offset==r.offset&&i.length==r.length?void(r.data=i.data):n(r.data).then(function(t){return r.data=t,n(i.data)}).then(function(t){i.data=t,r.data.set(i.data,i.offset-r.offset)})}}o.push(i)})},this.complete=function(t){return a=h||s?a.then(function(){return null}):a.then(function(){for(var e=[],i=0;i0&&e.trackNumber<127))throw"TrackNumber must be > 0 and < 127";return i.writeEBMLVarInt(e.trackNumber),i.writeU16BE(e.timecode),i.writeByte(128),{id:163,data:[i.getAsDataArray(),e.frame]}}function l(t){return{id:524531317,data:[{id:231,data:Math.round(t.timecode)}]}}function c(t,e,i){_.push({id:187,data:[{id:179,data:e},{id:183,data:[{id:247,data:t},{id:241,data:a(i)}]}]})}function p(){var e={id:475249515,data:_},i=new t(16+32*_.length);h(i,S.pos,e),S.write(i.getAsDataArray()),D.Cues.positionEBML.data=a(e.offset)}function m(){if(0!=T.length){for(var e=0,i=0;i=E&&m()}function y(){var e=new t(x.size),i=S.pos;h(e,x.dataOffset,x.data),S.seek(x.dataOffset),S.write(e.getAsDataArray()),S.seek(i)}function v(){var e=new t(8),i=S.pos;e.writeDoubleBE(U),S.seek(M.dataOffset),S.write(e.getAsDataArray()),S.seek(i)}var b,k,B,x,E=5e3,A=1,L=!1,T=[],U=0,F=0,I={quality:.95,fileWriter:null,fd:null,frameDuration:null,frameRate:null},D={Cues:{id:new Uint8Array([28,83,187,107]),positionEBML:null},SegmentInfo:{id:new Uint8Array([21,73,169,102]),positionEBML:null},Tracks:{id:new Uint8Array([22,84,174,107]),positionEBML:null}},M={id:17545,data:new s(0)},_=[],S=new e(n.fileWriter||n.fd);this.addFrame=function(t){if(L){if(t.width!=b||t.height!=k)throw"Frame size differs from previous frames"}else b=t.width,k=t.height,u(),L=!0;var e=r(t,{quality:n.quality});if(!e)throw"Couldn't decode WebP frame, does the browser support WebP?";g({frame:o(e),duration:n.frameDuration})},this.complete=function(){return m(),p(),y(),v(),S.complete("video/webm")},this.getWrittenSize=function(){return S.length},n=i(I,n||{}),w()}};"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=t(require("./ArrayBufferDataStream"),require("./BlobBuffer")):window.WebMWriter=t(ArrayBufferDataStream,BlobBuffer)}(),function(){function t(t){var e,i=new Uint8Array(t);for(e=0;e>18&63]+o[t>>12&63]+o[t>>6&63]+o[63&t]}var i,n,r,a=t.length%3,s="";for(i=0,r=t.length-a;in&&(e.push({blocks:o,length:i}),o=[],i=0),o.push(t),i+=t.headerLength+t.inputLength}),e.push({blocks:o,length:i}),e.forEach(function(e){var i=new Uint8Array(e.length),n=0;e.blocks.forEach(function(t){i.set(t.header,n),n+=t.headerLength,i.set(t.input,n),n+=t.inputLength}),t.push(i)}),t.push(new Uint8Array(2*r)),new Blob(t,{type:"octet/stream"})},t.prototype.clear=function(){this.written=0,this.out=n.clean(e)},window.Tar=t}(),function(t){function e(t,i){if({}.hasOwnProperty.call(e.cache,t))return e.cache[t];var n=e.resolve(t);if(!n)throw new Error("Failed to resolve module "+t);var r={id:t,require:e,filename:t,exports:{},loaded:!1,parent:i,children:[]};i&&i.children.push(r);var o=t.slice(0,t.lastIndexOf("/")+1);return e.cache[t]=r.exports,n.call(r.exports,r,r.exports,o,t),r.loaded=!0,e.cache[t]=r.exports}e.modules={},e.cache={},e.resolve=function(t){return{}.hasOwnProperty.call(e.modules,t)?e.modules[t]:void 0},e.define=function(t,i){e.modules[t]=i};var i=function(e){return e="/",{title:"browser",version:"v0.10.26",browser:!0,env:{},argv:[],nextTick:t.setImmediate||function(t){setTimeout(t,0)},cwd:function(){return e},chdir:function(t){e=t}}}();e.define("/gif.coffee",function(t,i,n,r){function o(t,e){return{}.hasOwnProperty.call(t,e)}function a(t,e){for(var i=0,n=e.length;ithis.frames.length;0<=this.frames.length?++e:--e)t.push(e);return t}.apply(this,arguments),n=0,r=i.length;ne;0<=e?++i:--i)t.push(i);return t}.apply(this,arguments),n=0,r=i.length;nt;this.freeWorkers.length<=t?++i:--i)e.push(i);return e}.apply(this,arguments).forEach(function(t){return function(e){var i;return console.log("spawning worker "+e),i=new Worker(t.options.workerScript),i.onmessage=function(t){return function(e){return t.activeWorkers.splice(t.activeWorkers.indexOf(i),1),t.freeWorkers.push(i),t.frameFinished(e.data)}}(t),t.freeWorkers.push(i)}}(this)),t},e.prototype.frameFinished=function(t){return console.log("frame "+t.index+" finished - "+this.activeWorkers.length+" active"),this.finishedFrames++,this.emit("progress",this.finishedFrames/this.frames.length),this.imageParts[t.index]=t,a(null,this.imageParts)?this.renderNextFrame():this.finishRendering()},e.prototype.finishRendering=function(){var t,e,i,n,r,o,a;r=0;for(var s=0,h=this.imageParts.length;s=this.frames.length?void 0:(t=this.frames[this.nextFrame++],i=this.freeWorkers.shift(),e=this.getTask(t),console.log("starting frame "+(e.index+1)+" of "+this.frames.length),this.activeWorkers.push(i),i.postMessage(e))},e.prototype.getContextData=function(t){return t.getImageData(0,0,this.options.width,this.options.height).data},e.prototype.getImageData=function(t){var e;return null!=this._canvas||(this._canvas=document.createElement("canvas"),this._canvas.width=this.options.width,this._canvas.height=this.options.height),e=this._canvas.getContext("2d"),e.setFill=this.options.background,e.fillRect(0,0,this.options.width,this.options.height),e.drawImage(t,0,0),this.getContextData(e)},e.prototype.getTask=function(t){var e,i;if(e=this.frames.indexOf(t),i={index:e,last:e===this.frames.length-1,delay:t.delay,transparent:t.transparent,width:this.options.width,height:this.options.height,quality:this.options.quality,repeat:this.options.repeat,canTransfer:"chrome"===h.name},null!=t.data)i.data=t.data;else if(null!=t.context)i.data=this.getContextData(t.context);else{if(null==t.image)throw new Error("Invalid frame");i.data=this.getImageData(t.image)}return i},e}(u),t.exports=l}),e.define("/browser.coffee",function(t,e,i,n){var r,o,a,s,h;s=navigator.userAgent.toLowerCase(),a=navigator.platform.toLowerCase(),h=s.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0],o="ie"===h[1]&&document.documentMode,r={name:"version"===h[1]?h[3]:h[1],version:o||parseFloat("opera"===h[1]&&h[4]?h[4]:h[2]),platform:{name:s.match(/ip(?:ad|od|hone)/)?"ios":(s.match(/(?:webos|android)/)||a.match(/mac|win|linux/)||["other"])[0]}},r[r.name]=!0,r[r.name+parseInt(r.version,10)]=!0,r.platform[r.platform.name]=!0,t.exports=r}),e.define("events",function(t,e,n,r){i.EventEmitter||(i.EventEmitter=function(){});var o=e.EventEmitter=i.EventEmitter,a="function"==typeof Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},s=10;o.prototype.setMaxListeners=function(t){this._events||(this._events={}),this._events.maxListeners=t},o.prototype.emit=function(t){if("error"===t&&(!this._events||!this._events.error||a(this._events.error)&&!this._events.error.length))throw arguments[1]instanceof Error?arguments[1]:new Error("Uncaught, unspecified 'error' event.");if(!this._events)return!1;var e=this._events[t];if(!e)return!1;if("function"!=typeof e){if(a(e)){for(var i=Array.prototype.slice.call(arguments,1),n=e.slice(),r=0,o=n.length;r0&&this._events[t].length>i&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),console.trace())}this._events[t].push(e)}else this._events[t]=[this._events[t],e];else this._events[t]=e;return this},o.prototype.on=o.prototype.addListener,o.prototype.once=function(t,e){var i=this;return i.on(t,function n(){i.removeListener(t,n),e.apply(this,arguments)}),this},o.prototype.removeListener=function(t,e){if("function"!=typeof e)throw new Error("removeListener only takes instances of Function");if(!this._events||!this._events[t])return this;var i=this._events[t];if(a(i)){var n=i.indexOf(e);if(n<0)return this;i.splice(n,1),0==i.length&&delete this._events[t]}else this._events[t]===e&&delete this._events[t];return this},o.prototype.removeAllListeners=function(t){return t&&this._events&&this._events[t]&&(this._events[t]=null),this},o.prototype.listeners=function(t){return this._events||(this._events={}),this._events[t]||(this._events[t]=[]),a(this._events[t])||(this._events[t]=[this._events[t]]),this._events[t]}}),t.GIF=e("/gif.coffee")}.call(this,this),function(){function t(t){return t&&t.Object===Object?t:null}function e(t){return String("0000000"+t).slice(-7)}function i(){function t(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()}function n(t){var e={};this.settings=t,this.on=function(t,i){e[t]=i},this.emit=function(t){var i=e[t];i&&i.apply(null,Array.prototype.slice.call(arguments,1))},this.filename=t.name||i(),this.extension="",this.mimeType=""}function r(t){n.call(this,t),this.extension=".tar",this.mimeType="application/x-tar",this.fileExtension="",this.baseFilename=this.filename,this.tape=null,this.count=0,this.part=1,this.frames=0}function o(t){r.call(this,t),this.type="image/png",this.fileExtension=".png"}function a(t){r.call(this,t),this.type="image/jpeg",this.fileExtension=".jpg",this.quality=t.quality/100||.8}function s(t){var e=document.createElement("canvas");"image/webp"!==e.toDataURL("image/webp").substr(5,10)&&console.log("WebP not supported - try another export format"),n.call(this,t),this.quality=t.quality/100||.8,this.extension=".webm",this.mimeType="video/webm",this.baseFilename=this.filename,this.framerate=t.framerate,this.frames=0,this.part=1,this.videoWriter=new WebMWriter({quality:this.quality,fileWriter:null,fd:null,frameRate:this.framerate})}function h(t){n.call(this,t),t.quality=t.quality/100||.8,this.encoder=new FFMpegServer.Video(t),this.encoder.on("process",function(){this.emit("process")}.bind(this)),this.encoder.on("finished",function(t,e){var i=this.callback;i&&(this.callback=void 0,i(t,e))}.bind(this)),this.encoder.on("progress",function(t){this.settings.onProgress&&this.settings.onProgress(t)}.bind(this)),this.encoder.on("error",function(t){alert(JSON.stringify(t,null,2))}.bind(this))}function f(t){n.call(this,t),this.framerate=this.settings.framerate,this.type="video/webm",this.extension=".webm",this.stream=null,this.mediaRecorder=null,this.chunks=[]}function u(t){n.call(this,t),t.quality=31-(30*t.quality/100||10),t.workers=t.workers||4,this.extension=".gif",this.mimeType="image/gif",this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.sizeSet=!1,this.encoder=new GIF({workers:t.workers,quality:t.quality,workerScript:t.workersPath+"gif.worker.js"}),this.encoder.on("progress",function(t){this.settings.onProgress&&this.settings.onProgress(t)}.bind(this)),this.encoder.on("finished",function(t){var e=this.callback;e&&(this.callback=void 0,e(t))}.bind(this))}function d(t){function e(){function t(){return this._hooked||(this._hooked=!0,this._hookedTime=this.currentTime||0,this.pause(),it.push(this)),this._hookedTime+M.startTime}b("Capturer start"),U=window.Date.now(),T=U+M.startTime,I=window.performance.now(),F=I+M.startTime,window.Date.prototype.getTime=function(){return T},window.Date.now=function(){return T},window.setTimeout=function(t,e){var i={callback:t,time:e,triggerTime:T+e};return _.push(i),b("Timeout set to "+i.time),i},window.clearTimeout=function(t){for(var e=0;e<_.length;e++)_[e]!=t||(_.splice(e,1),b("Timeout cleared"))},window.setInterval=function(t,e){var i={callback:t,time:e,triggerTime:T+e};return S.push(i),b("Interval set to "+i.time),i},window.clearInterval=function(t){return b("clear Interval"),null},window.requestAnimationFrame=function(t){W.push(t)},window.performance.now=function(){return F};try{Object.defineProperty(HTMLVideoElement.prototype,"currentTime",{get:t}),Object.defineProperty(HTMLAudioElement.prototype,"currentTime",{get:t})}catch(t){b(t)}}function i(){e(),D.start(),R=!0}function n(){R=!1,D.stop(),l()}function r(t,e){Z(t,0,e)}function d(){r(y)}function l(){b("Capturer stop"),window.setTimeout=Z,window.setInterval=J,window.clearInterval=Y,window.clearTimeout=$,window.requestAnimationFrame=Q,window.Date.prototype.getTime=et,window.Date.now=X,window.performance.now=tt}function c(){var t=C/M.framerate;(M.frameLimit&&C>=M.frameLimit||M.timeLimit&&t>=M.timeLimit)&&(n(),v());var e=new Date(null);e.setSeconds(t),M.motionBlurFrames>2?j.textContent="CCapture "+M.format+" | "+C+" frames ("+O+" inter) | "+e.toISOString().substr(11,8):j.textContent="CCapture "+M.format+" | "+C+" frames | "+e.toISOString().substr(11,8)}function p(t){N.width===t.width&&N.height===t.height||(N.width=t.width,N.height=t.height,z=new Uint16Array(N.height*N.width*4),V.fillStyle="#0",V.fillRect(0,0,N.width,N.height))}function m(t){V.drawImage(t,0,0),q=V.getImageData(0,0,N.width,N.height);for(var e=0;e2?(p(t),m(t),O>=.5*M.motionBlurFrames?w():d()):(D.add(t),C++,b("Full Frame! "+C)))}function y(){var t=1e3/M.framerate,e=(C+O/M.motionBlurFrames)*t;T=U+e,F=I+e,it.forEach(function(t){t._hookedTime=e/1e3}),c(),b("Frame: "+C+" "+O);for(var i=0;i<_.length;i++)T>=_[i].triggerTime&&(r(_[i].callback),_.splice(i,1));for(var i=0;i=S[i].triggerTime&&(r(S[i].callback),S[i].triggerTime+=S[i].time);W.forEach(function(t){r(t,T-k)}),W=[]}function v(t){t||(t=function(t){return download(t,D.filename+D.extension,D.mimeType),!1}),D.save(t)}function b(t){A&&console.log(t)}function B(t,e){P[t]=e}function x(t){var e=P[t];e&&e.apply(null,Array.prototype.slice.call(arguments,1))}function E(t){x("progress",t)}var A,L,T,U,F,I,d,D,M=t||{},_=(new Date,[]),S=[],C=0,O=0,W=[],R=!1,P={};M.framerate=M.framerate||60,M.motionBlurFrames=2*(M.motionBlurFrames||1),A=M.verbose||!1,L=M.display||!1,M.step=1e3/M.framerate,M.timeLimit=M.timeLimit||0,M.frameLimit=M.frameLimit||0,M.startTime=M.startTime||0;var j=document.createElement("div");j.style.position="absolute",j.style.left=j.style.top=0,j.style.backgroundColor="black",j.style.fontFamily="monospace",j.style.fontSize="11px",j.style.padding="5px",j.style.color="red",j.style.zIndex=1e5,M.display&&document.body.appendChild(j);var z,q,N=document.createElement("canvas"),V=N.getContext("2d");b("Step is set to "+M.step+"ms");var G={gif:u,webm:s,ffmpegserver:h,png:o,jpg:a,"webm-mediarecorder":f},H=G[M.format];if(!H)throw"Error: Incorrect or missing format: Valid formats are "+Object.keys(G).join(", ");if(D=new H(M),D.step=d,D.on("process",y),D.on("progress",E),"performance"in window==0&&(window.performance={}),Date.now=Date.now||function(){return(new Date).getTime()},"now"in window.performance==0){var K=Date.now();performance.timing&&performance.timing.navigationStart&&(K=performance.timing.navigationStart),window.performance.now=function(){return Date.now()-K}}var Z=window.setTimeout,J=window.setInterval,Y=window.clearInterval,$=window.clearTimeout,Q=window.requestAnimationFrame,X=window.Date.now,tt=window.performance.now,et=window.Date.prototype.getTime,it=[];return{start:i,capture:g,stop:n,save:v,on:B}}var l={function:!0,object:!0},c=(parseFloat,parseInt,l[typeof exports]&&exports&&!exports.nodeType?exports:void 0),p=l[typeof module]&&module&&!module.nodeType?module:void 0,m=p&&p.exports===c?c:void 0,w=t(c&&p&&"object"==typeof global&&global),g=t(l[typeof self]&&self),y=t(l[typeof window]&&window),v=t(l[typeof this]&&this),b=w||y!==(v&&v.window)&&y||g||v||Function("return this")();"gc"in window||(window.gc=function(){}),HTMLCanvasElement.prototype.toBlob||Object.defineProperty(HTMLCanvasElement.prototype,"toBlob",{value:function(t,e,i){for(var n=atob(this.toDataURL(e,i).split(",")[1]),r=n.length,o=new Uint8Array(r),a=0;a0&&this.frames/this.settings.framerate>=this.settings.autoSaveTime?this.save(function(t){this.filename=this.baseFilename+"-part-"+e(this.part),download(t,this.filename+this.extension,this.mimeType);var i=this.count;this.dispose(),this.count=i+1,this.part++,this.filename=this.baseFilename+"-part-"+e(this.part),this.frames=0,this.step()}.bind(this)):(this.count++,this.frames++,this.step())}.bind(this),i.readAsArrayBuffer(t)},r.prototype.save=function(t){t(this.tape.save())},r.prototype.dispose=function(){this.tape=new Tar,this.count=0},o.prototype=Object.create(r.prototype),o.prototype.add=function(t){t.toBlob(function(t){r.prototype.add.call(this,t)}.bind(this),this.type)},a.prototype=Object.create(r.prototype),a.prototype.add=function(t){t.toBlob(function(t){r.prototype.add.call(this,t)}.bind(this),this.type,this.quality)},s.prototype=Object.create(n.prototype),s.prototype.start=function(t){this.dispose()},s.prototype.add=function(t){this.videoWriter.addFrame(t),this.settings.autoSaveTime>0&&this.frames/this.settings.framerate>=this.settings.autoSaveTime?this.save(function(t){this.filename=this.baseFilename+"-part-"+e(this.part),download(t,this.filename+this.extension,this.mimeType),this.dispose(),this.part++,this.filename=this.baseFilename+"-part-"+e(this.part),this.step()}.bind(this)):(this.frames++,this.step())},s.prototype.save=function(t){this.videoWriter.complete().then(t)},s.prototype.dispose=function(t){this.frames=0,this.videoWriter=new WebMWriter({quality:this.quality,fileWriter:null,fd:null,frameRate:this.framerate})},h.prototype=Object.create(n.prototype),h.prototype.start=function(){this.encoder.start(this.settings)},h.prototype.add=function(t){this.encoder.add(t)},h.prototype.save=function(t){this.callback=t,this.encoder.end()},h.prototype.safeToProceed=function(){return this.encoder.safeToProceed()},f.prototype=Object.create(n.prototype),f.prototype.add=function(t){this.stream||(this.stream=t.captureStream(this.framerate),this.mediaRecorder=new MediaRecorder(this.stream),this.mediaRecorder.start(),this.mediaRecorder.ondataavailable=function(t){this.chunks.push(t.data)}.bind(this)),this.step()},f.prototype.save=function(t){this.mediaRecorder.onstop=function(e){var i=new Blob(this.chunks,{type:"video/webm"});this.chunks=[],t(i)}.bind(this),this.mediaRecorder.stop()},u.prototype=Object.create(n.prototype),u.prototype.add=function(t){this.sizeSet||(this.encoder.setOption("width",t.width),this.encoder.setOption("height",t.height),this.sizeSet=!0),this.canvas.width=t.width,this.canvas.height=t.height,this.ctx.drawImage(t,0,0),this.encoder.addFrame(this.ctx,{copy:!0,delay:this.settings.step}),this.step()},u.prototype.save=function(t){this.callback=t,this.encoder.render()},(y||g||{}).CCapture=d,"function"==typeof define&&"object"==typeof define.amd&&define.amd?define(function(){return d}):c&&p?(m&&((p.exports=d).CCapture=d),c.CCapture=d):b.CCapture=d}(); -------------------------------------------------------------------------------- /cellular-tile.glsl: -------------------------------------------------------------------------------- 1 | // Source: https://www.shadertoy.com/view/MscSDB by Shane 2 | // The cellular tile routine. Draw a few objects (four spheres, in this case) using a minumum 3 | // blend at various 3D locations on a cubic tile. Make the tile wrappable by ensuring the 4 | // objects wrap around the edges. That's it. 5 | // 6 | // Believe it or not, you can get away with as few as three spheres. If you sum the total 7 | // instruction count here, you'll see that it's way, way lower than 2nd order 3D Voronoi. 8 | // Not requiring a hash function provides the biggest benefit, but there is also less setup. 9 | // 10 | // The result isn't perfect, but 3D cellular tiles can enable you to put a Voronoi looking 11 | // surface layer on a lot of 3D objects for little cost. 12 | // 13 | float drawSphere(in vec3 p){ 14 | 15 | // Anything that wraps the domain will suffice, so any of the following will work. 16 | 17 | // // Angular regions 18 | // p *= 1.5; 19 | // p = 2. * abs(mod(p, 1.) - 0.5); 20 | // return 0.25 * dot(p, p); 21 | 22 | // p = cos(p*3.14159)*0.5; 23 | // p = abs(cos(p*3.14159)*0.5); 24 | // p = fract(p)-.5; 25 | // return dot(p, p); 26 | 27 | // // Other metrics to try. 28 | // p = abs(fract(p)-.5); 29 | // return dot(p, vec3(.5)); 30 | 31 | // p = abs(fract(p)-.5); 32 | // return max(max(p.x, p.y), p.z); 33 | 34 | p = abs(fract(p)-.5); 35 | p = cos(p*3.14159)*0.5; 36 | p = abs(cos(p*3.14159)*0.5); 37 | return max(max(p.x - p.y, p.y - p.z), p.z - p.x); 38 | // return min(min(p.x - p.y, p.y - p.z), p.z - p.x); 39 | 40 | } 41 | float drawSphere(in vec4 p){ 42 | 43 | // Anything that wraps the domain will suffice, so any of the following will work. 44 | // p = cos(p*3.14159)*0.5; 45 | // p = abs(cos(p*3.14159)*0.5); 46 | // p = fract(p)-.5; 47 | // return dot(p, p); 48 | 49 | // Other metrics to try. 50 | // p = abs(fract(p)-.5); 51 | // return dot(p, vec3(.5)); 52 | 53 | p = abs(fract(p)-.5); 54 | return max(max(p.x, p.y), max(p.z, p.w)); 55 | 56 | p = abs(fract(p)-.5); 57 | p = cos(p*3.14159)*0.5; 58 | p = abs(cos(p*3.14159)*0.5); 59 | return max(max(p.x - p.y, p.y - p.z), max(p.z - p.w, p.w - p.x)); 60 | // return min(min(p.x - p.y, p.y - p.z), p.z - p.x); 61 | } 62 | 63 | // Faster (I'm assuming), more streamlined version. See the comments below for an expanded explanation. 64 | // The function below is pretty quick also, and can be expanded to include more spheres. This one 65 | // takes advantage of the fact that only four object need sorting. With three spheres, it'd be even 66 | // better. 67 | float cellTile(in vec3 p) { 68 | // Draw four overlapping objects (spheres, in this case) at various positions throughout the tile. 69 | vec4 v, d; 70 | d.x = drawSphere(p - vec3(.81, .62, .53)); 71 | p.xy = vec2(p.y-p.x, p.y + p.x)*.7071; 72 | d.y = drawSphere(p - vec3(.39, .2, .11)); 73 | p.yz = vec2(p.z-p.y, p.z + p.y)*.7071; 74 | d.z = drawSphere(p - vec3(.62, .24, .06)); 75 | p.xz = vec2(p.z-p.x, p.z + p.x)*.7071; 76 | d.w = drawSphere(p - vec3(.2, .82, .64)); 77 | 78 | v.xy = min(d.xz, d.yw), v.z = min(max(d.x, d.y), max(d.z, d.w)), v.w = max(v.x, v.y); 79 | 80 | d.x = min(v.z, v.w) - min(v.x, v.y); // Maximum minus second order, for that beveled Voronoi look. Range [0, 1]. 81 | //d.x = min(v.x, v.y); 82 | return (d.x*2.66); // Normalize... roughly. 83 | } 84 | 85 | float cellTile(in vec4 p) { 86 | // Draw four overlapping objects (spheres, in this case) at various positions throughout the tile. 87 | vec4 v, d; 88 | d.x = drawSphere(p - vec4(.81, .62, .53, 0)); 89 | p.xy = vec2(p.y-p.x, p.y + p.x)*.7071; 90 | d.y = drawSphere(p - vec4(.39, .2, .11, 0)); 91 | p.yz = vec2(p.z-p.y, p.z + p.y)*.7071; 92 | d.z = drawSphere(p - vec4(.62, .24, .06, 0)); 93 | p.xz = vec2(p.z-p.x, p.z + p.x)*.7071; 94 | d.w = drawSphere(p - vec4(.2, .82, .64, 0)); 95 | 96 | v.xy = min(d.xz, d.yw), v.z = min(max(d.x, d.y), max(d.z, d.w)), v.w = max(v.x, v.y); 97 | 98 | d.x = min(v.z, v.w) - min(v.x, v.y); // Maximum minus second order, for that beveled Voronoi look. Range [0, 1]. 99 | //d.x = min(v.x, v.y); 100 | return (d.x*2.66); // Normalize... roughly. 101 | } 102 | 103 | #pragma glslify: export(cellTile) 104 | -------------------------------------------------------------------------------- /color-map/hsb2rgb.glsl: -------------------------------------------------------------------------------- 1 | // IQ's 2 | vec3 hsb2rgb( in vec3 c ){ 3 | vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 4 | 6.0)-3.0)-1.0, 5 | 0.0, 6 | 1.0 ); 7 | rgb = rgb*rgb*(3.0-2.0*rgb); 8 | return c.z * mix(vec3(1.0), rgb, c.y); 9 | } 10 | #pragma glslify: export(hsb2rgb) 11 | -------------------------------------------------------------------------------- /color-map/rainbow.glsl: -------------------------------------------------------------------------------- 1 | // source: 2 | // https://github.com/kbinani/glsl-colormap/blob/61886bd6e0dbaae1b0112b8a713b88a5248e9878/shaders/IDL_Rainbow.frag 3 | 4 | float colormap_red(float x) { 5 | if (x < 100.0) { 6 | return (-9.55123422981038E-02 * x + 5.86981763554179E+00) * x - 3.13964093701986E+00; 7 | } else { 8 | return 5.25591836734694E+00 * x - 8.32322857142857E+02; 9 | } 10 | } 11 | 12 | float colormap_green(float x) { 13 | if (x < 150.0) { 14 | return 5.24448979591837E+00 * x - 3.20842448979592E+02; 15 | } else { 16 | return -5.25673469387755E+00 * x + 1.34195877551020E+03; 17 | } 18 | } 19 | 20 | float colormap_blue(float x) { 21 | if (x < 80.0) { 22 | return 4.59774436090226E+00 * x - 2.26315789473684E+00; 23 | } else { 24 | return -5.25112244897959E+00 * x + 8.30385102040816E+02; 25 | } 26 | } 27 | 28 | vec4 colormap(float x) { 29 | float t = x * 255.0; 30 | float r = clamp(colormap_red(t) / 255.0, 0.0, 1.0); 31 | float g = clamp(colormap_green(t) / 255.0, 0.0, 1.0); 32 | float b = clamp(colormap_blue(t) / 255.0, 0.0, 1.0); 33 | return vec4(r, g, b, 1.0); 34 | } 35 | 36 | #pragma glslify: export(colormap) 37 | -------------------------------------------------------------------------------- /cross-hatching.glsl: -------------------------------------------------------------------------------- 1 | // source: 2 | // http://learningwebgl.com/blog/?p=2858 3 | float crossHatching (in vec3 color) { 4 | float v = 1.0; 5 | float l = color.x; 6 | 7 | float period = 0.021; 8 | float limit = period * 0.2; 9 | if (mod(dot(fragCoord.xy, vec2(1)), period) <= limit && l > 1.0) { 10 | v = 0.0; 11 | } 12 | if (mod(dot(fragCoord.xy, vec2(1, -1)), period) <= limit && l > 0.75) { 13 | v = 0.0; 14 | } 15 | if (mod(dot(fragCoord.xy, vec2(1)) - 5.0, period) <= limit && l > 0.5) { 16 | v = 0.0; 17 | } 18 | if (mod(dot(fragCoord.xy, vec2(1, -1)) - 5.0, period) <= limit && l > 0.3465) { 19 | v = 0.0; 20 | } 21 | 22 | return 1.0 - v; 23 | } 24 | 25 | #pragma glslify: export(crossHatching) 26 | -------------------------------------------------------------------------------- /dampening.js: -------------------------------------------------------------------------------- 1 | export function dampen (prev, next, factor) { 2 | return (1 - factor) * prev + factor * next 3 | } 4 | -------------------------------------------------------------------------------- /de-map.glsl: -------------------------------------------------------------------------------- 1 | vec3 de (in vec3 x, in float endt, in float dt) { 2 | float t = 0.0; 3 | 4 | for (int i = 0; i < MAX_IT; i ++) { 5 | if (t >= endt) break; 6 | 7 | x = field(x, dt); 8 | 9 | t += dt; 10 | } 11 | 12 | return x; 13 | } 14 | 15 | #pragma glslify: export(de) 16 | -------------------------------------------------------------------------------- /de/chen.glsl: -------------------------------------------------------------------------------- 1 | vec3 chen (in vec3 p, in float dt) { 2 | const float a = 40.0; 3 | const float b = 28.0; 4 | const float c = 3.0; 5 | 6 | float dxdt = dot(p.yx, vec2(a, -a)); 7 | float dydt = (c - a) * p.x - p.x * p.z + c * p.y; 8 | float dzdt = p.x * p.y - b * p.z; 9 | 10 | return p + dt * vec3(dxdt, dydt, dzdt); 11 | } 12 | 13 | #pragma glslify: export(chen) 14 | -------------------------------------------------------------------------------- /de/lorenz.glsl: -------------------------------------------------------------------------------- 1 | vec3 lorenz (in vec3 p, in float dt) { 2 | const float r = 28.0; 3 | const float s = 10.0; 4 | const float b = 2.666667; // 8/3 5 | 6 | float dxdt = s * dot(p.yx, vec2(1.0, -1.0)); 7 | float dydt = p.x * ( r - p.z ) - p.y; 8 | float dzdt = p.x * p.y - b * p.z; 9 | 10 | return p + dt * vec3(dxdt, dydt, dzdt); 11 | } 12 | 13 | #pragma glslify: export(lorenz) 14 | -------------------------------------------------------------------------------- /de/lui-chen.glsl: -------------------------------------------------------------------------------- 1 | vec3 equation (in vec3 p, in float dt) { 2 | const float a = 2.4; 3 | const float b = -3.78; 4 | const float c = 14.0; 5 | const float d = -11.0; 6 | const float e = 4.0; 7 | const float f = 5.58; 8 | const float r = 1.0; 9 | 10 | float dxdt = dot(p.yx, vec2(a, b)) + c * p.y * p.z; 11 | float dydt = dot(p.yz, vec2(d, -1)) + e * p.x * p.z; 12 | float dzdt = f * p.z + r * p.x * p.y; 13 | 14 | return p + dt * vec3(dxdt, dydt, dzdt); 15 | } 16 | 17 | #pragma glslify: export(equation) 18 | -------------------------------------------------------------------------------- /debug-color-clip.glsl: -------------------------------------------------------------------------------- 1 | void debugColor (inout vec3 color) { 2 | #ifdef enable 3 | if (color == vec3(0.)) { 4 | color = vec3(1., 0., 1.); 5 | } 6 | if (color == vec3(1.)) { 7 | color = vec3(0., 1., 0.); 8 | } 9 | #endif 10 | } 11 | 12 | #pragma glslify: export(debugColor) 13 | -------------------------------------------------------------------------------- /default-scene-renderer.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import drawTriangle from 'a-big-triangle' 3 | 4 | class DefaultSceneRenderer { 5 | constructor (gl) { 6 | assert(gl, 'A gl context is required') 7 | this.gl = gl 8 | this.width = window.innerWidth 9 | this.height = window.innerHeight 10 | } 11 | 12 | resize (width, height) { 13 | this.width = width 14 | this.height = height 15 | } 16 | 17 | render (shader, t) { 18 | let { width, height, gl } = this 19 | shader.uniforms.resolution = [width, height] 20 | gl.viewport(0, 0, width, height) 21 | 22 | var projectionMatrix = this.projectionMatrix(80, 1, 1) 23 | shader.uniforms.projectionMatrix = projectionMatrix 24 | 25 | drawTriangle(gl) 26 | } 27 | 28 | projectionMatrix (fov, aspect, zoom) { 29 | const near = 0.01 30 | const far = 10000 31 | const DEG2RAD = Math.PI / 180 32 | 33 | const skew = 0 34 | const filmGauge = 35 35 | 36 | var top = near * Math.tan( 37 | DEG2RAD * 0.5 * fov) / zoom 38 | 39 | var height = 2 * top 40 | var width = aspect * height 41 | var left = -0.5 * width 42 | 43 | const filmWidth = filmGauge * Math.min(aspect, 1) 44 | if (skew !== 0) left += near * skew / filmWidth 45 | 46 | return this.makeFrustum(left, left + width, top - height, top, near, far) 47 | } 48 | 49 | makeFrustum (left, right, bottom, top, near, far) { 50 | var te = [] 51 | var x = 2 * near / (right - left) 52 | var y = 2 * near / (top - bottom) 53 | 54 | var a = (right + left) / (right - left) 55 | var b = (top + bottom) / (top - bottom) 56 | var c = -(far + near) / (far - near) 57 | var d = -2 * far * near / (far - near) 58 | 59 | te[0] = x 60 | te[4] = 0 61 | te[8] = a 62 | te[12] = 0 63 | 64 | te[1] = 0 65 | te[5] = y 66 | te[9] = b 67 | te[13] = 0 68 | 69 | te[2] = 0 70 | te[6] = 0 71 | te[10] = c 72 | te[14] = d 73 | 74 | te[3] = 0 75 | te[7] = 0 76 | te[11] = -1 77 | te[15] = 0 78 | 79 | return te 80 | } 81 | } 82 | 83 | module.exports = DefaultSceneRenderer 84 | -------------------------------------------------------------------------------- /dispersion-ray-direction.glsl: -------------------------------------------------------------------------------- 1 | float chunkedHueIOR (in float hue, in float greenIOR, in float n1, in float between) { 2 | float redIORRatio = n1/(greenIOR - 1.0 * between); 3 | float yellowIORRatio = n1/(greenIOR - 0.5 * between); 4 | float greenIORRatio = n1/greenIOR; 5 | float cyanIORRatio = n1/(greenIOR + 0.5 * between); 6 | float blueIORRatio = n1/(greenIOR + 1.0 * between); 7 | float purpleIORRatio = n1/(greenIOR + 1.5 * between); 8 | 9 | float ior = mix(redIORRatio, yellowIORRatio, smoothstep(0.0, 60.0, hue)); 10 | ior = mix(ior, greenIORRatio, smoothstep(60.0, 120.0, hue)); 11 | ior = mix(ior, cyanIORRatio, smoothstep(120.0, 180.0, hue)); 12 | ior = mix(ior, blueIORRatio, smoothstep(180.0, 240.0, hue)); 13 | ior = mix(ior, purpleIORRatio, smoothstep(240.0, 270.0, hue)); 14 | 15 | return ior; 16 | } 17 | 18 | #pragma glslify: export(chunkedHueIOR) 19 | -------------------------------------------------------------------------------- /dispersion/hue-to-ior-exponential.glsl: -------------------------------------------------------------------------------- 1 | float hue2IORExp (in float hue, in float greenIOR, in float n1, in float between) { 2 | float relPos = 0.5 / 360.0 * (hue - 180.0); 3 | float ior = n1/(greenIOR + 0.50 * sign(relPos) * pow(3.5, abs(relPos))); 4 | return ior; 5 | } 6 | 7 | #pragma glslify: export(hue2IORExp) 8 | -------------------------------------------------------------------------------- /dispersion/hue-to-ior-polynomial.glsl: -------------------------------------------------------------------------------- 1 | float hue2IORPolynomial (in float hue, in float greenIOR, in float n1, in float null) { 2 | float x = 0.0005556 * hue - 0.25002; // 1.0 / (360.0 * 2.0) * ( 0.4 * hue - 180.0); 3 | float between = 0.05 * (x + 4.0) * (x + 2.0) * (x + 1.0) * (x - 1.0) * (x - 3.0) + 2.0; 4 | float ior = n1/(greenIOR + between); 5 | return ior; 6 | } 7 | 8 | #pragma glslify: export(hue2IORPolynomial) 9 | -------------------------------------------------------------------------------- /dispersion/hue-to-ior-sigmoid.glsl: -------------------------------------------------------------------------------- 1 | float sigmoid ( in float x ) { 2 | const float L = 1.0; 3 | const float k = 1.0; 4 | const float x0 = 4.0; 5 | 6 | x *= 8.0; // Scale so x [0, 1] 7 | 8 | return L / ( 1.0 + exp(-k * (x - x0)) ); 9 | } 10 | 11 | float hue2IORSigmoid (in float hue, in float greenIOR, in float n1, in float between) { 12 | float relPos = sigmoid(hue * 0.002778); 13 | float ior = n1/(greenIOR + relPos * between); 14 | return ior; 15 | } 16 | 17 | #pragma glslify: export(hue2IORSigmoid) 18 | -------------------------------------------------------------------------------- /dodec.glsl: -------------------------------------------------------------------------------- 1 | #define GDFVector0 vec3(1, 0, 0) 2 | #define GDFVector1 vec3(0, 1, 0) 3 | #define GDFVector2 vec3(0, 0, 1) 4 | 5 | #define GDFVector3 normalize(vec3(1, 1, 1 )) 6 | #define GDFVector4 normalize(vec3(-1, 1, 1)) 7 | #define GDFVector5 normalize(vec3(1, -1, 1)) 8 | #define GDFVector6 normalize(vec3(1, 1, -1)) 9 | 10 | #define GDFVector7 normalize(vec3(0, 1, PHI+1.)) 11 | #define GDFVector8 normalize(vec3(0, -1, PHI+1.)) 12 | #define GDFVector9 normalize(vec3(PHI+1., 0, 1)) 13 | #define GDFVector10 normalize(vec3(-PHI-1., 0, 1)) 14 | #define GDFVector11 normalize(vec3(1, PHI+1., 0)) 15 | #define GDFVector12 normalize(vec3(-1, PHI+1., 0)) 16 | 17 | #define GDFVector13 normalize(vec3(0, PHI, 1)) 18 | #define GDFVector13b normalize(vec3(0, PHI, -1)) 19 | #define GDFVector14 normalize(vec3(0, -PHI, 1)) 20 | #define GDFVector14b normalize(vec3(0, -PHI, -1)) 21 | #define GDFVector15 normalize(vec3(1, 0, PHI)) 22 | #define GDFVector15b normalize(vec3(1, 0, -PHI)) 23 | #define GDFVector16 normalize(vec3(-1, 0, PHI)) 24 | #define GDFVector16b normalize(vec3(-1, 0, -PHI)) 25 | #define GDFVector17 normalize(vec3(PHI, 1, 0)) 26 | #define GDFVector17b normalize(vec3(PHI, -1, 0)) 27 | #define GDFVector18 normalize(vec3(-PHI, 1, 0)) 28 | #define GDFVector18b normalize(vec3(-PHI, -1, 0)) 29 | 30 | #define fGDFBegin float d = 0.; 31 | 32 | // Version with variable exponent. 33 | // This is slow and does not produce correct distances, but allows for bulging of objects. 34 | #define fGDFExp(v) d += pow(abs(dot(p, v)), e); 35 | 36 | // Version with without exponent, creates objects with sharp edges and flat faces 37 | #define fGDF(v) d = max(d, abs(dot(p, v))); 38 | 39 | #define fGDFExpEnd return pow(d, 1./e) - r; 40 | #define fGDFEnd return d - r; 41 | 42 | // Primitives follow: 43 | 44 | float fDodecahedron(vec3 p, float r) { 45 | fGDFBegin 46 | fGDF(GDFVector13) fGDF(GDFVector14) fGDF(GDFVector15) fGDF(GDFVector16) 47 | fGDF(GDFVector17) fGDF(GDFVector18) 48 | fGDFEnd 49 | } 50 | 51 | #pragma glslify: export(fDodecahedron) 52 | -------------------------------------------------------------------------------- /dodecahedron.glsl: -------------------------------------------------------------------------------- 1 | #ifndef Iterations 2 | #define Iterations 10 3 | #endif 4 | 5 | float trapCalc (in vec3 p, in float k) { 6 | return dot(p, p) / (k * k); 7 | } 8 | 9 | #pragma glslify: dodecahedronFold = require(./folds/dodecahedron-fold, Iterations=1, kifsM=kifsM) 10 | #pragma glslify: octahedronFold = require(./folds/octahedron-fold, Iterations=1, kifsM=kifsM, trapCalc=trapCalc) 11 | 12 | vec2 dodecasierpinski(in vec3 p) { 13 | float minD = 10000.; 14 | 15 | for (int i = 0; i < Iterations; i++) { 16 | p = dodecahedronFold(p, minD); 17 | p = octahedronFold(p, minD); 18 | } 19 | 20 | return vec2((sqrt(length(p)) - 2.0) * pow(scale, -float(Iterations * 2)), minD); 21 | } 22 | 23 | #pragma glslify: export(dodecasierpinski) 24 | -------------------------------------------------------------------------------- /effects/god-rays-poisson.glsl: -------------------------------------------------------------------------------- 1 | #ifdef saturate 2 | // saturate is being scoped to the module so alias 3 | #define saturate_0(x) saturate(x) 4 | #else 5 | #define saturate(x) clamp(x, 0.0, 1.0) 6 | #endif 7 | 8 | // #ifdef maxDistance 9 | // // maxDistance is being scoped to the module so alias 10 | // #define maxDistance_0(x) maxDistance(x) 11 | // #else 12 | // #define maxDistance(x) 8.0 13 | // #endif 14 | 15 | // source: https://www.shadertoy.com/view/ml2GWc 16 | 17 | // Parameters 18 | #define NUM_STEPS 16 19 | // Dithering to smooth volume rays 20 | #define DITHERING 21 | 22 | // Lighting 23 | #define VOLUME_DENSITY 1.0 24 | #define VOLUME_ABSORBTION 0.5 25 | 26 | // light function 27 | // return the direction and the length of the light vector 28 | vec4 getLight(vec3 lightPos, vec3 p) { 29 | vec3 lig = lightPos - p; // light vector 30 | float l = length(lig); // length of the light vector 31 | lig = normalize(lig); // normalize it 32 | return vec4(lig,l); 33 | } 34 | 35 | // volume rendering 36 | // depth is the depth buffer (distance to the scene) 37 | vec4 renderVolume(vec3 ro, vec3 rd, in vec2 uv, float depth, in float time) { 38 | float tmax = min(10., depth); // max distance 39 | 40 | vec4 volumeColorSum = vec4(vec3(0), 1); // color and opacity 41 | 42 | float stepSize = tmax / float(NUM_STEPS); // step size 43 | float t = 0.; // distance travelled 44 | 45 | #ifdef DITHERING 46 | t += stepSize * noise(uv); 47 | #endif 48 | 49 | for (int i = 0; i < NUM_STEPS; i++) { // raymarching loop 50 | vec3 p = ro + rd * t; // current point 51 | float h = VOLUME_DENSITY * volumeNoise(8.*p); // density of the fog 52 | 53 | // lighting 54 | vec4 lighting = getLight(sunPos, p); // light direction & length of the light vector 55 | 56 | float sha = shadow(p, lighting.xyz, 0., lighting.w, time); // shadow of the fractal (god rays) 57 | 58 | // coloring 59 | vec3 col = sunColor(p) * sha / (lighting.w*lighting.w); // inverse square law 60 | 61 | volumeColorSum.rgb += h * stepSize * volumeColorSum.a * col; // add the color to the final result 62 | volumeColorSum.a *= exp(-h * stepSize * VOLUME_ABSORBTION); // beer's law 63 | 64 | if (volumeColorSum.a < .01) break; // optimization 65 | t += stepSize; // march 66 | } 67 | 68 | // output 69 | return volumeColorSum; 70 | } 71 | 72 | vec4 godRays ( in vec3 rayOrigin, in vec3 rayDirection, in vec4 t, in vec2 uv, in vec4 color, in float generalT) { 73 | vec4 volume = renderVolume(rayOrigin, rayDirection, uv, t.x, generalT); 74 | 75 | // // TODO Figure out how to factor in alpha for background 76 | color.rgb = color.rgb * volume.a + volume.rgb; 77 | 78 | // color = vec4(volume.rgb, 1.); 79 | 80 | // color = mix(color, volume, volume.a); 81 | // color += volume; 82 | 83 | return color; 84 | } 85 | 86 | #pragma glslify: export(godRays) 87 | -------------------------------------------------------------------------------- /effects/god-rays.glsl: -------------------------------------------------------------------------------- 1 | #ifdef saturate 2 | // saturate is being scoped to the module so alias 3 | #define saturate_0(x) saturate(x) 4 | #else 5 | #define saturate(x) clamp(x, 0.0, 1.0) 6 | #endif 7 | 8 | vec4 godRays ( in vec3 rayOrigin, in vec3 rayDirection, in vec4 t, in vec2 uv, in vec4 color, in float generalT) { 9 | const float maxGodRaySteps = 2.; 10 | 11 | // TODO try with gPos 12 | vec3 pos = rayOrigin + rayDirection * t.x; 13 | 14 | float godRayGlowIntensity = 1.; 15 | float godRayMask = 1.; // saturate(pow(1. - dot(uv, uv), 3. * (1. - godRayGlowIntensity))); 16 | if (godRayMask == 0.) { 17 | return color; 18 | } 19 | 20 | // Replace this with t.y or something 21 | float distanceToPos = distance(rayOrigin, pos); 22 | vec3 beamStep = rayDirection * distanceToPos / maxGodRaySteps; 23 | 24 | float illumination = 0.; 25 | float jitter = 0.0; // 0.05 * noise(uv); 26 | for (float i = 0.; i < maxGodRaySteps; i++) { 27 | vec3 samplePoint = rayOrigin + beamStep * (i + jitter); // Jitter so smooth sampling point. 28 | float shadow = softshadow(samplePoint, normalize(sunPos), 0.01, 1.0, generalT); 29 | // shadow = max(0.1, shadow); 30 | // illumination += saturate(shadow); // / (distance(rayOrigin, sunPos)); 31 | illumination += shadow; // / (distance(rayOrigin, sunPos)); 32 | } 33 | 34 | // illumination /= sqrt(maxGodRaySteps); 35 | // illumination /= pow(maxGodRaySteps, 0.85); 36 | // illumination *= 0.2 + 0.8 * godRayGlowIntensity; 37 | illumination *= godRayMask; 38 | 39 | float existingAlpha = color.a; 40 | // illumination = pow(illumination, 1. - 0.3 * (1. - existingAlpha)); 41 | // color = mix(color, vec4(sunColor, 1.), illumination); 42 | // color = vec4(vec3(illumination), 1.); 43 | // color += vec4(sunColor, 1.) * illumination; 44 | 45 | // Debug : Show only illumination value 46 | color = vec4(sunColor * illumination, 1.); 47 | 48 | return color; 49 | } 50 | 51 | #pragma glslify: export(godRays) 52 | -------------------------------------------------------------------------------- /env.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejeunerenard/raymarching-experiments/c6e629958fd5d4474c519f977752574bc08e3a01/env.jpg -------------------------------------------------------------------------------- /final-pass.glsl: -------------------------------------------------------------------------------- 1 | #define PI 3.1415926536 2 | #define TWO_PI 6.2831853072 3 | #define saturate(x) clamp(x, 0.0, 1.0) 4 | 5 | precision highp float; 6 | 7 | varying vec2 fragCoord; 8 | uniform vec2 resolution; 9 | uniform float time; 10 | uniform sampler2D base; 11 | uniform sampler2D buffer; 12 | uniform sampler2D prevBuffer; 13 | uniform float wet; 14 | 15 | uniform vec3 colors1; 16 | uniform vec3 colors2; 17 | 18 | #pragma glslify: import(./time) 19 | const float edge = 0.0024; 20 | 21 | #pragma glslify: cnoise2 = require(glsl-noise/classic/2d) 22 | #pragma glslify: import(./background) 23 | 24 | void colorMap (inout vec3 color) { 25 | float l = length(vec4(color, 1.)); 26 | // Light 27 | color = mix(#00b3c8, color, 1. - l * .05125); 28 | // Dark 29 | color = mix(#AF6747, color, clamp(exp(l) * .255, 0., 1.)); 30 | } 31 | 32 | void main() { 33 | modT = mod(time, totalT); 34 | norT = modT / totalT; 35 | cosT = TWO_PI / totalT * modT; 36 | const vec3 gamma = vec3(2.2); 37 | const vec3 gammaEnc = vec3(0.454545); 38 | 39 | vec2 uv = vec2(gl_FragCoord.xy / resolution.xy); 40 | vec2 uvBackground = fragCoord.xy; 41 | background = getBackground(uvBackground, step(0.5, norT)); 42 | 43 | vec4 baseColor = texture2D(base, uv); 44 | baseColor.rgb = pow(baseColor.rgb, gamma); 45 | 46 | vec4 bufferColor = texture2D(buffer, uv); 47 | bufferColor.rgb = pow(bufferColor.rgb, gamma); 48 | 49 | // uv = uvBackground; 50 | vec2 coord = uv - 0.5; // 2.0 * (uv - 0.5); 51 | vec2 norm = coord; 52 | norm *= 0.950; 53 | coord = norm + 0.5; 54 | // coord -= 0.001 * sin(vec2(cosT) + vec2(0, PI * 0.5)); 55 | 56 | vec4 prevBufferColor = texture2D(prevBuffer, coord); 57 | prevBufferColor.rgb = pow(prevBufferColor.rgb, gamma); 58 | 59 | // Fade 60 | // prevBufferColor.a *= 0.9975; 61 | // prevBufferColor.a *= smoothstep(0., 0.001, prevBufferColor.a); 62 | 63 | vec4 result = mix(vec4(background, 1.), baseColor, baseColor.a); 64 | // vec4 result = mix(prevBufferColor, baseColor, baseColor.a); 65 | 66 | gl_FragColor = wet * bufferColor + result; 67 | // gl_FragColor = vec4(vec3(baseColor.a), 1); 68 | // if (norT > 0.2) { 69 | // gl_FragColor = vec4(prevBufferColor.rgb, 1); 70 | // } 71 | 72 | // gl_FragColor = vec4(coord, 0, 1); 73 | // if (abs(coord.x - 0.5) < 0.01) { 74 | // gl_FragColor = vec4(1, 0, 1, 1); 75 | // } 76 | // if (abs(coord.y - 0.5) < 0.01) { 77 | // gl_FragColor = vec4(0, 1, 1, 1); 78 | // } 79 | 80 | // gl_FragColor = vec4( (0.995 * (coord - 0.5)) + 0.5, 0, 1 ); 81 | 82 | // gl_FragColor = mix(vec4(background, 1.), result, max(bufferColor.a, baseColor.a)); 83 | // gl_FragColor = vec4(background + result.rgb, 1.); 84 | 85 | // // Post process 86 | // vec3 colorBefore = gl_FragColor.rgb; 87 | // colorMap(gl_FragColor.rgb); 88 | // gl_FragColor.rgb = mix(gl_FragColor.rgb, colorBefore, 0.7); 89 | 90 | // gl_FragColor.gb = pow(gl_FragColor.gb, vec2(1.1)); 91 | 92 | // Gamma encode 93 | // gl_FragColor.rgb = pow(gl_FragColor.rgb, gammaEnc); 94 | 95 | // 'Film' Noise 96 | // const float filmNoiseScale = 1.5; 97 | // gl_FragColor.rgb += 0.030 * (cnoise2(filmNoiseScale * 560. * uv + sin(uv + time)) + cnoise2(filmNoiseScale * 800. * uv + 253.5 * vec2(0., time))); 98 | } 99 | -------------------------------------------------------------------------------- /foldInv.glsl: -------------------------------------------------------------------------------- 1 | void foldInv (inout vec2 p) { 2 | if (p.x - p.y < 0.) { 3 | float x1 = p.y; 4 | p.y = p.x; 5 | p.x = x1; 6 | } 7 | } 8 | void bfold (inout vec2 p) { 9 | const vec2 n = vec2(1., -1.); 10 | p -= min(0., dot(p, n)) * n; 11 | } 12 | 13 | #pragma glslify: export(bfold) 14 | -------------------------------------------------------------------------------- /foldNd.glsl: -------------------------------------------------------------------------------- 1 | void foldNd (inout vec3 z, vec3 n1) { 2 | z-=2.0 * min(0.0, dot(z, n1)) * n1; 3 | } 4 | void foldNd (inout vec2 z, vec2 n1) { 5 | z-=2.0 * min(0.0, dot(z, n1)) * n1; 6 | } 7 | 8 | #pragma glslify: export(foldNd) 9 | -------------------------------------------------------------------------------- /folds.glsl: -------------------------------------------------------------------------------- 1 | void fold (inout vec2 p) { 2 | if (p.x + p.y < 0.) { 3 | float x1 = -p.y; 4 | p.y = -p.x; 5 | p.x = x1; 6 | } 7 | } 8 | 9 | #pragma glslify: export(fold) 10 | -------------------------------------------------------------------------------- /folds/dodecahedron-fold.glsl: -------------------------------------------------------------------------------- 1 | #define _PHI_ (1.+sqrt(5.))/2. 2 | 3 | #define _IVNORM_ (0.5/_PHI_) 4 | #define _PHI1_ (_PHI_*_IVNORM_) 5 | #define _1PHI_ (_IVNORM_) 6 | #define _PHI2_ (_PHI_*_PHI_*_IVNORM_) 7 | 8 | #define _IKVNORM_ 1. / sqrt(pow(_PHI_*(1.+_PHI_),2.) + pow(pow(_PHI_, 2.) - 1., 2.) + pow(1.+_PHI_, 2.)) 9 | #define _C1_ (_PHI_*(1.+_PHI_)*_IKVNORM_) 10 | #define _C2_ ((_PHI_*_PHI_-1.)*_IKVNORM_) 11 | #define _1C_ ((1.+_PHI_)*_IKVNORM_) 12 | 13 | #pragma glslify: foldNd = require(../foldNd) 14 | 15 | vec3 dodecahedronFold (in vec3 p, inout float minD) { 16 | for(int i=0; i < Iterations; i++) { 17 | p=abs(p); 18 | 19 | vec3 axis = vec3(_PHI2_, _1PHI_, -_PHI1_); 20 | foldNd(p, axis); 21 | 22 | axis = vec3(-_PHI1_, _PHI2_, _1PHI_); 23 | foldNd(p, axis); 24 | 25 | axis = vec3(_1PHI_, -_PHI1_, _PHI2_); 26 | foldNd(p, axis); 27 | 28 | axis = vec3(-_C1_, _C2_, _1C_); 29 | foldNd(p, axis); 30 | 31 | axis = vec3(_1C_, -_C1_, _C2_); 32 | foldNd(p, axis); 33 | 34 | // Stretch 35 | p = (vec4(p, 1.) * kifsM).xyz; 36 | 37 | minD = min(length(p), minD); 38 | } 39 | 40 | return p; 41 | } 42 | 43 | #pragma glslify: export(dodecahedronFold) 44 | -------------------------------------------------------------------------------- /folds/half-tetrahedral.glsl: -------------------------------------------------------------------------------- 1 | vec3 fold (in vec3 q) { 2 | if (dot(q.xy, vec2(1)) < 0.) q.yx = vec2(-1) * q.xy; 3 | if (dot(q.xz, vec2(1)) < 0.) q.zx = vec2(-1) * q.xz; 4 | if (dot(q.yz, vec2(1)) < 0.) q.zy = vec2(-1) * q.yz; 5 | 6 | return q; 7 | } 8 | 9 | #pragma glslify: export(fold) 10 | -------------------------------------------------------------------------------- /folds/mandelbox-fold.glsl: -------------------------------------------------------------------------------- 1 | vec4 mandelboxFold (in vec4 z, inout float minD) { 2 | vec4 z0 = vec4(z.xyz, 1.); 3 | 4 | float minRadius2 = minRadius * minRadius; 5 | vec4 scalevec = vec4(s, s, s, abs(s)) / minRadius2; 6 | 7 | for (int i = 0; i < trap; i++) { 8 | // Box Fold 9 | z.xyz = clamp(z.xyz, -foldLimit, foldLimit) * 2. - z.xyz; 10 | 11 | // Ball fold 12 | float r2 = dot(z.xyz, z.xyz); 13 | z.xyzw *= clamp(max(minRadius2/r2, minRadius2), 0., 1.); 14 | 15 | z *= rotM; 16 | 17 | z.xyzw = z*scalevec + z0; 18 | 19 | minD = min(minD, trapCalc(z)); 20 | } 21 | 22 | return z; 23 | } 24 | 25 | #pragma glslify: export(mandelboxFold) 26 | -------------------------------------------------------------------------------- /folds/octahedron-fold.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: fold = require(../folds) 2 | #pragma glslify: foldInv = require(../foldInv) 3 | 4 | vec3 octahedronFold (in vec3 p, inout float minD) { 5 | float k = 1.0; 6 | 7 | for (int i = 0; i < Iterations; i++) { 8 | p = -abs(-p); 9 | 10 | // Folding 11 | fold(p.xy); 12 | fold(p.xz); 13 | foldInv(p.xy); 14 | foldInv(p.xz); 15 | 16 | p += 0.0625 * cos(2.0 * p.yzx); 17 | 18 | // Stretch 19 | p = (vec4(p, 1.) * kifsM).xyz; 20 | 21 | minD = min(trapCalc(p, k), minD); 22 | k *= 1.1; 23 | } 24 | 25 | return p; 26 | } 27 | 28 | #pragma glslify: export(octahedronFold) 29 | -------------------------------------------------------------------------------- /folds/tetrahedral.glsl: -------------------------------------------------------------------------------- 1 | vec3 fold (in vec3 q) { 2 | if (dot(q.xy, vec2(1, -1)) < 0.) q.yx = vec2(1) * q.xy; 3 | if (dot(q.xz, vec2(1, -1)) < 0.) q.zx = vec2(1) * q.xz; 4 | if (dot(q.yz, vec2(1, -1)) < 0.) q.zy = vec2(1) * q.yz; 5 | 6 | if (dot(q.xy, vec2(1)) < 0.) q.yx = vec2(-1) * q.xy; 7 | if (dot(q.xz, vec2(1)) < 0.) q.zx = vec2(-1) * q.xz; 8 | if (dot(q.yz, vec2(1)) < 0.) q.zy = vec2(-1) * q.yz; 9 | 10 | return q; 11 | } 12 | 13 | #pragma glslify: export(fold) 14 | -------------------------------------------------------------------------------- /gallery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ray Marching 6 | 19 | 20 | 21 |
22 |

Ray Marching Experiments

23 |

24 | The experiments in ray marching by lejeunerenard. 25 |

26 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /gallery/introspective-reflective/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | budo 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /get-normal.glsl: -------------------------------------------------------------------------------- 1 | vec3 getNormal( in vec3 p, in float eps, in float generalT ) { 2 | vec2 e = vec2(1.0,-1.0)*.05773*eps; 3 | return normalize( 4 | e.xyy * map( p + e.xyy, generalT).x + 5 | e.yyx * map( p + e.yyx, generalT).x + 6 | e.yxy * map( p + e.yxy, generalT).x + 7 | e.xxx * map( p + e.xxx, generalT).x ); 8 | } 9 | 10 | #pragma glslify: export(getNormal) 11 | -------------------------------------------------------------------------------- /glsl-dispersion.glsl: -------------------------------------------------------------------------------- 1 | #ifndef PI 2 | #define PI 3.1415926536 3 | #define TWO_PI 6.2831853072 4 | #endif 5 | 6 | // #define RGBCMY 1 7 | // #define REFR_INTEGRAL 1 8 | #define HUE 2 9 | #define HUE_NUM 1 10 | // #define COS_HUE 1 11 | #pragma glslify: hsv = require(glsl-hsv2rgb) 12 | #pragma glslify: cnoise3 = require(glsl-noise/classic/3d) 13 | #pragma glslify: snoise3 = require(glsl-noise/simplex/3d) 14 | #pragma glslify: rotationMatrix = require(./rotation-matrix3) 15 | 16 | #pragma glslify: hue2IOR = require(./dispersion-ray-direction) 17 | // #pragma glslify: hue2IOR = require(./dispersion/hue-to-ior-exponential) 18 | // #pragma glslify: hue2IOR = require(./dispersion/hue-to-ior-sigmoid) 19 | // #pragma glslify: hue2IOR = require(./dispersion/hue-to-ior-polynomial) 20 | 21 | vec3 nsin (in vec3 t) { 22 | return 0.5 + 0.5 * sin(TWO_PI * t); 23 | } 24 | 25 | vec3 intRefract (in vec3 l, in vec3 n, in float r1, in float r2) { 26 | float sqrt1minusC = sqrt(1.0 - dot(l, -n)); 27 | return 0.5 * ( 28 | (r2 * refract(l, n, r2) 29 | - n * asin(sqrt1minusC * r2) / sqrt1minusC) 30 | - 31 | (r1 * refract(l, n, r1) 32 | - n * asin(sqrt1minusC * r1) / sqrt1minusC)); 33 | } 34 | 35 | vec3 refractColors (in vec3 nor, in vec3 eye, in float n2, in float n1, in vec3 lightColor) { 36 | float between = amount; 37 | float greenIOR = n2; 38 | 39 | #ifdef RGBCMY 40 | float redIORRatio = hue2IOR(0.0, greenIOR, n1, between); 41 | float yellowIORRatio = hue2IOR(60.0, greenIOR, n1, between); 42 | float greenIORRatio = hue2IOR(120.0, greenIOR, n1, between); 43 | float cyanIORRatio = hue2IOR(180.0, greenIOR, n1, between); 44 | float blueIORRatio = hue2IOR(240.0, greenIOR, n1, between); 45 | float purpleIORRatio = hue2IOR(270.0, greenIOR, n1, between); 46 | 47 | vec3 redRefract = refract(eye, nor, redIORRatio); 48 | vec3 yellowRefract = refract(eye, nor, yellowIORRatio); 49 | vec3 greenRefract = refract(eye, nor, greenIORRatio); 50 | vec3 cyanRefract = refract(eye, nor, cyanIORRatio); 51 | vec3 blueRefract = refract(eye, nor, blueIORRatio); 52 | vec3 purpleRefract = refract(eye, nor, purpleIORRatio); 53 | 54 | float r = scene(redRefract, redIORRatio).r * 0.5; 55 | float y = dot(scene(yellowRefract, yellowIORRatio), vec3(2.0, 2.0, -1.0)) / 6.0; 56 | float g = scene(greenRefract, greenIORRatio).g * 0.5; 57 | float c = dot(scene(cyanRefract, cyanIORRatio), vec3(-1.0, 2.0, 2.0)) / 6.0; 58 | float b = scene(blueRefract, blueIORRatio).b * 0.5; 59 | float p = dot(scene(purpleRefract, purpleIORRatio), vec3(2.0, -1.0, 2.0)) / 6.0; 60 | 61 | float R = r + (2.0*p + 2.0*y - c)/3.0; 62 | float G = g + (2.0*y + 2.0*c - p)/3.0; 63 | float B = b + (2.0*c + 2.0*p - y)/3.0; 64 | 65 | #else 66 | 67 | #ifdef HUE 68 | const float hueStep = 1.0 / float(HUE_NUM); 69 | vec3 color = vec3(0.); 70 | 71 | vec3 variation = vec3(0); 72 | for (int i = 0; i < HUE_NUM; i++) { 73 | float hue = float(i) * hueStep; 74 | float ior = hue2IOR(360.0 * hue, greenIOR, n1, between); 75 | // variation += pow(0.5, float(i)) * cos(pow(2.0, float(i)) * variation + 0.1 * eye.yzx); 76 | 77 | vec3 iorRefract = refract(eye + 0.1 * variation, nor, ior); 78 | 79 | #ifdef COS_HUE 80 | color += (0.5 + 0.5 * cos(TWO_PI * (hue + vec3(0, 0.33, 0.67)))) * scene(iorRefract, ior); 81 | 82 | // Red / Teal 83 | // color += (vec3(0.8, 0.5, 0.4) + vec3(0.2, 0.4, 0.2) * cos(TWO_PI * (vec3(2, 1, 0) * hue + vec3(0, 0.25, 0.25)))) * scene(iorRefract, ior); 84 | 85 | // Simple 86 | // color += (0.5 + 0.5 * cos(TWO_PI * (hue + vec3(0, 0.1, 0.2)))) * scene(iorRefract, ior); 87 | 88 | // Simple 2 89 | // color += (0.5 + 0.5 * cos(TWO_PI * (vec3(1.5) * hue + vec3(0, 0.1, 0.2)))) * scene(iorRefract, ior); 90 | 91 | // Neon 92 | // color += (0.5 + 0.5 * cos(TWO_PI * (vec3(2, 1, 0) * hue + vec3(0.5, 0.2, 0.25)))) * scene(iorRefract, ior); 93 | 94 | // Something 1 95 | // color += (vec3(0.5, 0.3, 0.5) + vec3(0.25, 0.5, 0.7) * cos(TWO_PI * (vec3(2, 1, 0.5) * hue + vec3(0.6, 0.1, 0.25)))) * scene(iorRefract, ior); 96 | 97 | // RED CYAN 98 | // color += mix(#FF0000, #00FFFF, hue) * scene(iorRefract, ior); 99 | 100 | #else 101 | 102 | // -- Get Hue for given ior -- 103 | vec3 thisColor = vec3(0); 104 | 105 | // Index into Cosine palette 106 | vec3 dI = vec3(dot(nor, eye)); 107 | vec3 mixI = vec3(dI); 108 | // vec3 mixI = clamp(0.5 + 0.5 * sin(1.0 * dI + nor + vec3(0., 0.33, 0.67)), 0.0, 1.0); 109 | mixI += 0.2 * hue; 110 | 111 | // Cosine Palette based Hue 112 | vec3 cosOffset = vec3(0, 0.33, 0.67); 113 | 114 | // thisColor = vec3(1); 115 | 116 | thisColor = 0.5 + 0.5 * cos(TWO_PI * (mixI + cosOffset)); 117 | 118 | // Secondary cosine palette warp 119 | thisColor += 0.5 + 0.5 * cos(TWO_PI * (nor + eye + cosOffset - 0.2)); 120 | // thisColor *= 0.5; 121 | 122 | // // HSV based hue 123 | // thisColor += hsv(vec3(hue, 1.0, 1.0)); 124 | 125 | // // Composite color 126 | // thisColor += #BBBB44 * (0.5 + 0.5 * sin(nor)); 127 | // thisColor += #44BBBB * (0.5 + 0.5 * sin(eye)); 128 | // thisColor += #BB44BB * (0.5 + 0.5 * sin(PI * dot(eye, nor))); 129 | 130 | // thisColor *= 0.7; 131 | 132 | // -- Apply Scene Coloring -- 133 | vec3 sceneResult = scene(iorRefract, ior); 134 | thisColor *= sceneResult; 135 | 136 | // color = thisColor; 137 | color += thisColor; 138 | #endif 139 | } 140 | 141 | color *= pow(hueStep, 1.0); 142 | 143 | float R = color.r; 144 | float G = color.g; 145 | float B = color.b; 146 | 147 | #else 148 | 149 | #ifdef REFR_INTEGRAL 150 | float r1 = hue2IOR(0.0, greenIOR, n1, between); 151 | float r2 = hue2IOR(240.0, greenIOR, n1, between); 152 | // float r1 = (n2 - amount) / n1; 153 | // float r2 = (n2 + amount) / n1; 154 | vec3 intRefracted = intRefract(eye, nor, r1, r2); 155 | // intRefracted = 0.5 + 0.5 * cos(TWO_PI * (intRefracted + vec3(0, 0.33, 0.67))); 156 | // intRefracted = 0.5 + 0.5 * cos(intRefracted); 157 | vec3 color = scene(intRefracted, r1); 158 | // vec3 color = intRefracted; 159 | 160 | float R = color.r; 161 | float G = color.g; 162 | float B = color.b; 163 | 164 | #else 165 | 166 | float redIORRatio = hue2IOR(0.0, greenIOR, n1, between); 167 | float greenIORRatio = hue2IOR(120.0, greenIOR, n1, between); 168 | float blueIORRatio = hue2IOR(240.0, greenIOR, n1, between); 169 | 170 | vec3 redRefract = refract(eye, nor, redIORRatio); 171 | vec3 greenRefract = refract(eye, nor, greenIORRatio); 172 | vec3 blueRefract = refract(eye, nor, blueIORRatio); 173 | 174 | float r = scene(redRefract, redIORRatio).r; 175 | float g = scene(greenRefract, greenIORRatio).g; 176 | float b = scene(blueRefract, blueIORRatio).b; 177 | 178 | float R = r; 179 | float G = g; 180 | float B = b; 181 | #endif 182 | #endif 183 | #endif 184 | 185 | return vec3(R, G, B) * lightColor; 186 | } 187 | vec3 refractColors (in vec3 nor, in vec3 eye, in float n2) { 188 | return refractColors(nor, eye, n2, 1., vec3(1.0)); 189 | } 190 | vec3 refractColors (in vec3 nor, in vec3 eye, in float n2, in float n1) { 191 | return refractColors(nor, eye, n2, n1, vec3(1.0)); 192 | } 193 | vec3 refractColors (in vec3 nor, in vec3 eye, in float n2, in vec3 lightColor) { 194 | return refractColors(nor, eye, n2, 1., lightColor); 195 | } 196 | 197 | #pragma glslify: export(refractColors) 198 | -------------------------------------------------------------------------------- /gradient.glsl: -------------------------------------------------------------------------------- 1 | vec3 gradientColors (in float v) { 2 | #define SIZE 5 3 | 4 | vec3 colors[SIZE]; 5 | colors[0] = #E46BFF; 6 | colors[1] = #391B40; 7 | colors[2] = #AB51BF; 8 | colors[3] = #CD61E5; 9 | colors[4] = #72367F; 10 | 11 | v = mod(v, 1.); 12 | 13 | const float period = 1. / float(SIZE); 14 | 15 | vec3 color = colors[0]; 16 | for (int i = 1; i < SIZE; i++) { 17 | float a = smoothstep(period * float(i - 1), period * float(i), v); 18 | color = mix(color, colors[i], a); 19 | } 20 | 21 | // Final loop around 22 | float a = smoothstep(period * float(SIZE - 1), 1., v); 23 | color = mix(color, colors[0], a); 24 | 25 | return color; 26 | } 27 | #pragma glslify: export(gradientColors) 28 | -------------------------------------------------------------------------------- /hg_sdf/p-mod-interval1.glsl: -------------------------------------------------------------------------------- 1 | // Repeat only a few times: from indices to (similar to above, but more flexible) 2 | float pModInterval1(inout float p, float size, float start, float stop) { 3 | float halfsize = size*0.5; 4 | float c = floor((p + halfsize)/size); 5 | p = mod(p+halfsize, size) - halfsize; 6 | if (c > stop) { //yes, this might not be the best thing numerically. 7 | p += size*(c - stop); 8 | c = stop; 9 | } 10 | if (c = (repetitions/2.)) c = abs(c); 17 | return c; 18 | } 19 | void pModPolar(inout vec2 p, float repetitions) { 20 | float angle = 2.*PI/repetitions; 21 | float a = atan(p.y, p.x) + angle/2.; 22 | float r = length(p); 23 | a = mod(a,angle) - angle/2.; 24 | p = vec2(cos(a), sin(a))*r; 25 | } 26 | #pragma glslify: export(pModPolarC) 27 | -------------------------------------------------------------------------------- /hg_sdf/p-mod1.glsl: -------------------------------------------------------------------------------- 1 | // source: hg_sdf 2 | // Repeat space along one axis. Use like this to repeat along the x axis: 3 | // - using the return value is optional. 4 | float pMod1(inout float p, float size) { 5 | float halfsize = size*0.5; 6 | float c = floor((p + halfsize)/size); 7 | p = mod(p + halfsize, size) - halfsize; 8 | return c; 9 | } 10 | #pragma glslify: export(pMod1) 11 | -------------------------------------------------------------------------------- /hg_sdf/p-mod2.glsl: -------------------------------------------------------------------------------- 1 | // Repeat in two dimensions 2 | vec2 pMod2(inout vec2 p, vec2 size) { 3 | vec2 c = floor((p + size*0.5)/size); 4 | p = mod(p + size*0.5,size) - size*0.5; 5 | return c; 6 | } 7 | #pragma glslify: export(pMod2) 8 | -------------------------------------------------------------------------------- /hg_sdf/p-mod3.glsl: -------------------------------------------------------------------------------- 1 | // Repeat in two dimensions 2 | vec3 pMod3(inout vec3 p, vec3 size) { 3 | vec3 c = floor((p + size*0.5)/size); 4 | p = mod(p + size*0.5,size) - size*0.5; 5 | return c; 6 | } 7 | #pragma glslify: export(pMod3) 8 | -------------------------------------------------------------------------------- /hg_sdf/vmax.glsl: -------------------------------------------------------------------------------- 1 | // Maximum/minumum elements of a vector 2 | float vmax(vec2 v) { 3 | return max(v.x, v.y); 4 | } 5 | 6 | float vmax(vec3 v) { 7 | return max(max(v.x, v.y), v.z); 8 | } 9 | 10 | float vmax(vec4 v) { 11 | return max(max(v.x, v.y), max(v.z, v.w)); 12 | } 13 | 14 | #pragma glslify: export(vmax) 15 | -------------------------------------------------------------------------------- /hidden.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 7 | 8 | 15 | 22 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ` 43 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | budo 5 | 6 | 7 | 8 | 9 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import DefaultSceneRenderer from './default-scene-renderer' 2 | import CCaptureCapturer from './capturer/ccapture' 3 | import App from './app' 4 | 5 | import { name } from './info.json' 6 | 7 | const fr = 60 8 | const captureTime = 0 * 5 9 | const secondsLong = 10 10 | const capturing = false 11 | 12 | let app = new App() 13 | window.app = app 14 | 15 | app.width = window.devicePixelRatio * 1080 16 | app.height = window.devicePixelRatio * 1920 17 | 18 | let sceneRenderer = new DefaultSceneRenderer(app.gl) 19 | app.sceneRenderer = sceneRenderer.render.bind(sceneRenderer) 20 | 21 | // Setup resizing 22 | let resize = () => { 23 | let dim = app.getDimensions() 24 | sceneRenderer.resize(dim[0], dim[1]) 25 | } 26 | resize() 27 | 28 | app.totalTime = secondsLong 29 | // window.time = 0.3 30 | const still = false 31 | 32 | if (capturing) { 33 | let massagedName = name.replace(/ /g, '-') 34 | massagedName = massagedName.replace(/'/g, '') 35 | massagedName = massagedName.toLowerCase() 36 | 37 | let renderCount = 3 38 | 39 | let renderings = {} 40 | try { 41 | renderings = JSON.parse(window.localStorage.renderings) 42 | } catch (e) {} 43 | if (!renderings) renderings = {} 44 | 45 | if (massagedName in renderings) { 46 | renderCount = renderings[massagedName] + 1 47 | } 48 | 49 | renderings[massagedName] = renderCount 50 | 51 | let filename = massagedName + '-render' + renderCount 52 | console.log('filename', filename) 53 | let capturer = new CCaptureCapturer({ 54 | format: 'jpg', 55 | framerate: fr, 56 | filename, 57 | autoSaveTime: 5, 58 | quality: 98, 59 | startTime: captureTime, 60 | timeLength: secondsLong 61 | }) 62 | 63 | let currentTime = captureTime * 1000 64 | window.capturer = capturer 65 | 66 | let run = () => { 67 | capturer.start() 68 | app.run() 69 | } 70 | 71 | let currentRAF 72 | let tick = async (t) => { 73 | t = currentTime + 1000 / fr 74 | currentTime = t 75 | 76 | app.tick(t) 77 | await capturer.capture(app.canvas) 78 | 79 | if (currentTime <= 1000 * (secondsLong + captureTime) + 1000 / fr) { 80 | if (!still) { 81 | currentRAF = window.requestAnimationFrame(tick) 82 | } 83 | } else { 84 | capturer.save().then(() => { 85 | window.localStorage.renderings = JSON.stringify(renderings) 86 | window.setTimeout(() => { 87 | console.log('done sending message') 88 | var xmlHTTP = new XMLHttpRequest() 89 | xmlHTTP.open('GET', 'http://' + window.location.hostname + ':7321/', false) 90 | xmlHTTP.send(null) 91 | console.log('response text', xmlHTTP.responseText) 92 | }, 250) 93 | }) 94 | } 95 | } 96 | 97 | app.loaded.then(() => { 98 | if (!currentRAF) { 99 | currentRAF = window.requestAnimationFrame(tick) 100 | } 101 | run() 102 | }) 103 | } else { 104 | let gui = new dat.GUI() 105 | gui.remember(app) 106 | gui.closed = true 107 | 108 | let offsetF = gui.addFolder('Offset') 109 | offsetF.add(app.offset, '0', -5, 5).step(0.001).listen() 110 | offsetF.add(app.offset, '1', -5, 5).step(0.001).listen() 111 | offsetF.add(app.offset, '2', -5, 5).step(0.001).listen() 112 | 113 | gui.add(app, 'd', 0, 20).step(0.01).listen() 114 | gui.add(app, 'scale', -15, 15).step(0.0001).listen() 115 | gui.add(app, 'epsilon', 0.0000001, 0.05).step(0.0000001).listen() 116 | 117 | let angleCF = gui.addFolder('Angle Coefficients') 118 | angleCF.add(app, 'angle1C', -5, 5).step(0.0001).listen() 119 | angleCF.add(app, 'angle2C', -5, 5).step(0.0001).listen() 120 | angleCF.add(app, 'angle3C', -5, 5).step(0.001).listen() 121 | 122 | let rotationF = gui.addFolder('Rotation') 123 | rotationF.add(app.rot2angle, '0', 0, 2 * Math.PI).step(0.001).listen() 124 | rotationF.add(app.rot2angle, '1', 0, 2 * Math.PI).step(0.001).listen() 125 | rotationF.add(app.rot2angle, '2', 0, 2 * Math.PI).step(0.001).listen() 126 | 127 | let cameraF = gui.addFolder('Camera') 128 | 129 | let cameraPosF = cameraF.addFolder('Position') 130 | cameraPosF.add(app.cameraRo, '0', -20, 20).step(0.01).listen() 131 | cameraPosF.add(app.cameraRo, '1', -20, 20).step(0.01).listen() 132 | cameraPosF.add(app.cameraRo, '2', -20, 20).step(0.01).listen() 133 | 134 | let cameraRotF = cameraF.addFolder('Rotation') 135 | cameraRotF.add(app, 'LOOKAT') 136 | cameraRotF.add(app.cameraAngles, '0', -Math.PI, Math.PI).step(0.001).listen() 137 | cameraRotF.add(app.cameraAngles, '1', -Math.PI, Math.PI).step(0.001).listen() 138 | cameraRotF.add(app.cameraAngles, '2', -Math.PI, Math.PI).step(0.001).listen() 139 | 140 | let colorsF = gui.addFolder('Colors') 141 | colorsF.addColor(app, 'colors1').listen() 142 | colorsF.addColor(app, 'colors2').listen() 143 | 144 | // ----- Setup ----- 145 | window.addEventListener('resize', resize) 146 | 147 | // Run 148 | app.run() 149 | 150 | let currentRAF 151 | let tick = (t) => { 152 | currentRAF = window.requestAnimationFrame(tick) 153 | 154 | app.tick(t) 155 | } 156 | 157 | app.loaded.then(() => { 158 | if (!currentRAF) { 159 | currentRAF = window.requestAnimationFrame(tick) 160 | } 161 | }) 162 | } 163 | -------------------------------------------------------------------------------- /info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Overlay" 3 | } 4 | -------------------------------------------------------------------------------- /inner-glow.glsl: -------------------------------------------------------------------------------- 1 | vec3 innerGlow(in float trap) { 2 | float fGlow = clamp(trap * 0.1, 0.0, 1.0); 3 | fGlow = pow(fGlow, 3.5); 4 | 5 | return glowColor * 27.5 * fGlow; 6 | } 7 | #pragma glslify: export(innerGlow) 8 | -------------------------------------------------------------------------------- /iridescent.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: hsv = require(glsl-hsv2rgb) 2 | 3 | // @cabbibo 4 | // source: https://www.shadertoy.com/view/4llGDB 5 | 6 | #define STEPS 10 7 | vec3 iridescant( vec3 ro , vec3 rd, float lumShift ){ 8 | 9 | float lum = 1.; 10 | 11 | 12 | vec3 col = vec3(0.); 13 | for( int i = 0; i < STEPS; i++ ){ 14 | vec3 p = ro + rd * .1 * float( i ); 15 | float period = lumPeriod(p, i); 16 | 17 | lum += abs(sin( p.x * period * 20. ) + sin( p.y * period * 20. )); 18 | 19 | col += hsv( vec3(lum / 10. + lumShift, 1. , 1.) ) / lum; 20 | } 21 | 22 | return col/float(STEPS); 23 | } 24 | vec3 iridescant( vec3 ro , vec3 rd ){ 25 | return iridescant(ro, rd, 0.); 26 | } 27 | 28 | #pragma glslify: export(iridescant) 29 | -------------------------------------------------------------------------------- /lighting/simple-shadow.glsl: -------------------------------------------------------------------------------- 1 | // source: https://www.shadertoy.com/view/ml2GWc 2 | 3 | #define SHADOWS_QUALITY 1.0 4 | 5 | // shadow function 6 | float shadow(vec3 ro, vec3 rd, in float tmin, float tmax, in float time) { 7 | float t = tmin; 8 | for (float i=0.; i<100.; i++) { 9 | vec3 p = ro + rd*t; 10 | float h = map(p, time).x * SHADOWS_QUALITY; 11 | if (h<.001) return 0.; 12 | t += h; 13 | if (t >= tmax) break; 14 | } 15 | return 1.; 16 | } 17 | 18 | #pragma glslify: export(shadow) 19 | -------------------------------------------------------------------------------- /loop-noise.glsl: -------------------------------------------------------------------------------- 1 | float loopNoise (in vec3 q, in float startNoise, in float endNoise) { 2 | const float timeScale = 1.; 3 | // q must be normalized on `.x` 4 | float n1 = noise(vec3(timeScale, 1, 1) * q); 5 | float n2 = noise(vec3(timeScale, 0, 0) + vec3(-timeScale, 1, 1) * q); 6 | float noiseIndex = clamp((q.x - startNoise) / (endNoise - startNoise), 0., 1.); 7 | return mix(n1, n2, noiseIndex); 8 | } 9 | 10 | #pragma glslify: export(loopNoise) 11 | -------------------------------------------------------------------------------- /lorem.svg.js: -------------------------------------------------------------------------------- 1 | export default `` 2 | -------------------------------------------------------------------------------- /luminance.js: -------------------------------------------------------------------------------- 1 | // const color = [0, 0, 0] 2 | 3 | const redLuminanceCoefficient = 0.2126 4 | const greenLuminanceCoefficient = 0.7152 5 | const blueLuminanceCoefficient = 0.0722 6 | 7 | function getLuminance (color) { 8 | return redLuminanceCoefficient * color[0] + 9 | greenLuminanceCoefficient * color[1] + 10 | blueLuminanceCoefficient * color[2] 11 | } 12 | 13 | function getColorWFixedLuminance (red, green, blue, targetLuminance) { 14 | let luminanceCoefficient = 0 15 | 16 | if (!(!!red || !!green || !!blue)) { 17 | throw Error('One color must be omitted to calculate the new color') 18 | } 19 | 20 | if (!red) { 21 | luminanceCoefficient = redLuminanceCoefficient 22 | console.log('select red') 23 | } 24 | 25 | if (!green) { 26 | luminanceCoefficient = greenLuminanceCoefficient 27 | console.log('select green') 28 | } 29 | 30 | if (!blue) { 31 | luminanceCoefficient = blueLuminanceCoefficient 32 | console.log('select blue') 33 | } 34 | 35 | let tempLuminence = getLuminance([red || 0, green || 0, blue || 0]) 36 | 37 | if (tempLuminence > targetLuminance) throw Error('Target luminance is greater than provided color') 38 | 39 | return (targetLuminance - tempLuminence) / luminanceCoefficient 40 | } 41 | 42 | module.exports = { getColorWFixedLuminance, getLuminance } 43 | -------------------------------------------------------------------------------- /mandelbox.glsl: -------------------------------------------------------------------------------- 1 | #ifndef foldLimit 2 | #define foldLimit 1. 3 | #endif 4 | 5 | float C1 = abs(s - 1.); 6 | float C2 = pow(abs(s), float(1 - trap)); 7 | 8 | float trapCalc (in vec4 z) { 9 | return dot(z, vec4(1)) / z.w; 10 | } 11 | 12 | #pragma glslify: fold = require(./folds) 13 | #pragma glslify: mandelboxFold = require(./folds/mandelbox-fold, foldLimit=foldLimit, trap=trap, C1=C1, minRadius=minRadius, s=s, rotM=rotM, trapCalc=trapCalc) 14 | 15 | vec2 mandelbox (in vec3 z) { 16 | vec4 p = vec4(z.xyz, 1.); 17 | 18 | float minD = maxDistance * 2.; 19 | 20 | p = mandelboxFold(p, minD); 21 | 22 | return vec2((length(p.xyz) - C1) / p.w - C2, minD); 23 | } 24 | 25 | #pragma glslify: export(mandelbox) 26 | -------------------------------------------------------------------------------- /matCap.glsl: -------------------------------------------------------------------------------- 1 | vec3 matCap (vec3 ref) { 2 | float m = 2. * sqrt( pow( ref.x, 2. ) + pow( ref.y, 2. ) + pow( ref.z + 1., 2. ) ); 3 | vec2 vN = ref.xy / m + .5; 4 | return texture2D(texture, vN).rgb; 5 | } 6 | #pragma glslify: export(matCap) 7 | -------------------------------------------------------------------------------- /melt.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ` 23 | -------------------------------------------------------------------------------- /menger-sphere.glsl: -------------------------------------------------------------------------------- 1 | #ifndef Iterations 2 | #define Iterations 10 3 | #endif 4 | 5 | #pragma glslify: fold = require(./folds) 6 | #pragma glslify: foldInv = require(./foldInv) 7 | 8 | vec2 MengerSphere (inout vec3 p) { 9 | p = .5*p + vec3(.5); 10 | vec3 pp = abs(p -.5) - .5; 11 | 12 | float k = 2.0; 13 | 14 | float minD = 10000.; 15 | 16 | float d1 = max(pp.x, max(pp.y, pp.z)); 17 | float d = d1; 18 | 19 | for (int i = 0; i < Iterations; i++) { 20 | vec3 pa = mod(3. * p * k, 3.); 21 | k *= scale; 22 | 23 | pp = .5 - abs(pa-1.5); 24 | 25 | pp = (vec4(pp, 1.) * kifsM).xyz; 26 | 27 | //distance inside the 3 axis aligned square tubes 28 | d1 = min(max(pp.x, pp.z), min(max(pp.x, pp.y), max(pp.y, pp.z))) / k; 29 | 30 | d = max(d, d1); // intersect 31 | 32 | minD = min(abs(dot(pp, pp)), minD); 33 | } 34 | 35 | float e = clamp(length(pp) - 10., 0., 100.); 36 | d = max(d, e); 37 | return vec2(d, minD); 38 | } 39 | 40 | #pragma glslify: export(MengerSphere) 41 | -------------------------------------------------------------------------------- /mobius.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 9 | 13 | 16 | 18 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ` 35 | -------------------------------------------------------------------------------- /model/cluster.glsl: -------------------------------------------------------------------------------- 1 | vec3 cluster (in vec3 x) { 2 | const float scale = 0.4; 3 | x /= scale; 4 | 5 | const float angleSpan = PI * 0.85; 6 | 7 | vec3 res = vec3(1000.0, 1., 0.); 8 | for (int k=-1; k<=1; k++) { 9 | for (int j=-1; j<=1; j++) { 10 | for (int i=-1; i<=1; i++) { 11 | vec3 b = 1.5 * vec3(i, j, k); 12 | 13 | vec3 p = x + b; 14 | p *= rotationMatrix(vec3( 15 | noise(b), 16 | noise(1.1 * b - 20.3), 17 | noise(0.9 * b + 414.9)), angleSpan * noise(20.0 * b)); 18 | 19 | vec3 d = quartz(p, PI / 4.0 * noise(13.0 * b)); 20 | d.x *= scale; 21 | res = dMin( res, d ); 22 | } 23 | } 24 | } 25 | return res; 26 | } 27 | #pragma glslify: export(cluser) 28 | -------------------------------------------------------------------------------- /model/dodecahedral.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: import(./generalized-polyhedra-include) 2 | 3 | // p as usual, e exponent (p in the paper), r radius or something like that 4 | float dodecahedral(vec3 p, float e, float r) { 5 | float s = pow(abs(dot(p,n14)),e); 6 | s += pow(abs(dot(p,n15)),e); 7 | s += pow(abs(dot(p,n16)),e); 8 | s += pow(abs(dot(p,n17)),e); 9 | s += pow(abs(dot(p,n18)),e); 10 | s += pow(abs(dot(p,n19)),e); 11 | s = pow(s, 1./e); 12 | return s-r; 13 | } 14 | 15 | #pragma glslify: export(dodecahedral) 16 | -------------------------------------------------------------------------------- /model/generalized-polyhedra-include.glsl: -------------------------------------------------------------------------------- 1 | vec3 n4 = vec3(0.577,0.577,0.577); 2 | vec3 n5 = vec3(-0.577,0.577,0.577); 3 | vec3 n6 = vec3(0.577,-0.577,0.577); 4 | vec3 n7 = vec3(0.577,0.577,-0.577); 5 | vec3 n8 = vec3(0.000,0.357,0.934); 6 | vec3 n9 = vec3(0.000,-0.357,0.934); 7 | vec3 n10 = vec3(0.934,0.000,0.357); 8 | vec3 n11 = vec3(-0.934,0.000,0.357); 9 | vec3 n12 = vec3(0.357,0.934,0.000); 10 | vec3 n13 = vec3(-0.357,0.934,0.000); 11 | vec3 n14 = vec3(0.000,0.851,0.526); 12 | vec3 n15 = vec3(0.000,-0.851,0.526); 13 | vec3 n16 = vec3(0.526,0.000,0.851); 14 | vec3 n17 = vec3(-0.526,0.000,0.851); 15 | vec3 n18 = vec3(0.851,0.526,0.000); 16 | vec3 n19 = vec3(-0.851,0.526,0.000); 17 | -------------------------------------------------------------------------------- /model/gyroid-trianglewave.glsl: -------------------------------------------------------------------------------- 1 | float gyroidTriangle (in vec3 p, in float thickness) { 2 | float gyroid = dot(triangleWave(p), triangleWave(p.yzx + 0.5)); 3 | return abs(gyroid) - thickness; 4 | } 5 | 6 | #pragma glslify: export(gyroidTriangle) 7 | -------------------------------------------------------------------------------- /model/gyroid.glsl: -------------------------------------------------------------------------------- 1 | float gyroid (in vec3 p, in float thickness) { 2 | float gyroid = dot(sin(p), cos(p.yzx)); 3 | return abs(gyroid) - thickness; 4 | } 5 | 6 | #pragma glslify: export(gyroid) 7 | -------------------------------------------------------------------------------- /model/icosahedral.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: import(./generalized-polyhedra-include) 2 | 3 | // p as usual, e exponent (p in the paper), r radius or something like that 4 | float icosahedral(vec3 p, float e, float r) { 5 | // // Notes: shows and is super pointy. screen capture is at ~33fps 6 | // float s = pow(abs(dot(p,n4)),e); 7 | // s += pow(abs(dot(p,n5)),e); 8 | // s += pow(abs(dot(p,n6)),e); 9 | // s += pow(abs(dot(p,n7)),e); 10 | // s += pow(abs(dot(p,n8)),e); 11 | // s += pow(abs(dot(p,n9)),e); 12 | // s += pow(abs(dot(p,n10)),e); 13 | // s += pow(abs(dot(p,n11)),e); 14 | // s += pow(abs(dot(p,n12)),e); 15 | // s += pow(abs(dot(p,n13)),e); 16 | // s = pow(s, 1./e); 17 | 18 | // // dot product equivalent (or so i think) 19 | // // note: didnt show anything big nor small that I could see 20 | // float s = abs(dot(p,n4)); 21 | // s += abs(dot(p,n5)); 22 | // s += abs(dot(p,n6)); 23 | // s += abs(dot(p,n7)); 24 | // s += abs(dot(p,n8)); 25 | // s += abs(dot(p,n9)); 26 | // s += abs(dot(p,n10)); 27 | // s += abs(dot(p,n11)); 28 | // s += abs(dot(p,n12)); 29 | // s += abs(dot(p,n13)); 30 | // s = pow(s, 1./e); 31 | 32 | // Maxi metric version 33 | // Notes: shows and is super pointy. screen capture is at ~35fps 34 | float s = abs(dot(p,n4)); 35 | s = max(s, abs(dot(p,n5))); 36 | s = max(s, abs(dot(p,n6))); 37 | s = max(s, abs(dot(p,n7))); 38 | s = max(s, abs(dot(p,n8))); 39 | s = max(s, abs(dot(p,n9))); 40 | s = max(s, abs(dot(p,n10))); 41 | s = max(s, abs(dot(p,n11))); 42 | s = max(s, abs(dot(p,n12))); 43 | s = max(s, abs(dot(p,n13))); 44 | 45 | return s-r; 46 | } 47 | 48 | #pragma glslify: export(icosahedral) 49 | -------------------------------------------------------------------------------- /model/octahedral.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: import(./generalized-polyhedra-include) 2 | 3 | // p as usual, e exponent (p in the paper), r radius or something like that 4 | float octahedral(vec3 p, float e, float r) { 5 | float s = pow(abs(dot(p,n4)),e); 6 | s += pow(abs(dot(p,n5)),e); 7 | s += pow(abs(dot(p,n6)),e); 8 | s += pow(abs(dot(p,n7)),e); 9 | s = pow(s, 1./e); 10 | return s-r; 11 | } 12 | 13 | #pragma glslify: export(octahedral) 14 | -------------------------------------------------------------------------------- /model/quartz.glsl: -------------------------------------------------------------------------------- 1 | vec3 quartz (in vec3 p, in float angle) { 2 | vec3 quartzBody = vec3(sdHexPrism(p.xzy - vec3(0., -0.30, 0.0), vec2(0.5, 3.)), 1., 0.); 3 | 4 | const float dodecaScale = 1.00; 5 | vec3 dp = p; 6 | dp.y *= 0.416; 7 | dp *= rotationMatrix(vec3(0.000,PHI,1.), PI / 5. + angle) / dodecaScale; 8 | vec3 d = vec3(dodeca(dp, 1.) * dodecaScale, 1., 0.); 9 | 10 | return dMax(quartzBody, d); 11 | // return d; 12 | } 13 | 14 | #pragma glslify: export(quartz) 15 | -------------------------------------------------------------------------------- /model/sdf-fbm.glsl: -------------------------------------------------------------------------------- 1 | // #ifndef REPS 2 | // #define REPS 11 3 | // #endif 4 | 5 | // source: https://www.iquilezles.org/www/articles/fbmsdf/fbmsdf.htm 6 | float hash( in vec3 x ) { 7 | return sin(1.5*x.x)*sin(1.5*x.y)*sin(1.5*x.z); 8 | } 9 | 10 | float sph ( in ivec3 i, in vec3 f, in ivec3 c ) { 11 | float rad = 0.65 * hash(vec3(i + c)); 12 | return length(f - vec3(c)) - rad; 13 | } 14 | 15 | float sdBase ( in vec3 p ) { 16 | ivec3 i = ivec3(floor(p)); 17 | vec3 f = fract(p); 18 | 19 | return min(min(min(sph(i,f,ivec3(0,0,0)), 20 | sph(i,f,ivec3(0,0,1))), 21 | min(sph(i,f,ivec3(0,1,0)), 22 | sph(i,f,ivec3(0,1,1)))), 23 | min(min(sph(i,f,ivec3(1,0,0)), 24 | sph(i,f,ivec3(1,0,1))), 25 | min(sph(i,f,ivec3(1,1,0)), 26 | sph(i,f,ivec3(1,1,1))))); 27 | } 28 | 29 | float sdFBM (in vec3 p, float d, float th){ 30 | float s = 1.; 31 | 32 | for (int i = 0; i < REPS; i++) { 33 | float n = s * sdBase(p); 34 | 35 | #define FBM_SUBTRACT 1 36 | #ifdef FBM_SUBTRACT 37 | d = smax(d, -n, smoothness * s); 38 | #else 39 | n = smax(n, d - clipFactor * s, smoothness * s); 40 | d = smin(n, d , smoothness * s); 41 | #endif 42 | 43 | p = mat3( 0.00, 1.60, 1.20, 44 | -1.60, 0.72,-0.96, 45 | -1.20,-0.96, 1.28) * p; 46 | 47 | s = 0.5 * s; 48 | if (s < th) break; 49 | } 50 | 51 | return d; 52 | } 53 | 54 | float sdFBM (in vec3 p, float d) { 55 | return sdFBM(p, d, 1e-3); // ? not sure about this default number 56 | } 57 | 58 | #pragma glslify: export(sdFBM) 59 | -------------------------------------------------------------------------------- /model/tetrahedron.glsl: -------------------------------------------------------------------------------- 1 | // Equivalent to acos(1/sqrt(3)) 2 | #define FACE_VERTEX_EDGE 0.955317 3 | 4 | #pragma glslify: rotationMatrix = require(../rotation-matrix3) 5 | 6 | // Plane technique 7 | // source: https://www.shadertoy.com/view/MtV3Dy 8 | float plane(vec3 p, vec3 origin, vec3 normal){ 9 | return dot(p - origin,normal); 10 | } 11 | 12 | float planeTetrahedron (vec3 p, float d) { 13 | const float dn =1.0/sqrt(3.0); 14 | p *= rotationMatrix(normalize(vec3(-1.0, 0.0, 1.0)), FACE_VERTEX_EDGE); 15 | 16 | //The tetrahedran is the intersection of four planes: 17 | float sd1 = plane(p,vec3(d,d,d) ,vec3(-dn,dn,dn)) ; 18 | float sd2 = plane(p,vec3(d,-d,-d) ,vec3(dn,-dn,dn)) ; 19 | float sd3 = plane(p,vec3(-d,d,-d) ,vec3(dn,dn,-dn)) ; 20 | float sd4 = plane(p,vec3(-d,-d,d) ,vec3(-dn,-dn,-dn)) ; 21 | 22 | //max intersects shapes 23 | return max(max(sd1,sd2),max(sd3,sd4)); 24 | } 25 | 26 | #pragma glslify: export(planeTetrahedron) 27 | -------------------------------------------------------------------------------- /model/tri-prism.glsl: -------------------------------------------------------------------------------- 1 | float sdTriPrism( vec3 p, vec2 h ) { 2 | vec3 q = abs(p); 3 | return max(q.z-h.y,max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5); 4 | } 5 | 6 | #pragma glslify: export(sdTriPrism) 7 | -------------------------------------------------------------------------------- /modulo/neighbor-grid.glsl: -------------------------------------------------------------------------------- 1 | // Needs: 2 | // - map(vec2 pos, vec2 id) 3 | // - float numberOfNeighbors 4 | // - float maxDistance 5 | 6 | #pragma glslify: pMod2 = require(../hg_sdf/p-mod2.glsl) 7 | 8 | vec2 neighborGrid (in vec2 q, in vec2 size) { 9 | vec2 c = pMod2(q, size); 10 | 11 | float n = maxDistance; 12 | float m = -1.; // default material 13 | 14 | for (float x = -numberOfNeighbors; x < numberOfNeighbors + 1.; x++) 15 | for (float y = -numberOfNeighbors; y < numberOfNeighbors + 1.; y++) { 16 | vec2 shift = vec2(x, y); 17 | vec2 cell = map(q - size * shift, c + shift); 18 | if (cell.x < n) { 19 | m = cell.y; 20 | } 21 | n = min(n, cell.x); 22 | } 23 | 24 | return vec2(n, m); 25 | } 26 | 27 | // vec2 neighborGrid (in vec3 q, in vec2 size) { 28 | // vec2 c = pMod2(q.xz, size); 29 | 30 | // float n = maxDistance; 31 | // float m = -1.; // default material 32 | 33 | // for (float x = -numberOfNeighbors; x < numberOfNeighbors + 1.; x++) 34 | // for (float y = -numberOfNeighbors; y < numberOfNeighbors + 1.; y++) { 35 | // vec2 shift = vec2(x, y); 36 | // vec2 spaceShift = size * shift; 37 | // vec2 cell = map(q - vec3(spaceShift.x, 0, spaceShift.y), c + shift); 38 | // if (cell.x < n) { 39 | // m = cell.y; 40 | // } 41 | // n = min(n, cell.x); 42 | // } 43 | 44 | // return vec2(n, m); 45 | // } 46 | 47 | #pragma glslify: export(neighborGrid) 48 | -------------------------------------------------------------------------------- /modulo/p-mod4.glsl: -------------------------------------------------------------------------------- 1 | // Repeat in 4 dimensions 2 | vec4 pMod4(inout vec4 p, vec4 size) { 3 | vec4 c = floor((p + size*0.5)/size); 4 | p = mod(p + size*0.5,size) - size*0.5; 5 | return c; 6 | } 7 | #pragma glslify: export(pMod4) 8 | -------------------------------------------------------------------------------- /modulo/subdivide.glsl: -------------------------------------------------------------------------------- 1 | #define TWO_PI 6.2831853072 2 | 3 | #ifndef DIVIDE_ITERS 4 | #define DIVIDE_ITERS 3. 5 | #endif 6 | 7 | #pragma glslify: rotMat2 = require(../rotation-matrix2) 8 | 9 | vec3 subdivide (inout vec2 q, in float seed, in float t) { 10 | // Square sizing 11 | float size = 0.4; 12 | vec2 dMin = vec2(-size, -size); 13 | vec2 dMax = vec2( size, size); 14 | 15 | // // Custom Sizing 16 | // vec2 dMin = vec2(-0.4, -0.8); 17 | // vec2 dMax = vec2( 0.4, 0.8); 18 | 19 | float id = 0.; 20 | 21 | vec2 dim = dMax - dMin; 22 | 23 | // Params 24 | #ifndef MIN_SIZE 25 | float MIN_SIZE = 0.001; 26 | #endif 27 | #ifndef MIN_ITERS 28 | float MIN_ITERS = 1.; 29 | #endif 30 | 31 | // Include rotation 32 | // TODO Figure out if this is a meaningful technique 33 | float accumRot = 0.; 34 | 35 | vec2 currentCenter = (dMin + dMax) * 0.5; 36 | for (float i = 0.; i < DIVIDE_ITERS; i++) { 37 | vec2 generationVariability = currentCenter; 38 | // Noise point to divide box into 4 pieces 39 | vec2 divHash = 0.5 + 0.20 * vec2( 40 | noise(vec2(i + 0. * id, seed) + generationVariability), 41 | noise(vec2(i + 0. * id + 2.782, seed) + generationVariability) 42 | ); 43 | 44 | // Move the dividing line 45 | divHash += 0.04 * cos(TWO_PI * t + i + dot(generationVariability, vec2(1))); 46 | 47 | // #define GOOD_START 1 48 | #ifdef GOOD_START 49 | if (i == 0.) { 50 | divHash = vec2(0.49, 0.501); 51 | } 52 | #endif 53 | 54 | vec2 divide = divHash * dim + dMin; 55 | 56 | // Clamp division Line 57 | divide = clamp(divide, dMin + MIN_SIZE, dMax - MIN_SIZE); 58 | 59 | // Find the minimum dimension size 60 | vec2 minAxis = min(abs(dMin - divide), abs(dMax - divide)); 61 | float minSize = vmin(minAxis); 62 | 63 | // check if too small 64 | bool smallEnough = minSize < MIN_SIZE; 65 | if (smallEnough && i + 1. > MIN_ITERS) { break; } 66 | 67 | // Rotate space on iterating 68 | accumRot += 0.065 * TWO_PI; 69 | 70 | // update the box domain 71 | vec2 overLine = step(q, divide); 72 | dMax = mix( dMax, divide, overLine); 73 | dMin = mix(divide, dMin, overLine); 74 | 75 | // id 76 | vec2 diff2 = mix(-divide, divide, overLine); 77 | id = length(diff2 + 10.) * 100.; 78 | 79 | // recalculate the dimension 80 | dim = dMax - dMin; 81 | currentCenter = (dMin + dMax) * 0.5; 82 | } 83 | 84 | vec2 center = (dMin + dMax) * 0.5; 85 | q -= center; 86 | 87 | // q *= rotMat2(accumRot); 88 | 89 | return vec3(dim, id); 90 | } 91 | 92 | vec3 subdivide (inout vec2 q, in float seed) { 93 | return subdivide(q, seed, 0.); 94 | } 95 | #pragma glslify: export(subdivide) 96 | -------------------------------------------------------------------------------- /octahedron.glsl: -------------------------------------------------------------------------------- 1 | #ifndef Iterations 2 | #define Iterations 10 3 | #endif 4 | 5 | float trapCalc (in vec3 p, in float k) { 6 | return dot(p, p) / (k * k); 7 | } 8 | 9 | float sdBox( vec3 p, vec3 b ) { 10 | vec3 d = abs(p) - b; 11 | return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); 12 | } 13 | 14 | #pragma glslify: octahedronFold = require(./folds/octahedron-fold, Iterations=Iterations, kifsM=kifsM, trapCalc=trapCalc) 15 | 16 | vec2 kifs( inout vec3 p ) { 17 | float minD = 10000.; 18 | 19 | p = octahedronFold(p, minD); 20 | 21 | return vec2(sdBox(p, vec3(0.5)) * pow(scale, - float(Iterations)), minD); 22 | } 23 | 24 | #pragma glslify: export(kifs) 25 | -------------------------------------------------------------------------------- /outline3.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 17 | 21 | 25 | 26 | 27 | 31 | 40 | 41 | ` 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raymarch", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "dependencies": { 7 | "a-big-triangle": "^1.0.3", 8 | "canvas-fit": "^1.5.0", 9 | "ccapture.js": "file:../ccapture.js", 10 | "eases": "^1.0.8", 11 | "gl-audio-analyser": "^1.0.3", 12 | "gl-context": "^0.1.1", 13 | "gl-fbo": "^2.0.5", 14 | "gl-matrix": "^2.3.2", 15 | "gl-shader": "^4.2.1", 16 | "gl-texture2d": "^2.1.0", 17 | "glsl-checker": "^1.0.1", 18 | "glsl-easings": "^1.0.0", 19 | "glsl-fast-gaussian-blur": "^1.0.2", 20 | "glsl-hsv2rgb": "^1.0.0", 21 | "glsl-inverse": "^1.0.0", 22 | "glsl-noise": "0.0.0", 23 | "glsl-sdf-ops": "0.0.3", 24 | "glslify": "^7.1.0", 25 | "lerp": "^1.0.3", 26 | "ndarray-fill": "^0.1.0", 27 | "octavian": "^2.2.0", 28 | "raf": "^3.3.0", 29 | "svg-parser": "^2.0.4", 30 | "svg-path-parser": "^1.1.0", 31 | "tween.js": "^16.6.0", 32 | "webvr-polyfill": "^0.9.24" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.9.0", 36 | "@babel/plugin-transform-runtime": "^7.12.10", 37 | "@babel/preset-env": "^7.9.0", 38 | "babel-plugin-add-module-exports": "^0.2.1", 39 | "babelify": "^10.0.0", 40 | "browserify": "^13.1.1", 41 | "budo": "^11.6.4", 42 | "core-js": "^3.8.1", 43 | "eslint": "^9.5.0", 44 | "eslint-config-standard": "^6.2.1", 45 | "eslint-plugin-promise": "^3.4.0", 46 | "eslint-plugin-standard": "^2.0.1", 47 | "glslify-hex": "^2.1.1", 48 | "glslify-import": "^3.0.0", 49 | "inquirer": "^3.0.6", 50 | "uglify-js": "^2.7.5" 51 | }, 52 | "eslintConfig": { 53 | "extends": "standard", 54 | "globals": { 55 | "Audio": true, 56 | "AudioContext": true, 57 | "Image": true, 58 | "dat": true, 59 | "CCapture": true, 60 | "XMLHttpRequest": true, 61 | "WebVRManager": true 62 | } 63 | }, 64 | "scripts": { 65 | "start": "shader-reload-cli index.js:bundle.js --live", 66 | "build": "npm run build:bundle", 67 | "build:bundle": "browserify app.js --standalone RayMarch | uglifyjs -cm > bundle.js", 68 | "build:gallery": "npm run build:bundle; node bin/gallery.js", 69 | "gh-pages": "git checkout gh-pages; git rebase master; git push -f origin gh-pages; git checkout master", 70 | "test": "eslint '**/*.js'" 71 | }, 72 | "keywords": [], 73 | "author": "Sean Zellmer (http://lejeunerenard.com)", 74 | "license": "MIT", 75 | "babel": { 76 | "presets": [ 77 | "@babel/preset-env" 78 | ], 79 | "plugins": [ 80 | "add-module-exports", 81 | "@babel/plugin-transform-runtime" 82 | ] 83 | }, 84 | "browserify": { 85 | "transform": [ 86 | "babelify", 87 | "glslify" 88 | ] 89 | }, 90 | "glslify": { 91 | "transform": [ 92 | "glslify-import", 93 | "glslify-hex" 94 | ] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /patterns/herring-bone.glsl: -------------------------------------------------------------------------------- 1 | #ifndef PI 2 | #define PI 3.1415926536 3 | #define TWO_PI 6.2831853072 4 | #endif 5 | 6 | #pragma glslify: pMod1 = require(../hg_sdf/p-mod1.glsl) 7 | 8 | float herringBone ( in vec2 q, float size, float borderThickness, float frequency, float stop ) { 9 | const float edge = 0.0025; 10 | 11 | float n = 1.; 12 | 13 | float c = pMod1(q.x, size); 14 | 15 | q.x = abs(q.x); 16 | 17 | n = dot(q, vec2(-1, 1)); 18 | n *= frequency; 19 | n = sin(TWO_PI * n + phase(c)); 20 | 21 | n = smoothstep(stop, stop + edge, n); 22 | n *= smoothstep(edge, 0., q.x - 0.5 * size + borderThickness); 23 | 24 | return n; 25 | } 26 | 27 | float herringBone ( in vec2 q, float size, float borderThickness, float frequency ) { 28 | return herringBone(q, size, borderThickness, frequency, 0.); 29 | } 30 | 31 | #pragma glslify: export(herringBone) 32 | -------------------------------------------------------------------------------- /pie-slice.glsl: -------------------------------------------------------------------------------- 1 | #ifndef TWO_PI 2 | #define TWO_PI 6.2831853072 3 | #endif 4 | 5 | #pragma glslify: rotMat2 = require(./rotation-matrix2) 6 | 7 | vec3 dMin (vec3 d1, vec3 d2) { 8 | return (d1.x < d2.x) ? d1 : d2; 9 | } 10 | 11 | // Expects the following: 12 | // - map : SDF w/ a vec3 for position & a float for the index 13 | // - repetitions : SDF w/ a vec3 for position & a float for the index 14 | vec3 pieSlice (in vec3 p) { 15 | vec3 d = vec3(1000000.0, 0, 0); 16 | 17 | float fRepetitions = float(repetitions); 18 | 19 | const float warpScale = 1.; 20 | 21 | float angle = 0.; 22 | float a = atan(p.y, p.x); 23 | 24 | for (int i = 0; i < repetitions; i++) { 25 | vec3 wQ = p; 26 | wQ.xy *= rotMat2(angle); 27 | vec3 b = map(wQ, float(i)); 28 | d = dMin(d, b); 29 | angle += TWO_PI / fRepetitions; 30 | } 31 | 32 | return d; 33 | } 34 | 35 | #pragma glslify: export(pieSlice) 36 | -------------------------------------------------------------------------------- /presets.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "islamic lattice", 4 | "scale": 1.6, 5 | "d": 5.1, 6 | "offset": { 7 | "x": 1.8, 8 | "y": 3.8, 9 | "z": 2.3 10 | } 11 | }, 12 | { 13 | "name": "hive castle", 14 | "scale": 1.56, 15 | "d": 4.1, 16 | "offset": { 17 | "x": 3.9, 18 | "y": -.9, 19 | "z": 3.6 20 | } 21 | }, 22 | { 23 | "name": "triangle cake", 24 | "scale": 2.01, 25 | "d": 5, 26 | "offset": { 27 | "x": 0.8, 28 | "y": 2.54, 29 | "z": 1.8 30 | } 31 | }, 32 | { 33 | "name": "mobius sponge", 34 | "scale": 2.08, 35 | "d": 4.6, 36 | "offset": { 37 | "x": 0.78, 38 | "y": 2, 39 | "z": 2.1 40 | } 41 | }, 42 | { 43 | "comment": "FoldInv, FoldInv, Fold" 44 | }, 45 | { 46 | "name": "sierpinski what?", 47 | "scale": 1.43, 48 | "d": 1.8, 49 | "angle2": 0.39269908169872414, 50 | "offset": { 51 | "x": 0.34, 52 | "y": 2, 53 | "z": 0.4 54 | } 55 | }, 56 | { 57 | "name": "ornate as fuck", 58 | "scale": 1.23, 59 | "d": 0.9, 60 | "angle2": 0.39269908169872414, 61 | "offset": { 62 | "x": 0.3400000035762787, 63 | "y": 2, 64 | "z": 0.4000000059604645 65 | } 66 | }, 67 | { 68 | "name": "chesser cat", 69 | "scale": 2.85, 70 | "d": 5.79, 71 | "angle2": 1.2, 72 | "offset": { 73 | "x": 3.1, 74 | "y": 1.8, 75 | "z": 1.55 76 | } 77 | }, 78 | { 79 | "name": "circular banding debug", 80 | "scale": 1.43, 81 | "d": 4.6, 82 | "angle2": 0.8, 83 | "offset": { 84 | "x": 1.33, 85 | "y": 3.4, 86 | "z": 2.1 87 | } 88 | }, 89 | { 90 | "name": "sierpinski laborythn", 91 | "scale": 1.95, 92 | "d": 0.2, 93 | "angle2": 3.1, 94 | "offset": { 95 | "x": -0.3, 96 | "y": 0.1, 97 | "z": 3.4 98 | } 99 | } 100 | ] 101 | -------------------------------------------------------------------------------- /ray-apply-proj-matrix.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: inverse = require(glsl-inverse) 2 | 3 | // getRayDirection source: 4 | // https://bitbucket.org/jimbo00000/opengl-with-luajit/src/33a9abcd04a468d9974d7b35f869d3b0e8e696f4/scene/shadertoy_scene2.lua?at=master&fileviewer=file-view-default#shadertoy_scene2.lua-215 5 | // Construct the usual eye ray frustum oriented down the negative z axis. 6 | // http://antongerdelan.net/opengl/raycasting.html 7 | vec3 getRayDirection(vec2 uv, in mat4 projectionMatrix) { 8 | vec4 ray_clip = vec4(uv.x, uv.y, -1., 1.); 9 | vec4 ray_eye = inverse(projectionMatrix) * ray_clip; 10 | return normalize(vec3(ray_eye.x, ray_eye.y, -1.)); 11 | } 12 | 13 | #pragma glslify: export(getRayDirection) 14 | -------------------------------------------------------------------------------- /reflection.glsl: -------------------------------------------------------------------------------- 1 | vec4 marchRef (in vec3 rayOrigin, in vec3 rayDirection, in float deltaT) { 2 | float t = 0.; 3 | float maxI = 0.; 4 | 5 | float trap = maxDistance; 6 | 7 | for (int i = 0; i < maxSteps / 3; i++) { 8 | vec3 d = map(rayOrigin + rayDirection * t, deltaT); 9 | if (d.x < epsilon) return vec4(t + d.x, d.y, float(i), d.z); 10 | t += d.x; 11 | maxI = float(i); 12 | trap = d.z; 13 | if (t > maxDistance) break; 14 | } 15 | return vec4(-1., 0., maxI, trap); 16 | } 17 | vec3 reflection (in vec3 ro, in vec3 rd, in float deltaT) { 18 | rd = normalize(rd); 19 | vec4 t = marchRef(ro + rd * .01, rd, deltaT); 20 | vec3 pos = ro + rd * t.x; 21 | vec3 color = vec3(0.); 22 | if (t.x > 0.) { 23 | vec3 nor = getNormal(pos, .0001, deltaT); 24 | color = diffuseColor(pos, nor, rd, t.y, t.w, deltaT); 25 | color /= max(1.0, pow(t.x, 1.0)); 26 | } else { 27 | color = vec3(0.125); // 0.6 * vec3(getBackground(pos.xy)); 28 | } 29 | 30 | return color; 31 | } 32 | vec3 reflection (in vec3 ro, in vec3 rd) { 33 | return reflection(ro, rd, 0.); 34 | } 35 | 36 | #pragma glslify: export(reflection) 37 | -------------------------------------------------------------------------------- /relink-pkgs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shader-webvr-manager 4 | cd ~/subchroma-2016/shader-webvr-manager/ 5 | npm link 6 | cd - 7 | npm link shader-webvr-manager 8 | 9 | # shader-vr-effect 10 | cd ~/subchroma-2016/shader-vr-effect/ 11 | npm link 12 | cd - 13 | npm link shader-vr-effect 14 | 15 | # shader-vr-orbit-controls 16 | cd ~/subchroma-2016/shader-vr-orbit-controls/ 17 | npm link 18 | cd - 19 | npm link shader-vr-orbit-controls 20 | -------------------------------------------------------------------------------- /repeat.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 9 | 12 | 15 | 18 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ` 36 | -------------------------------------------------------------------------------- /rgb2hsv.glsl: -------------------------------------------------------------------------------- 1 | vec3 rgb2hsv(vec3 c) 2 | { 3 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 4 | vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); 5 | vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); 6 | 7 | float d = q.x - min(q.w, q.y); 8 | float e = 1.0e-10; 9 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); 10 | } 11 | 12 | #pragma glslify: export(rgb2hsv) 13 | -------------------------------------------------------------------------------- /rotated-map.glsl: -------------------------------------------------------------------------------- 1 | #ifndef PI 2 | #define PI 3.1415926536 3 | #define TWO_PI 6.2831853072 4 | #endif 5 | 6 | #pragma glslify: rotationMatrix = require(./rotation-matrix3) 7 | 8 | float rotatedMap (in vec3 p) { 9 | const float angleOffset = (TWO_PI + PI * 0.125) * 2.0 / float(rotSymmetry); 10 | mat3 rotOffset = rotationMatrix(vec3(0.0, 0.0, 1.0), angleOffset); 11 | mat3 modelRotOffset = rotationMatrix(vec3(0.0, 0.0, 1.0), 0.2 * TWO_PI); 12 | 13 | float outD = 1000.0; 14 | 15 | for (int i = 0; i < rotSymmetry; i++) { 16 | vec3 r = p; 17 | r.z += 0.25 * sin(PI * (0.75 * float(i))); 18 | 19 | float d = map(r, i); 20 | outD = min(outD, d); 21 | 22 | p *= rotOffset; 23 | p.xy *= 0.99; 24 | } 25 | 26 | return outD; 27 | } 28 | 29 | #pragma glslify: export(rotatedMap) 30 | -------------------------------------------------------------------------------- /rotation-matrix2.glsl: -------------------------------------------------------------------------------- 1 | mat2 rotationMatrix2 (in float a ) { 2 | float c = cos(a); 3 | float s = sin(a); 4 | return mat2(c, -s, s, c); 5 | } 6 | 7 | #pragma glslify: export(rotationMatrix2) 8 | -------------------------------------------------------------------------------- /rotation-matrix3.glsl: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------- 2 | // http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/ 3 | // -------------------------------------------------------- 4 | 5 | mat3 rotationMatrix(vec3 axis, float angle) 6 | { 7 | axis = normalize(axis); 8 | float s = sin(angle); 9 | float c = cos(angle); 10 | float oc = 1.0 - c; 11 | 12 | return mat3( 13 | oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 14 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 15 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c 16 | ); 17 | } 18 | 19 | #pragma glslify: export(rotationMatrix) 20 | -------------------------------------------------------------------------------- /rotation-matrix4.glsl: -------------------------------------------------------------------------------- 1 | mat4 rotationMatrix(vec3 axis, float angle) { 2 | axis = normalize(axis); 3 | float s = sin(angle); 4 | float c = cos(angle); 5 | float oc = 1.0 - c; 6 | 7 | return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, 8 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, 9 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 10 | 0.0, 0.0, 0.0, 1.0); 11 | } 12 | 13 | #pragma glslify: export(rotationMatrix) 14 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejeunerenard/raymarching-experiments/c6e629958fd5d4474c519f977752574bc08e3a01/screenshot.jpg -------------------------------------------------------------------------------- /shaders/base-svg-sdf.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | #define PI 3.1415926536 3 | #define TWO_PI 6.2831853072 4 | #define PHI (1.618033988749895) 5 | #define saturate(x) clamp(x, 0.0, 1.0) 6 | 7 | precision highp float; 8 | 9 | varying vec2 fragCoord; 10 | uniform vec2 resolution; 11 | uniform float scale; 12 | uniform vec3 cameraRo; 13 | 14 | // --- SVG --- 15 | float svgD = 1e38; 16 | float _b; 17 | 18 | #define N 20. // splines discretization. Lower it on slow GPUs 19 | // absolute main SVG commands 20 | #define M(x,y) x0 = _x = x; y0 = _y = y; 21 | #define L(x,y) svgD = smin(svgD, line(uv, vec2(_x,_y), vec2(x,y)) ); _x=x,_y=y; 22 | #define C(x1,y1,x2,y2,x,y) svgD = smin(svgD, bezier(uv, vec2(_x,_y), vec2(x1,y1),vec2(x2,y2), vec2(x,y)) ); _x=x,_y=y; 23 | #define H(x) svgD = smin(svgD, line(uv, vec2(_x,_y), vec2(x,_y)) ); _x=x; 24 | #define V(y) svgD = smin(svgD, line(uv, vec2(_x,_y), vec2(_x,y)) ); _y=y; 25 | #define Z svgD = smin(svgD, line(uv, vec2(_x,_y), vec2(x0,y0)) ); _x=x0,_y=y0; 26 | // relative main SVG commands 27 | #define m(x,y) M(_x+x,_y+y) 28 | #define l(x,y) L(_x+x,_y+y) 29 | #define c(x1,y1,x2,y2,x,y) C(_x+x1,_y+y1,_x+x2,_y+y2,_x+x,_y+y) 30 | #define h(x) H(_x+x) 31 | #define v(y) V(_y+y) 32 | #define svgz Z 33 | 34 | #define style(f,c,d) fill=f; S=d; COL= mod(vec4((c)/65536,(c)/256,c,1),256.)/255.; 35 | #define path(cmd) svgD = 1e38; cmd; if (fill>0.) svgz; draw(svgD,O); 36 | 37 | #pragma glslify: import(./svg.glsl) 38 | 39 | vec3 two_dimensional (in vec2 uv) { 40 | vec3 color = vec3(0); 41 | 42 | vec2 q = uv; // UV is centered at (0, 0) 43 | 44 | vec2 wQ = q; 45 | 46 | wQ.xy += cameraRo.xy; 47 | wQ *= cameraRo.z; 48 | 49 | wQ.y *= -1.; // SVG Coords are upsidedown 50 | 51 | vec2 totalScale = vec2(1.); 52 | 53 | // Adjust into pixel space : [-resolution, resolution] 54 | // Scale is the ratio of the FBO resolution divided by SVG resolution 55 | totalScale *= resolution / scale; 56 | // This Account for it being [-1, 1] & resolution being [0, 1]. So makes the 57 | // space [-resolution/2, resolution/2] 58 | totalScale *= 0.5; 59 | 60 | wQ *= totalScale; 61 | 62 | q = wQ; 63 | 64 | float logo = SVG(q); 65 | logo /= min(totalScale.x, totalScale.y); 66 | 67 | color = vec3(logo); 68 | // color = vec3(step(0.5, logo)); 69 | 70 | // // Debug space 71 | // color.rg = q; 72 | // color.rg = color.gr; // Look at y 73 | 74 | // Convert to [0, 1] so it fits in a texture pixel value 75 | color = 0.5 * (color + 1.0); 76 | 77 | // // Show max clip 78 | // vec3 clipColor = color; 79 | // if (color.x >= 1.) { 80 | // clipColor = vec3(1, 0, 1); 81 | // } 82 | // // "Zero" before [0, 1] scale 83 | // if (color.x < 0.5) { 84 | // clipColor = vec3(0, 0, 1); 85 | // } 86 | // // Sub zero 87 | // if (color.x < 0.) { 88 | // clipColor = vec3(0, 1, 0); 89 | // } 90 | // color = clipColor; 91 | 92 | return color.rgb; 93 | } 94 | 95 | void main() { 96 | vec2 uv = fragCoord.xy; 97 | gl_FragColor = saturate(vec4(two_dimensional(uv), 1)); 98 | } 99 | -------------------------------------------------------------------------------- /shaders/bloom.shader.js: -------------------------------------------------------------------------------- 1 | const glslify = require('glslify') 2 | 3 | module.exports = require('shader-reload')({ 4 | vertex: glslify('../vert.glsl'), 5 | fragment: glslify('../bloom.glsl') 6 | }) 7 | -------------------------------------------------------------------------------- /shaders/bright.shader.js: -------------------------------------------------------------------------------- 1 | const glslify = require('glslify') 2 | 3 | module.exports = require('shader-reload')({ 4 | vertex: glslify('../vert.glsl'), 5 | fragment: glslify('../bright.glsl') 6 | }) 7 | -------------------------------------------------------------------------------- /shaders/final-pass.shader.js: -------------------------------------------------------------------------------- 1 | const glslify = require('glslify') 2 | 3 | module.exports = require('shader-reload')({ 4 | vertex: glslify('../vert.glsl'), 5 | fragment: glslify('../final-pass.glsl') 6 | }) 7 | -------------------------------------------------------------------------------- /shaders/frag.shader.js: -------------------------------------------------------------------------------- 1 | const glslify = require('glslify') 2 | 3 | module.exports = require('shader-reload')({ 4 | vertex: glslify('../vert.glsl'), 5 | fragment: glslify('../frag.glsl') 6 | }) 7 | -------------------------------------------------------------------------------- /shaders/svg.glsl: -------------------------------------------------------------------------------- 1 | // Source: 2 | // https://www.shadertoy.com/view/ldXyRn 3 | 4 | // improved version of https://www.shadertoy.com/view/MlVSWc 5 | // === SVG Player ==== short spec: https://www.w3.org/TR/2008/REC-SVGTiny12-20081222/paths.html 6 | 7 | float bezier(vec2,vec2,vec2,vec2,vec2); 8 | float line(vec2,vec2,vec2); 9 | void draw(float,inout vec4); 10 | const float FILL=1., CONTOUR=0., DIR=1., INDIR=-1.; 11 | vec4 COL = vec4(0); float fill=FILL, S=DIR; // style state 12 | 13 | float dh2=1e38, dh1; 14 | 15 | // smin: min of |dist| keeping signed distance. 16 | // smin0() is for easy cases (i.e. along splines) 17 | // smin() deals with ambiguous distance sign in the exterior fan for angles > 90° 18 | #define smin0(a,b) abs(_b=b) < abs(a) ? _b : a 19 | #define smin(a,b) abs(_b=b) < abs(a)+1e-2 ? abs(abs(_b)-abs(a))<1e-2 ? dh1p.y) != (b.y>p.y) && 39 | pa.x < ba.x * pa.y / ba.y ) S = -S; // track interior vs exterior 40 | return dot(d,d); // optimization by deferring sqrt 41 | } 42 | 43 | float bezier ( vec2 uv, vec2 A, vec2 B, vec2 C, vec2 D) { 44 | //float svgD = 1e5; // for correct sign desambiguation with prev 45 | vec2 p = A; 46 | for (float t = 1.; t <= N; t++) { 47 | vec2 q = interpolateSVG(A, B, C, D, t/N); 48 | float l = line(uv, p, q); 49 | svgD = t==1. ? smin (svgD, l ) : smin0(svgD, l ); // t==1 : same thing 50 | p = q; 51 | } 52 | return svgD; 53 | } 54 | 55 | void draw(float d, inout float O) 56 | { 57 | d = sqrt(d); // optimization by deferring sqrt here 58 | 59 | d = fill>0. ? S * d : d; // Apply sidedness 60 | O = min(O, d); 61 | } 62 | 63 | // === SVG drawing =============================================================== 64 | float SVG (vec2 uv) { 65 | float O = 1e10; 66 | float _x, _y, x0, y0; 67 | 68 | // Copy-paste your SVG pathes here. Slight adaptations : 69 | // - add () around command params and comma between points, 70 | // - split polylines and polybéziers into sets of 1 vs 3 pairs of coordinates 71 | // - path( style( FILL/CONTOUR, color(hexa) ) 72 | // commands 73 | // ) 74 | 75 | #define SVG_PATHS 1 76 | 77 | return O; 78 | } 79 | // #pragma glslify: export(mainImage) 80 | -------------------------------------------------------------------------------- /shaders/svgSDF.js: -------------------------------------------------------------------------------- 1 | const glslify = require('glslify') 2 | 3 | // Base GLSL 4 | const SVG_BASE = glslify('./base-svg-sdf.glsl') 5 | 6 | function createSVGShader (svgPaths) { 7 | return SVG_BASE.replace(/\s*#define SVG_PATHS 1/, '\n' + svgPaths) 8 | } 9 | 10 | module.exports = function createSVGSDF (svgPaths) { 11 | return { 12 | vertex: glslify('../vert.glsl'), 13 | fragment: createSVGShader(svgPaths) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /simple-index.js: -------------------------------------------------------------------------------- 1 | import DefaultSceneRenderer from './default-scene-renderer' 2 | import App from './app' 3 | 4 | let app = new App({ sceneRenderer }) 5 | let sceneRenderer = new DefaultSceneRenderer(app.gl) 6 | app.sceneRenderer = sceneRenderer.render.bind(sceneRenderer) 7 | let resize = () => { 8 | let dim = app.getDimensions() 9 | sceneRenderer.resize(dim[0], dim[1]) 10 | } 11 | window.addEventListener('resize', resize) 12 | resize() 13 | 14 | app.run() 15 | 16 | let currentRAF 17 | let tick = (t) => { 18 | currentRAF = window.requestAnimationFrame(tick) 19 | 20 | app.tick(t) 21 | } 22 | 23 | app.loaded.then(() => { 24 | if (!currentRAF) { 25 | currentRAF = window.requestAnimationFrame(tick) 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /soft-shadows.glsl: -------------------------------------------------------------------------------- 1 | #define RAY_STEPS maxSteps 2 | // TECHNIQUE: 3 | // 0 : Default proportion of closest distance / distance from point 4 | // 1 : More pessimistic closest point based on previous & current sphere 5 | // 2 : Proportion but supports negative distances 6 | #define TECHNIQUE 2 7 | 8 | // Sources 9 | // - https://iquilezles.org/articles/rmshadows/ 10 | // - https://www.shadertoy.com/view/lsKcDD 11 | // - https://www.shadertoy.com/view/Xds3zN 12 | 13 | float softshadow( in vec3 ro, in vec3 rd, in float mint, in float maxt, in float k, in float generalT ) { 14 | float res = 1.0; 15 | #if TECHNIQUE == 1 16 | float ph = 1e10; // big so that y = 0 on the first iteration 17 | #endif 18 | float t = mint; 19 | 20 | for( int i=0; i= maxt) break; 46 | #else 47 | t += h; 48 | 49 | if(res < 0.0001 || t >= maxt) break; 50 | #endif 51 | } 52 | 53 | #if TECHNIQUE == 2 54 | res = max(res, -1.); 55 | return 0.25 * (1. + res) * (1. + res) * (2. - res); 56 | #else 57 | res = clamp( res, 0.0, 1.0 ); 58 | 59 | // Found this in iq's example implementations. Why it's there, I have no 60 | // clue. I don't notice a difference. 61 | // res = res * res * (3. - 2. * res); 62 | return res; 63 | #endif 64 | } 65 | 66 | float softshadow( in vec3 ro, in vec3 rd, in float mint, in float maxt ) { 67 | return softshadow( ro, rd, mint, maxt, 0.125, 0. ); 68 | } 69 | 70 | #pragma glslify: export(softshadow) 71 | -------------------------------------------------------------------------------- /sphere-fold.glsl: -------------------------------------------------------------------------------- 1 | void sphereFold (inout vec3 p, inout float dz) { 2 | const float fixedRadius2 = 1.; 3 | const float minRadius2 = .25; 4 | 5 | float r2 = dot(p, p); 6 | if (r2 < minRadius2) { 7 | float temp = (fixedRadius2 / minRadius2); 8 | p *= temp; 9 | dz *= temp; 10 | } else if (r2 < fixedRadius2) { 11 | float temp = (fixedRadius2 / r2); 12 | p *= temp; 13 | dz *= temp; 14 | } 15 | } 16 | 17 | #pragma glslify: export(sphereFold) 18 | -------------------------------------------------------------------------------- /split.glsl: -------------------------------------------------------------------------------- 1 | #ifndef TWO_PI 2 | #define TWO_PI 6.2831853072 3 | #endif 4 | 5 | // TODO properly test 6 | 7 | float sdBox( vec3 p, vec3 b ) { 8 | vec3 d = abs(p) - b; 9 | return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); 10 | } 11 | 12 | #pragma glslify: pMod1 = require(./hg_sdf/p-mod1) 13 | 14 | float split (inout vec3 p, in float splitTime) { 15 | float padding = size * (0.5 + 0.5 * cos(TWO_PI * splitTime - PI)); 16 | float index = floor(p.y / (2.0 * padding + size)); 17 | p.y = -index * 2.0 * padding + p.y; 18 | // Global offset 19 | p.y -= padding; 20 | 21 | vec3 cP = p; 22 | cP.y -= 0.5 * size + padding; 23 | float cI = pMod1(cP.y, 2.0 * padding + size); 24 | 25 | #ifndef breath 26 | const float breadth = 10.0; 27 | #endif 28 | 29 | return sdBox(cP, vec3(breadth, 0.5 * size, breadth)); 30 | } 31 | 32 | #pragma glslify: export(split) 33 | -------------------------------------------------------------------------------- /stone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejeunerenard/raymarching-experiments/c6e629958fd5d4474c519f977752574bc08e3a01/stone.jpg -------------------------------------------------------------------------------- /svg-to-glsl.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('svg-parser') 2 | const parseSVGPath = require('svg-path-parser') 3 | 4 | function float (number) { 5 | let str = number + '' 6 | if (!str.match(/\./)) { 7 | str += '.' 8 | } 9 | 10 | return str 11 | } 12 | 13 | function walk (node, metadata = {}) { 14 | if (node.type === 'element' && node.tagName === 'path') { 15 | if ('d' in node.properties) { 16 | const pathCommands = parseSVGPath(node.properties.d) 17 | console.log('pathCommands', pathCommands) 18 | const offset = metadata.offset ? metadata.offset : { x: 0, y: 0 } 19 | 20 | let bounds = { x: { min: Infinity, max: -Infinity }, y: { min: Infinity, max: -Infinity } } 21 | let pathGLSL = 'path(\n' 22 | 23 | // Style 24 | pathGLSL += ' style(FILL, 0x000000, DIR)\n' 25 | 26 | // Commands 27 | let hasMidPathClose = false 28 | let prevCommand = null 29 | let prevControlPoint = {} 30 | let currentPosition = {} 31 | for (let i = 0; i < pathCommands.length; i++) { 32 | const command = pathCommands[i] 33 | 34 | // Translate coordinate system 35 | if (!command.relative) { 36 | // Xs 37 | if ('x' in command) { 38 | command.x += offset.x 39 | } 40 | if ('x1' in command) { 41 | command.x1 += offset.x 42 | } 43 | if ('x2' in command) { 44 | command.x2 += offset.x 45 | } 46 | 47 | // Ys 48 | if ('y' in command) { 49 | command.y += offset.y 50 | } 51 | if ('y1' in command) { 52 | command.y1 += offset.y 53 | } 54 | if ('y2' in command) { 55 | command.y2 += offset.y 56 | } 57 | } 58 | 59 | switch (command.code) { 60 | case 'M': 61 | pathGLSL += ` M(${float(command.x)},${float(command.y)})\n` 62 | 63 | // Movement 64 | currentPosition.x = command.x 65 | currentPosition.y = command.y 66 | break 67 | case 'm': 68 | pathGLSL += ` m(${float(command.x)},${float(command.y)})\n` 69 | 70 | // Movement 71 | currentPosition.x += command.x 72 | currentPosition.y += command.y 73 | break 74 | case 'l': 75 | pathGLSL += ` l(${float(command.x)},${float(command.y)})\n` 76 | 77 | // Movement 78 | currentPosition.x += command.x 79 | currentPosition.y += command.y 80 | break 81 | case 'L': 82 | pathGLSL += ` L(${float(command.x)},${float(command.y)})\n` 83 | 84 | // Movement 85 | currentPosition.x = command.x 86 | currentPosition.y = command.y 87 | break 88 | case 'c': 89 | pathGLSL += ` c(${float(command.x1)},${float(command.y1)},${float(command.x2)},${float(command.y2)},${float(command.x)},${float(command.y)})\n` 90 | prevControlPoint.x = currentPosition.x + command.x2 91 | prevControlPoint.y = currentPosition.y + command.y2 92 | 93 | // Movement 94 | currentPosition.x += command.x 95 | currentPosition.y += command.y 96 | break 97 | case 'C': 98 | pathGLSL += ` C(${float(command.x1)},${float(command.y1)},${float(command.x2)},${float(command.y2)},${float(command.x)},${float(command.y)})\n` 99 | prevControlPoint.x = command.x2 100 | prevControlPoint.y = command.y2 101 | 102 | // Movement 103 | currentPosition.x = command.x 104 | currentPosition.y = command.y 105 | break 106 | case 's': 107 | const controlPoint1 = {} 108 | if (prevCommand.code.toLowerCase() === 'c' || 109 | prevCommand.code.toLowerCase() === 's') { 110 | // Compute control point as mirrored from previous control point and as relative 111 | // delta = prevControl - current 112 | // mirroredDelta = -delta 113 | // newControl = current + mirroredDelta 114 | // newControl = current - prevControl + current 115 | // newRelativeControl + current = newControl 116 | // newRelativeControl = newControl - current 117 | // newRelativeControl = current - prevControl + current - current 118 | // newRelativeControl = current - prevControl 119 | controlPoint1.x = currentPosition.x - prevControlPoint.x 120 | controlPoint1.y = currentPosition.y - prevControlPoint.y 121 | } 122 | 123 | pathGLSL += ` c(${float(controlPoint1.x)},${float(controlPoint1.y)},${float(command.x2)},${float(command.y2)},${float(command.x)},${float(command.y)})\n` 124 | 125 | prevControlPoint.x = currentPosition.x + command.x2 126 | prevControlPoint.y = currentPosition.y + command.y2 127 | 128 | // Movement 129 | currentPosition.x += command.x 130 | currentPosition.y += command.y 131 | break 132 | case 'h': 133 | pathGLSL += ` h(${float(command.x)})\n` 134 | 135 | // Movement 136 | currentPosition.x += command.x 137 | break 138 | case 'H': 139 | pathGLSL += ` H(${float(command.x)})\n` 140 | 141 | // Movement 142 | currentPosition.x = command.x 143 | break 144 | case 'v': 145 | pathGLSL += ` v(${float(command.y)})\n` 146 | 147 | // Movement 148 | currentPosition.y += command.y 149 | break 150 | case 'V': 151 | pathGLSL += ` V(${float(command.y)})\n` 152 | 153 | // Movement 154 | currentPosition.y = command.y 155 | break 156 | case 'Z': 157 | if (i !== pathCommands.length - 1) hasMidPathClose = true 158 | 159 | // Disabling the `Z` fixes a rendering issue that only shows when a 160 | // path without `Z`s mid way through it ends on a `Z`. This maybe 161 | // is caused by the last command ending on the starting coordinates 162 | // instead of relying on the `Z` command to complete the path? 163 | const disable = i !== pathCommands.length - 1 || hasMidPathClose ? '' : '// ' 164 | pathGLSL += ` ${disable}Z\n` 165 | break 166 | case 'z': 167 | pathGLSL += ` svgz\n` 168 | break 169 | default: 170 | console.log('skipped command', command) 171 | break 172 | } 173 | 174 | prevCommand = command 175 | 176 | bounds.x.min = Math.min(bounds.x.min, currentPosition.x) 177 | bounds.y.min = Math.min(bounds.y.min, currentPosition.y) 178 | bounds.x.max = Math.max(bounds.x.max, currentPosition.x) 179 | bounds.y.max = Math.max(bounds.y.max, currentPosition.y) 180 | } 181 | 182 | pathGLSL += ')\n' 183 | 184 | console.log('bounds', bounds) 185 | return { glsl: pathGLSL, metadata } 186 | } 187 | } else if (node.type === 'element' && node.tagName === 'style') { 188 | return { glsl: '', metadata } 189 | } else { 190 | let output = '' 191 | 192 | if (node.type === 'element' && node.tagName === 'svg' && 'viewBox' in node.properties) { 193 | const coordinates = node.properties.viewBox.split(' ') 194 | metadata.viewBox = { 195 | x1: coordinates[0], 196 | y1: coordinates[1], 197 | x2: coordinates[2], 198 | y2: coordinates[3] 199 | } 200 | metadata.offset = metadata.offset || { x: 0, y: 0 } 201 | metadata.offset.x = -(metadata.viewBox.x2 - metadata.viewBox.x1) / 2 202 | metadata.offset.y = -(metadata.viewBox.y2 - metadata.viewBox.y1) / 2 203 | console.log('metadata.viewBox', metadata.viewBox) 204 | } 205 | 206 | for (let child of node.children) { 207 | const { glsl, metadata: childMetadata } = walk(child, metadata) 208 | metadata = childMetadata 209 | output += glsl 210 | } 211 | 212 | return { glsl: output, metadata } 213 | } 214 | } 215 | 216 | module.exports = function convert (svgText) { 217 | const parsed = parse(svgText) 218 | console.log('parsed', parsed) 219 | 220 | return walk(parsed) 221 | } 222 | -------------------------------------------------------------------------------- /time.glsl: -------------------------------------------------------------------------------- 1 | const float totalT = 5.0; 2 | float modT = 0.; 3 | float norT = 0.; 4 | float cosT = 0.; 5 | -------------------------------------------------------------------------------- /twist.glsl: -------------------------------------------------------------------------------- 1 | vec3 opTwist( vec3 p, float angle ) { 2 | float c = cos(angle); 3 | float s = sin(angle); 4 | mat2 m = mat2(c,-s,s,c); 5 | return vec3(m*p.xz,p.y); 6 | } 7 | 8 | #pragma glslify: export(opTwist) 9 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from 'gl-matrix' 2 | 3 | export function isIOS () { 4 | return !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform) 5 | } 6 | export function isAndroid () { 7 | let ua = navigator.userAgent.toLowerCase() 8 | return ua.indexOf('android') > -1 9 | } 10 | 11 | export function rot4 (axis, angle) { 12 | const s = Math.sin(angle) 13 | const c = Math.cos(angle) 14 | const oc = 1.0 - c 15 | vec3.normalize(axis, axis) 16 | 17 | return mat4.fromValues( 18 | oc * axis[0] * axis[0] + c, oc * axis[0] * axis[1] - axis[2] * s, oc * axis[2] * axis[0] + axis[1] * s, 0.0, 19 | oc * axis[0] * axis[1] + axis[2] * s, oc * axis[1] * axis[1] + c, oc * axis[1] * axis[2] - axis[0] * s, 0.0, 20 | oc * axis[2] * axis[0] - axis[1] * s, oc * axis[1] * axis[2] + axis[0] * s, oc * axis[2] * axis[2] + c, 0.0, 21 | 0.0, 0.0, 0.0, 1.0) 22 | } 23 | -------------------------------------------------------------------------------- /vert.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec2 position; 4 | uniform vec2 resolution; 5 | varying vec2 fragCoord; 6 | 7 | void main() { 8 | // Adjust coordinates to (-1, -1) -> (1, 1) 9 | fragCoord = position.xy * resolution / max(resolution.y, resolution.x); 10 | fragCoord *= 0.80; // HAK adjustment to get sizing to work w/ minimal effort 11 | 12 | gl_Position = vec4(position.xy, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /voronoi.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: rotMat2 = require(./rotation-matrix2) 2 | #ifndef PI 3 | #define PI 3.1415926536 4 | #endif 5 | #ifndef TWO_PI 6 | #define TWO_PI 6.2831853072 7 | #endif 8 | 9 | vec2 hash( vec2 p ) { 10 | p = vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))); 11 | return fract(sin(p)*18.5453); 12 | } 13 | 14 | vec3 hash( vec3 x ) 15 | { 16 | x = vec3( dot(x,vec3(127.1,311.7, 74.7)), 17 | dot(x,vec3(269.5,183.3,246.1)), 18 | dot(x,vec3(113.5,271.9,124.6)) ); 19 | 20 | return fract(sin(x)*43758.5453123); 21 | } 22 | 23 | vec2 voronoi(in vec2 x, in float time) { 24 | vec2 p = floor(x); 25 | vec2 f = fract(x); 26 | 27 | vec3 res = vec3(8.0); 28 | for (int j=-1; j<=1; j++) { 29 | for (int i=-1; i<=1; i++) { 30 | vec2 b = vec2(i, j); 31 | vec2 offset = hash(p + b + time); 32 | offset = 0.5 + 0.5 * cos( time + TWO_PI * offset ); 33 | vec2 r = vec2(b) - f + offset; 34 | float d = dot( r, r ); 35 | // float d = dot( abs(r), vec2(1) ); 36 | // float d = min( abs(r.x), abs(r.y) ); 37 | // vec2 absR = abs(r); 38 | // float d = max( absR.x, absR.y ); 39 | 40 | // Mask output 41 | // float v = mask((p + b) * 0.090909); 42 | // if (d < res.x && v < 0.00125) { 43 | 44 | if (d < res.x) { 45 | res = vec3( d, offset ); 46 | } 47 | } 48 | } 49 | // Add mask to distance calculation 50 | // float v = mask(x * 0.090909); 51 | // res.x = min(res.x, -24. * v); 52 | // res.x = mix(1., res.x, smoothstep(0.00125, 0., v)); 53 | 54 | return vec2(sqrt( res.x ), dot(res.yz, vec2(1.0))); 55 | } 56 | vec2 voronoi(in vec2 x) { 57 | return voronoi(x, 0.0); 58 | } 59 | 60 | float metric (in vec3 r) { 61 | return length(r); 62 | } 63 | 64 | vec3 voronoi(in vec3 x, in float time) { 65 | vec3 p = floor(x); 66 | vec3 f = fract(x); 67 | 68 | float id = 0.0; 69 | vec2 res = vec2(100.0); 70 | for (int k=-1; k<=1; k++) 71 | for (int j=-1; j<=1; j++) 72 | for (int i=-1; i<=1; i++) 73 | { 74 | vec3 b = vec3(i, j, k); 75 | vec3 offset = hash(p + b + 5.34); 76 | vec3 r = b - f + offset; 77 | float d = metric(r); 78 | 79 | vec3 localId = p + b; 80 | vec3 absLocalId = abs(localId); 81 | 82 | if (d < res.x && max(absLocalId.x, max(absLocalId.y, absLocalId.z)) - 3. < 0.) { 83 | id = dot( p + b, vec3(1.0, 57.0, 113.0) ); 84 | res = vec2(d, res.x); 85 | } else if ( d < res.y ) { 86 | res.y = d; 87 | } 88 | } 89 | 90 | return vec3(sqrt(res), abs(id)); 91 | } 92 | 93 | #pragma glslify: export(voronoi) 94 | -------------------------------------------------------------------------------- /word.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 9 | 12 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ` 29 | -------------------------------------------------------------------------------- /year-6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /year-6.svg.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ` 28 | --------------------------------------------------------------------------------