├── .gitignore
├── .npmignore
├── LICENSE.md
├── README.md
├── index.html
├── index.js
├── package.json
├── src
├── audio.js
├── background.js
├── colors.js
├── engine.js
├── shader.js
├── shaders
│ ├── point.frag
│ └── point.vert
└── video.js
└── video
└── nyc.mp4
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules/
3 | !src/node_modules
4 | *.log
5 | .DS_Store
6 | bundle.js
7 | img/
8 | video/
9 | audio/
10 | !video/nyc.mp4
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | *.log
4 | .DS_Store
5 | bundle.js
6 | test
7 | test.js
8 | demo/
9 | .npmignore
10 | LICENSE.md
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2015 Matt DesLauriers
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
20 | OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ink
2 |
3 | [](http://github.com/badges/stability-badges)
4 |
5 | Experiments with particles, video, and SoundCloud.
6 |
7 | ## Usage
8 |
9 | [](https://www.npmjs.com/package/ink)
10 |
11 | ## License
12 |
13 | MIT, see [LICENSE.md](http://github.com/mattdesl/ink/blob/master/LICENSE.md) for details.
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | sketch
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const gl = require('webgl-context')()
2 | const canvas = gl.canvas
3 | const dpr = Math.min(1.5, window.devicePixelRatio)
4 | const fit = require('canvas-fit')(canvas, window, dpr)
5 | const loop = require('raf-loop')
6 | const engine = require('./src/engine')(gl)
7 | const background = require('./src/background')(gl)
8 |
9 | require('domready')(() => {
10 | window.addEventListener('resize', fit, false)
11 | document.body.appendChild(canvas)
12 | loop(draw).start()
13 | })
14 |
15 | function draw(dt) {
16 | const { drawingBufferWidth: width, drawingBufferHeight: height } = gl
17 |
18 | gl.viewport(0, 0, width, height)
19 |
20 | gl.disable(gl.DEPTH_TEST)
21 | gl.disable(gl.CULL_FACE)
22 | gl.disable(gl.BLEND)
23 |
24 | gl.clearColor(0, 0, 0, 1)
25 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
26 |
27 | background(dt)
28 |
29 | // gl.enable(gl.BLEND)
30 | // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
31 | engine(dt)
32 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ink",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "author": {
8 | "name": "Matt DesLauriers",
9 | "email": "dave.des@gmail.com",
10 | "url": "https://github.com/mattdesl"
11 | },
12 | "dependencies": {
13 | "array-range": "^1.0.1",
14 | "canvas-fit": "^1.4.0",
15 | "dom-events": "^0.1.1",
16 | "domready": "^1.0.7",
17 | "gl-audio-analyser": "^1.0.0",
18 | "gl-basic-shader": "^1.3.0",
19 | "gl-buffer": "^2.1.1",
20 | "gl-geometry": "^1.0.3",
21 | "gl-mat4": "^1.1.2",
22 | "gl-shader": "^4.0.1",
23 | "gl-texture2d": "^2.0.8",
24 | "gl-vao": "^1.2.0",
25 | "gl-vec3": "^1.0.3",
26 | "gl-vignette-background": "^1.0.4",
27 | "glsl-aastep": "^1.0.1",
28 | "glsl-easings": "^1.0.0",
29 | "glsl-luma": "^1.0.1",
30 | "glsl-noise": "0.0.0",
31 | "glslify": "^2.1.2",
32 | "hex-rgb": "^1.0.0",
33 | "icosphere": "^1.0.0",
34 | "img": "^1.0.0",
35 | "raf-loop": "^1.0.1",
36 | "randf": "^1.0.0",
37 | "soundcloud-badge": "^1.0.0",
38 | "webgl-context": "^2.1.2"
39 | },
40 | "devDependencies": {
41 | "babelify": "^6.0.2",
42 | "browserify": "^9.0.8",
43 | "budo": "^5.1.4",
44 | "garnish": "^3.2.1",
45 | "uglify-js": "^2.4.20",
46 | "watchify": "^3.1.1"
47 | },
48 | "scripts": {
49 | "build": "browserify index.js -t babelify -t glslify | uglifyjs -cm > bundle.js",
50 | "start": "budo index.js:bundle.js --live -- -t babelify -t glslify | garnish"
51 | },
52 | "keywords": [],
53 | "repository": {
54 | "type": "git",
55 | "url": "git://github.com/mattdesl/ink.git"
56 | },
57 | "homepage": "https://github.com/mattdesl/ink",
58 | "bugs": {
59 | "url": "https://github.com/mattdesl/ink/issues"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/audio.js:
--------------------------------------------------------------------------------
1 | import badge from 'soundcloud-badge'
2 | import Analyser from 'gl-audio-analyser'
3 |
4 | module.exports = function(gl) {
5 | const audio = new Audio()
6 | const analyser = Analyser(gl, audio)
7 |
8 | badge({
9 | client_id: 'b95f61a90da961736c03f659c03cb0cc',
10 | song: 'https://soundcloud.com/kartell/sg-lewis-no-less-kartell-remix-1',
11 | dark: false,
12 | getFonts: true
13 | }, function(err, src, json, div) {
14 | if (err) throw err
15 |
16 | //temp fix since CORS are not enabled on SoundCloud :(
17 | audio.crossOrigin = 'Anonymous'
18 | audio.src = src
19 | audio.loop = true
20 | audio.addEventListener('canplay', function() {
21 | console.log("Playing audio...")
22 | audio.play()
23 | }, false)
24 | })
25 |
26 | return function waveform(shader) {
27 | shader.uniforms.iWaveform = analyser.bindWaveform(1)
28 | }
29 | }
--------------------------------------------------------------------------------
/src/background.js:
--------------------------------------------------------------------------------
1 | import create from 'gl-vignette-background'
2 | import { rgb } from './colors'
3 |
4 | module.exports = function(gl) {
5 | const bg = create(gl)
6 |
7 | //optional styling
8 | bg.style({
9 | scale: [ 1, 1 ],
10 | aspect: 1,
11 | color1: rgb('#e4d3b6'),
12 | color2: rgb('#6f614a'),
13 | smoothing: [ -0.5, 1.0 ],
14 | noiseAlpha: 0.08,
15 | coloredNoise: true,
16 | offset: [ -0.05, -0.15 ]
17 | })
18 |
19 | return function() {
20 | bg.style({ aspect: gl.drawingBufferWidth / gl.drawingBufferHeight })
21 | bg.draw()
22 | }
23 | }
--------------------------------------------------------------------------------
/src/colors.js:
--------------------------------------------------------------------------------
1 | import hex from 'hex-rgb'
2 |
3 | export const rgb = (str) => hex(str).map(x => x/255)
4 | export const rgba = (str, a=1) => [...rgb(str), a]
5 | export default hex
6 |
--------------------------------------------------------------------------------
/src/engine.js:
--------------------------------------------------------------------------------
1 | import createGeom from 'gl-geometry'
2 | import range from 'array-range'
3 | import rand from 'randf'
4 | import { rgb } from './colors'
5 | import { length, random as rvec3 } from 'gl-vec3'
6 | import icosphere from 'icosphere'
7 |
8 | import createTexture from 'gl-texture2d'
9 | import createShader from './shader'
10 | import createVideo from './video'
11 | import createAudio from './audio'
12 | import loadImage from 'img'
13 |
14 | const lineTint = rgb('#be360d')
15 | const pointTint = rgb('#000')
16 | const sphere = icosphere(3)
17 |
18 | //get N random 3D unit vectors
19 | const N = 30000
20 | const unit = () => rand(-1, 1)
21 | const unitVector = () => range(3).map(unit)
22 | const sphereVector = () => rvec3([])
23 |
24 | const pointPositions = range(N).map(sphereVector)
25 | const pointOffsets = range(N).map(unitVector)
26 | const linePositions = sphere.positions
27 | const lineOffsets = pointOffsets
28 | //.map(x => x.map(y => y*2.5))
29 |
30 | module.exports = function(gl) {
31 | const points = createGeom(gl)
32 | .attr('position', pointPositions)
33 | .attr('offset', pointOffsets)
34 |
35 | const lines = createGeom(gl)
36 | .attr('position', linePositions)
37 | .attr('offset', lineOffsets)
38 | // .faces(sphere.cells)
39 |
40 | const program = createShader(gl)
41 | const audio = createAudio(gl)
42 |
43 | let texture
44 | const updateVideo = createVideo(gl, (err, tex) => {
45 | texture = tex
46 | })
47 | // loadImage('img/road.jpg', function(err, image) {
48 | // if (err)
49 | // console.error(err)
50 | // texture = createTexture(gl, image)
51 | // texture.filter = [ gl.LINEAR, gl.LINEAR ]
52 | // texture.wrap = [ gl.CLAMP_TO_EDGE, gl.CLAMP_TO_EDGE ]
53 | // })
54 |
55 | return function(dt) {
56 | if (!texture)
57 | return
58 |
59 |
60 | // gl.lineWidth(3)
61 |
62 | program.bind(dt)
63 | audio(program.shader)
64 | texture.bind(0)
65 | texture.update()
66 |
67 | program.tint(lineTint)
68 | lines.bind(program.shader)
69 | lines.draw(gl.LINE_LOOP)
70 | lines.unbind()
71 |
72 | program.tint(pointTint)
73 | points.bind(program.shader)
74 | points.draw(gl.POINTS)
75 | points.unbind()
76 | }
77 | }
--------------------------------------------------------------------------------
/src/shader.js:
--------------------------------------------------------------------------------
1 | import mat4 from 'gl-mat4'
2 | import createShader from 'gl-shader'
3 |
4 | const projection = mat4.create()
5 | const model = mat4.create()
6 | const view = mat4.create()
7 |
8 | // needed for AST
9 | const glslify = require('glslify')
10 |
11 | module.exports = function(gl) {
12 | const vert = glslify('./shaders/point.vert')
13 | const frag = glslify('./shaders/point.frag')
14 |
15 | var images = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS)
16 | if (images <= 0) {
17 | alert("Sorry, this demo isn't ready for your device yet!")
18 | console.error("no vertex texture image units")
19 | }
20 |
21 | const shader = createShader(gl, vert, frag)
22 |
23 | let time = 0
24 | let rotation = Math.PI/2
25 | let resolution = [gl.drawingBufferWidth, gl.drawingBufferHeight]
26 | let translation = [0, 0, -5]
27 |
28 | function bind(dt) {
29 | time += dt / 1000
30 |
31 | const width = gl.drawingBufferWidth
32 | const height = gl.drawingBufferHeight
33 | const aspect = width / height
34 |
35 | mat4.perspective(projection, Math.PI/4, aspect, 1, 1000)
36 |
37 | mat4.identity(model)
38 | mat4.translate(model, model, translation)
39 | mat4.rotateY(model, model, Math.PI/2)
40 | mat4.rotateX(model, model, rotation)
41 | // mat4.rotateZ(model, model, rotation)
42 | rotation += dt * 0.000025
43 |
44 | shader.bind()
45 | shader.uniforms.iGlobalTime = time
46 | shader.uniforms.iResolution = resolution
47 | shader.uniforms.iChannel0 = 0
48 | shader.uniforms.projection = projection
49 | shader.uniforms.model = model
50 | shader.uniforms.view = view
51 | }
52 |
53 | return {
54 | shader: shader,
55 | bind: bind,
56 | tint: (rgb) => { shader.uniforms.tint = rgb }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/shaders/point.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec2 iResolution;
4 | uniform vec3 tint;
5 | uniform sampler2D iChannel0;
6 | varying vec2 uv;
7 |
8 | #pragma glslify: luma = require('glsl-luma')
9 | // #pragma glslify: audio = require('gl-audio-analyser')
10 |
11 | void main() {
12 | float dist = length(uv - 0.5);
13 | dist = smoothstep(0.4, 0.5, dist);
14 |
15 | vec3 color = texture2D(iChannel0, uv + vec2(0.0, 0.05)).rgb;
16 |
17 | // color += max(0., +amplitude) * vec3(0.5, 1.0, 0.2);
18 | // color += max(0., -amplitude) * vec3(0.9, 0.1, 0.4);
19 |
20 |
21 | if (tint == vec3(0.0)) {
22 | gl_FragColor.rgb = vec3(luma(color));
23 | }
24 | else {
25 | gl_FragColor.rgb = tint;
26 | }
27 | gl_FragColor.a = 1.0;
28 | // gl_FragColor = vec4(1.0) * tint;
29 | }
30 |
--------------------------------------------------------------------------------
/src/shaders/point.vert:
--------------------------------------------------------------------------------
1 | #pragma glslify: noise = require('glsl-noise/simplex/4d')
2 | #pragma glslify: audio = require('gl-audio-analyser')
3 | #pragma glslify: ease = require('glsl-easings/sine-in-out')
4 |
5 | #define POINT_SIZE 2.2
6 |
7 | attribute vec4 position;
8 | attribute vec3 offset;
9 |
10 | uniform sampler2D iWaveform;
11 | uniform mat4 projection;
12 | uniform mat4 view;
13 | uniform mat4 model;
14 | uniform float iGlobalTime;
15 |
16 | varying vec2 uv;
17 |
18 | void main() {
19 | vec3 vpos = position.xyz;
20 | vec3 mult = offset;
21 |
22 |
23 | float audioCoord = vpos.x;
24 | float amplitude = max(0.0, audio(iWaveform, audioCoord));
25 | amplitude = smoothstep(0.0, 3.0, amplitude*3.0);
26 | amplitude = ease(amplitude);
27 |
28 | vec4 npos = vec4(vpos, sin(iGlobalTime * sin(2.85 * mult.x)));
29 | npos.x += amplitude*0.3;
30 |
31 | float fade = 1.0;
32 |
33 | mat4 projModelView = projection * view * model;
34 | vpos += noise(npos * 0.45) * mult;
35 |
36 |
37 |
38 | vec4 projected = projModelView * vec4(vpos, 1.0);
39 | float anim = sin(iGlobalTime*0.1)*0.5+0.5;
40 | float dist = length(position.zzz);
41 | // dist = smoothstep(, 0.0, dist);
42 | gl_Position = projected;
43 |
44 | vec2 screenPos = projected.xy / projected.w;
45 | vec2 sUv = screenPos.xy * 0.5 + 0.5;
46 | uv = sUv;
47 | uv.y = 1.0 - sUv.y;
48 |
49 | gl_PointSize = dist * POINT_SIZE + amplitude*0.05;
50 | // uv *= 1.5;
51 | }
--------------------------------------------------------------------------------
/src/video.js:
--------------------------------------------------------------------------------
1 | import createTexture from 'gl-texture2d'
2 | import loadImage from 'img'
3 | import events from 'dom-events'
4 |
5 | module.exports = function(gl, cb) {
6 | const element = document.createElement('video')
7 | element.setAttribute('loop', true)
8 | element.setAttribute('muted', 'muted')
9 | addSource('video/mp4', 'video/nyc.mp4')
10 |
11 | events.on(element, 'canplay', ev => {
12 | const texture = createTexture(gl, element)
13 | texture.update = update.bind(null, texture)
14 | element.play()
15 | cb(null, texture)
16 | })
17 |
18 | function addSource(type, path) {
19 | var source = document.createElement('source')
20 | source.src = path
21 | source.type = type
22 | return element.appendChild(source)
23 | }
24 |
25 | function update(texture) {
26 | texture.setPixels(element)
27 | }
28 | }
--------------------------------------------------------------------------------
/video/nyc.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdesl/ink/bb1c5e859594b1848e1fae80c0b7a8bd6e3191ad/video/nyc.mp4
--------------------------------------------------------------------------------