├── .gitignore ├── README.md ├── css └── base.css ├── favicon.ico ├── index.html ├── index2.html ├── index3.html ├── index4.html ├── index5.html ├── index6.html ├── index7.html └── js ├── Distortions.js ├── InfiniteLights.js ├── postprocessing.min.js └── three.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # High-speed Light Trails in Three.js 2 | A creative coding exploration into how to recreate a high-speed lights effect in real-time using Three.js. 3 | 4 | ![Light Trails Effect Article](https://tympanus.net/codrops/wp-content/uploads/2019/11/LightTrails_featured.jpg) 5 | 6 | [Article on Codrops](https://tympanus.net/codrops/?p=44516) 7 | 8 | [Demo](https://tympanus.net/Tutorials/InfiniteLights/) 9 | 10 | ## Credits 11 | 12 | - [three.js](https://threejs.org/) by Ricardo Cabello 13 | 14 | ## License 15 | This resource can be used freely if integrated or build upon in personal or commercial projects such as websites, web apps and web templates intended for sale. It is not allowed to take the resource "as-is" and sell it, redistribute, re-publish it, or sell "pluginized" versions of it. Free plugins built using this resource should have a visible mention and link to the original work. Always consider the licenses of all included libraries, scripts and images used. 16 | 17 | ## Misc 18 | 19 | Follow Daniel: [Twitter](https://twitter.com/Anemolito), [Codepen](https://codepen.io/Anemolo/), [CodeSandbox](https://codesandbox.io/u/Anemolo), [GitHub](https://github.com/Anemolo) 20 | 21 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [Google+](https://plus.google.com/101095823814290637419), [GitHub](https://github.com/codrops), [Pinterest](http://www.pinterest.com/codrops/), [Instagram](https://www.instagram.com/codropsss/) 22 | 23 | 24 | [© Codrops 2019](http://www.codrops.com) 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 18px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #fff; 14 | --color-bg: #000; 15 | --color-link: #5a5a5a; 16 | --color-link-hover: #3eaaf1; 17 | --color-alt: #3ba2e5; 18 | --color-info: #e93f3b; 19 | color: var(--color-text); 20 | background-color: var(--color-bg); 21 | font-family: ltc-bodoni-175, serif; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .demo-2 { 27 | --color-link-hover: #53C2C6; 28 | --color-alt: #53C2C6; 29 | --color-info: #FF5F73; 30 | } 31 | 32 | .demo-3 { 33 | --color-link-hover: #DFD98A; 34 | --color-alt: #DFD98A; 35 | --color-info: #A90519; 36 | } 37 | 38 | .demo-4 { 39 | --color-info: #D856BF; 40 | } 41 | 42 | .demo-5 { 43 | --color-link-hover: #334BF7; 44 | --color-alt: #334BF7; 45 | --color-info: #DC5B20; 46 | } 47 | 48 | .demo-6 { 49 | --color-info: #FF322F; 50 | } 51 | 52 | .demo-7 { 53 | --color-link-hover: #7686BF; 54 | --color-alt: #7686BF; 55 | --color-info: #E2173C; 56 | } 57 | 58 | /* Page Loader */ 59 | .js .loading::before, 60 | .js .loading::after { 61 | content: ''; 62 | position: fixed; 63 | z-index: 1000; 64 | } 65 | 66 | .js .loading::before { 67 | top: 0; 68 | left: 0; 69 | width: 100%; 70 | height: 100%; 71 | background: var(--color-bg); 72 | } 73 | 74 | .js .loading::after { 75 | top: 50%; 76 | left: 50%; 77 | width: 60px; 78 | height: 60px; 79 | margin: -30px 0 0 -30px; 80 | opacity: 0.4; 81 | background: var(--color-link); 82 | animation: loaderAnim 0.7s linear infinite alternate forwards; 83 | } 84 | 85 | @keyframes loaderAnim { 86 | to { 87 | opacity: 1; 88 | transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,180deg); 89 | border-radius: 50%; 90 | } 91 | } 92 | 93 | a { 94 | text-decoration: none; 95 | color: var(--color-link); 96 | outline: none; 97 | } 98 | 99 | a:hover, 100 | a:focus { 101 | color: var(--color-link-hover); 102 | outline: none; 103 | } 104 | 105 | .message { 106 | background: var(--color-text); 107 | color: var(--color-bg); 108 | padding: 1rem; 109 | text-align: center; 110 | } 111 | 112 | .frame { 113 | padding: 3rem 5vw; 114 | text-align: center; 115 | position: relative; 116 | z-index: 1000; 117 | } 118 | 119 | .frame__title { 120 | font-size: 1rem; 121 | margin: 0 0 1rem; 122 | font-weight: normal; 123 | } 124 | 125 | .frame__links { 126 | display: inline; 127 | } 128 | 129 | .frame__links a { 130 | display: block; 131 | margin: 0 1rem 0.5rem; 132 | } 133 | 134 | .frame__demos { 135 | margin: 1rem 0; 136 | } 137 | 138 | .frame__demo { 139 | display: block; 140 | padding-top: 0.5rem; 141 | } 142 | 143 | .frame__demo--current, 144 | .frame__demo--current:hover { 145 | color: var(--color-link-hover); 146 | } 147 | 148 | .frame__info { 149 | color: var(--color-info); 150 | } 151 | 152 | .content { 153 | display: flex; 154 | flex-direction: column; 155 | width: 100vw; 156 | height: calc(100vh - 13rem); 157 | position: relative; 158 | justify-content: flex-start; 159 | align-items: center; 160 | } 161 | 162 | .content__title-wrap { 163 | position: relative; 164 | text-align: center; 165 | pointer-events: none; 166 | -webkit-touch-callout: none; 167 | -webkit-user-select: none; 168 | -khtml-user-select: none; 169 | -moz-user-select: none; 170 | -ms-user-select: none; 171 | user-select: none; 172 | } 173 | 174 | .content__title-wrap a { 175 | pointer-events: auto; 176 | } 177 | 178 | .content__pretitle { 179 | color: var(--color-alt); 180 | font-size: 1.35rem; 181 | } 182 | 183 | .content__title { 184 | font-size: 6vw; 185 | font-weight: normal; 186 | margin: 0.5rem 0 1.5rem; 187 | font-family: azo-sans-uber, sans-serif; 188 | } 189 | 190 | .content__link { 191 | text-decoration: underline; 192 | font-family: azo-sans-uber, sans-serif; 193 | font-size: 1.1rem; 194 | color: inherit; 195 | } 196 | 197 | #app { 198 | width: 100%; 199 | height: 100vh; 200 | overflow: hidden; 201 | position: absolute; 202 | } 203 | 204 | canvas { 205 | width: 100%; 206 | height: 100%; 207 | } 208 | 209 | @media screen and (min-width: 53em) { 210 | .message { 211 | display: none; 212 | } 213 | .frame { 214 | position: fixed; 215 | text-align: left; 216 | z-index: 100; 217 | top: 0; 218 | left: 0; 219 | display: grid; 220 | align-content: space-between; 221 | width: 100%; 222 | max-width: none; 223 | height: 100vh; 224 | padding: 2.5rem 4.5rem; 225 | pointer-events: none; 226 | grid-template-columns: 75% 25%; 227 | grid-template-rows: auto auto auto; 228 | grid-template-areas: 'title info' 229 | '... ...' 230 | '... demos'; 231 | } 232 | .frame__title-wrap { 233 | grid-area: title; 234 | display: flex; 235 | } 236 | .frame__title { 237 | margin: 0 8vw 0 0; 238 | } 239 | .frame__demos { 240 | margin: 0; 241 | grid-area: demos; 242 | justify-self: end; 243 | } 244 | .frame__links a { 245 | display: inline-block; 246 | } 247 | .frame__info { 248 | justify-self: end; 249 | } 250 | .frame a { 251 | pointer-events: auto; 252 | } 253 | .content { 254 | height: 100vh; 255 | justify-content: center; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Anemolo/Infinite-Lights/e58d58520bc0dfde21f9e14e6a1b8c7f0a2a2a9e/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite Lights | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |

Infinite Lights

24 | 29 |
30 |
Hint: Press to speed up
31 | 40 |
41 |
42 |
43 |
44 | Indigo Road Systems 45 |

Traffic Ohio

46 | Live updates 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 124 | 125 | -------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite Lights | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |

Infinite Lights

24 | 29 |
30 |
Hint: Press to speed up
31 | 40 |
41 |
42 |
43 |
44 | Good Road Inc. 45 |

Headlights

46 | Learn more 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 124 | 125 | -------------------------------------------------------------------------------- /index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite Lights | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |

Infinite Lights

24 | 29 |
30 |
Hint: Press to speed up
31 | 40 |
41 |
42 |
43 |
44 | Indy Crossroads 45 |

Voltage Race

46 | Join us 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 124 | 125 | -------------------------------------------------------------------------------- /index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite Lights | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |

Infinite Lights

24 | 29 |
30 |
Hint: Press to speed up
31 | 40 |
41 |
42 |
43 |
44 | Cyber Mountain 45 |

Aerobase

46 | Play it 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 124 | 125 | -------------------------------------------------------------------------------- /index5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite Lights | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |

Infinite Lights

24 | 29 |
30 |
Hint: Press to speed up
31 | 40 |
41 |
42 |
43 |
44 | Lightining Hyenas 45 |

Statique Bolt

46 | Subscribe 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 124 | 125 | -------------------------------------------------------------------------------- /index6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite Lights | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |

Infinite Lights

24 | 29 |
30 |
Hint: Press to speed up
31 | 40 |
41 |
42 |
43 |
44 | CWS Investment 45 |

Ascendency

46 | Learn more 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 124 | 125 | -------------------------------------------------------------------------------- /index7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite Lights | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |
23 |

Infinite Lights

24 | 29 |
30 |
Hint: Press to speed up
31 | 40 |
41 |
42 |
43 |
44 | Aerial Promenades 45 |

Vertigo

46 | Discover 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 124 | 125 | -------------------------------------------------------------------------------- /js/Distortions.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Here on top you can find the uniforms for each distortion. 4 | 5 | // ShaderShaping funcitns 6 | https://thebookofshaders.com/05/ 7 | Steps 8 | 1. Write getDistortion in GLSL 9 | 2. Write custom uniforms for tweak parameters. Put them outside the object. 10 | 3. Re-create the GLSl funcion in javascript to get camera paning 11 | 12 | Notes: 13 | LookAtAmp AND lookAtOffset are hand tuned to get a good camera panning. 14 | */ 15 | 16 | const mountainUniforms = { 17 | // x, y, z 18 | uFreq: new THREE.Uniform(new THREE.Vector3(3, 6, 10)), 19 | uAmp: new THREE.Uniform(new THREE.Vector3(30, 30, 20)) 20 | }; 21 | 22 | const xyUniforms = { 23 | // x,y 24 | uFreq: new THREE.Uniform(new THREE.Vector2(5, 2)), 25 | uAmp: new THREE.Uniform(new THREE.Vector2(25, 15)) 26 | }; 27 | 28 | const LongRaceUniforms = { 29 | // x, y 30 | uFreq: new THREE.Uniform(new THREE.Vector2(2, 3)), 31 | uAmp: new THREE.Uniform(new THREE.Vector2(35, 10)) 32 | }; 33 | 34 | const turbulentUniforms = { 35 | // x,x, y,y 36 | uFreq: new THREE.Uniform(new THREE.Vector4(4, 8, 8, 1)), 37 | uAmp: new THREE.Uniform(new THREE.Vector4(25, 5, 10, 10)) 38 | }; 39 | 40 | const deepUniforms = { 41 | // x, y 42 | uFreq: new THREE.Uniform(new THREE.Vector2(4, 8)), 43 | uAmp: new THREE.Uniform(new THREE.Vector2(10, 20)), 44 | uPowY: new THREE.Uniform(new THREE.Vector2(20, 2)) 45 | }; 46 | 47 | let nsin = val => Math.sin(val) * 0.5 + 0.5; 48 | 49 | let mountainDistortion = { 50 | uniforms: mountainUniforms, 51 | getDistortion: ` 52 | 53 | uniform vec3 uAmp; 54 | uniform vec3 uFreq; 55 | 56 | #define PI 3.14159265358979 57 | 58 | float nsin(float val){ 59 | return sin(val) * 0.5+0.5; 60 | } 61 | 62 | vec3 getDistortion(float progress){ 63 | 64 | float movementProgressFix = 0.02; 65 | return vec3( 66 | cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x, 67 | nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y, 68 | nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z 69 | ); 70 | } 71 | `, 72 | getJS: (progress, time) => { 73 | let movementProgressFix = 0.02; 74 | 75 | let uFreq = mountainUniforms.uFreq.value; 76 | let uAmp = mountainUniforms.uAmp.value; 77 | 78 | let distortion = new THREE.Vector3( 79 | Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x - 80 | Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x, 81 | nsin(progress * Math.PI * uFreq.y + time) * uAmp.y - 82 | nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y, 83 | nsin(progress * Math.PI * uFreq.z + time) * uAmp.z - 84 | nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z 85 | ); 86 | 87 | let lookAtAmp = new THREE.Vector3(2, 2, 2); 88 | let lookAtOffset = new THREE.Vector3(0, 0, -5); 89 | return distortion.multiply(lookAtAmp).add(lookAtOffset); 90 | } 91 | }; 92 | 93 | let xyDistortion = { 94 | uniforms: xyUniforms, 95 | getDistortion: ` 96 | uniform vec2 uFreq; 97 | uniform vec2 uAmp; 98 | 99 | #define PI 3.14159265358979 100 | 101 | 102 | vec3 getDistortion(float progress){ 103 | 104 | float movementProgressFix = 0.02; 105 | return vec3( 106 | cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) *uAmp.x, 107 | sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y, 108 | 0. 109 | ); 110 | } 111 | `, 112 | getJS: (progress, time) => { 113 | let movementProgressFix = 0.02; 114 | 115 | let uFreq = xyUniforms.uFreq.value; 116 | let uAmp = xyUniforms.uAmp.value; 117 | 118 | let distortion = new THREE.Vector3( 119 | Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x - 120 | Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x, 121 | Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y - 122 | Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * 123 | uAmp.y, 124 | 0 125 | ); 126 | let lookAtAmp = new THREE.Vector3(2, 0.4, 1); 127 | let lookAtOffset = new THREE.Vector3(0, 0, -3); 128 | return distortion.multiply(lookAtAmp).add(lookAtOffset); 129 | } 130 | }; 131 | 132 | let LongRaceDistortion = { 133 | uniforms: LongRaceUniforms, 134 | getDistortion: ` 135 | 136 | uniform vec2 uFreq; 137 | uniform vec2 uAmp; 138 | #define PI 3.14159265358979 139 | 140 | vec3 getDistortion(float progress){ 141 | 142 | float camProgress = 0.0125; 143 | return vec3( 144 | sin(progress * PI * uFreq.x +uTime) * uAmp.x - sin(camProgress * PI * uFreq.x+uTime ) * uAmp.x, 145 | sin(progress * PI * uFreq.y +uTime) * uAmp.y - sin(camProgress * PI * uFreq.y+uTime ) * uAmp.y, 146 | 0. 147 | ); 148 | } 149 | `, 150 | getJS: (progress, time) => { 151 | let camProgress = 0.0125; 152 | 153 | let uFreq = LongRaceUniforms.uFreq.value; 154 | let uAmp = LongRaceUniforms.uAmp.value; 155 | // Uniforms 156 | 157 | let distortion = new THREE.Vector3( 158 | Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x - 159 | Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x, 160 | Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y - 161 | Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y, 162 | 0 163 | ); 164 | 165 | let lookAtAmp = new THREE.Vector3(1, 1, 0); 166 | let lookAtOffset = new THREE.Vector3(0, 0, -5); 167 | return distortion.multiply(lookAtAmp).add(lookAtOffset); 168 | } 169 | }; 170 | 171 | const turbulentDistortion = { 172 | uniforms: turbulentUniforms, 173 | getDistortion: ` 174 | uniform vec4 uFreq; 175 | uniform vec4 uAmp; 176 | float nsin(float val){ 177 | return sin(val) * 0.5+0.5; 178 | } 179 | 180 | #define PI 3.14159265358979 181 | float getDistortionX(float progress){ 182 | return 183 | ( 184 | cos( PI * progress * uFreq.r + uTime) * uAmp.r + 185 | pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)),2. )* uAmp.g 186 | 187 | ); 188 | } 189 | float getDistortionY(float progress){ 190 | return 191 | ( 192 | -nsin( PI * progress * uFreq.b + uTime) * uAmp.b + 193 | -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a) ),5.) * uAmp.a 194 | 195 | ); 196 | } 197 | vec3 getDistortion(float progress){ 198 | return vec3( 199 | getDistortionX(progress)-getDistortionX(0.0125) , 200 | getDistortionY(progress)- getDistortionY(0.0125), 201 | 0. 202 | ); 203 | } 204 | `, 205 | getJS: (progress, time) => { 206 | const uFreq = turbulentUniforms.uFreq.value; 207 | const uAmp = turbulentUniforms.uAmp.value; 208 | 209 | const getX = p => 210 | Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x + 211 | Math.pow( 212 | Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 213 | 2 214 | ) * 215 | uAmp.y; 216 | const getY = p => 217 | -nsin(Math.PI * p * uFreq.z + time) * uAmp.z - 218 | Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * 219 | uAmp.w; 220 | 221 | let distortion = new THREE.Vector3( 222 | getX(progress) - getX(progress + 0.007), 223 | getY(progress) - getY(progress + 0.007), 224 | 0 225 | ); 226 | let lookAtAmp = new THREE.Vector3(-2, -5, 0); 227 | let lookAtOffset = new THREE.Vector3(0, 0, -10); 228 | return distortion.multiply(lookAtAmp).add(lookAtOffset); 229 | } 230 | }; 231 | 232 | const turbulentDistortionStill = { 233 | uniforms: turbulentUniforms, 234 | getDistortion: ` 235 | uniform vec4 uFreq; 236 | uniform vec4 uAmp; 237 | float nsin(float val){ 238 | return sin(val) * 0.5+0.5; 239 | } 240 | 241 | #define PI 3.14159265358979 242 | float getDistortionX(float progress){ 243 | return 244 | ( 245 | cos( PI * progress * uFreq.r ) * uAmp.r + 246 | pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)),2. )* uAmp.g 247 | 248 | ); 249 | } 250 | float getDistortionY(float progress){ 251 | return 252 | ( 253 | -nsin( PI * progress * uFreq.b ) * uAmp.b + 254 | -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a) ),5.) * uAmp.a 255 | 256 | ); 257 | } 258 | vec3 getDistortion(float progress){ 259 | return vec3( 260 | getDistortionX(progress)-getDistortionX(0.02) , 261 | getDistortionY(progress)- getDistortionY(0.02), 262 | 0. 263 | ); 264 | } 265 | ` 266 | }; 267 | 268 | const deepDistortion = { 269 | uniforms: deepUniforms, 270 | getDistortion: ` 271 | uniform vec4 uFreq; 272 | uniform vec4 uAmp; 273 | uniform vec2 uPowY; 274 | float nsin(float val){ 275 | return sin(val) * 0.5+0.5; 276 | } 277 | 278 | #define PI 3.14159265358979 279 | float getDistortionX(float progress){ 280 | return 281 | ( 282 | sin(progress * PI * uFreq.x + uTime) * uAmp.x 283 | 284 | ); 285 | } 286 | float getDistortionY(float progress){ 287 | return 288 | ( 289 | pow(abs(progress * uPowY.x),uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y 290 | ); 291 | } 292 | vec3 getDistortion(float progress){ 293 | return vec3( 294 | getDistortionX(progress)-getDistortionX(0.02) , 295 | getDistortionY(progress)- getDistortionY(0.02), 296 | 0. 297 | ); 298 | } 299 | `, 300 | getJS: (progress, time) => { 301 | const uFreq = deepUniforms.uFreq.value; 302 | const uAmp = deepUniforms.uAmp.value; 303 | const uPowY = deepUniforms.uPowY.value; 304 | 305 | const getX = p => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x; 306 | const getY = p => 307 | Math.pow(p * uPowY.x, uPowY.y) + 308 | Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y; 309 | 310 | let distortion = new THREE.Vector3( 311 | getX(progress) - getX(progress + 0.01), 312 | getY(progress) - getY(progress + 0.01), 313 | 0 314 | ); 315 | let lookAtAmp = new THREE.Vector3(-2, -4, 0); 316 | let lookAtOffset = new THREE.Vector3(0, 0, -10); 317 | return distortion.multiply(lookAtAmp).add(lookAtOffset); 318 | } 319 | }; 320 | 321 | const deepDistortionStill = { 322 | uniforms: deepUniforms, 323 | getDistortion: ` 324 | uniform vec4 uFreq; 325 | uniform vec4 uAmp; 326 | uniform vec2 uPowY; 327 | float nsin(float val){ 328 | return sin(val) * 0.5+0.5; 329 | } 330 | 331 | #define PI 3.14159265358979 332 | float getDistortionX(float progress){ 333 | return 334 | ( 335 | sin(progress * PI * uFreq.x ) * uAmp.x * 2. 336 | 337 | ); 338 | } 339 | float getDistortionY(float progress){ 340 | return 341 | ( 342 | pow(abs(progress * uPowY.x),uPowY.y) + sin(progress * PI * uFreq.y ) * uAmp.y 343 | ); 344 | } 345 | vec3 getDistortion(float progress){ 346 | return vec3( 347 | getDistortionX(progress)-getDistortionX(0.02) , 348 | getDistortionY(progress)- getDistortionY(0.05), 349 | 0. 350 | ); 351 | } 352 | ` 353 | }; 354 | /** 355 | 356 | let tempUniforms ={}; 357 | LongRacetempDistortion = { 358 | uniforms: tempUniforms, 359 | getDistortion: ` 360 | 361 | #define PI 3.14159265358979 362 | 363 | vec3 getDistortion(float progress){ 364 | 365 | float movementProgressFix = 0.02; 366 | return vec3( 367 | sin(progress * PI * 4.), 368 | 0., 369 | 0. 370 | ); 371 | } 372 | ` , 373 | getJS: (progress,time)=>{ 374 | let movementProgressFix = 0.02; 375 | 376 | // Uniforms 377 | 378 | let distortion = new THREE.Vector3( 379 | Math.sin(progress * Math.PI * 4.), 380 | 0., 381 | 0. 382 | ); 383 | 384 | let lookAtAmp = new THREE.Vector3(0.,0.,0.); 385 | let lookAtOffset = new THREE.Vector3(0.,0.,0.); 386 | return distortion.multiply(lookAtAmp).add(lookAtOffset); 387 | } 388 | 389 | } 390 | 391 | 392 | */ 393 | -------------------------------------------------------------------------------- /js/InfiniteLights.js: -------------------------------------------------------------------------------- 1 | console.log(POSTPROCESSING); 2 | class App { 3 | constructor(container, options = {}) { 4 | // Init ThreeJS Basics 5 | this.options = options; 6 | 7 | if (this.options.distortion == null) { 8 | this.options.distortion = { 9 | uniforms: distortion_uniforms, 10 | getDistortion: distortion_vertex 11 | }; 12 | } 13 | this.container = container; 14 | this.renderer = new THREE.WebGLRenderer({ 15 | antialias: false 16 | }); 17 | this.renderer.setSize(container.offsetWidth, container.offsetHeight, false); 18 | this.renderer.setPixelRatio(window.devicePixelRatio); 19 | this.composer = new POSTPROCESSING.EffectComposer(this.renderer); 20 | container.append(this.renderer.domElement); 21 | 22 | this.camera = new THREE.PerspectiveCamera( 23 | options.fov, 24 | container.offsetWidth / container.offsetHeight, 25 | 0.1, 26 | 10000 27 | ); 28 | this.camera.position.z = -5; 29 | this.camera.position.y = 8; 30 | this.camera.position.x = 0; 31 | // this.camera.rotateX(-0.4); 32 | this.scene = new THREE.Scene(); 33 | 34 | let fog = new THREE.Fog( 35 | options.colors.background, 36 | options.length * 0.2, 37 | options.length * 500 38 | ); 39 | this.scene.fog = fog; 40 | this.fogUniforms = { 41 | fogColor: { type: "c", value: fog.color }, 42 | fogNear: { type: "f", value: fog.near }, 43 | fogFar: { type: "f", value: fog.far } 44 | }; 45 | this.clock = new THREE.Clock(); 46 | this.assets = {}; 47 | this.disposed = false; 48 | 49 | // Create Objects 50 | this.road = new Road(this, options); 51 | this.leftCarLights = new CarLights( 52 | this, 53 | options, 54 | options.colors.leftCars, 55 | options.movingAwaySpeed, 56 | new THREE.Vector2(0, 1 - options.carLightsFade) 57 | ); 58 | this.rightCarLights = new CarLights( 59 | this, 60 | options, 61 | options.colors.rightCars, 62 | options.movingCloserSpeed, 63 | new THREE.Vector2(1, 0 + options.carLightsFade) 64 | ); 65 | this.leftSticks = new LightsSticks(this, options); 66 | 67 | this.fovTarget = options.fov; 68 | 69 | this.speedUpTarget = 0; 70 | this.speedUp = 0; 71 | this.timeOffset = 0; 72 | 73 | // Binds 74 | this.tick = this.tick.bind(this); 75 | this.init = this.init.bind(this); 76 | this.setSize = this.setSize.bind(this); 77 | this.onMouseDown = this.onMouseDown.bind(this); 78 | this.onMouseUp = this.onMouseUp.bind(this); 79 | } 80 | initPasses() { 81 | this.renderPass = new POSTPROCESSING.RenderPass(this.scene, this.camera); 82 | this.bloomPass = new POSTPROCESSING.EffectPass( 83 | this.camera, 84 | new POSTPROCESSING.BloomEffect({ 85 | luminanceThreshold: 0.2, 86 | luminanceSmoothing: 0, 87 | resolutionScale: 1 88 | }) 89 | ); 90 | console.log(this.assets.smaa, this.camera); 91 | const smaaPass = new POSTPROCESSING.EffectPass( 92 | this.camera, 93 | new POSTPROCESSING.SMAAEffect( 94 | this.assets.smaa.search, 95 | this.assets.smaa.area, 96 | POSTPROCESSING.SMAAPreset.MEDIUM 97 | ) 98 | ); 99 | this.renderPass.renderToScreen = false; 100 | this.bloomPass.renderToScreen = false; 101 | smaaPass.renderToScreen = true; 102 | this.composer.addPass(this.renderPass); 103 | this.composer.addPass(this.bloomPass); 104 | this.composer.addPass(smaaPass); 105 | } 106 | loadAssets() { 107 | const assets = this.assets; 108 | return new Promise((resolve, reject) => { 109 | const manager = new THREE.LoadingManager(resolve); 110 | 111 | const searchImage = new Image(); 112 | const areaImage = new Image(); 113 | assets.smaa = {}; 114 | searchImage.addEventListener("load", function() { 115 | assets.smaa.search = this; 116 | manager.itemEnd("smaa-search"); 117 | }); 118 | 119 | areaImage.addEventListener("load", function() { 120 | assets.smaa.area = this; 121 | manager.itemEnd("smaa-area"); 122 | }); 123 | manager.itemStart("smaa-search"); 124 | manager.itemStart("smaa-area"); 125 | 126 | searchImage.src = POSTPROCESSING.SMAAEffect.searchImageDataURL; 127 | areaImage.src = POSTPROCESSING.SMAAEffect.areaImageDataURL; 128 | }); 129 | } 130 | init() { 131 | this.initPasses(); 132 | const options = this.options; 133 | this.road.init(); 134 | this.leftCarLights.init(); 135 | 136 | this.leftCarLights.mesh.position.setX( 137 | -options.roadWidth / 2 - options.islandWidth / 2 138 | ); 139 | this.rightCarLights.init(); 140 | this.rightCarLights.mesh.position.setX( 141 | options.roadWidth / 2 + options.islandWidth / 2 142 | ); 143 | this.leftSticks.init(); 144 | this.leftSticks.mesh.position.setX( 145 | -(options.roadWidth + options.islandWidth / 2) 146 | ); 147 | 148 | this.container.addEventListener("mousedown", this.onMouseDown); 149 | this.container.addEventListener("mouseup", this.onMouseUp); 150 | this.container.addEventListener("mouseout", this.onMouseUp); 151 | 152 | this.tick(); 153 | } 154 | onMouseDown(ev) { 155 | if (this.options.onSpeedUp) this.options.onSpeedUp(ev); 156 | this.fovTarget = this.options.fovSpeedUp; 157 | this.speedUpTarget = this.options.speedUp; 158 | } 159 | onMouseUp(ev) { 160 | if (this.options.onSlowDown) this.options.onSlowDown(ev); 161 | this.fovTarget = this.options.fov; 162 | this.speedUpTarget = 0; 163 | // this.speedupLerp = 0.1; 164 | } 165 | update(delta) { 166 | let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta); 167 | this.speedUp += lerp( 168 | this.speedUp, 169 | this.speedUpTarget, 170 | lerpPercentage, 171 | 0.00001 172 | ); 173 | this.timeOffset += this.speedUp * delta; 174 | 175 | let time = this.clock.elapsedTime + this.timeOffset; 176 | 177 | this.rightCarLights.update(time); 178 | this.leftCarLights.update(time); 179 | this.leftSticks.update(time); 180 | this.road.update(time); 181 | 182 | let updateCamera = false; 183 | let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage); 184 | if (fovChange !== 0) { 185 | this.camera.fov += fovChange * delta * 6; 186 | updateCamera = true; 187 | } 188 | 189 | if (this.options.distortion.getJS) { 190 | const distortion = this.options.distortion.getJS(0.025, time); 191 | 192 | this.camera.lookAt( 193 | new THREE.Vector3( 194 | this.camera.position.x + distortion.x, 195 | this.camera.position.y + distortion.y, 196 | this.camera.position.z + distortion.z 197 | ) 198 | ); 199 | updateCamera = true; 200 | } 201 | if (updateCamera) { 202 | this.camera.updateProjectionMatrix(); 203 | } 204 | } 205 | render(delta) { 206 | this.composer.render(delta); 207 | } 208 | dispose() { 209 | this.disposed = true; 210 | } 211 | setSize(width, height, updateStyles) { 212 | this.composer.setSize(width, height, updateStyles); 213 | } 214 | tick() { 215 | if (this.disposed || !this) return; 216 | if (resizeRendererToDisplaySize(this.renderer, this.setSize)) { 217 | const canvas = this.renderer.domElement; 218 | this.camera.aspect = canvas.clientWidth / canvas.clientHeight; 219 | this.camera.updateProjectionMatrix(); 220 | } 221 | const delta = this.clock.getDelta(); 222 | this.render(delta); 223 | this.update(delta); 224 | requestAnimationFrame(this.tick); 225 | } 226 | } 227 | 228 | const distortion_uniforms = { 229 | uDistortionX: new THREE.Uniform(new THREE.Vector2(80, 3)), 230 | uDistortionY: new THREE.Uniform(new THREE.Vector2(-40, 2.5)) 231 | }; 232 | 233 | const distortion_vertex = ` 234 | #define PI 3.14159265358979 235 | uniform vec2 uDistortionX; 236 | uniform vec2 uDistortionY; 237 | 238 | float nsin(float val){ 239 | return sin(val) * 0.5+0.5; 240 | } 241 | vec3 getDistortion(float progress){ 242 | progress = clamp(progress, 0.,1.); 243 | float xAmp = uDistortionX.r; 244 | float xFreq = uDistortionX.g; 245 | float yAmp = uDistortionY.r; 246 | float yFreq = uDistortionY.g; 247 | return vec3( 248 | xAmp * nsin(progress* PI * xFreq - PI / 2. ) , 249 | yAmp * nsin(progress * PI *yFreq - PI / 2. ) , 250 | 0. 251 | ); 252 | } 253 | `; 254 | 255 | const random = base => { 256 | if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0]; 257 | return Math.random() * base; 258 | }; 259 | const pickRandom = arr => { 260 | if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)]; 261 | return arr; 262 | }; 263 | function lerp(current, target, speed = 0.1, limit = 0.001) { 264 | let change = (target - current) * speed; 265 | if (Math.abs(change) < limit) { 266 | change = target - current; 267 | } 268 | return change; 269 | } 270 | class CarLights { 271 | constructor(webgl, options, colors, speed, fade) { 272 | this.webgl = webgl; 273 | this.options = options; 274 | this.colors = colors; 275 | this.speed = speed; 276 | this.fade = fade; 277 | } 278 | init() { 279 | const options = this.options; 280 | // Curve with length 1 281 | let curve = new THREE.LineCurve3( 282 | new THREE.Vector3(0, 0, 0), 283 | new THREE.Vector3(0, 0, -1) 284 | ); 285 | // Tube with radius = 1 286 | let geometry = new THREE.TubeBufferGeometry(curve, 40, 1, 8, false); 287 | 288 | let instanced = new THREE.InstancedBufferGeometry().copy(geometry); 289 | instanced.maxInstancedCount = options.lightPairsPerRoadWay * 2; 290 | 291 | let laneWidth = options.roadWidth / options.lanesPerRoad; 292 | 293 | let aOffset = []; 294 | let aMetrics = []; 295 | let aColor = []; 296 | 297 | let colors = this.colors; 298 | if (Array.isArray(colors)) { 299 | colors = colors.map(c => new THREE.Color(c)); 300 | } else { 301 | colors = new THREE.Color(colors); 302 | } 303 | 304 | for (let i = 0; i < options.lightPairsPerRoadWay; i++) { 305 | let radius = random(options.carLightsRadius); 306 | let length = random(options.carLightsLength); 307 | let speed = random(this.speed); 308 | 309 | let carLane = i % 3; 310 | let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2; 311 | 312 | let carWidth = random(options.carWidthPercentage) * laneWidth; 313 | // Drunk Driving 314 | let carShiftX = random(options.carShiftX) * laneWidth; 315 | // Both lights share same shiftX and lane; 316 | laneX += carShiftX; 317 | 318 | let offsetY = random(options.carFloorSeparation) + radius * 1.3; 319 | 320 | let offsetZ = -random(options.length); 321 | 322 | aOffset.push(laneX - carWidth / 2); 323 | aOffset.push(offsetY); 324 | aOffset.push(offsetZ); 325 | 326 | aOffset.push(laneX + carWidth / 2); 327 | aOffset.push(offsetY); 328 | aOffset.push(offsetZ); 329 | 330 | aMetrics.push(radius); 331 | aMetrics.push(length); 332 | aMetrics.push(speed); 333 | 334 | aMetrics.push(radius); 335 | aMetrics.push(length); 336 | aMetrics.push(speed); 337 | 338 | let color = pickRandom(colors); 339 | aColor.push(color.r); 340 | aColor.push(color.g); 341 | aColor.push(color.b); 342 | 343 | aColor.push(color.r); 344 | aColor.push(color.g); 345 | aColor.push(color.b); 346 | } 347 | instanced.addAttribute( 348 | "aOffset", 349 | new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false) 350 | ); 351 | instanced.addAttribute( 352 | "aMetrics", 353 | new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false) 354 | ); 355 | instanced.addAttribute( 356 | "aColor", 357 | new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false) 358 | ); 359 | let material = new THREE.ShaderMaterial({ 360 | fragmentShader: carLightsFragment, 361 | vertexShader: carLightsVertex, 362 | transparent: true, 363 | uniforms: Object.assign( 364 | { 365 | // uColor: new THREE.Uniform(new THREE.Color(this.color)), 366 | uTime: new THREE.Uniform(0), 367 | uTravelLength: new THREE.Uniform(options.length), 368 | uFade: new THREE.Uniform(this.fade) 369 | }, 370 | this.webgl.fogUniforms, 371 | options.distortion.uniforms 372 | ) 373 | }); 374 | material.onBeforeCompile = shader => { 375 | shader.vertexShader = shader.vertexShader.replace( 376 | "#include ", 377 | options.distortion.getDistortion 378 | ); 379 | console.log(shader.vertex); 380 | }; 381 | let mesh = new THREE.Mesh(instanced, material); 382 | mesh.frustumCulled = false; 383 | this.webgl.scene.add(mesh); 384 | this.mesh = mesh; 385 | } 386 | update(time) { 387 | this.mesh.material.uniforms.uTime.value = time; 388 | } 389 | } 390 | 391 | const carLightsFragment = ` 392 | 393 | #define USE_FOG; 394 | ${THREE.ShaderChunk["fog_pars_fragment"]} 395 | varying vec3 vColor; 396 | varying vec2 vUv; 397 | uniform vec2 uFade; 398 | void main() { 399 | vec3 color = vec3(vColor); 400 | float fadeStart = 0.4; 401 | float maxFade = 0.; 402 | float alpha = 1.; 403 | 404 | alpha = smoothstep(uFade.x, uFade.y, vUv.x); 405 | gl_FragColor = vec4(color,alpha); 406 | if (gl_FragColor.a < 0.0001) discard; 407 | ${THREE.ShaderChunk["fog_fragment"]} 408 | } 409 | `; 410 | 411 | const carLightsVertex = ` 412 | #define USE_FOG; 413 | ${THREE.ShaderChunk["fog_pars_vertex"]} 414 | attribute vec3 aOffset; 415 | attribute vec3 aMetrics; 416 | attribute vec3 aColor; 417 | 418 | 419 | 420 | uniform float uTravelLength; 421 | uniform float uTime; 422 | uniform float uSpeed; 423 | 424 | varying vec2 vUv; 425 | varying vec3 vColor; 426 | #include 427 | 428 | void main() { 429 | vec3 transformed = position.xyz; 430 | float radius = aMetrics.r; 431 | float myLength = aMetrics.g; 432 | float speed = aMetrics.b; 433 | 434 | transformed.xy *= radius ; 435 | transformed.z *= myLength; 436 | 437 | // Add my length to make sure it loops after the lights hits the end 438 | transformed.z += myLength-mod( uTime *speed + aOffset.z, uTravelLength); 439 | transformed.xy += aOffset.xy; 440 | 441 | 442 | float progress = abs(transformed.z / uTravelLength); 443 | transformed.xyz += getDistortion(progress); 444 | 445 | vec4 mvPosition = modelViewMatrix * vec4(transformed,1.); 446 | gl_Position = projectionMatrix * mvPosition; 447 | vUv = uv; 448 | vColor = aColor; 449 | ${THREE.ShaderChunk["fog_vertex"]} 450 | }`; 451 | 452 | class LightsSticks { 453 | constructor(webgl, options) { 454 | this.webgl = webgl; 455 | this.options = options; 456 | } 457 | init() { 458 | const options = this.options; 459 | const geometry = new THREE.PlaneBufferGeometry(1, 1); 460 | let instanced = new THREE.InstancedBufferGeometry().copy(geometry); 461 | let totalSticks = options.totalSideLightSticks; 462 | instanced.maxInstancedCount = totalSticks; 463 | 464 | let stickoffset = options.length / (totalSticks - 1); 465 | const aOffset = []; 466 | const aColor = []; 467 | const aMetrics = []; 468 | 469 | let colors = options.colors.sticks; 470 | if (Array.isArray(colors)) { 471 | colors = colors.map(c => new THREE.Color(c)); 472 | } else { 473 | colors = new THREE.Color(colors); 474 | } 475 | 476 | for (let i = 0; i < totalSticks; i++) { 477 | let width = random(options.lightStickWidth); 478 | let height = random(options.lightStickHeight); 479 | aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random()); 480 | 481 | let color = pickRandom(colors); 482 | aColor.push(color.r); 483 | aColor.push(color.g); 484 | aColor.push(color.b); 485 | 486 | aMetrics.push(width); 487 | aMetrics.push(height); 488 | } 489 | instanced.addAttribute( 490 | "aOffset", 491 | new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false) 492 | ); 493 | instanced.addAttribute( 494 | "aColor", 495 | new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false) 496 | ); 497 | instanced.addAttribute( 498 | "aMetrics", 499 | new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false) 500 | ); 501 | const material = new THREE.ShaderMaterial({ 502 | fragmentShader: sideSticksFragment, 503 | vertexShader: sideSticksVertex, 504 | // This ones actually need double side 505 | side: THREE.DoubleSide, 506 | uniforms: Object.assign( 507 | { 508 | uTravelLength: new THREE.Uniform(options.length), 509 | uTime: new THREE.Uniform(0) 510 | }, 511 | this.webgl.fogUniforms, 512 | options.distortion.uniforms 513 | ) 514 | }); 515 | 516 | material.onBeforeCompile = shader => { 517 | shader.vertexShader = shader.vertexShader.replace( 518 | "#include ", 519 | options.distortion.getDistortion 520 | ); 521 | }; 522 | 523 | const mesh = new THREE.Mesh(instanced, material); 524 | // The object is behind the camera before the vertex shader 525 | mesh.frustumCulled = false; 526 | // mesh.position.y = options.lightStickHeight / 2; 527 | this.webgl.scene.add(mesh); 528 | this.mesh = mesh; 529 | } 530 | update(time) { 531 | this.mesh.material.uniforms.uTime.value = time; 532 | } 533 | } 534 | 535 | const sideSticksVertex = ` 536 | #define USE_FOG; 537 | ${THREE.ShaderChunk["fog_pars_vertex"]} 538 | attribute float aOffset; 539 | attribute vec3 aColor; 540 | 541 | attribute vec2 aMetrics; 542 | 543 | uniform float uTravelLength; 544 | uniform float uTime; 545 | 546 | varying vec3 vColor; 547 | mat4 rotationY( in float angle ) { 548 | return mat4( cos(angle), 0, sin(angle), 0, 549 | 0, 1.0, 0, 0, 550 | -sin(angle), 0, cos(angle), 0, 551 | 0, 0, 0, 1); 552 | } 553 | 554 | 555 | 556 | #include 557 | void main(){ 558 | vec3 transformed = position.xyz; 559 | float width = aMetrics.x; 560 | float height = aMetrics.y; 561 | 562 | transformed.xy *= vec2(width,height); 563 | float time = mod(uTime * 60. *2. + aOffset , uTravelLength); 564 | 565 | transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz; 566 | 567 | transformed.z += - uTravelLength + time; 568 | 569 | 570 | float progress = abs(transformed.z / uTravelLength); 571 | transformed.xyz += getDistortion(progress); 572 | 573 | transformed.y += height /2.; 574 | transformed.x += -width/2.; 575 | vec4 mvPosition = modelViewMatrix * vec4(transformed,1.); 576 | gl_Position = projectionMatrix * mvPosition; 577 | vColor = aColor; 578 | ${THREE.ShaderChunk["fog_vertex"]} 579 | } 580 | `; 581 | const sideSticksFragment = ` 582 | #define USE_FOG; 583 | ${THREE.ShaderChunk["fog_pars_fragment"]} 584 | varying vec3 vColor; 585 | void main(){ 586 | vec3 color = vec3(vColor); 587 | gl_FragColor = vec4(color,1.); 588 | ${THREE.ShaderChunk["fog_fragment"]} 589 | } 590 | `; 591 | 592 | class Road { 593 | constructor(webgl, options) { 594 | this.webgl = webgl; 595 | this.options = options; 596 | 597 | this.uTime = new THREE.Uniform(0); 598 | } 599 | createIsland() { 600 | const options = this.options; 601 | let segments = 100; 602 | } 603 | // Side = 0 center, = 1 right = -1 left 604 | createPlane(side, width, isRoad) { 605 | const options = this.options; 606 | let segments = 100; 607 | const geometry = new THREE.PlaneBufferGeometry( 608 | isRoad ? options.roadWidth : options.islandWidth, 609 | options.length, 610 | 20, 611 | segments 612 | ); 613 | let uniforms = { 614 | uTravelLength: new THREE.Uniform(options.length), 615 | uColor: new THREE.Uniform( 616 | new THREE.Color( 617 | isRoad ? options.colors.roadColor : options.colors.islandColor 618 | ) 619 | ), 620 | uTime: this.uTime 621 | }; 622 | if (isRoad) { 623 | uniforms = Object.assign(uniforms, { 624 | uLanes: new THREE.Uniform(options.lanesPerRoad), 625 | uBrokenLinesColor: new THREE.Uniform( 626 | new THREE.Color(options.colors.brokenLines) 627 | ), 628 | uShoulderLinesColor: new THREE.Uniform( 629 | new THREE.Color(options.colors.shoulderLines) 630 | ), 631 | uShoulderLinesWidthPercentage: new THREE.Uniform( 632 | options.shoulderLinesWidthPercentage 633 | ), 634 | uBrokenLinesLengthPercentage: new THREE.Uniform( 635 | options.brokenLinesLengthPercentage 636 | ), 637 | uBrokenLinesWidthPercentage: new THREE.Uniform( 638 | options.brokenLinesWidthPercentage 639 | ) 640 | }); 641 | } 642 | const material = new THREE.ShaderMaterial({ 643 | fragmentShader: isRoad ? roadFragment : islandFragment, 644 | vertexShader: roadVertex, 645 | side: THREE.DoubleSide, 646 | uniforms: Object.assign( 647 | uniforms, 648 | this.webgl.fogUniforms, 649 | options.distortion.uniforms 650 | ) 651 | }); 652 | 653 | material.onBeforeCompile = shader => { 654 | shader.vertexShader = shader.vertexShader.replace( 655 | "#include ", 656 | options.distortion.getDistortion 657 | ); 658 | }; 659 | const mesh = new THREE.Mesh(geometry, material); 660 | mesh.rotation.x = -Math.PI / 2; 661 | // Push it half further away 662 | mesh.position.z = -options.length / 2; 663 | mesh.position.x += 664 | (this.options.islandWidth / 2 + options.roadWidth / 2) * side; 665 | this.webgl.scene.add(mesh); 666 | 667 | return mesh; 668 | } 669 | init() { 670 | this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true); 671 | this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true); 672 | this.island = this.createPlane(0, this.options.islandWidth, false); 673 | } 674 | update(time) { 675 | this.uTime.value = time; 676 | } 677 | } 678 | 679 | const roadBaseFragment = ` 680 | #define USE_FOG; 681 | varying vec2 vUv; 682 | uniform vec3 uColor; 683 | uniform float uTime; 684 | #include 685 | ${THREE.ShaderChunk["fog_pars_fragment"]} 686 | void main() { 687 | vec2 uv = vUv; 688 | vec3 color = vec3(uColor); 689 | 690 | #include 691 | 692 | gl_FragColor = vec4(color,1.); 693 | ${THREE.ShaderChunk["fog_fragment"]} 694 | } 695 | `; 696 | const islandFragment = roadBaseFragment 697 | .replace("#include ", "") 698 | .replace("#include ", ""); 699 | const roadMarkings_vars = ` 700 | uniform float uLanes; 701 | uniform vec3 uBrokenLinesColor; 702 | uniform vec3 uShoulderLinesColor; 703 | uniform float uShoulderLinesWidthPercentage; 704 | uniform float uBrokenLinesWidthPercentage; 705 | uniform float uBrokenLinesLengthPercentage; 706 | highp float random(vec2 co) 707 | { 708 | highp float a = 12.9898; 709 | highp float b = 78.233; 710 | highp float c = 43758.5453; 711 | highp float dt= dot(co.xy ,vec2(a,b)); 712 | highp float sn= mod(dt,3.14); 713 | return fract(sin(sn) * c); 714 | } 715 | `; 716 | const roadMarkings_fragment = ` 717 | 718 | uv.y = mod(uv.y + uTime * 0.1,1.); 719 | float brokenLineWidth = 1. / uLanes * uBrokenLinesWidthPercentage; 720 | // How much % of the lane's space is empty 721 | float laneEmptySpace = 1. - uBrokenLinesLengthPercentage; 722 | 723 | // Horizontal * vertical offset 724 | float brokenLines = step(1.-brokenLineWidth * uLanes,fract(uv.x * uLanes)) * step(laneEmptySpace, fract(uv.y * 100.)) ; 725 | // Remove right-hand lines on the right-most lane 726 | brokenLines *= step(uv.x * uLanes,uLanes-1.); 727 | color = mix(color, uBrokenLinesColor, brokenLines); 728 | 729 | 730 | float shoulderLinesWidth = 1. / uLanes * uShoulderLinesWidthPercentage; 731 | float shoulderLines = step(1.-shoulderLinesWidth, uv.x) + step(uv.x, shoulderLinesWidth); 732 | color = mix(color, uBrokenLinesColor, shoulderLines); 733 | 734 | vec2 noiseFreq = vec2(4., 7000.); 735 | float roadNoise = random( floor(uv * noiseFreq)/noiseFreq ) * 0.02 - 0.01; 736 | color += roadNoise; 737 | `; 738 | const roadFragment = roadBaseFragment 739 | .replace("#include ", roadMarkings_fragment) 740 | .replace("#include ", roadMarkings_vars); 741 | 742 | const roadVertex = ` 743 | #define USE_FOG; 744 | uniform float uTime; 745 | ${THREE.ShaderChunk["fog_pars_vertex"]} 746 | 747 | uniform float uTravelLength; 748 | 749 | varying vec2 vUv; 750 | #include 751 | void main() { 752 | vec3 transformed = position.xyz; 753 | 754 | vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength); 755 | transformed.x += distortion.x; 756 | transformed.z += distortion.y; 757 | transformed.y += -1.*distortion.z; 758 | 759 | vec4 mvPosition = modelViewMatrix * vec4(transformed,1.); 760 | gl_Position = projectionMatrix * mvPosition; 761 | vUv = uv; 762 | 763 | ${THREE.ShaderChunk["fog_vertex"]} 764 | }`; 765 | 766 | function resizeRendererToDisplaySize(renderer, setSize) { 767 | const canvas = renderer.domElement; 768 | const width = canvas.clientWidth; 769 | const height = canvas.clientHeight; 770 | const needResize = canvas.width !== width || canvas.height !== height; 771 | if (needResize) { 772 | setSize(width, height, false); 773 | } 774 | return needResize; 775 | } 776 | --------------------------------------------------------------------------------