diff --git a/app/assets/shaders/blur-two-pass/base.vert b/app/assets/shaders/blur-two-pass/base.vert new file mode 100644 index 0000000..b516156 --- /dev/null +++ b/app/assets/shaders/blur-two-pass/base.vert @@ -0,0 +1,18 @@ +// our vertex data +attribute vec3 aPosition; +attribute vec2 aTexCoord; + +// lets get texcoords just for fun! +varying vec2 vTexCoord; + +void main() { + // copy the texcoords + vTexCoord = aTexCoord; + + // copy the position data into a vec4, using 1.0 as the w component + vec4 positionVec4 = vec4(aPosition, 1.0); + positionVec4.xy = positionVec4.xy * 2.0 - 1.0; + + // send the vertex information on to the fragment shader + gl_Position = positionVec4; +} diff --git a/app/assets/shaders/blur-two-pass/blur.frag b/app/assets/shaders/blur-two-pass/blur.frag new file mode 100644 index 0000000..3074551 --- /dev/null +++ b/app/assets/shaders/blur-two-pass/blur.frag @@ -0,0 +1,80 @@ +precision mediump float; + +// texcoords from the vertex shader +varying vec2 vTexCoord; + +// our texture coming from p5 +uniform sampler2D tex0; + +// the size of a texel or 1.0 / width , 1.0 / height +uniform vec2 texelSize; + +// which way to blur, vec2(1.0, 0.0) is horizontal, vec2(0.0, 1.0) is vertical +uniform vec2 direction; + +// gaussian blur filter modified from Filip S. at intel +// https://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms +// this function takes three parameters, the texture we want to blur, the uvs, and the texelSize +vec3 gaussianBlur( sampler2D t, vec2 texUV, vec2 stepSize ){ + // a variable for our output + vec3 colOut = vec3( 0.0 ); + + // stepCount is 9 because we have 9 items in our array , const means that 9 will never change and is required loops in glsl + const int stepCount = 9; + + // these weights were pulled from the link above + float gWeights[stepCount]; + gWeights[0] = 0.10855; + gWeights[1] = 0.13135; + gWeights[2] = 0.10406; + gWeights[3] = 0.07216; + gWeights[4] = 0.04380; + gWeights[5] = 0.02328; + gWeights[6] = 0.01083; + gWeights[7] = 0.00441; + gWeights[8] = 0.00157; + + // these offsets were also pulled from the link above + float gOffsets[stepCount]; + gOffsets[0] = 0.66293; + gOffsets[1] = 2.47904; + gOffsets[2] = 4.46232; + gOffsets[3] = 6.44568; + gOffsets[4] = 8.42917; + gOffsets[5] = 10.41281; + gOffsets[6] = 12.39664; + gOffsets[7] = 14.38070; + gOffsets[8] = 16.36501; + + // lets loop nine times + for( int i = 0; i < stepCount; i++ ){ + + // multiply the texel size by the by the offset value + vec2 texCoordOffset = gOffsets[i] * stepSize; + + // sample to the left and to the right of the texture and add them together + vec3 col = texture2D( t, texUV + texCoordOffset ).xyz + texture2D( t, texUV - texCoordOffset ).xyz; + + // multiply col by the gaussian weight value from the array + col *= gWeights[i]; + + // add it all up + colOut += col; + } + + // our final value is returned as col out + return colOut; +} + + +void main() { + + vec2 uv = vTexCoord; + // the texture is loaded upside down and backwards by default so lets flip it + uv = 1.0 - uv; + + // use our blur function + vec3 blur = gaussianBlur(tex0, uv, texelSize * direction); + + gl_FragColor = vec4(blur, 1.0); +} diff --git a/app/sketch.js b/app/sketch.js index 884e13f..143560f 100644 --- a/app/sketch.js +++ b/app/sketch.js @@ -1,53 +1,137 @@ new p5(sketch => { - let width = document.documentElement.scrollWidth; - let height = document.documentElement.scrollHeight; - let maxD = Math.min(width, height) / 2; + // reused dimensions and a seed + let seed, width, height, maxD, goalInstances; - let buffer = sketch.createGraphics(maxD, maxD); + // offscreen layers + let buffer, pass1, pass2; - function generate() { - sketch.blendMode(sketch.REPLACE); - sketch.background('#000'); - sketch.blendMode(sketch.ADD); - for (let i = 0; i < 60; i++) { - buffer.background('#000'); - let d = maxD * sketch.random(0.2, 1); - let c = sketch.color(sketch.random(100), 100, 100, 80) - buffer.fill(c); - buffer.circle(maxD / 2, maxD / 2, d); - buffer.fill('#000'); - buffer.circle(sketch.random(maxD), sketch.random(maxD), sketch.random(0.2, 0.8) * d); - while (sketch.random() > 0.1) { - let a1 = sketch.random(2 * Math.PI); - let a2 = sketch.random(2 * Math.PI); - buffer.stroke(0); - buffer.strokeWeight(sketch.random(1, maxD * 0.1)); - buffer.line(maxD * (Math.sin(a1) + 0.5), maxD * (Math.cos(a1) + 0.5), - maxD * (Math.sin(a2) + 0.5), maxD * (Math.cos(a2) + 0.5)); - } - let w = sketch.random(-d, width + d); - let h = sketch.random(-d, height + d); - sketch.image(buffer, w, h); - } - } + // shaders + let blurH, blurV; sketch.preload = () => { - /* load images, music, etc */ - } - - sketch.keyPressed = () => { - generate(); + // shaders, we will use the same vertex shader and frag shaders for both passes + blurH = sketch.loadShader('shaders/blur-two-pass/base.vert', 'shaders/blur-two-pass/blur.frag'); + blurV = sketch.loadShader('shaders/blur-two-pass/base.vert', 'shaders/blur-two-pass/blur.frag'); } sketch.setup = () => { - sketch.createCanvas(width, height); + goalInstances = 100; + + seed = window.location.hash.substr(1); + sketch.noStroke(); sketch.colorMode(sketch.HSB, 100); + + width = document.documentElement.scrollWidth; + height = document.documentElement.scrollHeight; + maxD = (width + height) * 1.75 / Math.sqrt(goalInstances); + + sketch.createCanvas(width, height); + + buffer = sketch.createGraphics(maxD, maxD); + pass1 = sketch.createGraphics(maxD, maxD, sketch.WEBGL); + pass2 = sketch.createGraphics(maxD, maxD, sketch.WEBGL); + buffer.noStroke(); - buffer.blendMode(sketch.BLEND); + pass1.noStroke(); + pass2.noStroke(); generate(); } sketch.draw = () => { } + + sketch.keyPressed = () => { + if (sketch.key == ' ') { + seed = null; + generate(); + } + } + + function generate() { + if (seed) { + sketch.randomSeed(seed); + } else { + let seed = Math.floor(sketch.random(1000000000000)); + window.location.hash = seed; + sketch.randomSeed(seed); + } + + sketch.noiseSeed(sketch.random()); + sketch.blendMode(sketch.BLEND); + sketch.background('#000'); + sketch.blendMode(sketch.ADD); + let resolution = 2; + + let sqpxEach = width * height / goalInstances; // square pixels per circle, helps with gridding + let unit = Math.sqrt(sqpxEach); + let rows = Math.max(1, Math.round(height / unit)) + 1; + let cols = Math.max(1, Math.round(width / unit)) + 1; + let noiseOffset = sketch.random(0, 1000); + for (let i = 0; i < rows * cols; i++) { + // calculate row and col from i + let col = i % cols; + let row = Math.floor(i / cols); + + buffer.noStroke(); + buffer.background('#000'); + + // perlin noise "intensity" + let intensity = sketch.noise( + noiseOffset + row / rows * resolution, + noiseOffset + col / cols * resolution + ); + let d = maxD * intensity; // diameter + let c = sketch.color(100 * sketch.random(), 100, intensity * 90 + 10, intensity * 70 + 10); // color + buffer.fill(c); + buffer.circle(maxD / 2, maxD / 2, d); // always at the center of the buffer + + // giant, lo-fi blur + pass1.shader(blurH); + blurH.setUniform('tex0', buffer); + blurH.setUniform('texelSize', [8.0/maxD, 8.0/maxD]); + blurH.setUniform('direction', [1.0, 0.0]); + pass1.rect(0,0,maxD, maxD); + pass2.shader(blurV); + blurV.setUniform('tex0', pass1); + blurV.setUniform('texelSize', [8.0/maxD, 8.0/maxD]); + blurV.setUniform('direction', [0.0, 1.0]); + pass2.rect(0,0,maxD, maxD); + + // regular blur to hide artifacts + pass1.shader(blurH); + blurH.setUniform('tex0', pass2); + blurH.setUniform('texelSize', [1.0/maxD, 1.0/maxD]); + blurH.setUniform('direction', [1.0, 0.0]); + pass1.rect(0,0,maxD, maxD); + pass2.shader(blurV); + blurV.setUniform('tex0', pass1); + blurV.setUniform('texelSize', [1.0/maxD, 1.0/maxD]); + blurV.setUniform('direction', [0.0, 1.0]); + pass2.rect(0,0,maxD, maxD); + + buffer.image(pass2, 0, 0, maxD, maxD); + + buffer.fill('#000'); + let cutoutAngle = sketch.random(2 * Math.PI); + buffer.circle(d * Math.cos(cutoutAngle), d * Math.sin(cutoutAngle), sketch.random(0.3, 0.5) * d); + do { + let a1 = sketch.random(2 * Math.PI); + let a2 = sketch.random(2 * Math.PI); + buffer.stroke(0); + buffer.strokeWeight(1 + d * Math.pow(sketch.random(0.7368), 3));// as much as 0.4*d + buffer.line( + maxD * (Math.sin(a1) + 0.5), maxD * (Math.cos(a1) + 0.5), + maxD * (Math.sin(a2) + 0.5), maxD * (Math.cos(a2) + 0.5) + ); + } while (sketch.random() < 0.5 + 0.45 * intensity); + + let displacementAngle = sketch.random(0, Math.PI * 2); + let displacementAmount = sketch.random(unit); + let w = width / (cols - 1) * col + displacementAmount * Math.cos(displacementAngle); + let h = height / (rows - 1) * row + displacementAmount * Math.sin(displacementAngle); + + sketch.image(buffer, w - maxD / 2, h - maxD / 2); + } + } });