├── .babelrc ├── app ├── favicon.ico ├── assets │ └── textures │ │ ├── lut.png │ │ ├── dust.jpg │ │ └── dust_compressed.jpg ├── index.html └── style.css ├── .gitignore ├── lib ├── util │ ├── isIOS.js │ ├── isMobile.js │ ├── query.js │ ├── meshPool.js │ ├── loadTexture.js │ ├── random.js │ └── AssetManager.js ├── shaders │ ├── ssao-vignette.vert │ ├── fog.vert │ ├── decode-float.glsl │ ├── post-test.vert │ ├── decode-hdr.glsl │ ├── pass.vert │ ├── encode-pixel.frag │ ├── basic-hdr.frag │ ├── bloom │ │ ├── vignette.glsl │ │ ├── hash-blur.frag │ │ ├── threshold.frag │ │ ├── gaussian-blur.frag │ │ ├── ssao.frag │ │ └── combine.frag │ ├── rgbm-to-linear.glsl │ ├── encode-float.glsl │ ├── encode-hdr.glsl │ ├── read-depth.glsl │ ├── linear-to-rgbm.glsl │ ├── inscatter.glsl │ ├── fxaa.js │ ├── createPostShader.js │ ├── fxaa.vert │ ├── glsl-background.glsl │ ├── fxaa.frag │ ├── ssao-vignette.js │ ├── basic-hdr.vert │ ├── fog.frag │ ├── createFogMaterial.js │ ├── post-test.frag │ ├── shadertoy_kaleidoscope.frag │ ├── HDRBasicMaterial.js │ ├── standard-hdr.vert │ ├── HDRMaterial.js │ ├── tube.frag │ ├── standard-hdr.frag │ ├── ssao-vignette.frag │ ├── fxaa.glsl │ ├── SSAOShader.js │ └── tube.vert ├── components │ ├── createLines.js │ ├── BGPlaneSpawner.js │ ├── PlaneSpawner.js │ ├── Spawner.js │ ├── CylinderSpawner.js │ ├── createTubes.js │ ├── DotPanelSpawner.js │ ├── PhysicsSpawner.js │ ├── FoggyScene.js │ ├── SimpleSpawner.js │ └── createTest.js ├── post │ ├── Pass.js │ ├── ShaderPass.js │ ├── RenderPass.js │ ├── EffectComposer.js │ └── BloomTexturePass.js ├── context.js ├── geom │ └── createTubeGeometry.js ├── createAudio.js └── createApp.js ├── screenshots ├── 1.jpg └── 2.jpg ├── .npmignore ├── README.md ├── package.json └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [ "es2015" ] 3 | } -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/raylight/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /lib/util/isIOS.js: -------------------------------------------------------------------------------- 1 | module.exports = /(iOS|iPod|iPad|iPhone)/i.test(navigator.userAgent) -------------------------------------------------------------------------------- /screenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/raylight/HEAD/screenshots/1.jpg -------------------------------------------------------------------------------- /screenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/raylight/HEAD/screenshots/2.jpg -------------------------------------------------------------------------------- /app/assets/textures/lut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/raylight/HEAD/app/assets/textures/lut.png -------------------------------------------------------------------------------- /lib/util/isMobile.js: -------------------------------------------------------------------------------- 1 | module.exports = /(iPhone|iPad|iPod|Android)/i.test(window.navigator.userAgent); 2 | -------------------------------------------------------------------------------- /app/assets/textures/dust.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/raylight/HEAD/app/assets/textures/dust.jpg -------------------------------------------------------------------------------- /app/assets/textures/dust_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattdesl/raylight/HEAD/app/assets/textures/dust_compressed.jpg -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /lib/shaders/ssao-vignette.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | void main() { 3 | vUv = uv; 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 5 | } -------------------------------------------------------------------------------- /lib/components/createLines.js: -------------------------------------------------------------------------------- 1 | const HDRMaterial = require('../shaders/HDRMaterial'); 2 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 3 | 4 | module.exports = function () { 5 | 6 | } -------------------------------------------------------------------------------- /lib/shaders/fog.vert: -------------------------------------------------------------------------------- 1 | varying vec3 vWorldPos; 2 | void main() { 3 | vec4 tPos = vec4(position.xyz, 1.0); 4 | gl_Position = projectionMatrix * modelViewMatrix * tPos; 5 | vWorldPos = (modelMatrix * tPos).xyz; 6 | } 7 | -------------------------------------------------------------------------------- /lib/shaders/decode-float.glsl: -------------------------------------------------------------------------------- 1 | const vec4 decodeFactor = vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0); 2 | 3 | float DecodeFloat(vec4 rgba) { 4 | return dot(rgba, decodeFactor); 5 | } 6 | 7 | #pragma glslify: export(DecodeFloat); -------------------------------------------------------------------------------- /lib/shaders/post-test.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | uniform vec2 resolution; 3 | 4 | void main() { 5 | vUv = uv; 6 | gl_Position = projectionMatrix * 7 | modelViewMatrix * 8 | vec4(position,1.0); 9 | } 10 | -------------------------------------------------------------------------------- /lib/shaders/decode-hdr.glsl: -------------------------------------------------------------------------------- 1 | const vec4 decodeFactor = vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0); 2 | 3 | float DecodeHDRFloat(vec4 rgba) { 4 | float f = dot(rgba, decodeFactor); 5 | return f * 16.0; 6 | } 7 | 8 | #pragma glslify: export(DecodeHDRFloat); -------------------------------------------------------------------------------- /lib/shaders/pass.vert: -------------------------------------------------------------------------------- 1 | attribute vec4 position; 2 | attribute vec2 uv; 3 | uniform mat4 projectionMatrix; 4 | uniform mat4 modelViewMatrix; 5 | varying vec2 vUv; 6 | void main() { 7 | vUv = uv; 8 | gl_Position = projectionMatrix * modelViewMatrix * position; 9 | } 10 | -------------------------------------------------------------------------------- /lib/shaders/encode-pixel.frag: -------------------------------------------------------------------------------- 1 | #pragma glslify: encodeHDR = require('./encode-hdr'); 2 | 3 | vec4 encodePixel (float luminance) { 4 | #ifdef FLOAT_BUFFER 5 | return vec4(vec3(luminance), 1.0); 6 | #else 7 | return encodeHDR(luminance); 8 | #endif 9 | } 10 | 11 | #pragma glslify: export(encodePixel); -------------------------------------------------------------------------------- /lib/shaders/basic-hdr.frag: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform float emissiveFactor; 3 | 4 | #pragma glslify: encodeRGBM = require('./linear-to-rgbm'); 5 | #pragma glslify: encodePixel = require('./encode-pixel'); 6 | 7 | void main() { 8 | float f = diffuse.r * emissiveFactor; 9 | gl_FragColor = encodePixel(f); 10 | } 11 | -------------------------------------------------------------------------------- /lib/shaders/bloom/vignette.glsl: -------------------------------------------------------------------------------- 1 | float vignette (vec2 uv, vec2 resolution, float minVal, float maxVal, float scale) { 2 | vec2 vigUV = uv - 0.5; 3 | vigUV /= scale; 4 | vigUV.x *= resolution.x / resolution.y; 5 | float vigDist = length(vigUV); 6 | return smoothstep(minVal, maxVal, vigDist); 7 | } 8 | #pragma glslify: export(vignette); -------------------------------------------------------------------------------- /lib/shaders/rgbm-to-linear.glsl: -------------------------------------------------------------------------------- 1 | // reference: http://iwasbeingirony.blogspot.ca/2010/06/difference-between-rgbm-and-rgbd.html 2 | vec4 RGBMToLinear2( in vec4 value, in float maxRange ) { 3 | return vec4( value.xyz * value.w * maxRange, 1.0 ); 4 | } 5 | vec4 RGBMToLinear2( in vec4 value ) { 6 | return RGBMToLinear2(value, 16.0); 7 | } 8 | #pragma glslify: export(RGBMToLinear2); -------------------------------------------------------------------------------- /lib/shaders/encode-float.glsl: -------------------------------------------------------------------------------- 1 | const vec4 encodeFactorA = vec4(1.0, 255.0, 65025.0, 160581375.0); 2 | const vec4 encodeFactorB = vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0); 3 | 4 | vec4 EncodeFloat(float v) { 5 | v = min(0.9999999, v); 6 | vec4 enc = fract(encodeFactorA * v); 7 | enc -= enc.yzww * encodeFactorB; 8 | return enc; 9 | } 10 | 11 | #pragma glslify: export(EncodeFloat); -------------------------------------------------------------------------------- /lib/shaders/encode-hdr.glsl: -------------------------------------------------------------------------------- 1 | const vec4 encodeFactorA = vec4(1.0, 255.0, 65025.0, 160581375.0); 2 | const vec4 encodeFactorB = vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0); 3 | 4 | vec4 EncodeHDRFloat(float v) { 5 | v = min(0.9999999, v / 16.0); 6 | vec4 enc = fract(encodeFactorA * v); 7 | enc -= enc.yzww * encodeFactorB; 8 | return enc; 9 | } 10 | 11 | #pragma glslify: export(EncodeHDRFloat); -------------------------------------------------------------------------------- /lib/shaders/read-depth.glsl: -------------------------------------------------------------------------------- 1 | // 16-bit depth stored in G channel 2 | highp float readDepth(highp sampler2D map, in vec2 uv, float cameraNear, float cameraFar) { 3 | float cameraFarPlusNear = cameraFar + cameraNear; 4 | float cameraFarMinusNear = cameraFar - cameraNear; 5 | float cameraCoef = 2.0 * cameraNear; 6 | return cameraCoef / (cameraFarPlusNear - texture2D(map, uv).g * cameraFarMinusNear); 7 | } 8 | #pragma glslify: export(readDepth); -------------------------------------------------------------------------------- /lib/shaders/linear-to-rgbm.glsl: -------------------------------------------------------------------------------- 1 | vec4 LinearToRGBM2( in vec4 value, in float maxRange ) { 2 | float maxRGB = max( value.x, max( value.g, value.b ) ); 3 | float M = clamp( maxRGB / maxRange, 0.0, 1.0 ); 4 | M = ceil( M * 255.0 ) / 255.0; 5 | return vec4( value.rgb / ( M * maxRange ), M ); 6 | } 7 | vec4 LinearToRGBM2( in vec4 value ) { 8 | return LinearToRGBM2(value, 16.0); 9 | } 10 | #pragma glslify: export(LinearToRGBM2); -------------------------------------------------------------------------------- /lib/shaders/inscatter.glsl: -------------------------------------------------------------------------------- 1 | float InScatter(vec3 start, vec3 dir, vec3 lightPos, float d) { 2 | // http://blog.mmacklin.com/2010/06/10/faster-fog/ 3 | // calculate quadratic coefficients a,b,c 4 | vec3 q = start - lightPos; 5 | float b = dot(dir, q); 6 | float c = dot(q, q); 7 | 8 | // evaluate integral 9 | float s = 1.0 / sqrt(c - b * b); 10 | float l = s * (atan((d + b) * s) - atan(b * s)); 11 | return l; 12 | } 13 | #pragma glslify: export(InScatter); -------------------------------------------------------------------------------- /lib/shaders/fxaa.js: -------------------------------------------------------------------------------- 1 | var glslify = require('glslify') 2 | module.exports = threeShaderFXAA 3 | function threeShaderFXAA (opt) { 4 | if (typeof global.THREE === 'undefined') { 5 | throw new TypeError('You must have THREE in global scope for this module.') 6 | } 7 | opt = opt || {} 8 | return { 9 | uniforms: { 10 | tDiffuse: { type: 't', value: new THREE.Texture() }, 11 | resolution: { type: 'v2', value: opt.resolution || new THREE.Vector2() } 12 | }, 13 | vertexShader: glslify('./fxaa.vert'), 14 | fragmentShader: glslify('./fxaa.frag') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/shaders/createPostShader.js: -------------------------------------------------------------------------------- 1 | const assign = require('object-assign'); 2 | 3 | module.exports = createPostShader 4 | function createPostShader (opt) { 5 | if (typeof global.THREE === 'undefined') { 6 | throw new TypeError('You must have THREE in global scope for this module.'); 7 | } 8 | opt = opt || {}; 9 | return { 10 | uniforms: assign({ 11 | tDiffuse: { type: 't', value: new THREE.Texture() }, 12 | resolution: { type: 'v2', value: opt.resolution || new THREE.Vector2() } 13 | }, opt), 14 | vertexShader: opt.vertexShader, 15 | fragmentShader: opt.fragmentShader 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RAYLIGHT 2 | 3 | Experimental music visualizer. 4 | 5 | #### Demo 6 | 7 | http://raylight.surge.sh/ 8 | 9 | #### High Quality Settings 10 | 11 | http://raylight.surge.sh/?highQuality 12 | 13 | #### Screenshots 14 | 15 | 16 | 17 | 18 | 19 | #### Credits 20 | 21 | Designed & developed by Matt DesLauriers. 22 | 23 | Music by Ukioau: 24 | https://soundcloud.com/ukiyoau/kaleidoscope 25 | 26 | Dust & film noise texture: 27 | http://graphicburger.com/dust-noise-overlay-textures/ 28 | 29 | Original fog shader inspiration from Marcin Ignac: 30 | https://twitter.com/marcinignac/status/819977065751527424 -------------------------------------------------------------------------------- /lib/shaders/fxaa.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | varying vec2 v_rgbNW; 4 | varying vec2 v_rgbNE; 5 | varying vec2 v_rgbSW; 6 | varying vec2 v_rgbSE; 7 | varying vec2 v_rgbM; 8 | 9 | uniform vec2 resolution; 10 | 11 | void main() { 12 | vUv = uv; 13 | vec2 fragCoord = uv * resolution; 14 | vec2 inverseVP = 1.0 / resolution.xy; 15 | v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP; 16 | v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP; 17 | v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP; 18 | v_rgbSE = (fragCoord + vec2(1.0, 1.0)) * inverseVP; 19 | v_rgbM = vec2(fragCoord * inverseVP); 20 | 21 | gl_Position = projectionMatrix * 22 | modelViewMatrix * 23 | vec4(position,1.0); 24 | } 25 | -------------------------------------------------------------------------------- /lib/shaders/glsl-background.glsl: -------------------------------------------------------------------------------- 1 | vec2 backgroundUV (vec2 uv, vec2 resolution, vec2 texResolution) { 2 | float tAspect = texResolution.x / texResolution.y; 3 | float pAspect = resolution.x / resolution.y; 4 | float pwidth = resolution.x; 5 | float pheight = resolution.y; 6 | 7 | float width = 0.0; 8 | float height = 0.0; 9 | if (tAspect > pAspect) { 10 | height = pheight; 11 | width = height * tAspect; 12 | } else { 13 | width = pwidth; 14 | height = width / tAspect; 15 | } 16 | float x = (pwidth - width) / 2.0; 17 | float y = (pheight - height) / 2.0; 18 | vec2 nUv = uv; 19 | nUv -= vec2(x, y) / resolution; 20 | nUv /= vec2(width, height) / resolution; 21 | return nUv; 22 | } 23 | 24 | #pragma glslify: export(backgroundUV) -------------------------------------------------------------------------------- /lib/util/query.js: -------------------------------------------------------------------------------- 1 | const qs = require('query-string'); 2 | 3 | module.exports = parseOptions(); 4 | function parseOptions () { 5 | if (typeof window === 'undefined') return {}; 6 | const parsed = qs.parse(window.location.search); 7 | Object.keys(parsed).forEach(key => { 8 | if (parsed[key] === null) parsed[key] = true; 9 | if (parsed[key] === 'false') parsed[key] = false; 10 | if (parsed[key] === 'true') parsed[key] = true; 11 | if (isNumber(parsed[key])) { 12 | parsed[key] = Number(parsed[key]); 13 | } 14 | }); 15 | return parsed; 16 | } 17 | 18 | function isNumber (x) { 19 | if (typeof x === 'number') return true; 20 | if (/^0x[0-9a-f]+$/i.test(x)) return true; 21 | return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x); 22 | } 23 | -------------------------------------------------------------------------------- /lib/shaders/fxaa.frag: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | //texcoords computed in vertex step 4 | //to avoid dependent texture reads 5 | varying vec2 v_rgbNW; 6 | varying vec2 v_rgbNE; 7 | varying vec2 v_rgbSW; 8 | varying vec2 v_rgbSE; 9 | varying vec2 v_rgbM; 10 | 11 | //make sure to have a resolution uniform set to the screen size 12 | uniform vec2 resolution; 13 | uniform sampler2D tDiffuse; 14 | 15 | #pragma glslify: fxaa = require('./fxaa.glsl') 16 | #pragma glslify: rgbmToLinear = require('./rgbm-to-linear'); 17 | #pragma glslify: linearToRGBM = require('./linear-to-rgbm'); 18 | 19 | void main() { 20 | vec2 fragCoord = vUv * resolution; 21 | vec4 outCol = fxaa(tDiffuse, fragCoord, resolution, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); 22 | gl_FragColor = outCol; 23 | } 24 | -------------------------------------------------------------------------------- /lib/post/Pass.js: -------------------------------------------------------------------------------- 1 | module.exports = Pass; 2 | function Pass () { 3 | 4 | // if set to true, the pass is processed by the composer 5 | this.enabled = true; 6 | 7 | // if set to true, the pass indicates to swap read and write buffer after rendering 8 | this.needsSwap = true; 9 | 10 | // if set to true, the pass clears its buffer before rendering 11 | this.clear = false; 12 | 13 | // if set to true, the result of the pass is rendered to screen 14 | this.renderToScreen = false; 15 | 16 | }; 17 | 18 | Object.assign( Pass.prototype, { 19 | 20 | setSize: function( width, height ) {}, 21 | 22 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 23 | 24 | console.error( "Pass: .render() must be implemented in derived pass." ); 25 | 26 | } 27 | 28 | } ); -------------------------------------------------------------------------------- /lib/context.js: -------------------------------------------------------------------------------- 1 | const AssetManager = require('./util/AssetManager'); 2 | const query = require('./util/query'); 3 | const isMobile = require('./util/isMobile'); 4 | 5 | // Our WebGL renderer with alpha and device-scaled 6 | const renderer = new THREE.WebGLRenderer({ 7 | canvas: document.querySelector('#canvas'), 8 | alpha: false, 9 | stencil: false, 10 | depth: true, 11 | preserveDrawingBuffer: false, 12 | antialias: false 13 | }); 14 | 15 | const assets = new AssetManager({ 16 | renderer 17 | }); 18 | const useHalfFloat = !isMobile && query.float !== false; 19 | const floatBufferType = renderer.extensions.get('OES_texture_half_float') && useHalfFloat; 20 | const floatBufferDefine = {}; 21 | if (floatBufferType) { 22 | floatBufferDefine.FLOAT_BUFFER = ''; 23 | } 24 | 25 | module.exports = { 26 | renderer, 27 | assets, 28 | floatBufferType, 29 | floatBufferDefine 30 | }; 31 | -------------------------------------------------------------------------------- /lib/shaders/ssao-vignette.js: -------------------------------------------------------------------------------- 1 | var glslify = require('glslify') 2 | module.exports = threeShaderSSAO 3 | function threeShaderSSAO (opt) { 4 | if (typeof global.THREE === 'undefined') { 5 | throw new TypeError('You must have THREE in global scope for this module.') 6 | } 7 | opt = opt || {} 8 | return { 9 | uniforms: { 10 | 'tDiffuse': { type: "t", value: null }, 11 | 'tDepth': { type: "t", value: null }, 12 | 'resolution': { type: "v2", value: new THREE.Vector2( 512, 512 ) }, 13 | 'cameraNear': { type: "f", value: 1 }, 14 | 'cameraFar': { type: "f", value: 100 }, 15 | 'onlyAO': { type: "i", value: 0 }, 16 | 'aoClamp': { type: "f", value: 0.95 }, 17 | 'lumInfluence': { type: "f", value: 1 } 18 | }, 19 | vertexShader: glslify('./ssao-vignette.vert'), 20 | fragmentShader: glslify('./ssao-vignette.frag') 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/shaders/basic-hdr.vert: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | void main() { 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef USE_ENVMAP 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #endif 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | } 40 | -------------------------------------------------------------------------------- /lib/shaders/fog.frag: -------------------------------------------------------------------------------- 1 | varying vec3 vWorldPos; 2 | uniform float diffuse; 3 | uniform vec3 cameraWorldPosition; 4 | uniform vec3 pointLightPosition; 5 | uniform float pointLightDiffuse; 6 | uniform float fogLightStrength; 7 | 8 | #pragma glslify: encodeHDR = require('./encode-hdr'); 9 | #pragma glslify: InScatter = require('./inscatter'); 10 | 11 | void main() { 12 | float color = diffuse; 13 | 14 | //direction from camera 15 | vec3 positionToCamera = vWorldPos - cameraWorldPosition; 16 | float positionToCameraLength = length(positionToCamera); 17 | // normalize 18 | positionToCamera = normalize(positionToCamera); 19 | 20 | float scatter = InScatter(cameraWorldPosition, positionToCamera, pointLightPosition, positionToCameraLength); 21 | float fogAmt = pointLightDiffuse * scatter * fogLightStrength; 22 | color += fogAmt; 23 | 24 | #ifdef FLOAT_BUFFER 25 | // encode with zero depth 26 | gl_FragColor = vec4(vec3(color, 0.0, 0.0), 1.0); 27 | #else 28 | gl_FragColor = encodeHDR(color); 29 | #endif 30 | } 31 | -------------------------------------------------------------------------------- /lib/shaders/createFogMaterial.js: -------------------------------------------------------------------------------- 1 | const defined = require('defined'); 2 | const glslify = require('glslify'); 3 | const path = require('path'); 4 | const injectDefines = require('glsl-inject-defines'); 5 | const { floatBufferType, floatBufferDefine } = require('../context'); 6 | 7 | // Our custom shaders 8 | const fragmentShader = injectDefines(glslify(path.resolve(__dirname, 'fog.frag')), floatBufferDefine); 9 | const vertexShader = glslify(path.resolve(__dirname, 'fog.vert')); 10 | 11 | module.exports = function (opt) { 12 | return new THREE.ShaderMaterial({ 13 | vertexShader, 14 | fragmentShader, 15 | blending: THREE.NoBlending, 16 | transparent: !floatBufferType, 17 | uniforms: { 18 | cameraWorldPosition: { type: 'v3', value: new THREE.Vector3() }, 19 | pointLightPosition: { type: 'v3', value: new THREE.Vector3() }, 20 | pointLightDiffuse: { type: 'f', value: 1 }, 21 | fogLightStrength: { type: 'f', value: 0 }, 22 | diffuse: { type: 'f', value: defined(opt.diffuse, 1) } 23 | } 24 | }); 25 | } -------------------------------------------------------------------------------- /lib/shaders/bloom/hash-blur.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec2 vUv; 4 | uniform sampler2D tDiffuse; 5 | uniform vec2 resolution; 6 | uniform float radius; 7 | 8 | vec3 tex(vec2 uv); 9 | 10 | #define ITERATIONS 20 11 | #pragma glslify: blur = require('glsl-hash-blur', sample=tex, iterations=ITERATIONS); 12 | 13 | vec3 tex(vec2 uv) { 14 | vec3 rgb = texture2D(tDiffuse, uv).rgb; 15 | return rgb; 16 | // return threshold > 0.2 ? rgb : vec3(0.0); 17 | // return step(1.0 - t, rgb); 18 | // return smoothstep(vec3(0.0), vec3(, threshold); 19 | } 20 | 21 | void main () { 22 | float aspect = resolution.x / resolution.y; 23 | 24 | //jitter the noise but not every frame 25 | // float tick = 0.0;//floor(fract(iGlobalTime)*20.0); 26 | // float jitter = mod(tick * 382.0231, 21.321); 27 | 28 | // vec3 blurred = vec3(0.0); 29 | // blurred += 0.6 * blur(vUv, 0.3, 1.0 / aspect, jitter); 30 | 31 | vec3 blurred = blur(vUv, radius, 1.0 / aspect, radius); 32 | gl_FragColor.rgb = blurred; 33 | gl_FragColor.a = 1.0; 34 | 35 | // gl_FragColor = texture2D(tDiffuse, vUv); 36 | } -------------------------------------------------------------------------------- /lib/shaders/post-test.frag: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | //make sure to have a resolution uniform set to the screen size 4 | uniform vec2 resolution; 5 | uniform highp sampler2D tDiffuse; 6 | 7 | #pragma glslify: decodeFloat = require('./decode-float'); 8 | #pragma glslify: encodeFloat = require('./encode-float'); 9 | #pragma glslify: rgbmToLinear = require('./rgbm-to-linear'); 10 | #pragma glslify: luma = require('glsl-luma'); 11 | 12 | void main() { 13 | float t = 1.5; 14 | float n = decodeFloat(encodeFloat(t)); 15 | n = n > 10.0 ? 1.0 : 0.0; 16 | gl_FragColor = vec4(vec3(n), 1.0); 17 | 18 | // float f = decodeFloat(texture2D(tDiffuse, vUv)); 19 | // gl_FragColor = vec4(vec3(f), 1.0); 20 | 21 | // vec4 encoded = texture2D(tDiffuse, vUv); 22 | // vec4 outColor = rgbmToLinear(encoded, 16.0); 23 | // float L = luma(outColor.rgb); 24 | // bool isAbove = outColor.r > 1.0 || outColor.g > 1.0 || outColor.b > 1.0; 25 | // gl_FragColor.rgb = step(vec3(1.0), outColor.rgb); 26 | // gl_FragColor.a = 1.0; 27 | 28 | // float luminance = clamp(decodeFloat(encoded), 0.0, 1.0); 29 | // gl_FragColor = vec4(vec3(luminance), 1.0); 30 | } 31 | -------------------------------------------------------------------------------- /lib/shaders/bloom/threshold.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | varying vec2 vUv; 3 | uniform highp sampler2D tDiffuse; 4 | uniform vec3 background; 5 | uniform vec2 resolution; 6 | uniform float lumaThreshold; 7 | 8 | uniform float vignetteMin; 9 | uniform float vignetteMax; 10 | uniform float vignetteStrength; 11 | uniform float vignetteScale; 12 | 13 | #pragma glslify: decodeHDR = require('../decode-hdr'); 14 | #pragma glslify: encodeHDR = require('../encode-hdr'); 15 | #pragma glslify: vignette = require('./vignette'); 16 | #pragma glslify: luma = require('glsl-luma'); 17 | 18 | void main () { 19 | 20 | #ifdef FLOAT_BUFFER 21 | float color = texture2D(tDiffuse, vUv).r; 22 | #else 23 | float color = decodeHDR(texture2D(tDiffuse, vUv)); 24 | #endif 25 | 26 | // threshold 27 | float outColor = step(lumaThreshold, color); 28 | 29 | // allow vignette to peek through 30 | float v = vignette(vUv, resolution, vignetteMin, vignetteMax, vignetteScale); 31 | outColor = mix(outColor, min(3.0, color), v); 32 | 33 | #ifdef FLOAT_BUFFER 34 | gl_FragColor = vec4(vec3(outColor), 1.0); 35 | #else 36 | gl_FragColor = encodeHDR(outColor); 37 | #endif 38 | } 39 | -------------------------------------------------------------------------------- /lib/shaders/shadertoy_kaleidoscope.frag: -------------------------------------------------------------------------------- 1 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 2 | { 3 | vec2 uv = fragCoord.xy / iResolution.xy; 4 | float PI = 3.14; 5 | 6 | 7 | fragColor = vec4(vec3(0.0), 1.0); 8 | const int count = 6; 9 | float alpha = 0.85; 10 | float maxLuma = 0.15; 11 | for (int i = 0; i < count; i++) { 12 | float angleStep = PI / min(7.0, float(i) + 4.0); 13 | 14 | vec2 cUv = uv - 0.5; 15 | cUv.x *= iResolution.x / iResolution.y; 16 | 17 | float angle = atan(cUv.y, cUv.x); 18 | angle = abs(mod(angle, angleStep * 2.0) - angleStep); 19 | angle -= iGlobalTime * 0.2; 20 | 21 | float radius = length(cUv); 22 | uv.x = (radius * cos(angle)) + 0.5; 23 | uv.y = (radius * sin(angle)) + 0.5; 24 | 25 | vec3 rgb = texture(iChannel0, uv).rgb / float(count); 26 | float L = dot(rgb, vec3(0.299, 0.587, 0.114)); 27 | fragColor.rgb += smoothstep(maxLuma / float(count), 0.0, L) * alpha; 28 | 29 | } 30 | float L2 = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114)); 31 | fragColor.rgb = mix(fragColor.rgb, vec3(1.0, 0.5, 2.0), L2); 32 | 33 | } -------------------------------------------------------------------------------- /lib/util/meshPool.js: -------------------------------------------------------------------------------- 1 | const newArray = require('new-array'); 2 | const { randomFloat } = require('../util/random'); 3 | const defined = require('defined'); 4 | 5 | module.exports = function (opt = {}) { 6 | let geometries = defined(opt.geometries, []); 7 | const count = defined(opt.count, 10); 8 | const baseMaterial = defined(opt.baseMaterial, new THREE.MeshBasicMaterial({ color: 'white' })); 9 | geometries = Array.isArray(geometries) ? geometries : [ geometries ]; 10 | 11 | const meshes = newArray(count).map(() => { 12 | const geometry = geometries[Math.floor(randomFloat(0, geometries.length))]; 13 | const material = Array.isArray(baseMaterial) 14 | ? baseMaterial.map(m => m.clone()) 15 | : baseMaterial.clone(); 16 | 17 | const mesh = opt.createMesh ? opt.createMesh(geometry, material) : new THREE.Mesh(geometry, material); 18 | mesh.visible = false; 19 | mesh.active = false; 20 | return mesh; 21 | }); 22 | 23 | return { 24 | meshes, 25 | next () { 26 | const mesh = meshes.find(m => !m.active); 27 | if (mesh) { 28 | mesh.visible = true; 29 | mesh.active = true; 30 | } 31 | return mesh; 32 | }, 33 | free (mesh) { 34 | if (!mesh) return; 35 | mesh.active = false; 36 | mesh.visible = false; 37 | } 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /lib/post/ShaderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | module.exports = function(THREE, EffectComposer) { 6 | function ShaderPass( shader, textureID ) { 7 | if (!(this instanceof ShaderPass)) return new ShaderPass(shader, textureID); 8 | 9 | this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; 10 | 11 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 12 | 13 | this.material = new THREE.ShaderMaterial( { 14 | 15 | uniforms: this.uniforms, 16 | transparent: true, 17 | blending: THREE.NoBlending, 18 | vertexShader: shader.vertexShader, 19 | fragmentShader: shader.fragmentShader 20 | 21 | } ); 22 | 23 | this.renderToScreen = false; 24 | 25 | this.enabled = true; 26 | this.needsSwap = true; 27 | this.clear = false; 28 | 29 | }; 30 | 31 | ShaderPass.prototype = { 32 | 33 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 34 | 35 | if ( this.uniforms[ this.textureID ] ) { 36 | 37 | this.uniforms[ this.textureID ].value = readBuffer.texture; 38 | 39 | } 40 | 41 | EffectComposer.quad.material = this.material; 42 | 43 | if ( this.renderToScreen ) { 44 | 45 | renderer.render( EffectComposer.scene, EffectComposer.camera ); 46 | 47 | } else { 48 | 49 | renderer.render( EffectComposer.scene, EffectComposer.camera, writeBuffer, this.clear ); 50 | 51 | } 52 | 53 | } 54 | 55 | }; 56 | 57 | return ShaderPass; 58 | 59 | }; -------------------------------------------------------------------------------- /lib/shaders/HDRBasicMaterial.js: -------------------------------------------------------------------------------- 1 | const glslify = require('glslify'); 2 | const path = require('path'); 3 | const injectDefines = require('glsl-inject-defines'); 4 | const { floatBufferType, floatBufferDefine } = require('../context'); 5 | 6 | // Our custom shaders 7 | const vertexShader = glslify(path.resolve(__dirname, 'basic-hdr.vert')); 8 | const fragmentShader = injectDefines(glslify(path.resolve(__dirname, 'basic-hdr.frag')), floatBufferDefine); 9 | 10 | module.exports = HDRMaterial; 11 | function HDRMaterial (parameters = {}) { 12 | THREE.MeshBasicMaterial.call( this ); 13 | this.uniforms = THREE.UniformsUtils.merge([ 14 | THREE.ShaderLib.basic.uniforms, 15 | { 16 | time: { type: 'f', value: 1 }, 17 | emissiveFactor: { type: 'f', value: 1 }, 18 | } 19 | ]); 20 | setFlags(this); 21 | this.setValues(parameters); 22 | } 23 | 24 | HDRMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype ); 25 | HDRMaterial.prototype.constructor = HDRMaterial; 26 | HDRMaterial.prototype.isMeshBasicMaterial = true; 27 | 28 | HDRMaterial.prototype.copy = function ( source ) { 29 | THREE.MeshBasicMaterial.prototype.copy.call( this, source ); 30 | this.uniforms = THREE.UniformsUtils.clone(source.uniforms); 31 | setFlags(this); 32 | return this; 33 | }; 34 | 35 | function setFlags (material) { 36 | material.vertexShader = vertexShader; 37 | material.fragmentShader = fragmentShader; 38 | material.type = 'HDRMaterial'; 39 | material.blending = THREE.NoBlending; 40 | material.transparent = !floatBufferType; 41 | } -------------------------------------------------------------------------------- /lib/shaders/standard-hdr.vert: -------------------------------------------------------------------------------- 1 | #define PHYSICAL 2 | 3 | varying vec3 vViewPosition; 4 | varying vec3 vFogWorldPosition; 5 | 6 | #ifndef FLAT_SHADED 7 | 8 | varying vec3 vNormal; 9 | 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | void main() { 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED 38 | 39 | vNormal = normalize( transformedNormal ); 40 | 41 | #endif 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | vViewPosition = - mvPosition.xyz; 52 | vFogWorldPosition = (modelMatrix * vec4(transformed, 1.0)).xyz; 53 | 54 | #include 55 | #include 56 | #include 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RAYLIGHT 8 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | 25 | 26 |
27 |
28 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/post/RenderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | const Pass = require('./Pass'); 6 | 7 | module.exports = RenderPass; 8 | function RenderPass ( scene, camera, overrideMaterial, clearColor, clearAlpha ) { 9 | 10 | Pass.call( this ); 11 | 12 | this.scene = scene; 13 | this.camera = camera; 14 | 15 | this.overrideMaterial = overrideMaterial; 16 | 17 | this.clearColor = clearColor; 18 | this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; 19 | 20 | this.gammaInput = undefined; 21 | this.gammaOutput = undefined; 22 | this.clear = true; 23 | this.needsSwap = false; 24 | 25 | } 26 | 27 | RenderPass.prototype = Object.assign( Object.create( Pass.prototype ), { 28 | 29 | constructor: RenderPass, 30 | 31 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 32 | 33 | var oldAutoClear = renderer.autoClear; 34 | renderer.autoClear = false; 35 | var oldGammaInput = renderer.gammaInput; 36 | var oldGammaOutput = renderer.gammaOutput; 37 | 38 | if ( typeof this.gammaInput === 'boolean' ) { 39 | 40 | renderer.gammaInput = this.gammaInput; 41 | 42 | } 43 | 44 | if ( typeof this.gammaOutput === 'boolean' ) { 45 | 46 | renderer.gammaOutput = this.gammaOutput; 47 | 48 | } 49 | 50 | this.scene.overrideMaterial = this.overrideMaterial; 51 | 52 | var oldClearColor, oldClearAlpha; 53 | 54 | if ( this.clearColor ) { 55 | 56 | oldClearColor = renderer.getClearColor().getHex(); 57 | oldClearAlpha = renderer.getClearAlpha(); 58 | 59 | renderer.setClearColor( this.clearColor, this.clearAlpha ); 60 | 61 | } 62 | 63 | renderer.render( this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear ); 64 | 65 | if ( this.clearColor ) { 66 | 67 | renderer.setClearColor( oldClearColor, oldClearAlpha ); 68 | 69 | } 70 | 71 | this.scene.overrideMaterial = null; 72 | renderer.autoClear = oldAutoClear; 73 | renderer.gammaInput = oldGammaInput; 74 | renderer.gammaOutput = oldGammaOutput; 75 | } 76 | 77 | } ); 78 | -------------------------------------------------------------------------------- /lib/shaders/HDRMaterial.js: -------------------------------------------------------------------------------- 1 | const defined = require('defined'); 2 | const glslify = require('glslify'); 3 | const path = require('path'); 4 | const injectDefines = require('glsl-inject-defines'); 5 | const { floatBufferType, floatBufferDefine } = require('../context'); 6 | 7 | // This is the original source, we will copy + paste it for our own GLSL 8 | // const vertexShader = THREE.ShaderChunk.meshphysical_vert; 9 | // const fragmentShader = THREE.ShaderChunk.meshphysical_frag; 10 | 11 | // Our custom shaders 12 | const fragmentShader = injectDefines(glslify(path.resolve(__dirname, 'standard-hdr.frag')), floatBufferDefine); 13 | const vertexShader = glslify(path.resolve(__dirname, 'standard-hdr.vert')); 14 | 15 | module.exports = HDRMaterial; 16 | function HDRMaterial (parameters = {}) { 17 | THREE.MeshStandardMaterial.call( this ); 18 | this.uniforms = THREE.UniformsUtils.merge([ 19 | THREE.ShaderLib.standard.uniforms, 20 | { 21 | emissiveFactor: { type: 'f', value: 1 }, 22 | time: { type: 'f', value: 1 }, 23 | cameraMatrixWorld: { value: new THREE.Matrix4() }, 24 | fogLightStrength: { type: 'f', value: defined(parameters.fogLightStrength, 0.03) }, 25 | fogLightColor: { type: 'c', value: new THREE.Color(parameters.fogLightColor || 'white') }, 26 | fogLightPosition: { type: 'v3', value: parameters.fogLightPosition || new THREE.Vector3() }, 27 | } 28 | ]); 29 | setFlags(this); 30 | this.setValues(parameters); 31 | } 32 | 33 | HDRMaterial.prototype = Object.create( THREE.MeshStandardMaterial.prototype ); 34 | HDRMaterial.prototype.constructor = HDRMaterial; 35 | HDRMaterial.prototype.isMeshStandardMaterial = true; 36 | 37 | HDRMaterial.prototype.copy = function ( source ) { 38 | THREE.MeshStandardMaterial.prototype.copy.call( this, source ); 39 | this.uniforms = THREE.UniformsUtils.clone(source.uniforms); 40 | setFlags(this); 41 | return this; 42 | }; 43 | 44 | function setFlags (material) { 45 | material.vertexShader = vertexShader; 46 | material.fragmentShader = fragmentShader; 47 | material.type = 'HDRMaterial'; 48 | material.blending = THREE.NoBlending; 49 | material.transparent = !floatBufferType; 50 | } -------------------------------------------------------------------------------- /lib/util/loadTexture.js: -------------------------------------------------------------------------------- 1 | const noop = () => {}; 2 | 3 | module.exports = function loadTexture (src, opt, cb) { 4 | if (typeof opt === 'function') { 5 | cb = opt; 6 | opt = {}; 7 | } 8 | opt = Object.assign({}, opt); 9 | cb = cb || noop; 10 | 11 | const loader = new SimpleTextureLoader(); 12 | loader.encoding = opt.encoding || THREE.LinearEncoding; 13 | 14 | const texture = loader.load(src, texture => { 15 | texture.name = src; 16 | setTextureParams(texture, opt); 17 | if (opt.renderer) { 18 | // Force texture to be uploaded to GPU immediately, 19 | // this will avoid "jank" on first rendered frame 20 | opt.renderer.setTexture2D(texture, 0); 21 | } 22 | cb(null, texture); 23 | }, progress => { 24 | // nothing.. 25 | }, () => { 26 | const msg = `Could not load texture ${src}`; 27 | console.error(msg); 28 | cb(new Error(msg)); 29 | }, opt); 30 | return texture; 31 | } 32 | 33 | function setTextureParams (texture, opt) { 34 | texture.needsUpdate = true; 35 | if (typeof opt.flipY === 'boolean') texture.flipY = opt.flipY; 36 | if (typeof opt.mapping !== 'undefined') { 37 | texture.mapping = opt.mapping; 38 | } 39 | if (typeof opt.format !== 'undefined') texture.format = opt.format; 40 | if (opt.repeat) texture.repeat.copy(opt.repeat); 41 | texture.wrapS = opt.wrapS || THREE.ClampToEdgeWrapping; 42 | texture.wrapT = opt.wrapT || THREE.ClampToEdgeWrapping; 43 | texture.minFilter = opt.minFilter || THREE.LinearMipMapLinearFilter; 44 | texture.magFilter = opt.magFilter || THREE.LinearFilter; 45 | texture.generateMipmaps = opt.generateMipmaps !== false; 46 | } 47 | 48 | // The default ThreeJS Image/Texture loader has some 49 | // really weird code that breaks on Chrome sometimes. 50 | // Here's a simpler loader that seems to work better. 51 | 52 | function SimpleTextureLoader () { 53 | } 54 | 55 | SimpleTextureLoader.prototype.load = function (url, onLoad, onProgress, onErorr, opt) { 56 | var texture = new THREE.Texture(); 57 | if (opt && opt.encoding) texture.encoding = opt.encoding; 58 | 59 | var image = new window.Image(); 60 | image.onload = function () { 61 | texture.image = image; 62 | texture.needsUpdate = true; 63 | onLoad(texture); 64 | }; 65 | image.onerror = function (err) { 66 | onErorr(err); 67 | }; 68 | image.src = url; 69 | return texture; 70 | }; 71 | -------------------------------------------------------------------------------- /lib/geom/createTubeGeometry.js: -------------------------------------------------------------------------------- 1 | module.exports = createLineGeometry; 2 | function createLineGeometry (numSides = 8, subdivisions = 50, openEnded = false) { 3 | // create a base CylinderGeometry which handles UVs, end caps and faces 4 | const radius = 1; 5 | const length = 1; 6 | const baseGeometry = new THREE.CylinderGeometry(radius, radius, length, numSides, subdivisions, openEnded); 7 | 8 | // fix the orientation so X can act as arc length 9 | baseGeometry.rotateZ(Math.PI / 2); 10 | 11 | // compute the radial angle for each position for later extrusion 12 | const tmpVec = new THREE.Vector2(); 13 | const xPositions = []; 14 | const angles = []; 15 | const uvs = []; 16 | const vertices = baseGeometry.vertices; 17 | const faceVertexUvs = baseGeometry.faceVertexUvs[0]; 18 | 19 | // Now go through each face and un-index the geometry. 20 | baseGeometry.faces.forEach((face, i) => { 21 | const { a, b, c } = face; 22 | const v0 = vertices[a]; 23 | const v1 = vertices[b]; 24 | const v2 = vertices[c]; 25 | const verts = [ v0, v1, v2 ]; 26 | const faceUvs = faceVertexUvs[i]; 27 | 28 | // For each vertex in this face... 29 | verts.forEach((v, j) => { 30 | tmpVec.set(v.y, v.z).normalize(); 31 | 32 | // the radial angle around the tube 33 | const angle = Math.atan2(tmpVec.y, tmpVec.x); 34 | angles.push(angle); 35 | 36 | // "arc length" in range [-0.5 .. 0.5] 37 | xPositions.push(v.x); 38 | 39 | // copy over the UV for this vertex 40 | uvs.push(faceUvs[j].toArray()); 41 | }); 42 | }); 43 | 44 | // build typed arrays for our attributes 45 | const posArray = new Float32Array(xPositions); 46 | const angleArray = new Float32Array(angles); 47 | const uvArray = new Float32Array(uvs.length * 2); 48 | 49 | // unroll UVs 50 | for (let i = 0; i < posArray.length; i++) { 51 | const [ u, v ] = uvs[i]; 52 | uvArray[i * 2 + 0] = u; 53 | uvArray[i * 2 + 1] = v; 54 | } 55 | 56 | const geometry = new THREE.InstancedBufferGeometry(); 57 | geometry.addAttribute('position', new THREE.BufferAttribute(posArray, 1)); 58 | geometry.addAttribute('angle', new THREE.BufferAttribute(angleArray, 1)); 59 | geometry.addAttribute('uv', new THREE.BufferAttribute(uvArray, 2)); 60 | 61 | // dispose old geometry since we no longer need it 62 | baseGeometry.dispose(); 63 | return geometry; 64 | } 65 | -------------------------------------------------------------------------------- /lib/shaders/tube.frag: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | precision highp float; 3 | 4 | varying vec3 vNormal; 5 | varying vec2 vUv; 6 | varying vec3 vViewPosition; 7 | varying float vArclen; 8 | varying float vRandomScale; 9 | varying vec3 vBarycentric; 10 | 11 | uniform vec3 color; 12 | uniform vec3 tipColor; 13 | uniform float animateRadius; 14 | uniform float time; 15 | uniform float animateStrength; 16 | 17 | #pragma glslify: aastep = require('glsl-aastep'); 18 | #pragma glslify: faceNormal = require('glsl-face-normal'); 19 | #pragma glslify: noise = require('glsl-noise/simplex/4d'); 20 | 21 | float pattern(float v, float repeats, float threshold, float offset) { 22 | float result = mod(v * repeats + offset, 1.0); 23 | return step(threshold, result); 24 | } 25 | 26 | void main () { 27 | // handle flat and smooth normals 28 | vec3 normal = vNormal; 29 | #ifdef FLAT_SHADED 30 | normal = faceNormal(vViewPosition); 31 | #endif 32 | 33 | // Z-normal "fake" shading 34 | float diffuse = normal.z * 0.5 + 0.5; 35 | float isCap = step(0.999999, abs(vArclen * 2.0 - 1.0)); 36 | 37 | // add some "rim lighting" 38 | vec3 V = normalize(vViewPosition); 39 | float vDotN = 1.0 - max(dot(V, normal), 0.0); 40 | float rim = smoothstep(0.5, 1.0, vDotN); 41 | diffuse += mix(rim * 0.75, 0.0, isCap); 42 | 43 | // we'll animate in the new color from the center point 44 | float distFromCenter = clamp(length(vViewPosition) / 5.0, 0.0, 1.0); 45 | float edge = 0.05; 46 | float t = animateRadius; 47 | 48 | float pColor = pattern(vUv.x, 4.0, 0.5, 0.0); 49 | 50 | float wire = min(vBarycentric.x, min(vBarycentric.y, vBarycentric.z)); 51 | // diffuseColor = vec3(wire); 52 | wire = aastep(0.1, wire); 53 | // wire = aastep(0.5 * (sin(vRandomScale + time) * 0.5 + 0.5), wire); 54 | 55 | // float n = noise(vec4(vViewPosition * 10.0, time * 0.15)) * 0.5 + 0.5; 56 | vec3 bodyColor = color * 0.75; //mix(tipColor, color, pColor); 57 | vec3 capColor = tipColor; 58 | vec3 diffuseColor = vec3(mix(bodyColor, capColor, isCap)); 59 | 60 | 61 | // float diffuseColor = mix(pColor, capColor, isCap); 62 | // gl_FragColor = vec4(vec3(finalColor), 1.0); 63 | 64 | // vec3 curColor = mix(color, #fff, smoothstep(t - edge, t + edge, vUv.y) * animateStrength); 65 | 66 | 67 | 68 | // final color 69 | gl_FragColor = vec4(diffuse * diffuseColor, 1.0); 70 | // gl_FragColor = vec4(vec3(step(0.25, vUv.y)), 1.0); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /lib/util/random.js: -------------------------------------------------------------------------------- 1 | const defaultSeed = '2'; //String(Math.floor(Math.random() * 100000)); 2 | let currentSeed = String(require('./query').seed || defaultSeed); 3 | const seedRandom = require('seed-random'); 4 | 5 | module.exports.nextSeed = () => { 6 | let num = parseInt(currentSeed, 10) || 0; 7 | num++; 8 | currentSeed = String(num); 9 | module.exports.random = seedRandom(currentSeed); 10 | }; 11 | 12 | module.exports.random = seedRandom(currentSeed); 13 | 14 | module.exports.randomSign = () => module.exports.random() > 0.5 ? 1 : -1; 15 | 16 | module.exports.randomFloat = function (min, max) { 17 | if (max === undefined) { 18 | max = min; 19 | min = 0; 20 | } 21 | 22 | if (typeof min !== 'number' || typeof max !== 'number') { 23 | throw new TypeError('Expected all arguments to be numbers'); 24 | } 25 | 26 | return module.exports.random() * (max - min) + min; 27 | }; 28 | 29 | module.exports.randomCircle = function (out, scale) { 30 | scale = scale || 1.0; 31 | var r = module.exports.random() * 2.0 * Math.PI; 32 | out[0] = Math.cos(r) * scale; 33 | out[1] = Math.sin(r) * scale; 34 | return out; 35 | }; 36 | 37 | module.exports.randomSphere = function (out, scale) { 38 | scale = scale || 1.0; 39 | var r = module.exports.random() * 2.0 * Math.PI; 40 | var z = (module.exports.random() * 2.0) - 1.0; 41 | var zScale = Math.sqrt(1.0 - z * z) * scale; 42 | out[0] = Math.cos(r) * zScale; 43 | out[1] = Math.sin(r) * zScale; 44 | out[2] = z * scale; 45 | return out; 46 | }; 47 | 48 | module.exports.shuffle = function (arr) { 49 | if (!Array.isArray(arr)) { 50 | throw new TypeError('Expected Array, got ' + typeof arr); 51 | } 52 | 53 | var rand; 54 | var tmp; 55 | var len = arr.length; 56 | var ret = arr.slice(); 57 | 58 | while (len) { 59 | rand = Math.floor(module.exports.random() * len--); 60 | tmp = ret[len]; 61 | ret[len] = ret[rand]; 62 | ret[rand] = tmp; 63 | } 64 | 65 | return ret; 66 | }; 67 | 68 | module.exports.randomQuaternion = function (out) { 69 | const u1 = module.exports.random(); 70 | const u2 = module.exports.random(); 71 | const u3 = module.exports.random(); 72 | 73 | const sq1 = Math.sqrt(1 - u1); 74 | const sq2 = Math.sqrt(u1); 75 | 76 | const theta1 = Math.PI * 2 * u2; 77 | const theta2 = Math.PI * 2 * u3; 78 | 79 | const x = Math.sin(theta1) * sq1; 80 | const y = Math.cos(theta1) * sq1; 81 | const z = Math.sin(theta2) * sq2; 82 | const w = Math.cos(theta2) * sq2; 83 | out[0] = x; 84 | out[1] = y; 85 | out[2] = z; 86 | out[3] = w; 87 | return out; 88 | } -------------------------------------------------------------------------------- /lib/shaders/bloom/gaussian-blur.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec2 vUv; 4 | uniform highp sampler2D tDiffuse; 5 | uniform vec2 resolution; 6 | uniform vec2 direction; 7 | uniform float radius; 8 | 9 | uniform float vignetteMin; 10 | uniform float vignetteMax; 11 | uniform float vignetteStrength; 12 | uniform float vignetteScale; 13 | 14 | #pragma glslify: vignette = require('./vignette'); 15 | #pragma glslify: decodeHDR = require('../decode-hdr'); 16 | #pragma glslify: encodeHDR = require('../encode-hdr'); 17 | 18 | float sample (sampler2D image, vec2 uv) { 19 | #ifndef FLOAT_BUFFER 20 | return decodeHDR(texture2D(image, uv)); 21 | #else 22 | return texture2D(image, uv).r; 23 | #endif 24 | } 25 | 26 | float blur5 (sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 27 | float color = 0.0; 28 | vec2 off1 = vec2(1.3333333333333333) * direction; 29 | color += sample(image, uv) * 0.29411764705882354; 30 | color += sample(image, uv + (off1 / resolution)) * 0.35294117647058826; 31 | color += sample(image, uv - (off1 / resolution)) * 0.35294117647058826; 32 | return color; 33 | } 34 | 35 | float blur9 (sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 36 | float color = 0.0; 37 | vec2 off1 = vec2(1.3846153846) * direction; 38 | vec2 off2 = vec2(3.2307692308) * direction; 39 | color += sample(image, uv) * 0.2270270270; 40 | color += sample(image, uv + (off1 / resolution)) * 0.3162162162; 41 | color += sample(image, uv - (off1 / resolution)) * 0.3162162162; 42 | color += sample(image, uv + (off2 / resolution)) * 0.0702702703; 43 | color += sample(image, uv - (off2 / resolution)) * 0.0702702703; 44 | return color; 45 | } 46 | 47 | float blur13 (sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 48 | float color = 0.0; 49 | vec2 off1 = vec2(1.411764705882353) * direction; 50 | vec2 off2 = vec2(3.2941176470588234) * direction; 51 | vec2 off3 = vec2(5.176470588235294) * direction; 52 | color += sample(image, uv) * 0.1964825501511404; 53 | color += sample(image, uv + (off1 / resolution)) * 0.2969069646728344; 54 | color += sample(image, uv - (off1 / resolution)) * 0.2969069646728344; 55 | color += sample(image, uv + (off2 / resolution)) * 0.09447039785044732; 56 | color += sample(image, uv - (off2 / resolution)) * 0.09447039785044732; 57 | color += sample(image, uv + (off3 / resolution)) * 0.010381362401148057; 58 | color += sample(image, uv - (off3 / resolution)) * 0.010381362401148057; 59 | return color; 60 | } 61 | 62 | void main () { 63 | float v = vignette(vUv, resolution, vignetteMin, vignetteMax, vignetteScale); 64 | float edgeBlur = v + 1.0; 65 | float finalColor = blur13(tDiffuse, vUv, resolution.xy, direction * edgeBlur); 66 | 67 | #ifdef FLOAT_BUFFER 68 | gl_FragColor = vec4(vec3(finalColor), 1.0); 69 | #else 70 | gl_FragColor = encodeHDR(finalColor); 71 | #endif 72 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raylight", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "semistandard": { 7 | "global": [ 8 | "THREE" 9 | ] 10 | }, 11 | "author": { 12 | "name": "Matt DesLauriers", 13 | "email": "dave.des@gmail.com", 14 | "url": "https://github.com/mattdesl" 15 | }, 16 | "dependencies": { 17 | "analyser-frequency-average": "^1.0.0", 18 | "array-shuffle": "^1.0.1", 19 | "babel-polyfill": "^6.23.0", 20 | "circular-buffer": "^1.0.2", 21 | "convex-hull": "^1.0.3", 22 | "defined": "^1.0.0", 23 | "dom-css": "^2.1.0", 24 | "element-class": "^0.2.2", 25 | "fastclick": "^1.0.6", 26 | "glsl-aastep": "^1.0.1", 27 | "glsl-blend": "^1.0.3", 28 | "glsl-easings": "^1.0.0", 29 | "glsl-face-normal": "^1.0.2", 30 | "glsl-fast-gaussian-blur": "^1.0.2", 31 | "glsl-fxaa": "^3.0.0", 32 | "glsl-hash-blur": "^1.0.3", 33 | "glsl-luma": "^1.0.1", 34 | "glsl-lut": "^1.1.0", 35 | "glsl-noise": "0.0.0", 36 | "glsl-pi": "^1.0.0", 37 | "glsl-random": "0.0.5", 38 | "glsl-sdf-primitives": "0.0.1", 39 | "glslify-hex": "^2.1.1", 40 | "ios-safe-audio-context": "^1.0.1", 41 | "lerp": "^1.0.3", 42 | "load-img": "^1.0.0", 43 | "load-json-xhr": "^3.0.3", 44 | "map-limit": "0.0.1", 45 | "mouse-event-offset": "^3.0.2", 46 | "new-array": "^1.0.0", 47 | "nice-color-palettes": "^1.0.1", 48 | "object-assign": "^4.1.0", 49 | "orbit-controls": "^1.1.1", 50 | "query-string": "^4.2.3", 51 | "raf-loop": "^1.1.3", 52 | "seed-random": "^2.2.0", 53 | "simplex-noise": "^2.2.0", 54 | "smoothstep": "^1.0.1", 55 | "soundbank-reverb": "^1.1.2", 56 | "soundcloud-badge": "mattdesl/soundcloud-badge#feature/next", 57 | "soundcloud-resolve": "mattdesl/soundcloud-resolve#fix/https", 58 | "three-buffer-vertex-data": "^1.1.0", 59 | "three-copyshader": "0.0.1", 60 | "three-effectcomposer": "0.0.1", 61 | "three-shader-fxaa": "^5.1.1", 62 | "touches": "^1.2.2", 63 | "tweenr": "^2.2.0", 64 | "web-audio-player": "^1.3.1" 65 | }, 66 | "devDependencies": { 67 | "babel-preset-es2015": "^6.18.0", 68 | "babelify": "^7.3.0", 69 | "browserify": "^13.1.1", 70 | "budo": "^9.2.2", 71 | "cross-env": "^3.1.3", 72 | "glslify": "^6.0.1", 73 | "rimraf": "^2.5.4", 74 | "surge": "^0.18.0", 75 | "uglify-js": "^2.7.5" 76 | }, 77 | "glslify": { 78 | "transform": [ 79 | "glslify-hex" 80 | ] 81 | }, 82 | "scripts": { 83 | "start": "budo index.js --serve bundle.js --live --dir app -- -t babelify -t glslify", 84 | "deploy:upload": "surge -p app -d raylight.surge.sh", 85 | "deploy": "npm run build && npm run deploy:upload", 86 | "build": "browserify index.js -t babelify -t glslify | uglifyjs -m -c warnings=false > app/bundle.js" 87 | }, 88 | "keywords": [], 89 | "repository": { 90 | "type": "git", 91 | "url": "git://github.com/mattdesl/raylight.git" 92 | }, 93 | "homepage": "https://github.com/mattdesl/raylight", 94 | "bugs": { 95 | "url": "https://github.com/mattdesl/raylight/issues" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/shaders/bloom/ssao.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform float cameraNear; 4 | uniform float cameraFar; 5 | uniform bool onlyAO; 6 | uniform vec2 resolution; 7 | uniform float aoClamp; 8 | uniform float lumInfluence; 9 | uniform highp sampler2D tDiffuse; 10 | varying vec2 vUv; 11 | #pragma glslify: readDepth = require('../read-depth'); 12 | #pragma glslify: encodeFloat = require('../encode-float'); 13 | 14 | #define DL 2.399963229728653 15 | #define EULER 2.718281828459045 16 | 17 | const int samples = 4; 18 | const float radius = 5.0; 19 | const bool useNoise = false; 20 | const float noiseAmount = 0.0003; 21 | const float diffArea = 0.4; 22 | const float gDisplace = 0.4; 23 | 24 | highp vec2 rand( const vec2 coord ) { 25 | highp vec2 noise; 26 | if ( useNoise ) { 27 | float nx = dot ( coord, vec2( 12.9898, 78.233 ) ); 28 | float ny = dot ( coord, vec2( 12.9898, 78.233 ) * 2.0 ); 29 | noise = clamp( fract ( 43758.5453 * sin( vec2( nx, ny ) ) ), 0.0, 1.0 ); 30 | } else { 31 | highp float ff = fract( 1.0 - coord.s * ( resolution.x / 2.0 ) ); 32 | highp float gg = fract( coord.t * ( resolution.y / 2.0 ) ); 33 | noise = vec2( 0.25, 0.75 ) * vec2( ff ) + vec2( 0.75, 0.25 ) * gg; 34 | } 35 | return ( noise * 2.0 - 1.0 ) * noiseAmount; 36 | } 37 | 38 | float compareDepths( const in float depth1, const in float depth2, inout int far ) { 39 | float garea = 2.0; 40 | float diff = ( depth1 - depth2 ) * 100.0; 41 | if ( diff < gDisplace ) { 42 | garea = diffArea; 43 | } else { 44 | far = 1; 45 | } 46 | float dd = diff - gDisplace; 47 | float gauss = pow( EULER, -2.0 * dd * dd / ( garea * garea ) ); 48 | return gauss; 49 | } 50 | 51 | float calcAO( float depth, vec2 coord1, vec2 coord2 ) { 52 | float temp1 = 0.0; 53 | float temp2 = 0.0; 54 | int far = 0; 55 | temp1 = compareDepths( depth, readDepth(tDiffuse, coord1, cameraNear, cameraFar), far ); 56 | if ( far > 0 ) { 57 | temp2 = compareDepths( readDepth(tDiffuse, coord2, cameraNear, cameraFar), depth, far ); 58 | temp1 += ( 1.0 - temp1 ) * temp2; 59 | } 60 | return temp1; 61 | } 62 | 63 | void main() { 64 | float aspect = resolution.x / resolution.y; 65 | float threshold = 0.5; 66 | float thresholdSq = threshold * threshold; 67 | 68 | highp vec2 noise = rand( vUv ); 69 | float depth = readDepth(tDiffuse, vUv, cameraNear, cameraFar); 70 | float tt = clamp( depth, aoClamp, 1.0 ); 71 | vec2 texelRes = 1.0 / resolution / tt + noise * (1.0 - noise); 72 | 73 | float ao = 0.0; 74 | float dz = 1.0 / float( samples ); 75 | float z = 1.0 - dz / 2.0; 76 | float l = 0.0; 77 | for ( int i = 0; i <= samples; i ++ ) { 78 | float r = sqrt( 1.0 - z ); 79 | float pw = cos( l ) * r; 80 | float ph = sin( l ) * r; 81 | 82 | float dd = radius - depth * radius; 83 | vec2 vv = vec2( pw, ph ) * texelRes * dd; 84 | vec2 coord1 = vUv + vv; 85 | vec2 coord2 = vUv - vv; 86 | ao += calcAO( depth, coord1, coord2 ); 87 | z = z - dz; 88 | l = l + DL; 89 | } 90 | ao /= float( samples ); 91 | ao = 1.0 - ao; 92 | // vec3 lumcoeff = vec3( 0.299, 0.587, 0.114 ); 93 | // float lum = dot( color.rgb, lumcoeff ); 94 | // float aoVal = mix(ao, 1.0, lum * lumInfluence); 95 | // gl_FragColor = encodeFloat(ao); 96 | gl_FragColor = vec4(vec3(ao), 1.0); 97 | } -------------------------------------------------------------------------------- /lib/components/BGPlaneSpawner.js: -------------------------------------------------------------------------------- 1 | const Spawner = require('./Spawner'); 2 | const HDRMaterial = require('../shaders/HDRMaterial'); 3 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 4 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 5 | const tweenr = require('tweenr')(); 6 | const clamp = require('clamp'); 7 | const lerp = require('lerp'); 8 | 9 | const tmpColor = new THREE.Color(); 10 | const rotMat = new THREE.Matrix4(); 11 | const tmpArray4 = [ 0, 0, 0, 0 ]; 12 | 13 | module.exports = class DustSpawner extends Spawner { 14 | constructor (app, data) { 15 | const scale = 0.1; 16 | const geometry = new THREE.BoxGeometry(1, 1, 1); 17 | const emissiveMaterial = new HDRBasicMaterial({ 18 | }); 19 | const reflectiveMaterial = new HDRMaterial({ 20 | color: 'hsl(0, 0%, 100%)', 21 | // wireframe: true, 22 | metalness: 1, 23 | roughness: 1, 24 | // envMap: this.envMap, 25 | shading: THREE.FlatShading 26 | }); 27 | super(geometry, emissiveMaterial, 50); 28 | 29 | this.app = app; 30 | this.delay = [ 1, 2 ]; 31 | this.emissiveMaterial = emissiveMaterial; 32 | this.reflectiveMaterial = reflectiveMaterial; 33 | this.currentColor = new THREE.Color(); 34 | this.rotationOffset = 0; 35 | this.nextSpawnSize = new THREE.Vector3(); 36 | } 37 | 38 | emit () { 39 | this.currentColor.setStyle('#fff'); 40 | this.isNextSpawnEmissive = false; 41 | this.radius = 9; 42 | this.nextSpawnSize.set( 43 | 2, 44 | 8, 45 | 0.1 46 | ); 47 | super.emit(); 48 | } 49 | 50 | spawn (angle, x, y) { 51 | const mesh = super.spawn(angle, x, y); 52 | if (!mesh) return; 53 | 54 | const isEmissive = this.isNextSpawnEmissive; 55 | const nextMaterial = isEmissive ? this.emissiveMaterial : this.reflectiveMaterial; 56 | mesh.isEmissive = isEmissive; 57 | 58 | mesh.rotationZAmount = randomFloat(-0.1, 0.1); 59 | mesh.tiltAmount = randomFloat(-0.1, 0.1); 60 | // mesh.quaternion.fromArray(randomQuaternion(tmpArray4)); 61 | mesh.emissiveFactor = isEmissive ? randomFloat(1, 2) : 1; 62 | mesh.material = nextMaterial.clone(); 63 | mesh.material.uniforms.emissiveFactor.value = mesh.emissiveFactor; 64 | mesh.material.color.set(isEmissive ? this.currentColor : '#fff'); 65 | const zero = 1e-5; 66 | mesh.scale.set(zero, zero, this.nextSpawnSize.z); 67 | tweenr.to(mesh.scale, { 68 | x: this.nextSpawnSize.x, 69 | y: this.nextSpawnSize.y, 70 | z: this.nextSpawnSize.z, 71 | duration: 2, 72 | ease: 'linear' 73 | }); 74 | return mesh; 75 | } 76 | 77 | update (dt) { 78 | super.update(dt); 79 | this.meshPool.meshes.forEach(mesh => { 80 | if (mesh.isEmissive) { 81 | // const f = Math.sin(mesh.time * 1) * 0.5 + 0.5; 82 | const f = Math.sin(mesh.time - Math.PI * Math.tan(mesh.time * 1.0) * 0.01) * 0.5 + 0.5; 83 | const min = 0; 84 | const max = mesh.emissiveFactor; 85 | mesh.material.uniforms.emissiveFactor.value = lerp(min, max, f); 86 | } 87 | // rotMat.makeRotationZ(mesh.rotationZAmount * dt) 88 | // mesh.position.applyMatrix4(rotMat); 89 | // mesh.rotation.z += dt * mesh.tiltAmount; 90 | // mesh.quaternion.copy(this.app.camera.quaternion); 91 | }); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/components/PlaneSpawner.js: -------------------------------------------------------------------------------- 1 | const Spawner = require('./Spawner'); 2 | const HDRMaterial = require('../shaders/HDRMaterial'); 3 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 4 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 5 | const tweenr = require('tweenr')(); 6 | const clamp = require('clamp'); 7 | const lerp = require('lerp'); 8 | 9 | const tmpColor = new THREE.Color(); 10 | const rotMat = new THREE.Matrix4(); 11 | const tmpArray4 = [ 0, 0, 0, 0 ]; 12 | 13 | module.exports = class DustSpawner extends Spawner { 14 | constructor (app, data) { 15 | const scale = 0.1; 16 | const geometry = new THREE.BoxGeometry(1, 1, 1); 17 | const emissiveMaterial = new HDRBasicMaterial({ 18 | }); 19 | const reflectiveMaterial = new HDRMaterial({ 20 | color: 'hsl(0, 0%, 100%)', 21 | // wireframe: true, 22 | metalness: 1, 23 | roughness: 1, 24 | // envMap: this.envMap, 25 | shading: THREE.FlatShading 26 | }); 27 | super(geometry, [ emissiveMaterial, emissiveMaterial ], 50); 28 | 29 | this.app = app; 30 | this.delay = [ 2, 8 ]; 31 | this.emissiveMaterial = emissiveMaterial; 32 | this.reflectiveMaterial = reflectiveMaterial; 33 | this.currentColor = new THREE.Color(); 34 | this.rotationOffset = 0; 35 | this.nextSpawnSize = new THREE.Vector3(); 36 | } 37 | 38 | emit () { 39 | this.currentColor.setStyle('#fff'); 40 | this.isNextSpawnEmissive = randomFloat(1) > 0.5; 41 | this.radius = 6//randomFloat(3, 8); 42 | this.nextSpawnSize.set( 43 | randomFloat(0.2, 1), 44 | randomFloat(0.5, 3), 45 | 0.1 46 | ); 47 | super.emit(); 48 | } 49 | 50 | spawn (angle, x, y) { 51 | const mesh = super.spawn(angle, x, y); 52 | if (!mesh) return; 53 | 54 | const isEmissive = this.isNextSpawnEmissive; 55 | const nextMaterial = isEmissive ? this.emissiveMaterial : this.reflectiveMaterial; 56 | mesh.isEmissive = isEmissive; 57 | 58 | mesh.rotationZAmount = randomFloat(-0.1, 0.1); 59 | mesh.tiltAmount = randomFloat(-0.1, 0.1); 60 | // mesh.quaternion.fromArray(randomQuaternion(tmpArray4)); 61 | mesh.emissiveFactor = isEmissive ? randomFloat(1, 2) : 1; 62 | mesh.material[0] = nextMaterial.clone(); 63 | mesh.material[0].uniforms.emissiveFactor.value = mesh.emissiveFactor; 64 | mesh.material[0].color.set(isEmissive ? this.currentColor : '#fff'); 65 | mesh.material[1] = mesh.material[0]; 66 | const zero = 1e-5; 67 | mesh.scale.x = mesh.scale.z = randomFloat(2, 4) 68 | mesh.scale.y = zero; 69 | tweenr.to(mesh.scale, { 70 | y: randomFloat(0.5, 3), 71 | duration: 2, 72 | ease: 'linear' 73 | }); 74 | return mesh; 75 | } 76 | 77 | update (dt) { 78 | super.update(dt); 79 | this.meshPool.meshes.forEach(mesh => { 80 | if (!mesh.active) return; 81 | let val = 1; 82 | if (mesh.isEmissive) { 83 | // const f = Math.sin(mesh.time * 1) * 0.5 + 0.5; 84 | const f = Math.sin(mesh.time - Math.PI * Math.tan(mesh.time * 1.0) * 0.01) * 0.5 + 0.5; 85 | val = f * mesh.emissiveFactor; 86 | val += this.audio * 0.5; 87 | } 88 | mesh.material[0].uniforms.emissiveFactor.value = val * this.emission; 89 | mesh.material[1].uniforms.emissiveFactor.value = this.emission; 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/components/Spawner.js: -------------------------------------------------------------------------------- 1 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 2 | const meshPool = require('../util/meshPool'); 3 | const isMobile = require('../util/isMobile'); 4 | const defined = require('defined'); 5 | const tmpCameraPosition = new THREE.Vector3(); 6 | const tmpMeshWorldPosition = new THREE.Vector3(); 7 | 8 | module.exports = class SimpleSpawner extends THREE.Object3D { 9 | 10 | constructor (geometries, material, capacity) { 11 | super(); 12 | const defaultCapacity = isMobile ? 100 : 150; 13 | capacity = defined(capacity, defaultCapacity); 14 | this.delay = [ 0.1, 0.35 ]; 15 | this.currentDelay = this.computeDelay(); 16 | this.spawnTime = 0; 17 | this.time = 0; 18 | this.animation = 1; 19 | 20 | this.audio = 0; 21 | this.moveSpeed = 1; 22 | this.running = true; 23 | this.angleOffset = 0; 24 | this.sides = 3; 25 | this.radius = 4; 26 | this.initialZ = -20; 27 | 28 | this.meshPool = meshPool({ 29 | count: capacity, 30 | geometries, 31 | baseMaterial: material 32 | }); 33 | this.meshPool.meshes.forEach(m => { 34 | m.frustumCulled = false; 35 | this.add(m); 36 | }); 37 | } 38 | 39 | computeDelay () { 40 | return Array.isArray(this.delay) 41 | ? randomFloat(this.delay[0], this.delay[1]) 42 | : this.delay; 43 | } 44 | 45 | emit () { 46 | const sides = this.sides; 47 | for (let side = 0; side < sides; side++) { 48 | const rotOff = (Math.PI / 2) + this.angleOffset; 49 | const angle = Math.PI * 2 * (side / sides) - rotOff; 50 | const x = Math.cos(angle); 51 | const y = Math.sin(angle); 52 | // const rotationAngle = Math.atan2(y, x); 53 | this.spawn(angle, x, y); 54 | } 55 | } 56 | 57 | spawn (angle, x, y) { 58 | const mesh = this.meshPool.next(); 59 | if (!mesh) { 60 | return; 61 | } 62 | mesh.position.x = x * this.radius; 63 | mesh.position.y = y * this.radius; 64 | mesh.position.z = this.initialZ; 65 | mesh.time = 0; 66 | mesh.speed = randomFloat(0.5, 1) * 0.1; 67 | mesh.rotation.z = angle; 68 | return mesh; 69 | } 70 | 71 | update (dt) { 72 | this.time += dt; 73 | if (this.running) { 74 | this.spawnTime += dt; 75 | if (this.spawnTime > this.currentDelay) { 76 | this.spawnTime = 0; 77 | this.currentDelay = this.computeDelay(); 78 | this.emit(); 79 | } 80 | } 81 | 82 | tmpCameraPosition.setFromMatrixPosition(this.app.camera.matrixWorld); 83 | this.meshPool.meshes.forEach(mesh => { 84 | if (!mesh.active) return; 85 | 86 | mesh.time += dt; 87 | mesh.position.z += this.moveSpeed * mesh.speed; 88 | mesh.updateMatrixWorld(); 89 | if (Array.isArray(mesh.material)) { 90 | mesh.material.forEach(mat => { 91 | if (mat.uniforms && mat.uniforms.time) mat.uniforms.time.value = mesh.time; 92 | }); 93 | } else if (mesh.material.uniforms && mesh.material.uniforms.time) { 94 | mesh.material.uniforms.time.value = mesh.time; 95 | } 96 | 97 | // determine if culled 98 | tmpMeshWorldPosition.setFromMatrixPosition(mesh.matrixWorld); 99 | if (tmpMeshWorldPosition.z > tmpCameraPosition.z) { 100 | this.meshPool.free(mesh); 101 | } 102 | }); 103 | } 104 | }; -------------------------------------------------------------------------------- /app/style.css: -------------------------------------------------------------------------------- 1 | canvas { 2 | display: block; 3 | } 4 | * { 5 | -webkit-touch-callout:none; 6 | -webkit-text-size-adjust:none; 7 | -webkit-tap-highlight-color:rgba(0,0,0,0); 8 | -webkit-user-select:none; 9 | } 10 | body { 11 | font-family: 'Montserrat', 'Helvetica', sans-serif; 12 | font-weight: 400; 13 | background: black; 14 | color: hsl(0, 0%, 85%); 15 | overflow: hidden; 16 | margin: 0; 17 | } 18 | .noselect { 19 | -webkit-touch-callout: none; /* iOS Safari */ 20 | -webkit-user-select: none; /* Chrome/Safari/Opera */ 21 | -khtml-user-select: none; /* Konqueror */ 22 | -moz-user-select: none; /* Firefox */ 23 | -ms-user-select: none; /* Internet Explorer/Edge */ 24 | user-select: none; /* Non-prefixed version, currently 25 | not supported by any browser */ 26 | } 27 | .hide-cursor { 28 | cursor: none; 29 | } 30 | .clickable { 31 | cursor: pointer; 32 | } 33 | .grab { 34 | cursor: -webkit-grab; 35 | cursor: -moz-grab; 36 | } 37 | .grabbing { 38 | cursor: -webkit-grabbing; 39 | cursor: -moz-grabbing; 40 | } 41 | .hide-cursor { 42 | cursor: none; 43 | } 44 | #content { 45 | position: absolute; 46 | } 47 | #content, .overlay { 48 | position: absolute; 49 | top: 0; 50 | left: 0; 51 | width: 100%; 52 | height: 100%; 53 | } 54 | .overlay { 55 | background: rgba(0, 0, 0, 0.85); 56 | opacity: 1; 57 | pointer-events: none; 58 | } 59 | .npm-scb-wrap { 60 | z-index: 0 !important; 61 | } 62 | 63 | .soundcloud-badge { 64 | } 65 | 66 | .soundcloud-badge, .npm-scb-wrap, .npm-scb-inner, 67 | .npm-scb-info, .npm-scb-now-playing, .npm-scb-artist, 68 | .npm-scb-title { 69 | color: hsl(0, 0%, 85%) !important; 70 | font-family: 'Montserrat', 'Helvetica', sans-serif !important; 71 | } 72 | 73 | .npm-scb-now-playing { 74 | font-size: 0.675rem !important; 75 | font-weight: 300; 76 | margin-left: 0px !important; 77 | letter-spacing: 0.35px; 78 | } 79 | 80 | .npm-scb-artist { 81 | font-size: 0.675rem !important; 82 | font-weight: 300; 83 | margin-left: 20px !important; 84 | letter-spacing: 0.35px; 85 | } 86 | 87 | .npm-scb-title { 88 | font-size: 0.85rem !important; 89 | margin-left: 20px !important; 90 | font-weight: 400; 91 | padding-bottom: 5px !important; 92 | letter-spacing: 0.35px; 93 | } 94 | 95 | .info-wrapper { 96 | position: fixed; 97 | top: 50%; 98 | left: 50%; 99 | -webkit-transform: translate(-50%, -50%); 100 | transform: translate(-50%, -50%); 101 | } 102 | 103 | .info { 104 | text-align: left; 105 | } 106 | 107 | .header { 108 | font-size: 8rem; 109 | font-weight: 100; 110 | padding: 0; 111 | line-height: 8rem; 112 | margin: 0; 113 | } 114 | 115 | .subheader { 116 | position: absolute; 117 | left: 0.90rem; 118 | bottom: -1rem; 119 | color: hsla(0, 0%, 40%, 1); 120 | font-weight: 300; 121 | font-size: 0.75rem; 122 | letter-spacing: 0.5px; 123 | } 124 | /*.subheader::before { 125 | content: ' '; 126 | display: block; 127 | position: absolute; 128 | top: -1rem; 129 | left: -1px; 130 | width: 20%; 131 | height: 1px; 132 | background: hsla(0, 0%, 20%, 1); 133 | }*/ 134 | 135 | @media only screen and (max-width: 375px) { 136 | .header { 137 | font-size: 3.5rem; 138 | font-weight: 200; 139 | } 140 | .subheader { 141 | font-size: 0.7rem; 142 | left: 0.45rem; 143 | bottom: 1rem; 144 | color: hsla(0, 0%, 70%, 1); 145 | } 146 | } -------------------------------------------------------------------------------- /lib/components/CylinderSpawner.js: -------------------------------------------------------------------------------- 1 | const Spawner = require('./Spawner'); 2 | const HDRMaterial = require('../shaders/HDRMaterial'); 3 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 4 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 5 | const tweenr = require('tweenr')(); 6 | const clamp = require('clamp'); 7 | const lerp = require('lerp'); 8 | 9 | const tmpColor = new THREE.Color(); 10 | 11 | module.exports = class CylinderSpawner extends Spawner { 12 | constructor () { 13 | const geometries = [ 14 | createTubeGeometry(), 15 | createTubeGeometry(0.1) 16 | ]; 17 | const reflectiveMaterial = new HDRMaterial({ 18 | color: 'hsl(0, 0%, 100%)', 19 | metalness: 1, 20 | roughness: 1, 21 | // envMap: this.envMap, 22 | shading: THREE.FlatShading 23 | }); 24 | const emissiveMaterial = new HDRBasicMaterial({ 25 | }); 26 | const materials = [ emissiveMaterial, reflectiveMaterial ]; 27 | super(geometries, materials); 28 | 29 | this.delay = [ 0.1, 0.4 ]; 30 | this.emissiveMaterial = emissiveMaterial; 31 | this.reflectiveMaterial = reflectiveMaterial; 32 | this.currentColor = new THREE.Color(); 33 | } 34 | 35 | emit () { 36 | this.currentColor.setStyle('#fff'); 37 | this.isNextSpawnEmissive = randomFloat(1) > 0.5; 38 | super.emit(); 39 | } 40 | 41 | spawn (angle, x, y) { 42 | const mesh = super.spawn(angle, x, y); 43 | if (!mesh) return; 44 | 45 | const isEmissive = this.isNextSpawnEmissive; 46 | const nextMaterial = isEmissive ? this.emissiveMaterial : this.reflectiveMaterial; 47 | const matIndex = 0; 48 | mesh.isEmissive = isEmissive; 49 | mesh.emissiveFactor = isEmissive 50 | ? randomFloat(1, 2) 51 | : 1; 52 | mesh.materialIndex = matIndex; 53 | mesh.materialIndexOther = 1; 54 | mesh.material[matIndex] = nextMaterial.clone(); 55 | mesh.material[matIndex].uniforms.emissiveFactor.value = mesh.emissiveFactor; 56 | mesh.material[matIndex].color.set(isEmissive ? this.currentColor : '#fff'); 57 | mesh.scale.y = 1e-5; 58 | mesh.scale.x = mesh.scale.z = randomFloat(0.5, 2); 59 | tweenr.to(mesh.scale, { 60 | y: randomFloat(0.5, 2), 61 | duration: 2, 62 | ease: 'linear' 63 | }); 64 | return mesh; 65 | } 66 | 67 | update (dt) { 68 | super.update(dt); 69 | this.meshPool.meshes.forEach(mesh => { 70 | if (!mesh.active) return; 71 | let factor = 1; 72 | if (mesh.isEmissive) { 73 | // const f = Math.sin(mesh.time * 1) * 0.5 + 0.5; 74 | const f = Math.sin(mesh.time - Math.PI * Math.tan(mesh.time * 1.0) * 0.01) * 0.5 + 0.5; 75 | factor = f * mesh.emissiveFactor; 76 | factor += this.audio * 0.5; 77 | } 78 | mesh.material[mesh.materialIndex].uniforms.emissiveFactor.value = factor * this.emission; 79 | mesh.material[mesh.materialIndexOther].uniforms.emissiveFactor.value = this.emission; 80 | }); 81 | } 82 | } 83 | 84 | function createTubeGeometry (padding = 0) { 85 | const radius = 0.075; 86 | const length = 3; 87 | const sides = 5; 88 | const endCapLength = 0.25; 89 | const tubeGeometry = new THREE.CylinderGeometry(radius, radius, length, sides); 90 | const endCap = new THREE.CylinderGeometry(radius, radius, endCapLength, sides); 91 | const mults = [ 1, -1 ]; 92 | mults.forEach(mult => { 93 | tubeGeometry.merge( 94 | endCap, 95 | new THREE.Matrix4().makeTranslation(0, mult * (length / 2 + endCapLength / 2 + padding), 0), 96 | 1 97 | ); 98 | }); 99 | return tubeGeometry; 100 | } -------------------------------------------------------------------------------- /lib/shaders/standard-hdr.frag: -------------------------------------------------------------------------------- 1 | #define PHYSICAL 2 | 3 | uniform vec3 fogLightColor; 4 | uniform vec3 fogLightPosition; 5 | uniform mat4 cameraMatrixWorld; 6 | uniform float fogLightStrength; 7 | uniform float emissiveFactor; 8 | 9 | uniform vec3 diffuse; 10 | uniform vec3 emissive; 11 | uniform float roughness; 12 | uniform float metalness; 13 | uniform float opacity; 14 | 15 | #ifndef STANDARD 16 | uniform float clearCoat; 17 | uniform float clearCoatRoughness; 18 | #endif 19 | 20 | varying vec3 vViewPosition; 21 | varying vec3 vFogWorldPosition; 22 | 23 | #ifndef FLAT_SHADED 24 | 25 | varying vec3 vNormal; 26 | 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #pragma glslify: encodePixel = require('./encode-pixel'); 54 | 55 | #pragma glslify: InScatter = require('./inscatter'); 56 | 57 | void main2() { 58 | 59 | #include 60 | 61 | vec4 diffuseColor = vec4( diffuse * emissiveFactor, opacity ); 62 | ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); 63 | vec3 totalEmissiveRadiance = emissive; 64 | 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | 77 | // accumulation 78 | #include 79 | #include 80 | 81 | // modulation 82 | #include 83 | 84 | //direction from camera 85 | #if (NUM_POINT_LIGHTS > 0) 86 | vec3 positionToCamera = vFogWorldPosition - cameraPosition; 87 | float positionToCameraLength = length(positionToCamera); 88 | // normalize 89 | positionToCamera = normalize(positionToCamera); 90 | 91 | PointLight curPointLight; 92 | vec3 lightPos; 93 | float scatter; 94 | vec3 fogAmt; 95 | for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) { 96 | curPointLight = pointLights[ i ]; 97 | 98 | lightPos = (cameraMatrixWorld * vec4(curPointLight.position, 1.0)).xyz; 99 | scatter = InScatter(cameraPosition, positionToCamera, lightPos, positionToCameraLength); 100 | fogAmt = curPointLight.color * scatter * fogLightStrength; 101 | reflectedLight.directDiffuse += fogAmt; 102 | } 103 | #endif 104 | 105 | vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance; 106 | 107 | gl_FragColor = vec4( outgoingLight, diffuseColor.a ); 108 | 109 | #include 110 | #include 111 | #include 112 | #include 113 | 114 | gl_FragColor = encodePixel(gl_FragColor.r); 115 | } 116 | 117 | 118 | void main () { 119 | main2(); 120 | // gl_FragColor = vec4(diffuse, opacity); 121 | } -------------------------------------------------------------------------------- /lib/components/createTubes.js: -------------------------------------------------------------------------------- 1 | const createTubeGeometry = require('../geom/createTubeGeometry'); 2 | const glslify = require('glslify'); 3 | const path = require('path'); 4 | const newArray = require('new-array'); 5 | const tweenr = require('tweenr')(); 6 | const isMobile = require('../util/isMobile'); 7 | const bufferData = require('three-buffer-vertex-data'); 8 | 9 | const randomSamplesInMesh = require('../util/randomSamplesInMesh'); 10 | 11 | const { 12 | randomFloat, 13 | randomSphere, 14 | } = require('../util/random'); 15 | 16 | module.exports = function (app, opt = {}) { 17 | const bodyColor = '#1c2938'; 18 | const tipColor = opt.tipColor || 'white'; 19 | 20 | const container = new THREE.Object3D(); 21 | const totalMeshes = 8000; 22 | const isSquare = true; 23 | const subdivisions = 5; 24 | const numSides = 5; 25 | const openEnded = false; 26 | 27 | const baseGeometry = opt.baseGeometry; 28 | const baseMesh = new THREE.Mesh(baseGeometry, new THREE.MeshBasicMaterial({ 29 | colorWrite: false, 30 | color: bodyColor//app.renderer.getClearColor().clone(), 31 | // wireframe: true 32 | })); 33 | container.add(baseMesh); 34 | 35 | const geometry = createTubeGeometry(numSides, subdivisions, openEnded); 36 | 37 | // add barycentric coords 38 | const triCount = geometry.getAttribute('position').count; 39 | const barycentric = []; 40 | for (let i = 0; i < triCount / 3; i++) { 41 | barycentric.push([0, 0, 1], [0, 1, 0], [1, 0, 0]); 42 | } 43 | bufferData.attr(geometry, 'barycentric', barycentric, 3); 44 | 45 | geometry.maxInstancedCount = totalMeshes; 46 | 47 | const material = new THREE.RawShaderMaterial({ 48 | vertexShader: glslify(path.resolve(__dirname, '../shaders/tube.vert')), 49 | fragmentShader: glslify(path.resolve(__dirname, '../shaders/tube.frag')), 50 | side: THREE.FrontSide, 51 | transparent: true, 52 | extensions: { 53 | deriviatives: true 54 | }, 55 | defines: { 56 | lengthSegments: subdivisions.toFixed(1), 57 | ROBUST: false, 58 | ROBUST_NORMALS: true, // can be disabled for a slight optimization 59 | FLAT_SHADED: true 60 | }, 61 | // depthTest: false, 62 | uniforms: { 63 | thickness: { type: 'f', value: 0.01 }, 64 | time: { type: 'f', value: 0 }, 65 | color: { type: 'c', value: new THREE.Color(bodyColor) }, 66 | tipColor: { type: 'c', value: new THREE.Color(tipColor) }, 67 | animateRadius: { type: 'f', value: 0 }, 68 | animateStrength: { type: 'f', value: 0 }, 69 | index: { type: 'f', value: 0 }, 70 | totalMeshes: { type: 'f', value: totalMeshes }, 71 | radialSegments: { type: 'f', value: numSides } 72 | } 73 | }); 74 | 75 | const instanceIndices = new THREE.InstancedBufferAttribute(new Float32Array(totalMeshes * 1), 1, 1); 76 | geometry.addAttribute('instanceIndex', instanceIndices); 77 | 78 | const offsetScales = new THREE.InstancedBufferAttribute(new Float32Array(totalMeshes * 4), 4, 1); 79 | geometry.addAttribute('offsetScale', offsetScales); 80 | 81 | const surfaceNormals = new THREE.InstancedBufferAttribute(new Float32Array(totalMeshes * 3), 3, 1); 82 | geometry.addAttribute('surfaceNormal', surfaceNormals); 83 | 84 | const samples = randomSamplesInMesh(baseMesh, totalMeshes); 85 | if (samples.length !== totalMeshes) throw new Error('invalid sample count!'); 86 | 87 | for (let i = 0; i < totalMeshes; i++) { 88 | const t = totalMeshes <= 1 ? 0 : i / (totalMeshes - 1); 89 | const sample = samples[i]; 90 | const p = sample.position; 91 | const n = sample.normal; 92 | // const scale = randomFloat(1) > 0.9 ? randomFloat(1.0, 2.0) : randomFloat(0.5, 1.0); 93 | surfaceNormals.setXYZ(i, n.x, n.y, n.z); 94 | offsetScales.setXYZW(i, p.x, p.y, p.z, randomFloat(0.0, 1)); 95 | instanceIndices.setX(i, t); 96 | } 97 | 98 | const mesh = new THREE.Mesh(geometry, material); 99 | mesh.frustumCulled = false; // to avoid ThreeJS errors 100 | container.add(mesh); 101 | 102 | return { 103 | object3d: container, 104 | update 105 | }; 106 | 107 | function update (dt) { 108 | dt = dt / 1000; 109 | mesh.material.uniforms.time.value += dt; 110 | } 111 | }; 112 | -------------------------------------------------------------------------------- /lib/util/AssetManager.js: -------------------------------------------------------------------------------- 1 | // This is a lot like our 'preloader' npm module 2 | // but a bit more specific to WebGL & WebAudio. 3 | // It does *not* import anything project-specific! 4 | 5 | const noop = () => {}; 6 | const isImage = (ext) => /\.(jpe?g|png|gif|bmp|tga|tif)$/i.test(ext); 7 | const isSVG = (ext) => /\.svg$/i.test(ext); 8 | 9 | const path = require('path'); 10 | const mapLimit = require('map-limit'); 11 | const loadImage = require('load-img'); 12 | const loadTexture = require('./loadTexture'); 13 | 14 | module.exports = class AssetManager { 15 | 16 | constructor (opt = {}) { 17 | this._cache = {}; 18 | this._queue = []; 19 | this._audioContext = opt.audioContext; 20 | this._renderer = opt.renderer; 21 | this._asyncLimit = 10; 22 | this._onProgressListeners = []; 23 | } 24 | 25 | addProgressListener (fn) { 26 | if (typeof fn !== 'function') { 27 | throw new TypeError('onProgress must be a function'); 28 | } 29 | this._onProgressListeners.push(fn); 30 | } 31 | 32 | // Add an asset to be queued, format: { url, ...options } 33 | queue (opt = {}) { 34 | if (!opt || typeof opt !== 'object') { 35 | throw new Error('First parameter must be an object!'); 36 | } 37 | if (!opt.url) throw new TypeError('Must specify a URL or opt.url for AssetManager#queue()'); 38 | opt = Object.assign({}, opt); 39 | opt.key = opt.key || opt.url; 40 | const queued = this._getQueued(opt.key); 41 | if (!queued) this._queue.push(opt); 42 | return opt.key; 43 | } 44 | 45 | // Fetch a loaded asset by key or URL 46 | get (key = '') { 47 | if (!key) throw new TypeError('Must specify a key or URL for AssetManager#get()'); 48 | if (!(key in this._cache)) { 49 | throw new Error(`Could not find an asset by the key or URL ${key}`); 50 | } 51 | return this._cache[key]; 52 | } 53 | 54 | // Loads all queued assets 55 | loadQueued (cb = noop) { 56 | const queue = this._queue.slice(); 57 | this._queue.length = 0; // clear queue 58 | let count = 0; 59 | let total = queue.length; 60 | if (process.env.NODE_ENV === 'development') { 61 | console.log(`[assets] Loading ${total} queued items`); 62 | } 63 | mapLimit(queue, this._asyncLimit, (item, next) => { 64 | this.load(item, (err, result) => { 65 | const percent = total <= 1 ? 1 : (count / (total - 1)); 66 | this._onProgressListeners.forEach(fn => fn(percent)); 67 | if (err) { 68 | console.error(`[assets] Skipping ${item.key} from asset loading:`); 69 | console.error(err); 70 | } 71 | count++; 72 | next(null, result); 73 | }); 74 | }, cb); 75 | } 76 | 77 | // Loads a single asset on demand, returning from 78 | // cache if it exists otherwise adding it to the cache 79 | // after loading. 80 | load (item, cb = noop) { 81 | const url = item.url; 82 | const ext = path.extname(url); 83 | const key = item.key || url; 84 | const cache = this._cache; 85 | const renderer = this._renderer; 86 | 87 | if (key in cache) { 88 | const ret = cache[key]; 89 | process.nextTick(() => cb(null, ret)); 90 | return ret; 91 | } else { 92 | if (process.env.NODE_ENV === 'development') { 93 | console.log(`[assets] Loading ${key}`); 94 | } 95 | const done = (err, data) => { 96 | if (err) { 97 | delete cache[key]; 98 | } else { 99 | cache[key] = data; 100 | } 101 | if (cb) cb(err, data); 102 | }; 103 | if (isSVG(ext) || isImage(ext)) { 104 | let ret; 105 | if (item.texture !== false) { 106 | const opts = Object.assign({ renderer }, item); 107 | ret = loadTexture(url, opts, done); 108 | } else { 109 | ret = loadImage(url, item, done); 110 | } 111 | cache[key] = ret; 112 | return ret; 113 | } else { 114 | throw new Error(`Could not load ${url}, unknown file extension!`); 115 | } 116 | } 117 | } 118 | 119 | _getQueued (key) { 120 | for (let i = 0; i < this._queue.length; i++) { 121 | const item = this._queue[i]; 122 | if (item.key === key) return item; 123 | } 124 | return null; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/shaders/ssao-vignette.frag: -------------------------------------------------------------------------------- 1 | uniform float cameraNear; 2 | uniform float cameraFar; 3 | uniform bool onlyAO; 4 | uniform vec2 resolution; 5 | uniform float aoClamp; 6 | uniform float lumInfluence; 7 | uniform sampler2D tDiffuse; 8 | uniform highp sampler2D tDepth; 9 | varying vec2 vUv; 10 | 11 | // #define CLIP_RADIUS 12 | 13 | #define DL 2.399963229728653 14 | #define EULER 2.718281828459045 15 | 16 | const int samples = 4; 17 | const float radius = 5.0; 18 | const bool useNoise = false; 19 | const float noiseAmount = 0.0003; 20 | const float diffArea = 0.4; 21 | const float gDisplace = 0.4; 22 | 23 | highp vec2 rand( const vec2 coord ) { 24 | highp vec2 noise; 25 | if ( useNoise ) { 26 | float nx = dot ( coord, vec2( 12.9898, 78.233 ) ); 27 | float ny = dot ( coord, vec2( 12.9898, 78.233 ) * 2.0 ); 28 | noise = clamp( fract ( 43758.5453 * sin( vec2( nx, ny ) ) ), 0.0, 1.0 ); 29 | } else { 30 | highp float ff = fract( 1.0 - coord.s * ( resolution.x / 2.0 ) ); 31 | highp float gg = fract( coord.t * ( resolution.y / 2.0 ) ); 32 | noise = vec2( 0.25, 0.75 ) * vec2( ff ) + vec2( 0.75, 0.25 ) * gg; 33 | } 34 | return ( noise * 2.0 - 1.0 ) * noiseAmount; 35 | } 36 | 37 | float readDepth( const in vec2 coord ) { 38 | float cameraFarPlusNear = cameraFar + cameraNear; 39 | float cameraFarMinusNear = cameraFar - cameraNear; 40 | float cameraCoef = 2.0 * cameraNear; 41 | return cameraCoef / ( cameraFarPlusNear - texture2D( tDepth, coord ).x * cameraFarMinusNear ); 42 | } 43 | 44 | float compareDepths( const in float depth1, const in float depth2, inout int far ) { 45 | float garea = 2.0; 46 | float diff = ( depth1 - depth2 ) * 100.0; 47 | if ( diff < gDisplace ) { 48 | garea = diffArea; 49 | } else { 50 | far = 1; 51 | } 52 | float dd = diff - gDisplace; 53 | float gauss = pow( EULER, -2.0 * dd * dd / ( garea * garea ) ); 54 | return gauss; 55 | } 56 | 57 | float calcAO( float depth, vec2 coord1, vec2 coord2 ) { 58 | float temp1 = 0.0; 59 | float temp2 = 0.0; 60 | int far = 0; 61 | temp1 = compareDepths( depth, readDepth( coord1 ), far ); 62 | if ( far > 0 ) { 63 | temp2 = compareDepths( readDepth( coord2 ), depth, far ); 64 | temp1 += ( 1.0 - temp1 ) * temp2; 65 | } 66 | return temp1; 67 | } 68 | 69 | bool isInside (vec2 uv, float thresholdSq, float aspect, out float alpha) { 70 | vec2 center = vUv - 0.5; 71 | center.x *= aspect; 72 | 73 | float centerDistSq = center.x * center.x + center.y * center.y; 74 | alpha = 1.0 - min(1.0, centerDistSq / thresholdSq); 75 | return centerDistSq < thresholdSq; 76 | } 77 | 78 | bool isInside (vec2 uv, float thresholdSq, float aspect) { 79 | float alpha = 1.0; 80 | return isInside(uv, thresholdSq, aspect, alpha); 81 | } 82 | 83 | void main() { 84 | float aspect = resolution.x / resolution.y; 85 | float threshold = 0.5; 86 | float thresholdSq = threshold * threshold; 87 | 88 | vec4 color = texture2D( tDiffuse, vUv ); 89 | 90 | #ifdef CLIP_RADIUS 91 | if (!isInside(vUv, thresholdSq, aspect)) { 92 | gl_FragColor = color; 93 | return; 94 | } 95 | #endif 96 | 97 | highp vec2 noise = rand( vUv ); 98 | float depth = readDepth( vUv ); 99 | float tt = clamp( depth, aoClamp, 1.0 ); 100 | vec2 texelRes = 1.0 / resolution / tt + noise * (1.0 - noise); 101 | 102 | float ao = 0.0; 103 | float dz = 1.0 / float( samples ); 104 | float z = 1.0 - dz / 2.0; 105 | float l = 0.0; 106 | for ( int i = 0; i <= samples; i ++ ) { 107 | float r = sqrt( 1.0 - z ); 108 | float pw = cos( l ) * r; 109 | float ph = sin( l ) * r; 110 | 111 | float dd = radius - depth * radius; 112 | vec2 vv = vec2( pw, ph ) * texelRes * dd; 113 | vec2 coord1 = vUv + vv; 114 | vec2 coord2 = vUv - vv; 115 | #ifdef CLIP_RADIUS 116 | float strength = 1.0; 117 | if (isInside(coord1, thresholdSq, aspect, strength)) { 118 | ao += calcAO( depth, coord1, coord2 ) * strength; 119 | } 120 | #else 121 | ao += calcAO( depth, coord1, coord2 ); 122 | #endif 123 | z = z - dz; 124 | l = l + DL; 125 | } 126 | ao /= float( samples ); 127 | ao = 1.0 - ao; 128 | vec3 lumcoeff = vec3( 0.299, 0.587, 0.114 ); 129 | float lum = dot( color.rgb, lumcoeff ); 130 | float aoVal = mix(ao, 1.0, lum * lumInfluence); 131 | vec3 final = vec3( color.rgb * aoVal ); 132 | if ( onlyAO ) { 133 | final = vec3(aoVal); 134 | } 135 | gl_FragColor = vec4(final, 1.0); 136 | } -------------------------------------------------------------------------------- /lib/shaders/fxaa.glsl: -------------------------------------------------------------------------------- 1 | /** 2 | Basic FXAA implementation based on the code on geeks3d.com with the 3 | modification that the texture2DLod stuff was removed since it's 4 | unsupported by WebGL. 5 | 6 | -- 7 | 8 | From: 9 | https://github.com/mitsuhiko/webgl-meincraft 10 | 11 | Copyright (c) 2011 by Armin Ronacher. 12 | 13 | Some rights reserved. 14 | 15 | Redistribution and use in source and binary forms, with or without 16 | modification, are permitted provided that the following conditions are 17 | met: 18 | 19 | * Redistributions of source code must retain the above copyright 20 | notice, this list of conditions and the following disclaimer. 21 | 22 | * Redistributions in binary form must reproduce the above 23 | copyright notice, this list of conditions and the following 24 | disclaimer in the documentation and/or other materials provided 25 | with the distribution. 26 | 27 | * The names of the contributors may not be used to endorse or 28 | promote products derived from this software without specific 29 | prior written permission. 30 | 31 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 32 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 33 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 34 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 35 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 36 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 37 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 38 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 39 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 40 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 41 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 | */ 43 | 44 | #ifndef FXAA_REDUCE_MIN 45 | #define FXAA_REDUCE_MIN (1.0/ 128.0) 46 | #endif 47 | #ifndef FXAA_REDUCE_MUL 48 | #define FXAA_REDUCE_MUL (1.0 / 8.0) 49 | #endif 50 | #ifndef FXAA_SPAN_MAX 51 | #define FXAA_SPAN_MAX 8.0 52 | #endif 53 | 54 | //optimized version for mobile, where dependent 55 | //texture reads can be a bottleneck 56 | vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, 57 | vec2 v_rgbNW, vec2 v_rgbNE, 58 | vec2 v_rgbSW, vec2 v_rgbSE, 59 | vec2 v_rgbM) { 60 | vec4 color; 61 | mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); 62 | vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; 63 | vec3 rgbNE = texture2D(tex, v_rgbNE).xyz; 64 | vec3 rgbSW = texture2D(tex, v_rgbSW).xyz; 65 | vec3 rgbSE = texture2D(tex, v_rgbSE).xyz; 66 | vec4 texColor = texture2D(tex, v_rgbM); 67 | vec3 rgbM = texColor.xyz; 68 | vec3 luma = vec3(0.299, 0.587, 0.114); 69 | float lumaNW = dot(rgbNW, luma); 70 | float lumaNE = dot(rgbNE, luma); 71 | float lumaSW = dot(rgbSW, luma); 72 | float lumaSE = dot(rgbSE, luma); 73 | float lumaM = dot(rgbM, luma); 74 | float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); 75 | float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); 76 | 77 | mediump vec2 dir; 78 | dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); 79 | dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); 80 | 81 | float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 82 | (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); 83 | 84 | float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); 85 | dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), 86 | max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), 87 | dir * rcpDirMin)) * inverseVP; 88 | 89 | vec3 rgbA = 0.5 * ( 90 | min(vec3(1.0), texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz) + 91 | min(vec3(1.0), texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz)); 92 | vec3 rgbB = rgbA * 0.5 + 0.25 * ( 93 | min(vec3(1.0), texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz) + 94 | min(vec3(1.0), texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz)); 95 | 96 | float lumaB = dot(rgbB, luma); 97 | if ((lumaB < lumaMin) || (lumaB > lumaMax)) 98 | color = vec4(rgbA, texColor.a); 99 | else 100 | color = vec4(rgbB, texColor.a); 101 | return color; 102 | } 103 | 104 | #pragma glslify: export(fxaa) 105 | -------------------------------------------------------------------------------- /lib/components/DotPanelSpawner.js: -------------------------------------------------------------------------------- 1 | const Spawner = require('./Spawner'); 2 | const HDRMaterial = require('../shaders/HDRMaterial'); 3 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 4 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 5 | const tweenr = require('tweenr')(); 6 | const clamp = require('clamp'); 7 | const defined = require('defined'); 8 | const lerp = require('lerp'); 9 | 10 | const tmpColor = new THREE.Color(); 11 | 12 | module.exports = class CylinderSpawner extends Spawner { 13 | constructor () { 14 | const r = 0.075; 15 | const length = 3; 16 | const sides = 4; 17 | const geometries = []; 18 | geometries.push(createDotsGeometry({ xCount: 6 })); 19 | geometries.push(createDotsGeometry({ xCount: 4 })); 20 | geometries.push(createDotsGeometry({ xSpacing: 0.5, xCount: 6 })); 21 | geometries.push(createDotsGeometry({ xSpacing: 0.5, xCount: 6, yCount: 2 })); 22 | geometries.push(createDotsGeometry({ xSpacing: 0.5, ySpacing: 0.25, xCount: 4, yCount: 3 })); 23 | const reflectiveMaterial = new HDRMaterial({ 24 | color: 'hsl(0, 0%, 100%)', 25 | metalness: 1, 26 | roughness: 1, 27 | // envMap: this.envMap, 28 | shading: THREE.FlatShading 29 | }); 30 | const emissiveMaterial = new HDRBasicMaterial({ 31 | }); 32 | const materials = [ emissiveMaterial, reflectiveMaterial ]; 33 | super(geometries, materials); 34 | 35 | this.emissiveMaterial = emissiveMaterial; 36 | this.reflectiveMaterial = reflectiveMaterial; 37 | this.currentColor = new THREE.Color(); 38 | this.nextSpawnSize = new THREE.Vector3(); 39 | this.delay = [ 1, 4 ]; 40 | this.radius = 5; 41 | } 42 | 43 | emit () { 44 | this.currentColor.setStyle('#fff'); 45 | this.isNextSpawnEmissive = randomFloat(1) > 0.5; 46 | this.radius = randomFloat(3, 4); 47 | this.nextSpawnSize.set(1, 1, 1); 48 | super.emit(); 49 | } 50 | 51 | spawn (angle, x, y) { 52 | const mesh = super.spawn(angle, x, y); 53 | if (!mesh) return; 54 | 55 | const isEmissive = this.isNextSpawnEmissive; 56 | const nextMaterial = isEmissive ? this.emissiveMaterial : this.reflectiveMaterial; 57 | const matIndex = 0; 58 | mesh.isEmissive = isEmissive; 59 | mesh.emissiveFactor = isEmissive ? randomFloat(1, 2) : 1; 60 | mesh.materialIndex = matIndex; 61 | mesh.materialIndexOther = 1; 62 | mesh.material[matIndex] = nextMaterial.clone(); 63 | mesh.material[matIndex].uniforms.emissiveFactor.value = mesh.emissiveFactor; 64 | mesh.material[matIndex].color.set(isEmissive ? this.currentColor : '#fff'); 65 | const zero = 1e-5; 66 | mesh.scale.set(zero, zero, this.nextSpawnSize.z); 67 | tweenr.to(mesh.scale, { 68 | x: this.nextSpawnSize.x, 69 | y: this.nextSpawnSize.y, 70 | z: this.nextSpawnSize.z, 71 | duration: 2, 72 | ease: 'linear' 73 | }); 74 | return mesh; 75 | } 76 | 77 | update (dt) { 78 | super.update(dt); 79 | this.meshPool.meshes.forEach(mesh => { 80 | if (!mesh.active) return; 81 | let val = 1; 82 | if (mesh.isEmissive) { 83 | // const f = Math.sin(mesh.time * 1) * 0.5 + 0.5; 84 | // const f = Math.sin(mesh.time - Math.PI * Math.tan(mesh.time * 1.0) * 0.01) * 0.5 + 0.5; 85 | // const min = 0; 86 | // const max = mesh.emissiveFactor; 87 | val = mesh.emissiveFactor; 88 | val += this.audio * 0.5; 89 | } 90 | mesh.material[mesh.materialIndex].uniforms.emissiveFactor.value = val * this.emission; 91 | mesh.material[mesh.materialIndexOther].uniforms.emissiveFactor.value = this.emission; 92 | }); 93 | } 94 | } 95 | 96 | function createDotsGeometry (opt = {}) { 97 | const dot = new THREE.CircleGeometry(0.05, 8); 98 | const result = new THREE.Geometry(); 99 | const xCount = defined(opt.xCount, 6); 100 | const yCount = defined(opt.yCount, 1); 101 | const tmpMat = new THREE.Matrix4(); 102 | const xSpacing = defined(opt.xSpacing, 1); 103 | const ySpacing = defined(opt.ySpacing, 0.25); 104 | const brokenIndex = opt.hasBrokenDot ? Math.floor(randomFloat(0, xCount)) : -1; 105 | for (let j = 0; j < yCount; j++) { 106 | for (let i = 0; i < xCount; i++) { 107 | const t = i / Math.max(1, xCount); 108 | const k = j / Math.max(1, yCount); 109 | const x = t * 2 - 1; 110 | const y = k * 2 - 1; 111 | const matIndex = i === brokenIndex ? 1 : 0; 112 | result.merge(dot, 113 | tmpMat.makeTranslation(y * ySpacing, x * xSpacing, 0), 114 | matIndex 115 | ); 116 | } 117 | } 118 | return result; 119 | } -------------------------------------------------------------------------------- /lib/createAudio.js: -------------------------------------------------------------------------------- 1 | const createPlayer = require('web-audio-player'); 2 | const Reverb = require('soundbank-reverb'); 3 | const soundcloud = require('soundcloud-badge'); 4 | const isMobile = require('./util/isMobile'); 5 | const createContext = require('ios-safe-audio-context'); 6 | const average = require('analyser-frequency-average') 7 | const smoothstep = require('smoothstep'); 8 | const CircularBuffer = require('circular-buffer'); 9 | 10 | const urls = [ 11 | 'https://soundcloud.com/ukiyoau/kaleidoscope', 12 | ]; 13 | 14 | const AudioCtx = window.AudioContext || window.webkitAudioContext; 15 | const isIOS = require('./util/isIOS'); 16 | 17 | module.exports = createAudio; 18 | function createAudio (cb) { 19 | if (!AudioCtx || isIOS) { 20 | return process.nextTick(() => { 21 | cb(null); 22 | }); 23 | } 24 | 25 | const audioContext = createContext(); 26 | 27 | const analyserNode = audioContext.createAnalyser(); 28 | const freqArray = new Uint8Array(analyserNode.frequencyBinCount); 29 | 30 | // If rate is not 44100, the reverb module bugs out 31 | const supportReverb = audioContext.sampleRate <= 96000; 32 | const effectNode = createEffectNode(analyserNode); 33 | 34 | let effect = 0; 35 | let ampBuffer = new CircularBuffer(4); // N samples 36 | 37 | soundcloud({ 38 | el: document.querySelector('.soundcloud-badge'), 39 | client_id: 'b95f61a90da961736c03f659c03cb0cc', 40 | song: urls[0], 41 | dark: false, 42 | getFonts: false 43 | }, (err, src, meta, div) => { 44 | if (err) return cb(err); 45 | const audio = createPlayer(src, { 46 | loop: true, 47 | crossOrigin: 'Anonymous', 48 | context: audioContext 49 | }); 50 | audio.node.connect(effectNode); 51 | analyserNode.connect(audioContext.destination); 52 | 53 | if (isMobile) { 54 | if (div.parentNode) div.parentNode.removeChild(div); 55 | // const parent = document.querySelector('#content'); 56 | // parent.insertBefore(div, parent.firstChild); 57 | const el = document.querySelector('#content'); 58 | const onPlay = () => { 59 | el.removeEventListener('touchend', onPlay); 60 | audio.play(); 61 | }; 62 | el.addEventListener('touchend', onPlay); 63 | } else { 64 | audio.play(); 65 | } 66 | 67 | Object.defineProperty(audio, 'effect', { 68 | get: function () { 69 | return effect; 70 | }, 71 | set: function (val) { 72 | effect = val; 73 | effectNode.wet.value = val; 74 | effectNode.dry.value = 1 - val; 75 | } 76 | }); 77 | 78 | audio.update = () => { 79 | analyserNode.getByteFrequencyData(freqArray); 80 | return freqArray; 81 | }; 82 | 83 | audio.signal = () => { 84 | // find an average signal between two Hz ranges 85 | var minHz = 30; 86 | var maxHz = 80; 87 | const s = average(analyserNode, freqArray, minHz, maxHz); 88 | return smoothstep(0.8, 0.95, s); 89 | }; 90 | 91 | audio.amplitude = () => { 92 | // find an average signal between two Hz ranges 93 | var minHz = 100; 94 | var maxHz = 8000; 95 | let s = average(analyserNode, freqArray, minHz, maxHz); 96 | s = smoothstep(0.2, 0.6, s); 97 | ampBuffer.enq(s); 98 | const len = ampBuffer.size(); 99 | let avg = 0; 100 | for (let i = 0; i < len; i++) { 101 | avg += ampBuffer.get(i); 102 | } 103 | avg /= len; 104 | return avg; 105 | }; 106 | 107 | cb(null, audio); 108 | }); 109 | 110 | function createEffectNode (output) { 111 | if (supportReverb) { 112 | const reverb = Reverb(audioContext); 113 | reverb.time = 2.5; // seconds 114 | reverb.wet.value = 0; 115 | reverb.dry.value = 1; 116 | reverb.filterType = 'highpass'; 117 | reverb.cutoff.value = 500; // Hz 118 | reverb.connect(output); 119 | return reverb; 120 | } else { 121 | const node = audioContext.createGain(); 122 | const dry = audioContext.createGain(); 123 | const wet = audioContext.createGain(); 124 | const filter = audioContext.createBiquadFilter(); 125 | 126 | node.connect(dry); 127 | node.connect(wet); 128 | 129 | filter.type = 'lowpass'; 130 | filter.frequency.value = 1000; 131 | 132 | dry.connect(output); 133 | wet.connect(filter); 134 | filter.connect(output); 135 | 136 | Object.defineProperties(node, { 137 | wet: { get: () => wet.gain }, 138 | dry: { get: () => dry.gain } 139 | }); 140 | node.wet.value = 0; 141 | node.dry.value = 1; 142 | return node; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/components/PhysicsSpawner.js: -------------------------------------------------------------------------------- 1 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 2 | const meshPool = require('../util/meshPool'); 3 | const newArray = require('new-array'); 4 | const HDRMaterial = require('../shaders/HDRMaterial'); 5 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 6 | const tweenr = require('tweenr')(); 7 | 8 | const tmpVec = new THREE.Vector3(); 9 | const tmpCameraPosition = new THREE.Vector3(); 10 | const tmpMeshWorldPosition = new THREE.Vector3(); 11 | 12 | module.exports = class SimpleSpawner extends THREE.Object3D { 13 | 14 | constructor (app, sceneData) { 15 | super(); 16 | 17 | this.delay = [ 0.1, 0.45 ]; 18 | this.currentDelay = this.computeDelay(); 19 | this.spawnTime = 0; 20 | this.time = 0; 21 | this.app = null; 22 | 23 | this.angleOffset = 0; 24 | this.sides = 3; 25 | this.radius = 4; 26 | this.initialZ = -20; 27 | 28 | this.sceneData = sceneData; 29 | 30 | this.reflectiveMaterial = new HDRMaterial({ 31 | color: 'hsl(0, 0%, 100%)', 32 | metalness: 1, 33 | roughness: 1, 34 | // envMap: this.envMap, 35 | shading: THREE.FlatShading 36 | }); 37 | this.emissiveMaterial = new HDRBasicMaterial(); 38 | 39 | // one of the groups of meshes 40 | const group = this.sceneData.findObjects(/^cylinder/i); 41 | 42 | this.items = newArray(50).map(() => { 43 | const container = new THREE.Object3D(); 44 | group.forEach(mesh => { 45 | const child = mesh.clone(); 46 | child.initialPosition = child.position.clone(); 47 | child.friction = 0.98; 48 | child.velocity = new THREE.Vector3().fromArray(randomSphere([], 1)); 49 | container.add(child); 50 | }); 51 | container.active = false; 52 | container.visible = false; 53 | container.frustumCulled = false; 54 | this.add(container); 55 | return container; 56 | }); 57 | } 58 | 59 | computeDelay () { 60 | return Array.isArray(this.delay) 61 | ? randomFloat(this.delay[0], this.delay[1]) 62 | : this.delay; 63 | } 64 | 65 | emit () { 66 | const sides = this.sides; 67 | for (let side = 0; side < sides; side++) { 68 | const rotOff = (Math.PI / 2) + this.angleOffset; 69 | const angle = Math.PI * 2 * (side / sides) - rotOff; 70 | const x = Math.cos(angle); 71 | const y = Math.sin(angle); 72 | // const rotationAngle = Math.atan2(y, x); 73 | this.spawn(angle, x, y); 74 | } 75 | } 76 | 77 | spawn (angle, x, y) { 78 | const mesh = this.items.find(item => !item.active); 79 | if (!mesh) { 80 | console.log('skip') 81 | return; 82 | } 83 | 84 | mesh.visible = true; 85 | mesh.active = true; 86 | 87 | mesh.position.x = x * this.radius; 88 | mesh.position.y = y * this.radius; 89 | mesh.position.z = this.initialZ; 90 | mesh.time = 0; 91 | mesh.speed = randomFloat(0.5, 1) * 0.1; 92 | mesh.rotation.z = angle; 93 | 94 | const isEmissive = randomFloat(1) > 0.5; 95 | mesh.children.forEach(child => { 96 | child.isEmissive = isEmissive; 97 | child.material = child.isEmissive ? this.emissiveMaterial : this.reflectiveMaterial 98 | child.position.copy(child.initialPosition); 99 | child.velocity.fromArray(randomSphere([], 1)); 100 | child.speed = randomFloat(0.1, 0.3) * 0.1; 101 | }); 102 | return mesh; 103 | } 104 | 105 | update (dt) { 106 | this.time += dt; 107 | this.spawnTime += dt; 108 | if (this.spawnTime > this.currentDelay) { 109 | this.spawnTime = 0; 110 | this.currentDelay = this.computeDelay(); 111 | this.emit(); 112 | } 113 | 114 | tmpCameraPosition.setFromMatrixPosition(this.app.camera.matrixWorld); 115 | this.items.forEach(mesh => { 116 | if (!mesh.active) return; 117 | mesh.time += dt; 118 | mesh.position.z += mesh.speed; 119 | mesh.updateMatrixWorld(); 120 | // if (Array.isArray(mesh.material)) { 121 | // mesh.material.forEach(mat => { 122 | // if (mat.uniforms && mat.uniforms.time) mat.uniforms.time.value = mesh.time; 123 | // }); 124 | // } else if (mesh.material.uniforms && mesh.material.uniforms.time) { 125 | // mesh.material.uniforms.time.value = mesh.time; 126 | // } 127 | 128 | mesh.children.forEach(child => { 129 | tmpVec.copy(child.velocity).multiplyScalar(child.speed); 130 | child.position.add(tmpVec); 131 | child.velocity.multiplyScalar(child.friction); 132 | }); 133 | 134 | // determine if culled 135 | tmpMeshWorldPosition.setFromMatrixPosition(mesh.matrixWorld); 136 | if (tmpMeshWorldPosition.z > tmpCameraPosition.z) { 137 | mesh.active = false; 138 | mesh.visible = false; 139 | } 140 | }); 141 | } 142 | }; -------------------------------------------------------------------------------- /lib/post/EffectComposer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | module.exports = EffectComposer; 6 | 7 | var CopyShader = EffectComposer.CopyShader = require('three-copyshader') 8 | , RenderPass = EffectComposer.RenderPass = require('three-effectcomposer/lib/renderpass')(THREE) 9 | , ShaderPass = EffectComposer.ShaderPass = require('./ShaderPass')(THREE, EffectComposer) 10 | , MaskPass = EffectComposer.MaskPass = require('three-effectcomposer/lib/maskpass')(THREE) 11 | , ClearMaskPass = EffectComposer.ClearMaskPass = require('three-effectcomposer/lib/clearmaskpass')(THREE) 12 | 13 | function EffectComposer( renderer, renderTarget1, renderTarget2, initialRenderTarget ) { 14 | this.renderer = renderer; 15 | 16 | if ( renderTarget1 === undefined ) { 17 | throw new Error('must specify targets'); 18 | } 19 | 20 | this.renderTarget1 = renderTarget1; 21 | this.renderTarget2 = renderTarget2; 22 | this.initialRenderTarget = initialRenderTarget; 23 | 24 | this.writeBuffer = this.renderTarget1; 25 | this.readBuffer = this.renderTarget2; 26 | 27 | this.passes = []; 28 | 29 | this.copyPass = new ShaderPass( CopyShader ); 30 | }; 31 | 32 | EffectComposer.prototype = { 33 | swapBuffers: function() { 34 | 35 | var tmp = this.readBuffer; 36 | this.readBuffer = this.writeBuffer; 37 | this.writeBuffer = tmp; 38 | 39 | }, 40 | 41 | addPass: function ( pass ) { 42 | 43 | this.passes.push( pass ); 44 | 45 | }, 46 | 47 | clearPasses: function () { 48 | this.passes.length = 0; 49 | }, 50 | 51 | insertPass: function ( pass, index ) { 52 | 53 | this.passes.splice( index, 0, pass ); 54 | this.initialClearColor = new THREE.Color(1, 0, 0); 55 | }, 56 | 57 | render: function ( delta ) { 58 | // 1st - write into DEPTH FBO 59 | // 2nd - read from DEPTH FBO, write into TMP1 FBO 60 | // 3rd - read from TMP1 FBO, write into TMP2 FBO 61 | // 4th - ping-pong TMP1/TMP2 62 | 63 | var useInitial = Boolean(this.initialRenderTarget); 64 | // assumes first pass is a render pass which writes to readBuffer 65 | this.writeBuffer = this.renderTarget1; 66 | this.readBuffer = this.renderTarget2; 67 | 68 | var maskActive = false; 69 | 70 | var pass, i, passIndex, il = this.passes.length; 71 | 72 | for ( i = 0, passIndex = 0; i < il; i ++ ) { 73 | 74 | pass = this.passes[ i ]; 75 | 76 | if ( !pass.enabled ) { 77 | continue; 78 | } 79 | 80 | if (useInitial) { 81 | if (passIndex <= 1) { 82 | this.readBuffer = this.initialRenderTarget; 83 | this.writeBuffer = this.renderTarget1; 84 | } else if (passIndex === 2) { 85 | this.readBuffer = this.renderTarget1; 86 | this.writeBuffer = this.renderTarget2; 87 | } 88 | } 89 | 90 | var readTarget = this.readBuffer; 91 | var writeTarget = this.writeBuffer; 92 | 93 | var depthTexture; 94 | if (this.depthTexture) { 95 | depthTexture = this.depthTexture; 96 | } else if (this.initialRenderTarget) { 97 | depthTexture = passIndex === 0 98 | ? undefined 99 | : this.initialRenderTarget.depthTexture; 100 | } 101 | var attachments = this.initialRenderTarget ? this.initialRenderTarget.attachments : undefined; 102 | pass.render( this.renderer, writeTarget, readTarget, delta, maskActive, depthTexture, attachments ); 103 | 104 | if ( pass.needsSwap ) { 105 | 106 | if ( maskActive ) { 107 | 108 | var context = this.renderer.context; 109 | 110 | context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); 111 | 112 | this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta ); 113 | 114 | context.stencilFunc( context.EQUAL, 1, 0xffffffff ); 115 | 116 | } 117 | 118 | this.swapBuffers(); 119 | 120 | } 121 | 122 | if ( pass instanceof MaskPass ) { 123 | 124 | maskActive = true; 125 | 126 | } else if ( pass instanceof ClearMaskPass ) { 127 | 128 | maskActive = false; 129 | 130 | } 131 | 132 | passIndex++; 133 | } 134 | 135 | }, 136 | 137 | reset: function ( renderTarget ) { 138 | 139 | if ( renderTarget === undefined ) { 140 | 141 | renderTarget = this.renderTarget1.clone(); 142 | 143 | renderTarget.width = window.innerWidth; 144 | renderTarget.height = window.innerHeight; 145 | 146 | } 147 | 148 | this.renderTarget1 = renderTarget; 149 | this.renderTarget2 = renderTarget.clone(); 150 | 151 | this.writeBuffer = this.renderTarget1; 152 | this.readBuffer = this.renderTarget2; 153 | 154 | }, 155 | 156 | setSize: function ( width, height ) { 157 | 158 | var renderTarget = this.renderTarget1.clone(); 159 | 160 | renderTarget.width = width; 161 | renderTarget.height = height; 162 | 163 | this.reset( renderTarget ); 164 | 165 | } 166 | 167 | }; 168 | 169 | // shared ortho camera 170 | 171 | EffectComposer.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); 172 | 173 | EffectComposer.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 174 | 175 | EffectComposer.scene = new THREE.Scene(); 176 | EffectComposer.scene.add( EffectComposer.quad ); 177 | -------------------------------------------------------------------------------- /lib/components/FoggyScene.js: -------------------------------------------------------------------------------- 1 | const HDRMaterial = require('../shaders/HDRMaterial'); 2 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 3 | const createFogMaterial = require('../shaders/createFogMaterial'); 4 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 5 | 6 | const CylinderSpawner = require('./CylinderSpawner'); 7 | const PlaneSpawner = require('./PlaneSpawner'); 8 | const DotPanelSpawner = require('./DotPanelSpawner'); 9 | // const PhysicsSpawner = require('./PhysicsSpawner'); 10 | 11 | // const palette = palettes[Math.floor(randomFloat(0, palettes.length))]; 12 | 13 | // const sceneKey = assets.queue({ 14 | // scene: true, 15 | // url: 'assets/blender_cracked_tube.json' 16 | // }); 17 | 18 | module.exports = class FoggyScene extends THREE.Object3D { 19 | 20 | constructor (app) { 21 | super(); 22 | 23 | this.fogStrength = 0.03; 24 | this.emissiveFactor = 1.5; 25 | this.app = app; 26 | this.setupLights(); 27 | this.setupSkybox(); 28 | // this.setupScene(); 29 | this._updateFog(); 30 | this.audio = 0; 31 | this.twist = 0; 32 | this.running = true; 33 | this.moveSpeed = 1; 34 | this.animation = 1; 35 | this.emission = 1; 36 | 37 | this.spawners = [ 38 | new CylinderSpawner(this.app), 39 | new PlaneSpawner(this.app), 40 | new DotPanelSpawner(this.app) 41 | ]; 42 | this.spawners.forEach(s => this.add(s)); 43 | 44 | this.sideCountList = [ 3, 4, 5, 6 ]; 45 | this.rotationOffset = 0; 46 | this.tubeSpawnTime = 0; 47 | this.cubeSpawnTime = 0; 48 | this.tubeSpawnMin = 0.1; 49 | this.tubeSpawnMax = 0.35; 50 | 51 | this.cubeSpawnMin = 2; 52 | this.cubeSpawnMax = 8; 53 | this.tubeSpawnDelay = randomFloat(this.tubeSpawnMin, this.tubeSpawnMax); 54 | this.cubeSpawnDelay = randomFloat(this.cubeSpawnMin, this.cubeSpawnMax); 55 | this.sideTime = 0; 56 | this.sideDelay = 6; 57 | this.currentSideIndex = 0; 58 | this.currentSideCount = this.sideCountList[0]; 59 | } 60 | 61 | setupLights () { 62 | this.pointLights = [ '#fff' ].map((color, i, list) => { 63 | const t = i / list.length; 64 | const startAngle = Math.PI / 2; 65 | const angle = Math.PI * 2 * t + startAngle; 66 | 67 | const intensity = 1; 68 | const distance = 50; 69 | const pointLight = new THREE.PointLight(color, intensity, distance); 70 | const helper = new THREE.PointLightHelper(pointLight, 1); 71 | const r = 3; 72 | // pointLight.position.x = Math.cos(angle) * r; 73 | // pointLight.position.y = Math.sin(angle) * r; 74 | pointLight.position.z = -2; 75 | // this.add(helper); 76 | this.add(pointLight); 77 | return pointLight; 78 | }); 79 | } 80 | 81 | setupSkybox () { 82 | const skyGeometry = new THREE.IcosahedronGeometry(20, 1); 83 | const skyMaterial = createFogMaterial({ 84 | // depthTest: false, 85 | // depthWrite: false, 86 | // wireframe: true, 87 | // envMap: this.envMap, 88 | side: THREE.FrontSide, 89 | diffuse: 0 90 | // shading: THREE.FlatShading, 91 | }); 92 | const skyBox = new THREE.Mesh(skyGeometry, skyMaterial); 93 | skyBox.scale.x *= -1; 94 | this.skyBox = skyBox; 95 | this.add(skyBox); 96 | } 97 | 98 | _updateFog () { 99 | const pointLight = this.pointLights[0]; 100 | pointLight.updateMatrixWorld(); 101 | 102 | const camera = this.app.camera; 103 | camera.updateMatrixWorld(); 104 | this.traverse(child => { 105 | if (!child.material || !child.material.uniforms) return; 106 | 107 | if (child.material.uniforms.cameraMatrixWorld) { 108 | child.material.uniforms.cameraMatrixWorld.value.copy(camera.matrixWorld); 109 | } 110 | if (child.material.uniforms.fogLightStrength) { 111 | child.material.uniforms.fogLightStrength.value = this.fogStrength * this.emission; 112 | } 113 | if (child.material.uniforms.cameraWorldPosition) { 114 | child.material.uniforms.cameraWorldPosition.value.setFromMatrixPosition(camera.matrixWorld); 115 | } 116 | if (child.material.uniforms.pointLightPosition) { 117 | child.material.uniforms.pointLightPosition.value.setFromMatrixPosition(pointLight.matrixWorld); 118 | } 119 | if (child.material.uniforms.pointLightDiffuse) { 120 | const I = pointLight.color.r * pointLight.intensity; 121 | child.material.uniforms.pointLightDiffuse.value = I; 122 | } 123 | }); 124 | } 125 | 126 | 127 | 128 | update (dt) { 129 | // this.tubeSpawnTime += dt; 130 | // if (this.tubeSpawnTime > this.tubeSpawnDelay) { 131 | // this.tubeSpawnTime = 0; 132 | // this.tubeSpawnDelay = randomFloat(this.tubeSpawnMin, this.tubeSpawnMax); 133 | // this.spawn(false); 134 | // } 135 | 136 | // this.cubeSpawnTime += dt; 137 | // if (this.cubeSpawnTime > this.cubeSpawnDelay) { 138 | // this.cubeSpawnTime = 0; 139 | // this.cubeSpawnDelay = randomFloat(this.cubeSpawnMin, this.cubeSpawnMax); 140 | // this.spawn(true); 141 | // } 142 | 143 | this.sideTime += dt; 144 | if (this.sideTime > this.sideDelay) { 145 | this.sideTime = 0; 146 | this.currentSideCount = this.sideCountList[this.currentSideIndex++ % this.sideCountList.length]; 147 | } 148 | this._updateFog(); 149 | 150 | this.spawners.forEach(spawner => { 151 | spawner.app = this.app; 152 | spawner.animation = this.animation; 153 | spawner.audio = this.audio; 154 | spawner.emission = this.emission; 155 | spawner.sides = this.currentSideCount; 156 | spawner.running = this.running; 157 | spawner.moveSpeed = this.moveSpeed; 158 | if (spawner.update) spawner.update(dt); 159 | }); 160 | } 161 | } -------------------------------------------------------------------------------- /lib/components/SimpleSpawner.js: -------------------------------------------------------------------------------- 1 | const HDRMaterial = require('../shaders/HDRMaterial'); 2 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 3 | const newArray = require('new-array'); 4 | const { randomFloat, randomSphere, randomQuaternion, nextSeed } = require('../util/random'); 5 | const clamp = require('clamp'); 6 | const lerp = require('lerp'); 7 | const shuffle = require('array-shuffle'); 8 | const palettes = require('nice-color-palettes'); 9 | const randomSamplesInMesh = require('../util/randomSamplesInMesh'); 10 | const { assets } = require('../context'); 11 | const tweenr = require('tweenr')(); 12 | 13 | module.exports = class SimpleSpawner extends THREE.Object3D { 14 | 15 | constructor () { 16 | super(); 17 | 18 | const cubeBaseMeshes = []; 19 | const tubeMeshBase = this.createTubeBaseMesh(); 20 | const boxGeometry = new THREE.BoxGeometry(1, 1, 1); 21 | const cubeMats = [ 22 | this.emissiveMaterial, 23 | this.emissiveMaterial 24 | ]; 25 | cubeBaseMeshes.push(new THREE.Mesh(boxGeometry, cubeMats)); 26 | 27 | const dotScale = 0.1; 28 | const dotGeom = new THREE.BoxGeometry(dotScale, dotScale, dotScale); 29 | const dotsGeom = new THREE.Geometry(); 30 | dotsGeom.noScale = true; 31 | const dotCount = 6; 32 | for (let i = 0; i < dotCount; i++) { 33 | const t = i / Math.max(1, dotCount); 34 | const tNorm = t * 2 - 1; 35 | const spacing = 1; 36 | dotsGeom.merge(dotGeom, 37 | new THREE.Matrix4().makeTranslation(0, tNorm * spacing, 0) 38 | ); 39 | } 40 | cubeBaseMeshes.push(new THREE.Mesh(dotsGeom, cubeMats)); 41 | 42 | const maxMeshCount = 500; 43 | this.tubeMeshes = newArray(maxMeshCount).map((_, i) => { 44 | const isCubeType = i % 2 === 0; 45 | const mesh = isCubeType 46 | ? cubeBaseMeshes[Math.floor(randomFloat(0, cubeBaseMeshes.length))].clone() 47 | : tubeMeshBase.clone(); 48 | mesh.material = mesh.material.map(m => m.clone()); 49 | mesh.frustumCulled = false; 50 | mesh.isCubeType = isCubeType; 51 | mesh.active = false; 52 | mesh.visible = false; 53 | return mesh; 54 | }); 55 | 56 | this.tubeMeshContainer = new THREE.Object3D(); 57 | this.tubeMeshes.forEach(m => this.tubeMeshContainer.add(m)); 58 | this.add(this.tubeMeshContainer); 59 | } 60 | 61 | createTubeBaseMesh () { 62 | this.reflectiveMaterial = new HDRMaterial({ 63 | color: 'hsl(0, 0%, 100%)', 64 | metalness: 1, 65 | roughness: 1, 66 | // envMap: this.envMap, 67 | shading: THREE.FlatShading 68 | }); 69 | this.emissiveMaterial = new HDRBasicMaterial() 70 | 71 | const radius = 0.075; 72 | const length = 3; 73 | const sides = 3; 74 | const endCapLength = 0.25; 75 | const tubeGeometry = new THREE.CylinderGeometry(radius, radius, length, sides); 76 | const endCap = new THREE.CylinderGeometry(radius, radius, endCapLength, sides); 77 | const mults = [ 1, -1 ]; 78 | const padding = 0.1; 79 | mults.forEach(mult => { 80 | tubeGeometry.merge( 81 | endCap, 82 | new THREE.Matrix4().makeTranslation(0, mult * (length / 2 + endCapLength / 2 + padding), 0), 83 | 1 84 | ); 85 | }); 86 | 87 | tubeGeometry.computeBoundingBox(); 88 | 89 | const tubeMesh = new THREE.Mesh(tubeGeometry, [ 90 | this.emissiveMaterial, 91 | this.reflectiveMaterial 92 | ]); 93 | 94 | return tubeMesh; 95 | } 96 | 97 | getFreeTubeMesh () { 98 | return this.tubeMeshes.find(m => !m.active); 99 | } 100 | 101 | spawn (sides) { 102 | tmpColor.setStyle(palette[Math.floor(randomFloat(0, palette.length))]) 103 | 104 | const speed = randomFloat(0.5, 1); 105 | const radius = isCubeType ? 6 : 4; 106 | for (let side = 0; side < sides; side++) { 107 | const rotOff = (Math.PI / 2) + this.twist; 108 | const angle = Math.PI * 2 * (side / sides) - rotOff; 109 | const x = Math.cos(angle) * radius; 110 | const y = Math.sin(angle) * radius; 111 | const rotationAngle = Math.atan2(y, x); 112 | 113 | const mesh = this.tubeMeshes.find(m => m.isCubeType === isCubeType && !m.active); 114 | if (!mesh) break; 115 | const startZ = -20; 116 | const isEmissive = isCubeType || randomFloat(1) > 0.5; 117 | const nextMaterial = isEmissive ? this.emissiveMaterial : this.reflectiveMaterial; 118 | const matIndex = isCubeType ? 1 : 0; 119 | mesh.materialIndex = matIndex; 120 | mesh.material[matIndex] = nextMaterial.clone(); 121 | mesh.emissiveFactor = isEmissive ? randomFloat(1, 2) : 1; 122 | mesh.material[matIndex].uniforms.emissiveFactor.value = mesh.emissiveFactor; 123 | mesh.active = true; 124 | mesh.speed = speed; 125 | mesh.time = 0; 126 | mesh.isEmissive = isEmissive; 127 | mesh.visible = true; 128 | mesh.position.x = x; 129 | mesh.position.y = y; 130 | mesh.position.z = startZ; 131 | mesh.rotation.z = rotationAngle; 132 | mesh.material[matIndex].color.set(isEmissive ? tmpColor : '#fff'); 133 | if (mesh.geometry.noScale !== false) { 134 | mesh.scale.y = 1e-5; 135 | mesh.scale.x = mesh.scale.z = isCubeType 136 | ? randomFloat(2, 4) 137 | : randomFloat(0.5, 2) 138 | tweenr.to(mesh.scale, { 139 | y: isCubeType ? randomFloat(0.5, 3) : randomFloat(0.5, 2), 140 | duration: 2, 141 | ease: 'linear' 142 | }); 143 | } 144 | } 145 | } 146 | 147 | update (dt) { 148 | 149 | // this.twist += 0.001; 150 | // this.tubeMeshes.forEach(mesh => { 151 | // if (!mesh.active) return; 152 | // mesh.time += dt; 153 | // mesh.position.z += 0.1 * mesh.speed; 154 | // mesh.updateMatrixWorld(); 155 | 156 | // mesh.material.forEach(mat => { 157 | // if (mat.uniforms && mat.uniforms.time) mat.uniforms.time.value = mesh.time; 158 | // }); 159 | 160 | // if (mesh.isEmissive) { 161 | // // const f = Math.sin(mesh.time * 1) * 0.5 + 0.5; 162 | // const f = Math.sin(mesh.time - Math.PI * Math.tan(mesh.time * 1.0) * 0.01) * 0.5 + 0.5; 163 | // const min = 0; 164 | // const max = mesh.emissiveFactor; 165 | // mesh.material[mesh.materialIndex].uniforms.emissiveFactor.value = lerp(min, max, f); 166 | // } 167 | 168 | // // determine if culled 169 | // tmpMeshWorldPosition.setFromMatrixPosition(mesh.matrixWorld); 170 | // if (tmpMeshWorldPosition.z > tmpCameraPosition.z) { 171 | // mesh.active = false; 172 | // mesh.visible = false; 173 | // } 174 | // }); 175 | } 176 | } -------------------------------------------------------------------------------- /lib/shaders/SSAOShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Screen-space ambient occlusion shader 5 | * - ported from 6 | * SSAO GLSL shader v1.2 7 | * assembled by Martins Upitis (martinsh) (http://devlog-martinsh.blogspot.com) 8 | * original technique is made by ArKano22 (http://www.gamedev.net/topic/550699-ssao-no-halo-artifacts/) 9 | * - modifications 10 | * - modified to use RGBA packed depth texture (use clear color 1,1,1,1 for depth pass) 11 | * - refactoring and optimizations 12 | */ 13 | 14 | module.exports = { 15 | 16 | uniforms: { 17 | 18 | "tDiffuse": { type: "t", value: null }, 19 | "tDepth": { type: "t", value: null }, 20 | "resolution": { type: "v2", value: new THREE.Vector2( 512, 512 ) }, 21 | "cameraNear": { type: "f", value: 1 }, 22 | "cameraFar": { type: "f", value: 100 }, 23 | "onlyAO": { type: "i", value: 0 }, 24 | "aoClamp": { type: "f", value: 0.55 }, 25 | "lumInfluence": { type: "f", value: 1 } 26 | 27 | }, 28 | 29 | vertexShader: [ 30 | 31 | "varying vec2 vUv;", 32 | 33 | "void main() {", 34 | 35 | "vUv = uv;", 36 | 37 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 38 | 39 | "}" 40 | 41 | ].join( "\n" ), 42 | 43 | fragmentShader: [ 44 | 45 | "uniform float cameraNear;", 46 | "uniform float cameraFar;", 47 | 48 | "uniform bool onlyAO;", // use only ambient occlusion pass? 49 | 50 | "uniform vec2 resolution;", // texture width, height 51 | "uniform float aoClamp;", // depth clamp - reduces haloing at screen edges 52 | 53 | "uniform float lumInfluence;", // how much luminance affects occlusion 54 | 55 | "uniform sampler2D tDiffuse;", 56 | "uniform highp sampler2D tDepth;", 57 | 58 | "varying vec2 vUv;", 59 | 60 | // "#define PI 3.14159265", 61 | "#define DL 2.399963229728653", // PI * ( 3.0 - sqrt( 5.0 ) ) 62 | "#define EULER 2.718281828459045", 63 | 64 | // user variables 65 | 66 | "const int samples = 4;", // ao sample count 67 | "const float radius = 5.0;", // ao radius 68 | 69 | "const bool useNoise = false;", // use noise instead of pattern for sample dithering 70 | "const float noiseAmount = 0.0003;", // dithering amount 71 | 72 | "const float diffArea = 0.4;", // self-shadowing reduction 73 | "const float gDisplace = 0.4;", // gauss bell center 74 | 75 | 76 | // generating noise / pattern texture for dithering 77 | // "highp float random(vec2 co) {", 78 | // "highp float a = 12.9898;", 79 | // "highp float b = 78.233;", 80 | // "highp float c = 43758.5453;", 81 | // "highp float dt= dot(co.xy ,vec2(a,b));", 82 | // "highp float sn= mod(dt,3.14);", 83 | // "return fract(sin(sn) * c);", 84 | // "}", 85 | 86 | "highp vec2 rand( const vec2 coord ) {", 87 | 88 | "highp vec2 noise;", 89 | 90 | "if ( useNoise ) {", 91 | 92 | "float nx = dot ( coord, vec2( 12.9898, 78.233 ) );", 93 | "float ny = dot ( coord, vec2( 12.9898, 78.233 ) * 2.0 );", 94 | 95 | "noise = clamp( fract ( 43758.5453 * sin( vec2( nx, ny ) ) ), 0.0, 1.0 );", 96 | 97 | "} else {", 98 | 99 | "highp float ff = fract( 1.0 - coord.s * ( resolution.x / 2.0 ) );", 100 | "highp float gg = fract( coord.t * ( resolution.y / 2.0 ) );", 101 | 102 | "noise = vec2( 0.25, 0.75 ) * vec2( ff ) + vec2( 0.75, 0.25 ) * gg;", 103 | 104 | "}", 105 | 106 | "return ( noise * 2.0 - 1.0 ) * noiseAmount;", 107 | 108 | "}", 109 | 110 | "float readDepth( const in vec2 coord ) {", 111 | 112 | "float cameraFarPlusNear = cameraFar + cameraNear;", 113 | "float cameraFarMinusNear = cameraFar - cameraNear;", 114 | "float cameraCoef = 2.0 * cameraNear;", 115 | 116 | "return cameraCoef / ( cameraFarPlusNear - texture2D( tDepth, coord ).x * cameraFarMinusNear );", 117 | 118 | "}", 119 | 120 | "float compareDepths( const in float depth1, const in float depth2, inout int far ) {", 121 | 122 | "float garea = 2.0;", // gauss bell width 123 | "float diff = ( depth1 - depth2 ) * 100.0;", // depth difference (0-100) 124 | 125 | // reduce left bell width to avoid self-shadowing 126 | 127 | "if ( diff < gDisplace ) {", 128 | 129 | "garea = diffArea;", 130 | 131 | "} else {", 132 | 133 | "far = 1;", 134 | 135 | "}", 136 | 137 | "float dd = diff - gDisplace;", 138 | "float gauss = pow( EULER, -2.0 * dd * dd / ( garea * garea ) );", 139 | "return gauss;", 140 | 141 | "}", 142 | 143 | "float calcAO( float depth, float dw, float dh ) {", 144 | 145 | "float dd = radius - depth * radius;", 146 | "vec2 vv = vec2( dw, dh );", 147 | 148 | "vec2 coord1 = vUv + dd * vv;", 149 | "vec2 coord2 = vUv - dd * vv;", 150 | 151 | "float temp1 = 0.0;", 152 | "float temp2 = 0.0;", 153 | 154 | "int far = 0;", 155 | "temp1 = compareDepths( depth, readDepth( coord1 ), far );", 156 | 157 | // DEPTH EXTRAPOLATION 158 | 159 | "if ( far > 0 ) {", 160 | 161 | "temp2 = compareDepths( readDepth( coord2 ), depth, far );", 162 | "temp1 += ( 1.0 - temp1 ) * temp2;", 163 | 164 | "}", 165 | 166 | "return temp1;", 167 | 168 | "}", 169 | 170 | "void main() {", 171 | 172 | "highp vec2 noise = rand( vUv );", 173 | "float depth = readDepth( vUv );", 174 | "float tt = clamp( depth, aoClamp, 1.0 );", 175 | 176 | "float w = ( 1.0 / resolution.x ) / tt + ( noise.x * ( 1.0 - noise.x ) );", 177 | "float h = ( 1.0 / resolution.y ) / tt + ( noise.y * ( 1.0 - noise.y ) );", 178 | 179 | "float ao = 0.0;", 180 | 181 | "float dz = 1.0 / float( samples );", 182 | "float z = 1.0 - dz / 2.0;", 183 | "float l = 0.0;", 184 | 185 | "for ( int i = 0; i <= samples; i ++ ) {", 186 | 187 | "float r = sqrt( 1.0 - z );", 188 | 189 | "float pw = cos( l ) * r;", 190 | "float ph = sin( l ) * r;", 191 | "ao += calcAO( depth, pw * w, ph * h );", 192 | "z = z - dz;", 193 | "l = l + DL;", 194 | 195 | "}", 196 | 197 | "ao /= float( samples );", 198 | "ao = 1.0 - ao;", 199 | 200 | "vec3 color = texture2D( tDiffuse, vUv ).rgb;", 201 | 202 | "vec3 lumcoeff = vec3( 0.299, 0.587, 0.114 );", 203 | "float lum = dot( color.rgb, lumcoeff );", 204 | "vec3 luminance = vec3( lum );", 205 | 206 | "vec3 final = vec3( color * mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );", // mix( color * ao, white, luminance ) 207 | 208 | "if ( onlyAO ) {", 209 | 210 | "final = vec3( mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );", // ambient occlusion only 211 | 212 | "}", 213 | 214 | "gl_FragColor = vec4( final, 1.0 );", 215 | 216 | "}" 217 | 218 | ].join( "\n" ) 219 | 220 | }; 221 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | require('fastclick')(document.body); 3 | 4 | const FoggyScene = require('./lib/components/FoggyScene'); 5 | 6 | const { assets, renderer } = require('./lib/context'); 7 | const css = require('dom-css'); 8 | const query = require('./lib/util/query'); 9 | const isMobile = require('./lib/util/isMobile'); 10 | const createApp = require('./lib/createApp'); 11 | const createAudio = require('./lib/createAudio'); 12 | const createLoop = require('raf-loop'); 13 | const isIOS = require('./lib/util/isIOS'); 14 | const lerp = require('lerp'); 15 | 16 | const tweenr = require('tweenr'); 17 | const tunnelTimeline = tweenr(); 18 | const audioTimeline = tweenr(); 19 | const uiTimeline = tweenr(); 20 | 21 | console.log('Loading...'); 22 | 23 | const canvas = document.querySelector('#canvas'); 24 | const content = document.querySelector('#content'); 25 | content.style.visibility = 'hidden'; 26 | canvas.style.visibility = 'hidden'; 27 | document.body.classList.add('hide-cursor'); 28 | 29 | if (isIOS) { 30 | document.querySelector('.subheader').textContent = 'click and drag to enjoy the visuals'; 31 | } 32 | 33 | renderer.domElement.style.display = 'none'; 34 | assets.loadQueued(err => { 35 | if (err) console.error(err); 36 | console.log('Finished!'); 37 | createAudio((err, audio) => { 38 | if (err) { 39 | console.error(err); 40 | } 41 | start(audio); 42 | }); 43 | }); 44 | 45 | function start (audio) { 46 | renderer.domElement.style.display = ''; 47 | document.querySelector('.info').style.display = ''; 48 | const soundcloudDiv = document.querySelector('.soundcloud-badge') 49 | const app = createApp({ 50 | alpha: true 51 | }); 52 | 53 | // const background = '#f2f2f2'; 54 | const background = 'black'; 55 | document.body.style.background = background; 56 | app.renderer.setClearColor(background, 1); 57 | app.renderer.gammaOutput = false; 58 | 59 | const headerTween = { opacity: 0, y: 40 }; 60 | const subheaderTween = { opacity: 0, y: 40 }; 61 | const overlayTween = { opacity: 1 }; 62 | const components = []; 63 | const tunnel = addComponent(new FoggyScene(app)); 64 | document.body.classList.remove('hide-cursor'); 65 | document.body.classList.add('grab'); 66 | canvas.style.visibility = ''; 67 | content.style.visibility = ''; 68 | animateInContent(); 69 | 70 | const touchDown = (ev) => { 71 | if (typeof ev.button === 'number' && ev.button !== 0) { 72 | return; 73 | } 74 | ev.preventDefault(); 75 | document.body.classList.remove('grab'); 76 | document.body.classList.add('grabbing'); 77 | animateOutContent(); 78 | tunnelTimeline.cancel(); 79 | tunnelTimeline.to(app.getBloom(), { 80 | animation: 1, 81 | ease: 'quintOut', 82 | duration: 0.5 83 | }); 84 | tunnelTimeline.to(tunnel, { 85 | ease: 'expoOut', 86 | animation: 1, 87 | moveSpeed: 0.5, 88 | duration: 0.5 89 | }); 90 | if (audio) { 91 | audioTimeline.cancel().to(audio, { 92 | effect: 1, 93 | duration: 1, 94 | ease: 'quadOut' 95 | }); 96 | } 97 | }; 98 | const touchUp = (ev) => { 99 | if (typeof ev.button === 'number' && ev.button !== 0) { 100 | return; 101 | } 102 | animateInContent(); 103 | ev.preventDefault(); 104 | document.body.classList.remove('grabbing'); 105 | document.body.classList.add('grab'); 106 | tunnelTimeline.cancel(); 107 | tunnelTimeline.to(app.getBloom(), { 108 | animation: 0, 109 | ease: 'quintOut', 110 | duration: 0.5 111 | }); 112 | tunnelTimeline.to(tunnel, { 113 | ease: 'expoOut', 114 | animation: 0, 115 | moveSpeed: 1, 116 | duration: 0.5 117 | }); 118 | if (audio) { 119 | audioTimeline.cancel().to(audio, { 120 | effect: 0, 121 | duration: 1, 122 | ease: 'quadOut' 123 | }); 124 | } 125 | }; 126 | window.addEventListener('mousedown', touchDown); 127 | window.addEventListener('mouseup', touchUp); 128 | window.addEventListener('touchstart', touchDown); 129 | window.addEventListener('touchend', touchUp); 130 | 131 | const skipFrames = query.skipFrames; 132 | let intervalTime = 0; 133 | 134 | // no context menu on mobile... 135 | if (isMobile) canvas.oncontextmenu = () => false; 136 | 137 | if (query.renderOnce) tick(0); 138 | else createLoop(tick).start(); 139 | 140 | function addComponent (c) { 141 | components.push(c); 142 | app.scene.add(c); 143 | return c; 144 | } 145 | 146 | function tick (dt = 0) { 147 | intervalTime += dt; 148 | if (intervalTime > 1000 / 20) { 149 | intervalTime = 0; 150 | } else if (skipFrames) { 151 | return; 152 | } 153 | 154 | dt = Math.min(30, dt); 155 | dt /= 1000; 156 | if (audio && !isIOS) { 157 | audio.update(); 158 | tunnel.audio = audio.signal(); 159 | tunnel.emission = lerp(0.5, 1.0, audio.amplitude()); 160 | } 161 | components.forEach(c => { 162 | if (c.update) c.update(dt); 163 | }); 164 | app.tick(dt); 165 | app.render(); 166 | } 167 | 168 | // ugly code... 169 | function animateInContent () { 170 | const header = document.querySelector('.header'); 171 | const subheader = document.querySelector('.subheader'); 172 | const update = () => { 173 | css(header, { opacity: headerTween.opacity }); 174 | css(subheader, { opacity: subheaderTween.opacity }); 175 | if (soundcloudDiv) { 176 | css(soundcloudDiv, { opacity: subheaderTween.opacity * 1 }); 177 | } 178 | }; 179 | update(); 180 | uiTimeline.cancel(); 181 | uiTimeline.to(overlayTween, { 182 | duration: 1, opacity: 1, 183 | ease: 'sineOut' 184 | }).on('update', update); 185 | uiTimeline.to(headerTween, { 186 | duration: 6, opacity: 1, 187 | delay: 0.5, 188 | ease: 'sineOut' 189 | }).on('update', update); 190 | uiTimeline.to(subheaderTween, { 191 | duration: 4, opacity: 1, 192 | delay: 1, 193 | ease: 'sineOut' 194 | }).on('update', update); 195 | } 196 | 197 | function animateOutContent () { 198 | const header = document.querySelector('.header'); 199 | const subheader = document.querySelector('.subheader'); 200 | const update = () => { 201 | css(header, { opacity: headerTween.opacity }); 202 | css(subheader, { opacity: subheaderTween.opacity }); 203 | if (soundcloudDiv) { 204 | css(soundcloudDiv, { opacity: subheaderTween.opacity * 1 }); 205 | } 206 | }; 207 | update(); 208 | uiTimeline.cancel(); 209 | uiTimeline.to(overlayTween, { 210 | duration: 2, opacity: 0, 211 | delay: 0.25, 212 | ease: 'quadOut' 213 | }).on('update', update); 214 | uiTimeline.to(headerTween, { 215 | duration: 0.5, opacity: 0, 216 | ease: 'quadOut' 217 | }).on('update', update); 218 | uiTimeline.to(subheaderTween, { 219 | duration: 0.5, opacity: 0, 220 | ease: 'quadOut' 221 | }).on('update', update); 222 | } 223 | } 224 | console.log(window.innerWidth, window.innerHeight) -------------------------------------------------------------------------------- /lib/shaders/tube.vert: -------------------------------------------------------------------------------- 1 | // attributes of our mesh 2 | attribute float position; 3 | attribute float angle; 4 | attribute float instanceIndex; 5 | attribute vec4 offsetScale; 6 | attribute vec3 barycentric; 7 | attribute vec3 surfaceNormal; 8 | attribute vec2 uv; 9 | 10 | // built-in uniforms from ThreeJS camera and Object3D 11 | uniform mat4 projectionMatrix; 12 | uniform mat4 modelViewMatrix; 13 | uniform mat3 normalMatrix; 14 | 15 | // custom uniforms to build up our tubes 16 | uniform float thickness; 17 | uniform float time; 18 | uniform float animateRadius; 19 | uniform float animateStrength; 20 | uniform float radialSegments; 21 | 22 | // pass a few things along to the vertex shader 23 | varying float vArclen; 24 | varying vec2 vUv; 25 | varying vec3 vViewPosition; 26 | varying vec3 vNormal; 27 | varying vec3 vBarycentric; 28 | varying float vRandomScale; 29 | 30 | // Import a couple utilities 31 | #pragma glslify: PI = require('glsl-pi'); 32 | #pragma glslify: ease = require('glsl-easings/exponential-in-out'); 33 | #pragma glslify: noise = require('glsl-noise/simplex/4d'); 34 | 35 | // Some constants for the robust version 36 | #ifdef ROBUST 37 | const float MAX_NUMBER = 1.79769313e+308; 38 | const float EPSILON = 1.19209290e-7; 39 | #endif 40 | 41 | void rotateByAxisAngle (inout vec3 normal, vec3 axis, float angle) { 42 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/instanceIndex.htm 43 | // assumes axis is normalized 44 | float halfAngle = angle / 2.0; 45 | float s = sin(halfAngle); 46 | vec4 quat = vec4(axis * s, cos(halfAngle)); 47 | normal = normal + 2.0 * cross(quat.xyz, cross(quat.xyz, normal) + quat.w * normal); 48 | } 49 | 50 | // Angles to spherical coordinates 51 | vec3 spherical (float r, float phi, float theta) { 52 | return r * vec3( 53 | cos(phi) * cos(theta), 54 | cos(phi) * sin(theta), 55 | sin(phi) 56 | ); 57 | } 58 | 59 | // Flying a curve along a sine wave 60 | vec3 sample (float t) { 61 | float len = offsetScale.w; 62 | vec3 normal = surfaceNormal; 63 | float beta = t * PI; 64 | 65 | normal *= noise(vec4(normalize(offsetScale.xyz) * 3.0, time * 0.5)) * 0.5 + 0.5; 66 | rotateByAxisAngle(normal.xyz, normalize(offsetScale.xyz), t * 0.5 * sin(time * 0.5 + instanceIndex * 0.5)); 67 | normal *= 0.15 * offsetScale.w; 68 | 69 | return offsetScale.xyz + normal * t; 70 | } 71 | 72 | // Creates an animated torus knot 73 | // vec3 sample (float t) { 74 | // float beta = t * PI; 75 | 76 | // float ripple = ease(sin(t * 2.0 * PI + time) * 0.5 + 0.5) * 0.5; 77 | // float noise = time + instanceIndex * ripple * 8.0; 78 | 79 | // // animate radius on click 80 | // float radiusAnimation = animateRadius * animateStrength * 0.25; 81 | // float r = sin(instanceIndex * 0.75 + beta * 2.0) * (0.75 + radiusAnimation); 82 | // float theta = 4.0 * beta + instanceIndex * 0.25; 83 | // float phi = sin(instanceIndex * 2.0 + beta * 8.0 + noise); 84 | 85 | // return spherical(r, phi, theta); 86 | // } 87 | 88 | #ifdef ROBUST 89 | // ------ 90 | // Robust handling of Frenet-Serret frames with Parallel Transport 91 | // ------ 92 | vec3 getTangent (vec3 a, vec3 b) { 93 | return normalize(b - a); 94 | } 95 | 96 | void createTube (float t, vec2 volume, out vec3 outPosition, out vec3 outNormal) { 97 | // Reference: 98 | // https://github.com/mrdoob/three.js/blob/b07565918713771e77b8701105f2645b1e5009a7/src/extras/core/Curve.js#L268 99 | float nextT = t + (1.0 / lengthSegments); 100 | 101 | // find first tangent 102 | vec3 point0 = sample(0.0); 103 | vec3 point1 = sample(1.0 / lengthSegments); 104 | 105 | vec3 lastTangent = getTangent(point0, point1); 106 | vec3 absTangent = abs(lastTangent); 107 | #ifdef ROBUST_NORMAL 108 | float min = MAX_NUMBER; 109 | vec3 tmpNormal = vec3(0.0); 110 | if (absTangent.x <= min) { 111 | min = absTangent.x; 112 | tmpNormal.x = 1.0; 113 | } 114 | if (absTangent.y <= min) { 115 | min = absTangent.y; 116 | tmpNormal.y = 1.0; 117 | } 118 | if (absTangent.z <= min) { 119 | tmpNormal.z = 1.0; 120 | } 121 | #else 122 | vec3 tmpNormal = vec3(1.0, 0.0, 0.0); 123 | #endif 124 | vec3 tmpVec = normalize(cross(lastTangent, tmpNormal)); 125 | vec3 lastNormal = cross(lastTangent, tmpVec); 126 | vec3 lastBinormal = cross(lastTangent, lastNormal); 127 | vec3 lastPoint = point0; 128 | 129 | vec3 normal; 130 | vec3 tangent; 131 | vec3 binormal; 132 | vec3 point; 133 | float maxLen = (lengthSegments - 1.0); 134 | float epSq = EPSILON * EPSILON; 135 | for (float i = 1.0; i < lengthSegments; i += 1.0) { 136 | float u = i / maxLen; 137 | // could avoid additional sample here at expense of ternary 138 | // point = i == 1.0 ? point1 : sample(u); 139 | point = sample(u); 140 | tangent = getTangent(lastPoint, point); 141 | normal = lastNormal; 142 | binormal = lastBinormal; 143 | 144 | tmpVec = cross(lastTangent, tangent); 145 | if ((tmpVec.x * tmpVec.x + tmpVec.y * tmpVec.y + tmpVec.z * tmpVec.z) > epSq) { 146 | tmpVec = normalize(tmpVec); 147 | float tangentDot = dot(lastTangent, tangent); 148 | float theta = acos(clamp(tangentDot, -1.0, 1.0)); // clamp for floating pt errors 149 | rotateByAxisAngle(normal, tmpVec, theta); 150 | } 151 | 152 | binormal = cross(tangent, normal); 153 | if (u >= t) break; 154 | 155 | lastPoint = point; 156 | lastTangent = tangent; 157 | lastNormal = normal; 158 | lastBinormal = binormal; 159 | } 160 | 161 | // extrude outward to create a tube 162 | float tubeAngle = angle; 163 | float circX = cos(tubeAngle); 164 | float circY = sin(tubeAngle); 165 | 166 | // compute the TBN matrix 167 | vec3 T = tangent; 168 | vec3 B = binormal; 169 | vec3 N = -normal; 170 | 171 | // extrude the path & create a new normal 172 | outNormal.xyz = normalize(B * circX + N * circY); 173 | outPosition.xyz = point + B * volume.x * circX + N * volume.y * circY; 174 | } 175 | #else 176 | // ------ 177 | // Fast version; computes the local Frenet-Serret frame 178 | // ------ 179 | void createTube (float t, vec2 volume, out vec3 offset, out vec3 normal) { 180 | // find next sample along curve 181 | float nextT = t + (1.0 / lengthSegments); 182 | 183 | // sample the curve in two places 184 | vec3 current = sample(t); 185 | vec3 next = sample(nextT); 186 | 187 | // compute the TBN matrix 188 | vec3 T = normalize(next - current); 189 | vec3 B = normalize(cross(T, next + current)); 190 | vec3 N = -normalize(cross(B, T)); 191 | 192 | // extrude outward to create a tube 193 | float tubeAngle = angle; 194 | float circX = cos(tubeAngle); 195 | float circY = sin(tubeAngle); 196 | 197 | // compute position and normal 198 | normal.xyz = normalize(B * circX + N * circY); 199 | offset.xyz = current + B * volume.x * circX + N * volume.y * circY; 200 | } 201 | #endif 202 | 203 | void main() { 204 | // current position to sample at 205 | // [-0.5 .. 0.5] to [0.0 .. 1.0] 206 | float t = (position * 2.0) * 0.5 + 0.5; 207 | 208 | // build our tube geometry 209 | vec2 volume = vec2(thickness); 210 | 211 | // animate the per-vertex curve thickness 212 | float volumeAngle = t * lengthSegments * 1.75 + instanceIndex * 5.0 + time * 10.5; 213 | float volumeMod = sin(volumeAngle) * 0.5 + 0.5; 214 | volume *= offsetScale.w; 215 | volume += 0.01 * volumeMod; 216 | // volume = mix(volume * 0.0, volume, t); 217 | 218 | // build our geometry 219 | vec3 transformed; 220 | vec3 objectNormal; 221 | createTube(t, volume, transformed, objectNormal); 222 | 223 | // pass the normal and UV along 224 | vec3 transformedNormal = normalMatrix * objectNormal; 225 | vNormal = normalize(transformedNormal); 226 | vUv = uv.yx; // swizzle this to match expectations 227 | vArclen = t; 228 | vRandomScale = offsetScale.w; 229 | vBarycentric = barycentric; 230 | 231 | // project our vertex position 232 | vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0); 233 | vViewPosition = -mvPosition.xyz; 234 | gl_Position = projectionMatrix * mvPosition; 235 | } 236 | -------------------------------------------------------------------------------- /lib/createApp.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is a generic "ThreeJS Application" 3 | helper which sets up a renderer and camera 4 | controls. 5 | */ 6 | 7 | const createControls = require('orbit-controls'); 8 | const assign = require('object-assign'); 9 | const lerp = require('lerp'); 10 | const defined = require('defined'); 11 | const mouseTimeline = require('tweenr')(); 12 | const angleOffsetTimeline = require('tweenr')(); 13 | const isMobile = require('./util/isMobile'); 14 | const touches = require('touches'); 15 | const createPostShader = require('./shaders/createPostShader'); 16 | const glslify = require('glslify'); 17 | const path = require('path'); 18 | 19 | const EffectComposer = require('./post/EffectComposer'); 20 | const BloomTexturePass = require('./post/BloomTexturePass'); 21 | const RenderPass = require('./post/RenderPass'); 22 | const SSAO = require('./shaders/SSAOShader'); 23 | const FXAA = require('./shaders/fxaa'); 24 | const query = require('./util/query'); 25 | const isIOS = require('./util/isIOS'); 26 | const isHighQuality = Boolean(query.highQuality); 27 | 28 | const { renderer, floatBufferType } = require('./context'); 29 | 30 | module.exports = createApp; 31 | function createApp (opt = {}) { 32 | // Scale for retina 33 | const defaultDPR = isHighQuality 34 | ? 2 35 | : 1.5; 36 | const dpr = defined(query.dpr, Math.min(defaultDPR, window.devicePixelRatio)); 37 | const needsDepth = isHighQuality; 38 | 39 | let time = 0; 40 | const cameraDistance = { value: 0, start: 8, end: 5 }; 41 | // zoomTimeline.to(cameraDistance, { 42 | // value: 1, 43 | // delay: 0.5, 44 | // duration: 7, 45 | // ease: 'quartOut' 46 | // }); 47 | 48 | const theta = 0 * Math.PI / 180; 49 | const angleOffsetMax = 15; 50 | const angleOffsetTween = { value: 0 }; 51 | const mouseOffset = new THREE.Vector2(); 52 | const tmpQuat1 = new THREE.Quaternion(); 53 | const tmpQuat2 = new THREE.Quaternion(); 54 | const AXIS_X = new THREE.Vector3(1, 0, 0); 55 | const AXIS_Y = new THREE.Vector3(0, 1, 0); 56 | 57 | renderer.setPixelRatio(dpr); 58 | renderer.gammaFactor = 2.2; 59 | renderer.gammaOutput = false; 60 | renderer.gammaInput = false; 61 | renderer.sortObjects = false; 62 | 63 | // Add the to DOM body 64 | const canvas = renderer.domElement; 65 | 66 | // perspective camera 67 | const near = 1; 68 | const far = 50; 69 | const fieldOfView = 65; 70 | const camera = new THREE.PerspectiveCamera(fieldOfView, 1, near, far); 71 | const target = new THREE.Vector3(); 72 | 73 | // 3D scene 74 | const scene = new THREE.Scene(); 75 | 76 | // post processing 77 | let bloom, fxaa; 78 | const postPasses = []; 79 | const hdrTarget = createTarget(floatBufferType); 80 | const ldrTarget = createTarget(false); 81 | const renderTargets = [ hdrTarget, ldrTarget ]; 82 | 83 | // slick 3D orbit controller with damping 84 | const useOrbitControls = false;//query.orbitControls; 85 | let controls; 86 | if (useOrbitControls) { 87 | controls = createControls(assign({ 88 | canvas, 89 | theta, 90 | // phiBounds: [ 0, Math.PI / 2 ], 91 | // thetaBounds: [ -Math.PI / 2, Math.PI / 2 ], 92 | // target: [ 0, 1, 0 ], 93 | distanceBounds: [ 0.5, 20 ], 94 | distance: cameraDistance.start 95 | }, opt)); 96 | } 97 | 98 | // Update renderer size 99 | window.addEventListener('resize', resize); 100 | 101 | const app = assign({}, { 102 | tick, 103 | camera, 104 | scene, 105 | renderer, 106 | canvas, 107 | render, 108 | getBloom: () => bloom 109 | }); 110 | 111 | app.width = 0; 112 | app.height = 0; 113 | app.top = 0; 114 | app.left = 0; 115 | 116 | // Setup initial size & aspect ratio 117 | setupPost(); 118 | resize(); 119 | tick(); 120 | createMouseParallax(); 121 | return app; 122 | 123 | function setupPost () { 124 | bloom = new BloomTexturePass(scene, camera, { 125 | gammaOutput: renderer.gammaFactor 126 | }); 127 | postPasses.push(bloom); 128 | 129 | if (!isMobile && query.fxaa !== false) { 130 | fxaa = new EffectComposer.ShaderPass(FXAA()); 131 | postPasses.push(fxaa); 132 | } 133 | } 134 | 135 | function tick (dt = 0) { 136 | const aspect = app.width / app.height; 137 | 138 | if (useOrbitControls) { 139 | // update camera controls 140 | controls.update(); 141 | camera.position.fromArray(controls.position); 142 | camera.up.fromArray(controls.up); 143 | camera.lookAt(target.fromArray(controls.target)); 144 | } else { 145 | const phi = Math.PI / 2; 146 | camera.position.x = Math.sin(phi) * Math.sin(theta); 147 | camera.position.y = Math.cos(phi); 148 | camera.position.z = Math.sin(phi) * Math.cos(theta); 149 | 150 | const radius = lerp(cameraDistance.start, cameraDistance.end, cameraDistance.value); 151 | const radianOffset = angleOffsetTween.value * Math.PI / 180; 152 | const xOff = mouseOffset.y * radianOffset; 153 | const yOff = mouseOffset.x * radianOffset; 154 | tmpQuat1.setFromAxisAngle(AXIS_X, -xOff); 155 | tmpQuat2.setFromAxisAngle(AXIS_Y, -yOff); 156 | tmpQuat1.multiply(tmpQuat2); 157 | camera.position.applyQuaternion(tmpQuat1); 158 | camera.position.multiplyScalar(radius); 159 | 160 | target.set(0, 0, 0); 161 | camera.lookAt(target); 162 | } 163 | 164 | // Update camera matrices 165 | camera.aspect = aspect; 166 | camera.updateProjectionMatrix(); 167 | 168 | postPasses.forEach(pass => { 169 | if (typeof pass.tick === 'function') pass.tick(dt); 170 | }); 171 | } 172 | 173 | function render () { 174 | if (postPasses.length > 0) { 175 | // render scene into HDR buffer 176 | renderer.render(scene, camera, hdrTarget, true); 177 | // if we have FXAA, render to it, otherwise to screen 178 | const nextBuffer = fxaa ? ldrTarget : undefined; 179 | // if there's no next buffer, render bloom to screen 180 | bloom.renderToScreen = !nextBuffer; 181 | // apply bloom 182 | bloom.render(renderer, nextBuffer, hdrTarget); 183 | // apply FXAA 184 | if (fxaa) { 185 | fxaa.renderToScreen = true; 186 | fxaa.render(renderer, undefined, ldrTarget); 187 | } 188 | } else { 189 | renderer.render(scene, camera); 190 | } 191 | } 192 | 193 | function resize () { 194 | let width = defined(query.width, window.innerWidth); 195 | let height = defined(query.height, window.innerHeight); 196 | if (isIOS) height += 1; 197 | 198 | app.width = width; 199 | app.height = height; 200 | renderer.setSize(width, height); 201 | 202 | const rtWidth = Math.floor(width * dpr); 203 | const rtHeight = Math.floor(height * dpr); 204 | postPasses.forEach(pass => { 205 | if (pass.uniforms && pass.uniforms.resolution) { 206 | pass.uniforms.resolution.value.set(rtWidth, rtHeight); 207 | } 208 | }); 209 | 210 | renderTargets.forEach(t => { 211 | t.setSize(rtWidth, rtHeight); 212 | }); 213 | 214 | tick(0); 215 | render(); 216 | } 217 | 218 | function createMouseParallax () { 219 | let isDown = false; 220 | touches(window, { filtered: true }) 221 | .on('start', (ev) => { 222 | if (typeof ev.button === 'number' && ev.button !== 0) return; 223 | ev.preventDefault(); 224 | isDown = true; 225 | angleOffsetTimeline.cancel().to(angleOffsetTween, { 226 | value: angleOffsetMax, 227 | duration: 2, 228 | ease: 'quadOut' 229 | }); 230 | }) 231 | .on('end', (ev) => { 232 | if (typeof ev.button === 'number' && ev.button !== 0) return; 233 | ev.preventDefault(); 234 | isDown = false; 235 | angleOffsetTimeline.cancel().to(angleOffsetTween, { 236 | value: 0, 237 | duration: 2, 238 | ease: 'quadOut' 239 | }); 240 | }) 241 | .on('move', (ev, tmp) => { 242 | if (typeof ev.button === 'number' && ev.button !== 0 || !isDown) return; 243 | mouseTimeline.cancel().to(mouseOffset, { 244 | x: (tmp[0] / app.width * 2 - 1), 245 | y: (tmp[1] / app.height * 2 - 1), 246 | ease: 'expoOut', 247 | duration: 2 248 | }); 249 | }); 250 | } 251 | 252 | function createTarget (hdrType) { 253 | const rt = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight); 254 | rt.texture.minFilter = THREE.NearestFilter; 255 | rt.texture.magFilter = THREE.NearestFilter; 256 | rt.texture.generateMipmaps = false; 257 | rt.texture.format = hdrType ? THREE.RGBFormat : THREE.RGBAFormat; 258 | rt.texture.type = hdrType ? THREE.HalfFloatType : THREE.UnsignedByteType; 259 | return rt; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /lib/shaders/bloom/combine.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | #define LUT_FLIP_Y 4 | #define LENS_DISTORT 5 | // #define FILM_GRAIN 6 | #define VIGNETTE 7 | #define DUST_OVERLAY 8 | // #define USE_LUT 9 | // #define ALLOW_GRAYSCALE 10 | 11 | varying vec2 vUv; 12 | uniform highp sampler2D tDiffuse; 13 | uniform highp sampler2D tBloomDiffuse; 14 | uniform vec2 resolution; 15 | uniform vec3 color1; 16 | uniform vec3 color2; 17 | uniform float cameraFar; 18 | uniform float cameraNear; 19 | uniform vec2 dustMapResolution; 20 | uniform sampler2D dustMap; 21 | uniform sampler2D lookupMap; 22 | uniform float time; 23 | uniform float steps; 24 | uniform float animation; 25 | 26 | #ifdef INCLUDE_SSAO 27 | uniform highp sampler2D tSSAO; 28 | #endif 29 | 30 | #ifdef LENS_DISTORT 31 | uniform float lensDistort; 32 | uniform float lensDistortK; 33 | uniform float lensDistortCubicK; 34 | uniform float lensDistortScale; 35 | #endif 36 | 37 | #ifdef ALLOW_GRAYSCALE 38 | uniform bool grayscale; 39 | #endif 40 | 41 | #ifdef FILM_GRAIN 42 | uniform float grainStrength; 43 | #endif 44 | 45 | #ifdef VIGNETTE 46 | uniform float vignetteMin; 47 | uniform float vignetteMax; 48 | uniform float vignetteStrength; 49 | uniform float vignetteScale; 50 | #pragma glslify: vignette = require('./vignette'); 51 | 52 | #endif 53 | 54 | uniform float bloomMultiply; 55 | uniform float bloomOpacity; 56 | 57 | #pragma glslify: random = require('glsl-random'); 58 | #pragma glslify: luma = require('glsl-luma'); 59 | #pragma glslify: PI = require('glsl-pi'); 60 | #pragma glslify: lut = require('glsl-lut'); 61 | #pragma glslify: screen = require('glsl-blend/screen'); 62 | #pragma glslify: backgroundUV = require('../glsl-background'); 63 | #pragma glslify: rgbmToLinear = require('../rgbm-to-linear'); 64 | #pragma glslify: decodeHDR = require('../decode-hdr'); 65 | #pragma glslify: decodeFloat = require('../decode-float'); 66 | 67 | #pragma glslify: blendScreen = require(glsl-blend/screen) 68 | 69 | 70 | #define saturate(a) clamp( a, 0.0, 1.0 ) 71 | #define toneMappingExposure 1.0 72 | #define toneMappingWhitePoint 1.0 73 | 74 | // exposure only 75 | vec3 LinearToneMapping( vec3 color ) { 76 | 77 | return toneMappingExposure * color; 78 | 79 | } 80 | 81 | // source: https://www.cs.utah.edu/~reinhard/cdrom/ 82 | vec3 ReinhardToneMapping( vec3 color ) { 83 | 84 | color *= toneMappingExposure; 85 | return saturate( color / ( vec3( 1.0 ) + color ) ); 86 | 87 | } 88 | 89 | // source: http://filmicgames.com/archives/75 90 | #define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) ) 91 | vec3 Uncharted2ToneMapping( vec3 color ) { 92 | 93 | // John Hable's filmic operator from Uncharted 2 video game 94 | color *= toneMappingExposure; 95 | return saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) ); 96 | 97 | } 98 | 99 | // source: http://filmicgames.com/archives/75 100 | vec3 OptimizedCineonToneMapping( vec3 color ) { 101 | 102 | // optimized filmic operator by Jim Hejl and Richard Burgess-Dawson 103 | color *= toneMappingExposure; 104 | color = max( vec3( 0.0 ), color - 0.004 ); 105 | return pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) ); 106 | 107 | } 108 | 109 | float smootherstep(float edge0, float edge1, float x) { 110 | float t = (x - edge0)/(edge1 - edge0); 111 | float t1 = t*t*t*(t*(t*6. - 15.) + 10.); 112 | return clamp(t1, 0.0, 1.0); 113 | } 114 | 115 | vec4 sample (sampler2D map, vec2 uv) { 116 | #ifndef FLOAT_BUFFER 117 | float f = decodeHDR(texture2D(map, uv)); 118 | return vec4(f, f, f, 1.0); 119 | #else 120 | return texture2D(map, uv).rrra; 121 | #endif 122 | } 123 | 124 | vec3 applyLensDistort (sampler2D map, vec2 uv, float distort, float k, float kCube, float scale) { 125 | vec3 eta = vec3(1.0 + distort * 0.9, 1.0 + distort * 0.6, 1.0 + distort * 0.3); 126 | 127 | //texture coordinates 128 | vec2 delta = uv - 0.5; 129 | float r2 = delta.x * delta.x + delta.y * delta.y; 130 | float f = 0.0; 131 | 132 | //only compute the cubic distortion if necessary 133 | if( kCube == 0.0) 134 | { 135 | f = 1.0 + r2 * k; 136 | }else { 137 | f = 1.0 + r2 * (k + kCube * sqrt(r2)); 138 | } 139 | 140 | // get the right pixel for the current position 141 | vec2 rCoords = (f * eta.r) * scale * (delta) + 0.5; 142 | vec2 gCoords = (f * eta.g) * scale * (delta) + 0.5; 143 | vec2 bCoords = (f * eta.b) * scale * (delta) + 0.5; 144 | 145 | vec3 inputDistort = vec3(0.0); 146 | inputDistort.r = sample(map, rCoords).r; 147 | inputDistort.g = sample(map, gCoords).g; 148 | inputDistort.b = sample(map, bCoords).b; 149 | return inputDistort; 150 | } 151 | 152 | vec2 kaleidoscope (vec2 uv, float n) { 153 | float aspect = resolution.x / resolution.y; 154 | float skew = 2.0; 155 | float angleStep = (PI * 2.0) / n; 156 | 157 | vec2 cUv = uv; 158 | cUv -= 0.5; 159 | cUv.x *= aspect; 160 | cUv.x *= skew * 0.5; 161 | 162 | float angle = atan(cUv.y, cUv.x); 163 | // angle = abs(mod(angle, angleStep * 2.0) - angleStep); 164 | angle += -PI / 2.0 + time * -0.025; 165 | angle = mod(angle, angleStep) / angleStep; 166 | angle = 1.0 - abs(angle * 2.0 - 1.0) * 0.5 + 0.5; 167 | angle = mix(angle, angle + 0.2, animation); 168 | angle *= angleStep; 169 | 170 | float radius = length(cUv); 171 | 172 | uv = vec2(cos(angle), sin(angle)) * radius; 173 | uv.x /= aspect; 174 | uv.x *= skew * 0.95; 175 | #ifdef IS_PORTRAIT 176 | uv *= mix(1.0, 1.5, animation); 177 | #else 178 | uv *= mix(1.25, 1.5, animation); 179 | #endif 180 | uv = uv * 0.5 + 0.5; 181 | return uv; 182 | } 183 | 184 | vec2 kaleidoscope2 (vec2 uv, float n) { 185 | float angleStep = PI / 3.0; 186 | vec2 targetResolution = vec2(911.0, 502.0); 187 | // uv = backgroundUV(uv, resolution, targetResolution); 188 | 189 | vec2 cUv = uv; 190 | float aspect = resolution.x / resolution.y; 191 | cUv -= 0.5; 192 | cUv.x *= aspect; 193 | 194 | float angle = atan(cUv.y, cUv.x); 195 | angle += time * 0.0; 196 | angle = abs(mod(angle, angleStep * 2.0) - angleStep); 197 | // angle += PI / 2.0; 198 | 199 | // angle -= time * 0.2; 200 | 201 | float radius = length(cUv); 202 | // uv.x = (radius * cos(angle)) + 0.5; 203 | // uv.y = (radius * sin(angle)) + 0.5; 204 | uv = vec2(cos(angle), sin(angle)) * radius; 205 | uv.x /= aspect; 206 | uv *= 2.0; 207 | uv = uv * 0.5 + 0.5; 208 | // float len = 0.5; 209 | // uv.x = len - abs(uv.x - len); 210 | // uv.y = 1.0 - (len - abs(uv.y - len)); 211 | return uv; 212 | } 213 | 214 | vec2 kaleidoscope3 (vec2 uv, float n) { 215 | vec2 targetResolution = vec2(911.0, 502.0); 216 | float aspect = resolution.x / resolution.y; 217 | float targetAspect = targetResolution.x / targetResolution.y; 218 | uv = uv * 2.0 - 1.0; 219 | 220 | float kScale = 1.0; 221 | 222 | // uv.x *= resolution.x / resolution.y; 223 | float repeatAngle = PI / floor(n); 224 | 225 | uv.x *= aspect; 226 | uv.x *= targetAspect * 0.5; 227 | float r = length(uv); 228 | float originalAngle = atan(uv.y, uv.x); 229 | float a = originalAngle / repeatAngle; 230 | a += time * -0.025 + PI / 2.0; 231 | a += PI / 2.0; 232 | a = mix(fract(a), 1.0 - fract(a), mod(floor(a), 2.0)) * repeatAngle; 233 | vec2 newUV = (vec2(cos(a), sin(a)) * r); 234 | newUV.x /= aspect; 235 | newUV.x *= targetAspect * 1.0; 236 | // #ifdef IS_PORTRAIT 237 | // newUV *= 1.0; 238 | // #else 239 | newUV *= kScale; 240 | // #endif 241 | newUV = newUV * 0.5 + 0.5; 242 | return newUV; 243 | } 244 | 245 | void main () { 246 | 247 | #ifdef VIGNETTE 248 | float v = vignette(vUv, resolution, vignetteMin, vignetteMax, vignetteScale); 249 | #endif 250 | 251 | vec2 texCoord = vUv; 252 | // texCoord = backgroundUV(texCoord, resolution, vec2(911.0, 502.0)); 253 | // float anim = sin(time) * 0.5 + 0.5; 254 | vec2 modUV = kaleidoscope(texCoord, 3.0); 255 | // vec2 modUV = kaleidoscope2(texCoord * 2.0 - 1.0, 3.0); 256 | 257 | // texCoord = modUV; 258 | texCoord = modUV; 259 | // texCoord = mix(modUV, texCoord, v); 260 | 261 | #ifdef LENS_DISTORT 262 | vec3 distortRGB = applyLensDistort(tDiffuse, texCoord, lensDistort, lensDistortK, lensDistortCubicK, lensDistortScale); 263 | vec4 background = vec4(distortRGB, 1.0); 264 | #else 265 | vec4 background = (sample(tDiffuse, texCoord)); 266 | #endif 267 | 268 | #ifdef INCLUDE_SSAO 269 | float texDepth = texture2D(tSSAO, texCoord).r; 270 | background.rgb *= texDepth; 271 | #endif 272 | 273 | vec3 foreground = sample(tBloomDiffuse, texCoord).rgb * bloomMultiply; 274 | vec2 cUv = vUv - 0.5; 275 | cUv.x *= resolution.x / resolution.y; 276 | 277 | // foreground.rgb += deband * 150.0 * smoothstep(0.0, 0.1, L); 278 | // foreground.rgb += deband * 40.0; 279 | 280 | // gl_FragColor = foreground; 281 | gl_FragColor.rgb = background.rgb + foreground.rgb * bloomOpacity; 282 | // gl_FragColor.rgb = mix(gl_FragColor.rgb, background.rgb * 0.5 + foreground.rgb, animation); 283 | 284 | // gl_FragColor.rgb = blendScreen(background.rgb, foreground.rgb); 285 | gl_FragColor.a = 1.0; 286 | 287 | #ifdef VIGNETTE 288 | gl_FragColor.rgb = mix(gl_FragColor.rgb, foreground.rgb, v); 289 | #endif 290 | 291 | 292 | float dist = length(cUv); 293 | vec3 overlay = vec3(smoothstep(1.5, 0.0, dist)); 294 | // gl_FragColor.rgb += overlay * 0.01; 295 | float deband = random(gl_FragCoord.xy) / 255.0; 296 | float L = luma(gl_FragColor.rgb); 297 | // gl_FragColor.rgb += deband * 30.0; 298 | 299 | // L = luma(gl_FragColor.rgb); 300 | // float whiteSpot = smoothstep(1.0, 4.0, L); 301 | // gl_FragColor = clamp(gl_FragColor, 0.0, 1.0); 302 | // gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(1.0), whiteSpot); 303 | // gl_FragColor = background; 304 | 305 | 306 | gl_FragColor.rgb = mix(vec3(color1), vec3(color2), L); 307 | L = luma(gl_FragColor.rgb); 308 | gl_FragColor.rgb += deband * 40.0 * smoothstep(0.0, 0.5, L); 309 | 310 | #if defined(DUST_OVERLAY) && !defined(IS_MOBILE) 311 | vec2 bgUV = backgroundUV(vUv, resolution, dustMapResolution); 312 | vec4 dustOverlay = texture2D(dustMap, bgUV); 313 | // vec3 colorWithDust = gl_FragColor.rgb + dustOverlay.r; 314 | vec3 colorWithDust = screen(gl_FragColor.rgb, vec3(dustOverlay.r)); 315 | float dustFactor = smoothstep(0.0, 0.3, luma(foreground.rgb)); 316 | #ifdef VIGNETTE 317 | dustFactor = mix(dustFactor * 0.5, dustFactor, v); 318 | #endif 319 | gl_FragColor.rgb = mix(gl_FragColor.rgb, colorWithDust, dustFactor * 1.0); 320 | #endif 321 | 322 | #if defined(USE_LUT) && !defined(IS_MOBILE) 323 | gl_FragColor.rgb = mix(gl_FragColor.rgb, lut(gl_FragColor, lookupMap).rgb, 0.5); 324 | // gl_FragColor.rgb = mix(gl_FragColor.rgb, effected, animation); 325 | #endif 326 | #ifdef FLOAT_BUFFER 327 | gl_FragColor.rgb = min(gl_FragColor.rgb, 1.0); 328 | #endif 329 | } -------------------------------------------------------------------------------- /lib/components/createTest.js: -------------------------------------------------------------------------------- 1 | const HDRMaterial = require('../shaders/HDRMaterial'); 2 | const HDRBasicMaterial = require('../shaders/HDRBasicMaterial'); 3 | const newArray = require('new-array'); 4 | const { randomFloat, randomSphere } = require('../util/random'); 5 | const clamp = require('clamp'); 6 | const shuffle = require('array-shuffle'); 7 | const randomSamplesInMesh = require('../util/randomSamplesInMesh'); 8 | 9 | const palettes = shuffle(require('nice-color-palettes')); 10 | const palette = [ '#fff' ] 11 | // const palette = shuffle(palettes[0]).slice(0, 3); 12 | const tweenr = require('tweenr')(); 13 | const tmpVec = new THREE.Vector3(); 14 | 15 | module.exports = function (app, opt = {}) { 16 | const container = new THREE.Object3D(); 17 | 18 | 19 | const light = new THREE.PointLight('#fff', 1, 7); 20 | light.position.set(1, 3, 1); 21 | container.add(light); 22 | 23 | // const light = new THREE.RectAreaLight('#fff', 5, 1, 0.5); 24 | // light.position.set(1, 4, 1.5); 25 | // light.lookAt(new THREE.Vector3()); 26 | // container.add(light); 27 | 28 | // const helper = new THREE.RectAreaLightHelper(light); 29 | // helper.update(); 30 | // helper.children.forEach(child => { 31 | // child.material = new HDRBasicMaterial({ 32 | // lights: false, 33 | // color: child.material.color, 34 | // wireframe: child.material.wireframe, 35 | // side: child.material.side, 36 | // fog: child.material.fog 37 | // }); 38 | // }); 39 | // light.add(helper); 40 | 41 | const ambient = new THREE.AmbientLight('hsl(0, 0%, 10%)'); 42 | container.add(ambient); 43 | 44 | const icosphere = new THREE.IcosahedronGeometry(1, 0); 45 | 46 | const particleSize = 0.05; 47 | const particleGeometry = new THREE.CircleGeometry(particleSize, 4); 48 | const particleMaterial = new HDRBasicMaterial({ 49 | // wireframe: true, 50 | color: new THREE.Color('white').multiplyScalar(10), 51 | }); 52 | let count = 1000; 53 | let samples = newArray(count); 54 | let highSamples = []; 55 | if (opt.textGeometry) { 56 | const textMesh = new THREE.Mesh(opt.textGeometry, new HDRMaterial({ 57 | // wireframe: true, 58 | color: new THREE.Color('white').multiplyScalar(0.65) 59 | })) 60 | samples = randomSamplesInMesh(textMesh, count); 61 | highSamples = randomSamplesInMesh(textMesh, 1000); 62 | container.add(textMesh); 63 | if (opt.wireGeometry) textMesh.geometry = opt.wireGeometry; 64 | } 65 | 66 | const particles = samples.map((sample, i) => { 67 | const t = i / (count - 1); 68 | const velocity = new THREE.Vector3(); 69 | // const emitVelocity = randomFloat(1, 5); 70 | // velocity.set(0, i%2 === 0 ? 1 : -1, 0) 71 | velocity.fromArray(randomSphere([], 1)); 72 | // velocity.multiplyScalar(10); 73 | 74 | const position = new THREE.Vector3(); 75 | const angle = t * 2 * Math.PI; 76 | // tmpVec.x = Math.sin(angle) * r; 77 | 78 | tmpVec.fromArray(randomSphere([], randomFloat(0, 4))); 79 | tmpVec.x = (t * 2 - 1) * 2; 80 | tmpVec.z *= 1.5; 81 | 82 | if (sample) { 83 | const norm = sample.normal.clone().multiplyScalar(randomFloat(0.5, 2)); 84 | tmpVec.copy(sample.position).add(norm); 85 | 86 | // velocity.copy(tmpVec).sub(sample.position).normalize().negate(); 87 | // velocity.copy(sample.normal).negate(); 88 | } 89 | // const oval = 1.5; 90 | // tmpVec.x *= oval; 91 | // tmpVec.y *= oval; 92 | // tmpVec.z *= 2; 93 | position.add(tmpVec); 94 | 95 | // position.y += 1; 96 | // position.copy(icosphere.vertices[Math.floor(randomFloat(0, icosphere.vertices.length))]); 97 | 98 | const friction = 0.98; 99 | const speed = randomFloat(0.01, 0.25); 100 | velocity.multiplyScalar(speed); 101 | 102 | const mesh = new THREE.Mesh(particleGeometry, particleMaterial); 103 | // container.add(mesh); 104 | 105 | const maxVelocity = 0.015; 106 | const particleAngle = null;// randomFloat(1) > 0.5 ? 135 : 135; 107 | return { 108 | textThreshold: 0.01, 109 | attractionThreshold: randomFloat(1, 5), 110 | // attractionIncrease: 0.000001, 111 | attraction: -3.5, //randomFloat(-0.01, -0.01), 112 | target: new THREE.Vector3().fromArray(randomSphere([], randomFloat(0, 4))), 113 | mesh, 114 | thickness: randomFloat(0.01, 0.05), 115 | angle: particleAngle, 116 | maxVelocity, 117 | speed, 118 | points: [], 119 | position, 120 | friction, 121 | velocity 122 | }; 123 | }); 124 | 125 | // new THREE.JSONLoader().load('assets/ground.json', geometry => { 126 | // const material = new HDRMaterial({ 127 | // shading: THREE.SmoothShading, 128 | // // emissive: new THREE.Color('white').multiplyScalar(1), 129 | // roughness: 1, 130 | // metalness: 0.9 131 | // }) 132 | // const ground = new THREE.Mesh(geometry, material); 133 | // ground.rotation.y = -Math.PI / 4; 134 | // container.add(ground); 135 | // }) 136 | 137 | // create(); 138 | 139 | const stemCount = 50; 140 | for (let i = 0; i < stemCount; i++) { 141 | tick(); 142 | } 143 | 144 | const reflectionProbe = new THREE.CubeCamera(0.01, 100, 1024); 145 | reflectionProbe.position.set(0, 0, 0); 146 | reflectionProbe.renderTarget.texture.mapping = THREE.CubeRefractionMapping; 147 | container.add(reflectionProbe); 148 | 149 | const baseMaterial = new HDRMaterial({ 150 | shading: THREE.FlatShading, 151 | side: THREE.DoubleSide, 152 | metalness: 0.75, 153 | roughness: 1, 154 | // color: isEmissive ? new THREE.Color('red').multiplyScalar(10) : 'white' 155 | }); 156 | const reflMaterial = baseMaterial.clone(); 157 | reflMaterial.roughness = 0; 158 | reflMaterial.metalness = 1; 159 | reflMaterial.envMap = reflectionProbe.renderTarget.texture; 160 | 161 | let updateCubeMap = true; 162 | const paths = particles.map((p, i) => { 163 | const spline = new THREE.CatmullRomCurve3(p.points); 164 | const pointCount = Math.floor(p.points.length / 2); 165 | const baseGeometry = new THREE.TubeGeometry(spline, pointCount, p.thickness, 3, spline.closed); 166 | const geometry = new THREE.BufferGeometry().fromGeometry(baseGeometry); 167 | baseGeometry.dispose(); 168 | 169 | const isEmissive = randomFloat(1) > 0.75; 170 | const material = baseMaterial.clone(); 171 | const color = palette[Math.floor(randomFloat(0, palette.length))]; 172 | // material.wireframe = randomFloat(1) > 0.95; 173 | material.emissive = isEmissive ? new THREE.Color(color).multiplyScalar(1) : undefined; 174 | 175 | const drawRangeMax = geometry.getAttribute('position').count; 176 | const mesh = new THREE.Mesh(geometry, material); 177 | mesh.drawRangeMax = drawRangeMax; 178 | mesh.tween = { value: 0 }; 179 | return mesh; 180 | }); 181 | paths.forEach(p => container.add(p)); 182 | 183 | paths.forEach((p, i) => { 184 | p.geometry.drawRange = { start: 0, count: 0 }; 185 | tweenr.to(p.tween, { 186 | duration: randomFloat(1, 4), 187 | delay: 0.5 + randomFloat(0, 4), 188 | // delay: 0.5 + i * 0.01, 189 | ease: 'expoOut', 190 | value: 1 191 | }); 192 | }); 193 | 194 | return { 195 | update (dt = 0) { 196 | // if (updateCubeMap) { 197 | // updateCubeMap = false; 198 | // paths.forEach(p => { 199 | // p.geometry.drawRange.count = p.drawRangeMax; 200 | // }); 201 | // reflectionProbe.updateCubeMap(app.renderer, app.scene); 202 | // paths.forEach(p => { 203 | // const emissive = p.material.emissive; 204 | // p.material = reflMaterial.clone(); 205 | // // p.material.emissive = emissive; 206 | // }); 207 | // return; 208 | // } 209 | paths.forEach(p => { 210 | p.geometry.drawRange.count = Math.round(p.drawRangeMax * p.tween.value); 211 | }); 212 | }, 213 | object3d: container 214 | }; 215 | 216 | function create () { 217 | const emitter = new THREE.Mesh(icosphere, new HDRMaterial({ 218 | emissive: new THREE.Color('white').multiplyScalar(10), 219 | // color: new THREE.Color('white').multiplyScalar(2), 220 | shading: THREE.FlatShading, 221 | roughness: 1, 222 | metalness: 0 223 | })); 224 | container.add(emitter); 225 | } 226 | 227 | function tick () { 228 | particles.forEach(p => { 229 | let thresholdSq = p.attractionThreshold * p.attractionThreshold; 230 | let textThresholdSq = p.textThreshold * p.textThreshold; 231 | 232 | if (opt.textGeometry) { 233 | let highSample; 234 | for (let i = 0; i < highSamples.length; i++) { 235 | const o = highSamples[i]; 236 | if (o.position.distanceToSquared(p.position) <= textThresholdSq) { 237 | highSample = o; 238 | break; 239 | } 240 | } 241 | if (highSample) { 242 | // p.velocity.fromArray() 243 | p.velocity.copy(highSample.normal); 244 | tmpVec.copy(highSample.normal).multiplyScalar(p.textThreshold * 1.25); 245 | p.position.copy(highSample.position).add(tmpVec); 246 | // p.velocity.set(0, 0, 0) 247 | } 248 | } 249 | // attract 250 | particles.forEach(other => { 251 | if (p === other) return; 252 | 253 | // const hit = other.points.some(o => o.distanceToSquared(p.position) <= thresholdSq); 254 | // if (hit) { 255 | // p.velocity.set(0, randomFloat(-1, 1), 0).multiplyScalar(p.speed) 256 | // p.velocity.fromArray(randomSphere([], p.speed)) 257 | // p.angle = 54; 258 | // p.velocity.multiplyScalar(0); 259 | // const distScale = 1 - clamp(distSq / thresholdSq, 0, 1); 260 | // } 261 | const distSq = p.position.distanceToSquared(other.position); 262 | if (distSq <= thresholdSq) { 263 | const distScale = 1 - clamp(distSq / thresholdSq, 0, 1); 264 | attractTo(p, other.position, distScale); 265 | } 266 | }); 267 | }); 268 | 269 | particles.forEach(p => { 270 | if (p.angle !== null) { 271 | tmpVec.copy(p.velocity).normalize(); 272 | let vecLen = p.velocity.length(); 273 | let roundAngle = p.angle * Math.PI / 180; 274 | const angle = Math.atan2(tmpVec.y, tmpVec.x); 275 | if (angle % roundAngle !== 0) { 276 | const newAngle = Math.round(angle / roundAngle) * roundAngle; 277 | tmpVec.x = Math.cos(newAngle); 278 | tmpVec.y = Math.sin(newAngle); 279 | } 280 | tmpVec.multiplyScalar(vecLen); 281 | p.velocity.copy(tmpVec); 282 | } 283 | 284 | p.velocity.clampScalar(-p.maxVelocity, p.maxVelocity); 285 | p.position.add(p.velocity); 286 | p.velocity.multiplyScalar(p.friction); 287 | p.mesh.position.copy(p.position); 288 | p.points.push(p.position.clone()); 289 | }); 290 | } 291 | 292 | function attractTo (p, otherPosition, distScale = 1, direction = 1) { 293 | const curSpeed = p.velocity.length(); 294 | 295 | tmpVec.copy(otherPosition).sub(p.position).normalize(); 296 | tmpVec.multiplyScalar(p.attraction * p.speed * distScale * direction * curSpeed); 297 | p.velocity.add(tmpVec); 298 | } 299 | }; 300 | -------------------------------------------------------------------------------- /lib/post/BloomTexturePass.js: -------------------------------------------------------------------------------- 1 | 2 | const glslify = require('glslify'); 3 | const path = require('path'); 4 | const clamp = require('clamp'); 5 | const CopyShader = require('three-copyshader'); 6 | const injectDefines = require('glsl-inject-defines'); 7 | const { assets, floatBufferType, floatBufferDefine } = require('../context'); 8 | const isMobile = require('../util/isMobile'); 9 | const assign = require('object-assign'); 10 | 11 | const downsample = 2.5; 12 | const ssaoDownsample = 2; 13 | const maxSize = 2048; 14 | const blurLevels = [ 2, 1, 1 ]; 15 | 16 | const dustKey = assets.queue({ 17 | url: 'assets/textures/dust_compressed.jpg', 18 | minFilter: THREE.LinearFilter, 19 | generateMipmaps: false 20 | }); 21 | 22 | const lutKey = assets.queue({ 23 | url: 'assets/textures/lut.png', 24 | minFilter: THREE.NearestFilter, 25 | magFilter: THREE.NearestFilter, 26 | generateMipmaps: false 27 | }); 28 | 29 | const palettes = [ 30 | ['hsl(0, 0%, 9%)', 'hsl(0, 0%, 85%)'], 31 | // ['#12052b', '#cc3116'], 32 | // ['#070b1c', '#9731ea'], 33 | // ['#12052b', '#ea31bf'], 34 | // ['#12052b', '#ffaa00'], 35 | // ['#49173f', '#3089e8'], 36 | // ['#4f0c3b', '#ffaa00'], 37 | ] 38 | 39 | module.exports = BloomPass; 40 | function BloomPass (scene, camera, opt = {}) { 41 | this.scene = scene; 42 | this.camera = camera; 43 | 44 | this.debugCopyShader = new THREE.ShaderMaterial(CopyShader); 45 | 46 | this._lastWidth = null; 47 | this._lastHeight = null; 48 | this._blurTarget = null; // lazily created 49 | this._thresholdTarget = null; 50 | this._useSSAO = false; 51 | 52 | this.enabled = true; 53 | this.needsSwap = true; 54 | this.oldColor = new THREE.Color(); 55 | this.oldAlpha = 1; 56 | this.clearColor = new THREE.Color('#fff'); 57 | this.clearAlpha = 0; 58 | 59 | this.vignetteScale = 1; 60 | this.vignetteSize = 0.6; 61 | this.vignetteMin = 0.428; 62 | this.vignetteMax = 0.713; 63 | this.vignetteStrength = 0.611; 64 | 65 | if (this._useSSAO && floatBufferType) { 66 | this.ssaoShader = new THREE.RawShaderMaterial({ 67 | blending: THREE.NoBlending, 68 | vertexShader: glslify(path.resolve(__dirname + '/../shaders/pass.vert')), 69 | fragmentShader: glslify(path.resolve(__dirname + '/../shaders/bloom/ssao.frag')), 70 | uniforms: { 71 | cameraFar: { type: 'f', value: 0 }, 72 | cameraNear: { type: 'f', value: 0 }, 73 | tDiffuse: { type: 't', value: null }, 74 | resolution: { type: "v2", value: new THREE.Vector2( 512, 512 ) }, 75 | onlyAO: { type: "i", value: 0 }, 76 | aoClamp: { type: "f", value: 0.15 }, 77 | lumInfluence: { type: "f", value: 1 } 78 | } 79 | }); 80 | this.ssaoShader.name = 'bloom-gaussian-blur-material'; 81 | } 82 | 83 | this.thresholdBackground = new THREE.Color(opt.background); 84 | this.thresholdShader = new THREE.RawShaderMaterial({ 85 | blending: THREE.NoBlending, 86 | vertexShader: glslify(path.resolve(__dirname + '/../shaders/pass.vert')), 87 | fragmentShader: injectDefines(glslify(path.resolve(__dirname + '/../shaders/bloom/threshold.frag')), floatBufferDefine), 88 | uniforms: { 89 | vignetteScale: { type: 'f', value: this.vignetteScale }, 90 | vignetteMin: { type: 'f', value: this.vignetteMin }, 91 | vignetteMax: { type: 'f', value: this.vignetteMax }, 92 | vignetteStrength: { type: 'f', value: this.vignetteStrength }, 93 | lumaThreshold: { type: 'f', value: 1.0 }, 94 | background: { type: 'c', value: this.thresholdBackground }, 95 | tDiffuse: { type: 't', value: null }, 96 | resolution: { type: 'v2', value: new THREE.Vector2(1, 1) } 97 | } 98 | }); 99 | this.thresholdShader.name = 'bloom-threhsold-material'; 100 | 101 | this.gaussianShader = new THREE.RawShaderMaterial({ 102 | blending: THREE.NoBlending, 103 | vertexShader: glslify(path.resolve(__dirname + '/../shaders/pass.vert')), 104 | fragmentShader: injectDefines(glslify(path.resolve(__dirname + '/../shaders/bloom/gaussian-blur.frag')), floatBufferDefine), 105 | uniforms: { 106 | vignetteScale: { type: 'f', value: this.vignetteScale }, 107 | vignetteMin: { type: 'f', value: this.vignetteMin }, 108 | vignetteMax: { type: 'f', value: this.vignetteMax }, 109 | vignetteStrength: { type: 'f', value: this.vignetteStrength }, 110 | tDiffuse: { type: 't', value: null }, 111 | direction: { type: 'v2', value: new THREE.Vector2() }, 112 | resolution: { type: 'v2', value: new THREE.Vector2(1, 1) } 113 | } 114 | }); 115 | this.gaussianShader.name = 'bloom-gaussian-blur-material'; 116 | 117 | const defines = assign({}, floatBufferDefine); 118 | if (opt.gammaOutput) { 119 | defines.GAMMA_OUTPUT = opt.gammaOutput.toFixed(1); 120 | } 121 | if (this._useSSAO) { 122 | defines.INCLUDE_SSAO = ''; 123 | } 124 | if (isMobile) { 125 | defines.IS_PORTRAIT = ''; 126 | defines.IS_MOBILE = ''; 127 | } 128 | const dustMap = assets.get(dustKey); 129 | const lutMap = assets.get(lutKey); 130 | this.combineShader = new THREE.RawShaderMaterial({ 131 | blending: THREE.NoBlending, 132 | vertexShader: glslify(path.resolve(__dirname + '/../shaders/pass.vert')), 133 | fragmentShader: injectDefines(glslify(path.resolve(__dirname + '/../shaders/bloom/combine.frag')), defines), 134 | uniforms: { 135 | animation: { type: 'f', value: 0 }, 136 | dustMap: { type: 't', value: dustMap }, 137 | lookupMap: { type: 't', value: lutMap }, 138 | dustMapResolution: { type: 'v2', value: new THREE.Vector2(dustMap.image.width, dustMap.image.height) }, 139 | vignetteScale: { type: 'f', value: this.vignetteScale }, 140 | vignetteMin: { type: 'f', value: this.vignetteMin }, 141 | vignetteMax: { type: 'f', value: this.vignetteMax }, 142 | vignetteStrength: { type: 'f', value: this.vignetteStrength }, 143 | lensDistort: { type: 'f', value: 0.005 }, 144 | lensDistortK: { type: 'f', value: 0.5 }, 145 | lensDistortCubicK: { type: 'f', value: 1 }, 146 | time: { type: 'f', value: 0 }, 147 | lensDistortScale: { type: 'f', value: 0.96 }, 148 | vignetteStrength: { type: 'f', value: 1 }, 149 | bloomOpacity: { type: 'f', value: 0.95 }, 150 | bloomMultiply: { type: 'f', value: 1.15 }, 151 | resolution: { type: 'v2', value: new THREE.Vector2() }, 152 | grayscale: { type: 'i', value: 0 }, 153 | grainStrength: { type: 'f', value: window.devicePixelRatio >= 2 ? 5 : 0 }, 154 | tBloomDiffuse: { type: 't', value: null }, 155 | tDiffuse: { type: 't', value: null }, 156 | tSSAO: { type: 't', value: null }, 157 | shockwavePosition: { type: 'v2', value: new THREE.Vector2() }, 158 | shockwaveStrength: { type: 'f', value: 0 }, 159 | shockwaveRadius: { type: 'f', value: 0 }, 160 | steps: { type: 'f', value: 4 }, 161 | color1: { type: 'c', value: new THREE.Color() }, 162 | color2: { type: 'c', value: new THREE.Color() } 163 | } 164 | }); 165 | this.combineShader.name = 'bloom-combine-material'; 166 | 167 | this.postCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 168 | this.postScene = new THREE.Scene(); 169 | 170 | this.postQuad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2)); 171 | this.postQuad.name = 'godray-post-quad'; 172 | this.postScene.add(this.postQuad); 173 | 174 | this.renderToScreen = false; 175 | this.blurRadius = 1; 176 | this.time = 0; 177 | this.animation = 0; 178 | 179 | const recolor = () => { 180 | const [ a, b ] = palettes[this.paletteIndex++ % palettes.length]; 181 | this.combineShader.uniforms.color1.value.set(a); 182 | this.combineShader.uniforms.color2.value.set(b); 183 | }; 184 | this.paletteIndex = 0; 185 | recolor(); 186 | window.addEventListener('mousedown', recolor); 187 | 188 | const gui = opt.gui; 189 | if (gui) { 190 | const folder = gui.addFolder('post-fx'); 191 | const params = { 192 | radius: this.blurRadius, 193 | grayscale: this.combineShader.uniforms.grayscale.value === 1, 194 | lensDistort: this.combineShader.uniforms.lensDistort.value, 195 | lensDistortK: this.combineShader.uniforms.lensDistortK.value, 196 | lensDistortCubicK: this.combineShader.uniforms.lensDistortCubicK.value, 197 | lensDistortScale: this.combineShader.uniforms.lensDistortScale.value, 198 | grain: this.combineShader.uniforms.grainStrength.value, 199 | opacity: this.combineShader.uniforms.bloomOpacity.value, 200 | threshold: this.thresholdShader.uniforms.lumaThreshold.value, 201 | vignetteMin: this.combineShader.uniforms.vignetteMin.value, 202 | vignetteMax: this.combineShader.uniforms.vignetteMax.value, 203 | vignetteStrength: this.combineShader.uniforms.vignetteStrength.value, 204 | }; 205 | const onChange = () => { 206 | this.blurRadius = params.radius; 207 | this.combineShader.uniforms.grayscale.value = params.grayscale ? 1 : 0; 208 | this.combineShader.uniforms.grainStrength.value = params.grain; 209 | this.combineShader.uniforms.bloomOpacity.value = params.opacity; 210 | this.combineShader.uniforms.vignetteMin.value = params.vignetteMin; 211 | this.combineShader.uniforms.vignetteMax.value = params.vignetteMax; 212 | this.combineShader.uniforms.vignetteStrength.value = params.vignetteStrength; 213 | this.combineShader.uniforms.lensDistort.value = params.lensDistort; 214 | this.combineShader.uniforms.lensDistortK.value = params.lensDistortK; 215 | this.combineShader.uniforms.lensDistortCubicK.value = params.lensDistortCubicK; 216 | this.combineShader.uniforms.lensDistortScale.value = params.lensDistortScale; 217 | this.thresholdShader.uniforms.lumaThreshold.value = params.threshold; 218 | }; 219 | folder.add(params, 'threshold', 0, 1).onChange(onChange); 220 | folder.add(params, 'radius', 0, 2).onChange(onChange); 221 | folder.add(params, 'opacity', 0, 2).onChange(onChange); 222 | folder.add(params, 'grain', 0, 10).onChange(onChange); 223 | folder.add(params, 'vignetteMin', 0, 1).onChange(onChange); 224 | folder.add(params, 'vignetteMax', 0, 1).onChange(onChange); 225 | folder.add(params, 'vignetteStrength', 0, 1).onChange(onChange); 226 | folder.add(params, 'grayscale').onChange(onChange); 227 | // folder.add(params, 'lensDistort', 0, 1).onChange(onChange); 228 | // folder.add(params, 'lensDistortK', 0, 1).onChange(onChange); 229 | // folder.add(params, 'lensDistortCubicK', 0, 1).onChange(onChange); 230 | // folder.add(params, 'lensDistortScale', 0, 1).onChange(onChange); 231 | } 232 | } 233 | 234 | BloomPass.prototype = { 235 | 236 | tick: function (dt) { 237 | this.time += dt; 238 | }, 239 | 240 | _updateVignette: function (shader) { 241 | const resolution = shader.uniforms.resolution.value; 242 | const aspect = resolution.x / resolution.y; 243 | const vignetteScale = resolution.x > resolution.y 244 | ? 1 * aspect 245 | : 1 / aspect; 246 | shader.uniforms.vignetteScale.value = vignetteScale * this.vignetteSize; 247 | }, 248 | 249 | setShockwave: function (position, radius, strength) { 250 | this.combineShader.uniforms.shockwavePosition.value.copy(position); 251 | this.combineShader.uniforms.shockwaveRadius.value = radius; 252 | this.combineShader.uniforms.shockwaveStrength.value = strength; 253 | }, 254 | 255 | _updateTargets: function (renderTarget) { 256 | var width = renderTarget.width; 257 | var height = renderTarget.height; 258 | var downWidth = clamp(Math.floor(width / downsample), 2, maxSize); 259 | var downHeight = clamp(Math.floor(height / downsample), 2, maxSize); 260 | if (!this._thresholdTarget || !this._blurTarget) { 261 | this._blurTarget = new THREE.WebGLRenderTarget(downWidth, downHeight); 262 | this._blurTarget.texture.minFilter = floatBufferType ? THREE.LinearFilter : THREE.NearestFilter; 263 | this._blurTarget.texture.magFilter = floatBufferType ? THREE.LinearFilter : THREE.NearestFilter; 264 | this._blurTarget.texture.generateMipmaps = false; 265 | this._blurTarget.texture.type = floatBufferType ? THREE.HalfFloatType : THREE.UnsignedByteType; 266 | this._blurTarget.texture.format = floatBufferType ? THREE.RGBFormat : THREE.RGBAFormat; 267 | this._blurTarget.depthBuffer = false; 268 | this._blurTarget.stencilBuffer = false; 269 | this._blurTarget2 = this._blurTarget.clone(); 270 | this._ldrBlurTarget = this._blurTarget.clone(); 271 | this._ldrBlurTarget.texture.type = THREE.UnsignedByteType; 272 | this._ldrBlurTarget.texture.format = THREE.RGBFormat; 273 | this._thresholdTarget = this._blurTarget.clone(); 274 | } else if (this._thresholdTarget.width !== downWidth || this._thresholdTarget.height !== downHeight) { 275 | this._thresholdTarget.setSize(downWidth, downHeight); 276 | this._blurTarget.setSize(downWidth, downHeight); 277 | this._blurTarget2.setSize(downWidth, downHeight); 278 | this._ldrBlurTarget.setSize(downWidth, downHeight); 279 | } 280 | 281 | if (this._useSSAO){ 282 | var ssaoDownWidth = clamp(Math.floor(width / ssaoDownsample), 2, maxSize); 283 | var ssaoDownHeight = clamp(Math.floor(height / ssaoDownsample), 2, maxSize); 284 | if (!this._ldrTarget) { 285 | this._ldrTarget = new THREE.WebGLRenderTarget(ssaoDownWidth, ssaoDownHeight); 286 | this._ldrTarget.texture.minFilter = THREE.LinearFilter; 287 | this._ldrTarget.texture.magFilter = THREE.LinearFilter; 288 | this._ldrTarget.texture.generateMipmaps = false; 289 | this._ldrTarget.depthBuffer = false; 290 | this._ldrTarget.stencilBuffer = false; 291 | this._ldrTarget.texture.type = THREE.UnsignedByteType; 292 | this._ldrTarget.texture.format = THREE.RGBFormat; 293 | } else if (this._ldrTarget.width !== ssaoDownWidth || this._ldrTarget.height !== ssaoDownHeight) { 294 | this._ldrTarget.setSize(ssaoDownWidth, ssaoDownHeight); 295 | } 296 | } 297 | }, 298 | 299 | render: function (renderer, writeBuffer, readBuffer) { 300 | var dpr = renderer.getPixelRatio(); 301 | this._updateTargets(readBuffer); 302 | var finalBuffer = this.renderToScreen ? undefined : writeBuffer; 303 | 304 | // 1. First, render scene into downsampled FBO and threshold color 305 | this.oldColor.copy(renderer.getClearColor()); 306 | this.oldAlpha = renderer.getClearAlpha(); 307 | var oldAutoClear = renderer.autoClear; 308 | 309 | // Clear target 310 | renderer.setClearColor(this.clearColor, this.clearAlpha); 311 | renderer.autoClear = false; 312 | renderer.clearTarget(this._thresholdTarget, true, true, false); 313 | 314 | // Draw existing texture into smaller target & also threshold colors 315 | this.postScene.overrideMaterial = this.thresholdShader; 316 | this.thresholdShader.uniforms.resolution.value.set(this._thresholdTarget.width, this._thresholdTarget.height); 317 | this.thresholdShader.uniforms.tDiffuse.value = readBuffer.texture; 318 | this._updateVignette(this.thresholdShader); 319 | renderer.render(this.postScene, this.postCamera, this._thresholdTarget, true); 320 | 321 | let nextTarget; 322 | let bufferA = this._blurTarget; 323 | let bufferB = this._blurTarget2; 324 | const iterations = blurLevels.length * 2; 325 | 326 | // apply gaussian blur 327 | this.postScene.overrideMaterial = this.gaussianShader; 328 | this.gaussianShader.uniforms.resolution.value.set(this._thresholdTarget.width, this._thresholdTarget.height); 329 | this._updateVignette(this.gaussianShader); 330 | for (var i = 0; i < iterations; i++) { 331 | const blurAmount = blurLevels[Math.floor(i / 2)]; 332 | const radius = this.blurRadius * blurAmount; 333 | 334 | if (i === 0) { 335 | bufferA = this._thresholdTarget; 336 | } else if (i === iterations - 1 && floatBufferType) { 337 | // last iteration, let's use LDR target to save bandwidth 338 | // if no float buffer, then just use default pattern for convenience 339 | bufferB = this._ldrBlurTarget; 340 | } 341 | this.gaussianShader.uniforms.tDiffuse.value = bufferA.texture; 342 | const direction = this.gaussianShader.uniforms.direction.value; 343 | if (i % 2 === 0) direction.set(radius, 0); 344 | else direction.set(0, radius); 345 | 346 | // render to buffer 347 | renderer.render(this.postScene, this.postCamera, bufferB, true); 348 | 349 | // swap buffers if we have another iteration 350 | if (i < (iterations - 1)) { 351 | var t = bufferA; 352 | bufferA = bufferB; 353 | bufferB = t; 354 | } 355 | } 356 | 357 | nextTarget = bufferB; 358 | 359 | if (this._useSSAO) { 360 | // If SSAO is enabled, we draw into a downsampled LDR target 361 | // and apply SSAO to the depth (G channel of tDiffuse). 362 | this.postScene.overrideMaterial = this.ssaoShader; 363 | this.ssaoShader.uniforms.tDiffuse.value = readBuffer.texture; 364 | this.ssaoShader.uniforms.cameraFar.value = this.camera.far; 365 | this.ssaoShader.uniforms.cameraNear.value = this.camera.near; 366 | this.ssaoShader.uniforms.resolution.value.set(this._ldrTarget.width, this._ldrTarget.height); 367 | renderer.render(this.postScene, this.postCamera, this._ldrTarget, true); 368 | } 369 | 370 | // Now we render back to original scene, with additive blending! 371 | this.postScene.overrideMaterial = this.combineShader; 372 | if (this._useSSAO) { 373 | this.combineShader.uniforms.tSSAO.value = this._ldrTarget.texture; 374 | } 375 | this.combineShader.uniforms.tDiffuse.value = readBuffer.texture; 376 | this.combineShader.uniforms.tBloomDiffuse.value = nextTarget.texture; 377 | this.combineShader.uniforms.time.value = this.time; 378 | this.combineShader.uniforms.animation.value = this.animation; 379 | 380 | this.combineShader.uniforms.resolution.value.set( 381 | finalBuffer ? finalBuffer.width : (window.innerWidth * dpr), 382 | finalBuffer ? finalBuffer.height : (window.innerHeight * dpr) 383 | ); 384 | this._updateVignette(this.combineShader); 385 | renderer.render(this.postScene, this.postCamera, finalBuffer, true); 386 | 387 | renderer.setClearColor(this.oldColor, this.oldAlpha); 388 | renderer.autoClear = oldAutoClear; 389 | }, 390 | }; 391 | --------------------------------------------------------------------------------