|
29 | 29 |
|
30 | 30 | import * as THREE from 'three';
|
31 | 31 |
|
32 |
| - import { abs, color, div, float, Fn, instancedBufferAttribute, materialColor, min, normalWorldGeometry, pass, positionGeometry, positionLocal, reflector, screenUV, sin, sub, texture, time, uniform, uv, varyingProperty } from 'three/tsl'; |
| 32 | + import { abs, blendOverlay, color, div, float, Fn, instancedBufferAttribute, materialColor, min, mrt, normalView, normalWorldGeometry, output, pass, positionGeometry, positionLocal, reflector, screenUV, sin, sub, texture, time, uniform, uv, varyingProperty, vec3 } from 'three/tsl'; |
| 33 | + import { ao } from 'three/addons/tsl/display/GTAONode.js'; |
| 34 | + import { denoise } from 'three/addons/tsl/display/DenoiseNode.js'; |
33 | 35 | import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';
|
34 | 36 |
|
35 | 37 | import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
44 | 46 |
|
45 | 47 | // below uniforms will be animated via TWEEN.js
|
46 | 48 |
|
47 |
| - const uniformLife = uniform( 0 ); |
48 | 49 | const uniformEffector1 = uniform( - 0.2 );
|
49 | 50 | const uniformEffector2 = uniform( - 0.2 );
|
50 | 51 |
|
|
56 | 57 | camera.position.set( 4, 2, 4 );
|
57 | 58 |
|
58 | 59 | scene = new THREE.Scene();
|
59 |
| - scene.fog = new THREE.Fog( 0x0487e2, 7, 25 ); |
60 |
| - scene.backgroundNode = normalWorldGeometry.y.mix( color( 0x0487e2 ), color( 0x0066ff ) ); |
| 60 | + scene.fog = new THREE.Fog( 0x4195a4, 1, 25 ); |
| 61 | + scene.backgroundNode = normalWorldGeometry.y.mix( color( 0x4195a4 ), color( 0x0066ff ) ); |
61 | 62 | camera.lookAt( 0, 1, 0 );
|
62 | 63 |
|
63 |
| - const sunLight = new THREE.DirectionalLight( 0xFFE499, 3 ); |
64 |
| - sunLight.position.set( 7, 3, 7 ); |
| 64 | + const sunLight = new THREE.DirectionalLight( 0xFFE499, 2 ); |
| 65 | + sunLight.position.set( 7, 5, 7 ); |
| 66 | + sunLight.castShadow = true; |
| 67 | + sunLight.shadow.camera.zoom = 1.5; |
| 68 | + sunLight.shadow.mapSize.set( 1024, 1024 ); |
| 69 | + scene.add( sunLight ); |
65 | 70 |
|
66 |
| - const waterAmbientLight = new THREE.HemisphereLight( 0x333366, 0x74ccf4, 3 ); |
67 |
| - const skyAmbientLight = new THREE.HemisphereLight( 0x74ccf4, 0, 1 ); |
| 71 | + const backLight = new THREE.DirectionalLight( 0x0487e2, 0.5 ); |
| 72 | + backLight.position.set( 7, - 5, 7 ); |
| 73 | + scene.add( backLight ); |
68 | 74 |
|
69 |
| - scene.add( sunLight ); |
70 |
| - scene.add( skyAmbientLight ); |
71 |
| - scene.add( waterAmbientLight ); |
72 | 75 |
|
73 | 76 | // textures
|
74 | 77 |
|
|
86 | 89 | // tree
|
87 | 90 |
|
88 | 91 | const treeMesh = createTreeMesh();
|
| 92 | + treeMesh.castShadow = true; |
| 93 | + treeMesh.receiveShadow = true; |
89 | 94 | scene.add( treeMesh );
|
90 | 95 |
|
91 | 96 | // floor
|
|
103 | 108 |
|
104 | 109 | const floor = new THREE.Mesh( new THREE.BoxGeometry( 50, .001, 50 ), floorMaterial );
|
105 | 110 | floor.position.set( 0, 0, 0 );
|
| 111 | + floor.receiveShadow = true; |
106 | 112 | scene.add( floor );
|
107 | 113 |
|
108 | 114 | // renderer
|
|
111 | 117 | renderer.setPixelRatio( window.devicePixelRatio );
|
112 | 118 | renderer.setSize( window.innerWidth, window.innerHeight );
|
113 | 119 | renderer.setAnimationLoop( animate );
|
| 120 | + renderer.shadowMap.enabled = true; |
| 121 | + renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
| 122 | + renderer.toneMapping = THREE.ACESFilmicToneMapping; |
114 | 123 | document.body.appendChild( renderer.domElement );
|
115 | 124 |
|
116 | 125 | stats = new Stats();
|
|
137 | 146 | const scenePassColorBlurred = gaussianBlur( scenePassColor );
|
138 | 147 | scenePassColorBlurred.directionNode = scenePassDepth;
|
139 | 148 |
|
140 |
| - const vignette = screenUV.distance( .5 ).mul( 1.35 ).clamp().oneMinus(); |
| 149 | + const vignette = screenUV.distance( .5 ).mul( 1.25 ).clamp().oneMinus().sub( 0.2); |
141 | 150 |
|
142 | 151 | postProcessing = new THREE.PostProcessing( renderer );
|
143 |
| - postProcessing.outputNode = scenePassColorBlurred.mul( vignette ); |
| 152 | + postProcessing.outputNode = blendOverlay( scenePassColorBlurred, vignette ); |
144 | 153 |
|
145 | 154 | //
|
146 | 155 |
|
147 | 156 | window.addEventListener( 'resize', onWindowResize );
|
148 | 157 |
|
149 |
| - // |
| 158 | + const effectTween = new TWEEN.Tween( uniformEffector1 ) |
| 159 | + .to( { value: 1.2 }, 3000 ) |
| 160 | + .delay( 800 ) |
| 161 | + .repeat( Infinity ) |
| 162 | + .easing( TWEEN.Easing.Sinusoidal.InOut ) |
| 163 | + .start(); |
150 | 164 |
|
151 |
| - startTweens(); |
| 165 | + const effect2Tween = new TWEEN.Tween( uniformEffector2 ) |
| 166 | + .to( { value: 1.2 }, 3000 ) |
| 167 | + .repeat( Infinity ) |
| 168 | + .easing( TWEEN.Easing.Sinusoidal.InOut ) |
| 169 | + .start(); |
152 | 170 |
|
153 | 171 | }
|
154 | 172 |
|
|
173 | 191 |
|
174 | 192 | }
|
175 | 193 |
|
176 |
| - function startTweens() { |
177 |
| - |
178 |
| - const lifeTween = new TWEEN.Tween( uniformLife ) |
179 |
| - .to( { value: 1 }, 5000 ) |
180 |
| - .easing( TWEEN.Easing.Bounce.Out ); |
181 |
| - lifeTween.start(); |
182 |
| - |
183 |
| - const effectTween = new TWEEN.Tween( uniformEffector1 ) |
184 |
| - .to( { value: 1.2 }, 2500 ) |
185 |
| - .delay( 3000 ) |
186 |
| - .easing( TWEEN.Easing.Sinusoidal.InOut ) |
187 |
| - .onComplete( function () { |
188 |
| - |
189 |
| - secondaryTweens(); |
190 |
| - |
191 |
| - } ); |
192 |
| - effectTween.start(); |
193 |
| - |
194 |
| - } |
195 |
| - |
196 |
| - function secondaryTweens() { |
197 |
| - |
198 |
| - uniformEffector1.value = - 0.2; |
199 |
| - uniformEffector2.value = - 0.2; |
200 |
| - |
201 |
| - const effect2Tween = new TWEEN.Tween( uniformEffector2 ) |
202 |
| - .to( { value: 1.2 }, 3000 ) |
203 |
| - .repeat( Infinity ) |
204 |
| - .easing( TWEEN.Easing.Sinusoidal.InOut ); |
205 |
| - effect2Tween.start(); |
206 |
| - |
207 |
| - const effectTween = new TWEEN.Tween( uniformEffector1 ) |
208 |
| - .to( { value: 1.2 }, 3000 ) |
209 |
| - .delay( 800 ) |
210 |
| - .repeat( Infinity ) |
211 |
| - .easing( TWEEN.Easing.Sinusoidal.InOut ); |
212 |
| - effectTween.start(); |
213 |
| - |
214 |
| - } |
| 194 | + function random () { return ( Math.random() - 0.5 ) * 2.0 }; |
215 | 195 |
|
216 | 196 | function createTreeMesh() {
|
217 | 197 |
|
218 |
| - const maxSteps = 6; |
219 |
| - const angleLeft = Math.PI / 180 * 30; |
220 |
| - const angleRight = Math.PI / 180 * 40; |
221 |
| - const lengthMult = 0.88; |
| 198 | + const maxSteps = 5; |
| 199 | + const angleOffset = Math.PI / 180 * 40; |
| 200 | + const lengthMult = 0.8; |
222 | 201 |
|
223 | 202 | const positions = [];
|
224 | 203 | const normals = [];
|
|
234 | 213 |
|
235 | 214 | function createTreePart( angle, x, y, z, length, count ) {
|
236 | 215 |
|
| 216 | + if ( Math.random() > ( maxSteps / count ) * 0.25 ) return; |
| 217 | + |
237 | 218 | if ( count < maxSteps ) {
|
238 | 219 |
|
239 | 220 | const newLength = length * lengthMult;
|
|
246 | 227 | if ( size > 25 ) size = 25;
|
247 | 228 | if ( size < 10 ) size = 10;
|
248 | 229 |
|
249 |
| - size = size / 10; |
| 230 | + size = size / 100; |
250 | 231 |
|
251 |
| - const subSteps = 60; |
| 232 | + const subSteps = 200; |
252 | 233 |
|
253 | 234 | // below loop generates the instanced data for a tree part
|
254 | 235 |
|
|
270 | 251 |
|
271 | 252 | positions.push( position.x, position.y, position.z );
|
272 | 253 |
|
273 |
| - const scale = 0.25 * Math.random(); |
| 254 | + const scale = Math.random(); |
274 | 255 |
|
275 | 256 | // normal
|
276 | 257 |
|
|
279 | 260 |
|
280 | 261 | // color
|
281 | 262 |
|
282 |
| - color.setHSL( 0.55 + Math.random() * 0.05, 1.0, 0.7 + Math.random() * 0.3 ); |
| 263 | + color.setHSL( ( count / maxSteps ) * 0.5 + Math.random() * 0.05, 0.75, 0.7 + Math.random() * 0.1 ); |
283 | 264 | colors.push( color.r, color.g, color.b );
|
284 | 265 |
|
285 | 266 | // to save vertex buffers, we store the size, time and seed in a single attribute
|
|
292 | 273 |
|
293 | 274 | }
|
294 | 275 |
|
295 |
| - createTreePart( angle - angleRight, newX, newY, newZ, newLength, count + 1 ); |
296 |
| - createTreePart( angle + angleLeft, newX, newY, newZ, newLength, count + 1 ); |
| 276 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 277 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 278 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 279 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 280 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 281 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
297 | 282 |
|
298 | 283 | }
|
299 | 284 |
|
|
321 | 306 |
|
322 | 307 | // TSL
|
323 | 308 |
|
324 |
| - const vVisbility = varyingProperty( 'float' ); |
325 |
| - |
326 | 309 | const instancePosition = instancedBufferAttribute( attributePosition );
|
327 | 310 | const instanceNormal = instancedBufferAttribute( attributeNormal );
|
328 | 311 | const instanceColor = instancedBufferAttribute( attributeColor );
|
|
342 | 325 | const dif2 = abs( instanceTime.sub( uniformEffector2 ) ).toConst();
|
343 | 326 | effect = dif2.lessThanEqual( 0.15 ).select( sub( 0.15, dif2 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), effect );
|
344 | 327 |
|
345 |
| - // life (controls the visibility and initial scale of the cubes) |
346 |
| - |
347 |
| - const scale = uniformLife.greaterThan( instanceTime ).select( min( 1, div( uniformLife.sub( instanceTime ), 0 ) ) ).oneMinus(); |
348 |
| - vVisbility.assign( uniformLife.greaterThan( instanceTime ).select( 1, 0 ) ); |
349 |
| - |
350 | 328 | // accumulate different vertex animations
|
351 | 329 |
|
352 | 330 | let animated = positionLocal.add( instancePosition ).toVar();
|
353 | 331 | const direction = positionGeometry.normalize().toConst();
|
354 | 332 |
|
355 | 333 | animated = animated.add( direction.mul( effect.add( instanceSize ) ) );
|
356 |
| - animated = animated.sub( direction.mul( scale ) ); |
| 334 | + animated = animated.sub( direction.mul( effect ) ); |
357 | 335 | animated = animated.add( instanceNormal.mul( effect.mul( 1 ) ) );
|
358 | 336 | animated = animated.add( instanceNormal.mul( abs( sin( time.add( instanceSeed.mul( 2 ) ) ).mul( 1.5 ) ) ) );
|
359 | 337 |
|
|
363 | 341 |
|
364 | 342 | material.colorNode = Fn( () => {
|
365 | 343 |
|
366 |
| - vVisbility.equal( 0 ).discard(); |
367 |
| - |
368 | 344 | return materialColor.mul( instanceColor );
|
369 | 345 |
|
370 | 346 | } )();
|
371 | 347 |
|
| 348 | + material.emissiveNode = Fn( () => { |
| 349 | + |
| 350 | + const instanceTime = instanceData.y; |
| 351 | + |
| 352 | + const dif1 = abs( instanceTime.sub( uniformEffector1 ) ).toConst(); |
| 353 | + const effect1 = dif1.lessThanEqual( 0.15 ).select( sub( 0.15, dif1 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), float( 0 ) ); |
| 354 | + |
| 355 | + const dif2 = abs( instanceTime.sub( uniformEffector2 ) ).toConst(); |
| 356 | + const effect2 = dif2.lessThanEqual( 0.15 ).select( sub( 0.15, dif2 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), effect1 ); |
| 357 | + |
| 358 | + return vec3( effect1, 0, effect2 ).mul( instanceColor ); |
| 359 | + |
| 360 | + } )(); |
| 361 | + |
372 | 362 | return mesh;
|
373 | 363 |
|
374 | 364 | }
|
|
0 commit comments