├── .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 |
24 |
25 |
26 |
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