1
+ import * as THREE from 'three' ;
2
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' ;
3
+ import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js" ;
4
+ import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js" ;
5
+ import { GammaCorrectionShader } from "three/examples/jsm/shaders/GammaCorrectionShader.js" ;
6
+ import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js" ;
7
+ import { RGBShiftShader } from "three/examples/jsm/shaders/RGBShiftShader.js" ;
8
+
9
+ // toggle-dark-mode
10
+ async function main ( ) {
11
+ const canvas = document . querySelector ( 'canvas.webgl' ) ;
12
+
13
+ // Scene
14
+ const scene = new THREE . Scene ( ) ;
15
+
16
+ // Fog
17
+ const fog = new THREE . Fog ( "#1f2937" , 1 , 2.5 ) ;
18
+ scene . fog = fog ;
19
+
20
+ // Objects
21
+ const planeSize = { w : 24 , h : 48 } ;
22
+ const planeSizeUnits = { w : 1 , h : 2 } ;
23
+
24
+ // TODO: generate noise map
25
+ // const noiseData = new Uint8Array(planeSize.w * planeSize.h);
26
+ // for (let i = 0; i < noiseData.length; i++) {
27
+ // const v = Math.floor(Math.random() * 255);
28
+ // noiseData[i] = v;
29
+ // }
30
+ // const noiseTexture = new THREE.DataTexture({
31
+ // data: noiseData,
32
+ // width: planeSize.w,
33
+ // height: planeSize.h,
34
+ // format: THREE.LuminanceFormat
35
+ // });
36
+ // noiseTexture.needsUpdate = true;
37
+
38
+ const textureLoader = new THREE . TextureLoader ( ) ;
39
+ const noiseTexture = await textureLoader . loadAsync ( '/displacement.png' ) ;
40
+
41
+ const geometry = new THREE . PlaneGeometry ( planeSizeUnits . w , planeSizeUnits . h , planeSize . w , planeSize . h ) ;
42
+ let uniforms = {
43
+ segU : { value : planeSize . w } ,
44
+ segV : { value : planeSize . h } ,
45
+ isWire : { value : false } ,
46
+ wireWidthFactor : { value : 2 } ,
47
+ wireColor : { value : new THREE . Color ( 0xcccccc ) } ,
48
+ dmap : { value : noiseTexture } ,
49
+ scroll : { value : 0.0 }
50
+ }
51
+ const material = new THREE . MeshStandardMaterial ( {
52
+ metalness : 1 ,
53
+ roughness : 0.3 ,
54
+ onBeforeCompile : shader => {
55
+ shader . uniforms . segU = uniforms . segU ;
56
+ shader . uniforms . segV = uniforms . segV ;
57
+ shader . uniforms . wireColor = uniforms . wireColor ;
58
+ shader . uniforms . isWire = uniforms . isWire ;
59
+ shader . uniforms . wireWidthFactor = uniforms . wireWidthFactor ;
60
+ shader . uniforms . dmap = uniforms . dmap ;
61
+ shader . uniforms . scroll = uniforms . scroll ;
62
+ shader . fragmentShader = `
63
+ uniform float segU;
64
+ uniform float segV;
65
+ uniform vec3 wireColor;
66
+ uniform float isWire;
67
+ uniform float wireWidthFactor;
68
+
69
+ ${ shader . fragmentShader }
70
+ ` . replace (
71
+ `#include <dithering_fragment>` ,
72
+ `
73
+ #include <dithering_fragment>
74
+
75
+ // http://madebyevan.com/shaders/grid/
76
+ vec2 coord = vUv * vec2(segU, segV);
77
+
78
+ vec2 grid = abs(fract(coord - 0.5) - 0.5) / fwidth(coord);
79
+ float line = min(grid.x, grid.y) / wireWidthFactor;
80
+ line = 1.0 - min(line, 1.0);
81
+
82
+ if (isWire > 0.5 && line < 0.5) discard;
83
+ if (isWire > 0.5) gl_FragColor = vec4(0);
84
+ float fade = 1.0 - vUv.y;
85
+ gl_FragColor = mix(gl_FragColor, vec4(wireColor, 1.0), line * fade * fade);
86
+ `
87
+ ) ;
88
+ console . log ( shader . fragmentShader ) ;
89
+
90
+ shader . vertexShader = `
91
+ uniform sampler2D dmap;
92
+ uniform float scroll;
93
+ ${ shader . vertexShader }
94
+ ` . replace (
95
+ `#include <begin_vertex>` ,
96
+ `
97
+ #include <begin_vertex>
98
+ // offset the plane textures in the same increments as scrolling moves the plane
99
+ // but also shift the sampling position by 1 square after every increment
100
+ float offset = floor(scroll * ${ planeSize . h / planeSizeUnits . h } .0) / ${ planeSize . h } .0;
101
+ transformed.z += texture(dmap, vec2(uv.x, fract(uv.y + offset))).r * 0.4;
102
+ `
103
+ ) ;
104
+ console . log ( shader . vertexShader )
105
+ } ,
106
+ } ) ;
107
+ material . defines = { 'USE_UV' : '' } ;
108
+
109
+ const plane = new THREE . Mesh ( geometry , material ) ;
110
+
111
+ // Here we position our plane flat in front of the camera
112
+ plane . rotation . x = - Math . PI * 0.5 ;
113
+ plane . position . y = 0.0 ;
114
+ plane . position . z = 0.0 ;
115
+ scene . add ( plane ) ;
116
+
117
+ // Light
118
+ // Ambient Light
119
+ const ambientLight = new THREE . AmbientLight ( "#ffffff" , 10 ) ;
120
+ scene . add ( ambientLight ) ;
121
+
122
+ // Right Spotlight aiming to the left
123
+ const spotlight = new THREE . SpotLight ( "#5bcffa" , 20 , 25 , Math . PI * 0.1 , 0.25 ) ;
124
+ spotlight . position . set ( 0.5 , 0.75 , 2.2 ) ;
125
+ // Target the spotlight to a specific point to the left of the scene
126
+ spotlight . target . position . x = - 0.25 ;
127
+ spotlight . target . position . y = 0.25 ;
128
+ spotlight . target . position . z = 0.25 ;
129
+ scene . add ( spotlight ) ;
130
+ scene . add ( spotlight . target ) ;
131
+
132
+ // Left Spotlight aiming to the right
133
+ const spotlight2 = new THREE . SpotLight ( "#f5abb9" , 20 , 25 , Math . PI * 0.1 , 0.25 ) ;
134
+ spotlight2 . position . set ( - 0.5 , 0.75 , 2.2 ) ;
135
+ // Target the spotlight to a specific point to the right side of the scene
136
+ spotlight2 . target . position . x = 0.25 ;
137
+ spotlight2 . target . position . y = 0.25 ;
138
+ spotlight2 . target . position . z = 0.25 ;
139
+ scene . add ( spotlight2 ) ;
140
+ scene . add ( spotlight2 . target ) ;
141
+
142
+ // Sizes
143
+ const sizes = {
144
+ width : window . innerWidth ,
145
+ height : window . innerHeight ,
146
+ } ;
147
+
148
+ // Camera
149
+ const camera = new THREE . PerspectiveCamera (
150
+ // field of view
151
+ 75 ,
152
+ // aspect ratio
153
+ sizes . width / sizes . height ,
154
+ // near plane: it's low since we want our mesh to be visible even from very close
155
+ 0.01 ,
156
+ // far plane: how far we're rendering
157
+ 20
158
+ ) ;
159
+
160
+ // Position the camera a bit higher on the y axis and a bit further back from the center
161
+ camera . position . x = 0 ;
162
+ camera . position . y = 0.1 ;
163
+ camera . position . z = 1.1 ;
164
+
165
+ // Controls
166
+ // These are custom controls I like using for dev: we can drag/rotate the scene easily
167
+ const controls = new OrbitControls ( camera , canvas ) ;
168
+ controls . enableDamping = true ;
169
+
170
+ // Renderer
171
+ const renderer = new THREE . WebGLRenderer ( {
172
+ canvas : canvas
173
+ } ) ;
174
+ renderer . setSize ( sizes . width , sizes . height ) ;
175
+ renderer . setPixelRatio ( Math . min ( window . devicePixelRatio , 2 ) ) ;
176
+ renderer . setClearColor ( 0x1f2937 , 0 ) ;
177
+
178
+ // Post Processing
179
+ const effectComposer = new EffectComposer ( renderer ) ;
180
+ effectComposer . setSize ( sizes . width , sizes . height ) ;
181
+ effectComposer . setPixelRatio ( Math . min ( window . devicePixelRatio , 2 ) ) ;
182
+
183
+ const renderPass = new RenderPass ( scene , camera ) ;
184
+ effectComposer . addPass ( renderPass ) ;
185
+
186
+ const rgbShiftPass = new ShaderPass ( RGBShiftShader ) ;
187
+ rgbShiftPass . uniforms [ "amount" ] . value = 0.0015 ;
188
+
189
+ effectComposer . addPass ( rgbShiftPass ) ;
190
+
191
+ const gammaCorrectionPass = new ShaderPass ( GammaCorrectionShader ) ;
192
+ effectComposer . addPass ( gammaCorrectionPass ) ;
193
+
194
+ // Event listener to handle screen resize
195
+ window . addEventListener ( 'resize' , ( ) => {
196
+ // Update sizes
197
+ sizes . width = window . innerWidth ;
198
+ sizes . height = window . innerHeight ;
199
+
200
+ // Update camera's aspect ratio and projection matrix
201
+ camera . aspect = sizes . width / sizes . height ;
202
+ camera . updateProjectionMatrix ( ) ;
203
+
204
+ // Update renderer
205
+ renderer . setSize ( sizes . width , sizes . height ) ;
206
+ // Note: We set the pixel ratio of the renderer to at most 2
207
+ renderer . setPixelRatio ( Math . min ( window . devicePixelRatio , 2 ) ) ;
208
+ effectComposer . render ( ) ;
209
+ } ) ;
210
+
211
+ // const animate = () => {
212
+ // effectComposer.render();
213
+ // };
214
+ // renderer.setAnimationLoop(animate);
215
+
216
+ const setupColours = ( ) => {
217
+ const darkMode = document . documentElement . classList . contains ( 'dark' ) ;
218
+ fog . color = new THREE . Color ( darkMode ? 0x1f2937 : 0xffffff ) ;
219
+ uniforms . wireColor . value = new THREE . Color ( darkMode ? 0x9e9e9e : 0xffffff ) ;
220
+ material . roughness = darkMode ? 0.30 : 0.75 ;
221
+ const article = document . querySelector ( 'article' ) ;
222
+ if ( article ) {
223
+ article . style . color = darkMode ? '#ffffff' : '#000000' ;
224
+ article . style . textShadow = `0 0 0.5em ${ darkMode ? '#000000' : '#ffffff' } ` ;
225
+ }
226
+ } ;
227
+
228
+ setupColours ( ) ;
229
+ effectComposer . render ( ) ;
230
+
231
+ const darkmodeToggle = document . querySelector ( '.toggle-dark-mode' ) ;
232
+ if ( darkmodeToggle ) {
233
+ darkmodeToggle . addEventListener ( 'click' , ( ) => {
234
+ setupColours ( ) ;
235
+ effectComposer . render ( ) ;
236
+ } ) ;
237
+ }
238
+
239
+ document . body . onscroll = ( ) => {
240
+ uniforms . scroll . value = (
241
+ ( document . documentElement . scrollTop || document . body . scrollTop ) / (
242
+ ( document . documentElement . scrollHeight || document . body . scrollHeight )
243
+ - document . documentElement . clientHeight
244
+ )
245
+ ) ;
246
+ // move the plane forward by 1 unit when scrolled all the way down
247
+ const squaresPerUnit = planeSize . h / planeSizeUnits . h ;
248
+ plane . position . z = ( ( uniforms . scroll . value * squaresPerUnit ) % 1.0 ) / squaresPerUnit ;
249
+ effectComposer . render ( ) ;
250
+ } ;
251
+ }
252
+
253
+ main ( ) ;
0 commit comments