├── .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 | 
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 |
--------------------------------------------------------------------------------