|
10 | 10 |
|
11 | 11 | <div id="info">
|
12 | 12 | <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - reflection<br/>
|
13 |
| - Based on <a href="https://web.archive.org/web/20210101053442/http://oos.moxiecode.com/js_webgl/recursive_tree_cubes/" target="_blank" rel="noopener">Recursive Tree Cubes</a> |
| 13 | + Based on <a href="https://oosmoxiecode.com/archive/js_webgl/recursive_tree_cubes/" target="_blank" rel="noopener">Recursive Tree Cubes</a> |
14 | 14 | by <a href="https://github.com/oosmoxiecode" target="_blank" rel="noopener">oosmoxiecode</a>
|
15 | 15 | </div>
|
16 | 16 |
|
|
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, float, Fn, instancedBufferAttribute, materialColor, normalWorldGeometry, pass, positionGeometry, positionLocal, reflector, screenUV, sin, sub, texture, time, uniform, uv, vec3 } from 'three/tsl'; |
33 | 33 | import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';
|
34 | 34 |
|
35 | 35 | import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
44 | 44 |
|
45 | 45 | // below uniforms will be animated via TWEEN.js
|
46 | 46 |
|
47 |
| - const uniformLife = uniform( 0 ); |
48 | 47 | const uniformEffector1 = uniform( - 0.2 );
|
49 | 48 | const uniformEffector2 = uniform( - 0.2 );
|
50 | 49 |
|
51 | 50 | init();
|
52 | 51 |
|
53 |
| - function init() { |
| 52 | + async function init() { |
54 | 53 |
|
55 | 54 | camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.25, 30 );
|
56 | 55 | camera.position.set( 4, 2, 4 );
|
57 | 56 |
|
58 | 57 | scene = new THREE.Scene();
|
59 |
| - scene.fog = new THREE.Fog( 0x0487e2, 7, 25 ); |
60 |
| - scene.backgroundNode = normalWorldGeometry.y.mix( color( 0x0487e2 ), color( 0x0066ff ) ); |
| 58 | + scene.fog = new THREE.Fog( 0x4195a4, 1, 25 ); |
| 59 | + scene.backgroundNode = normalWorldGeometry.y.mix( color( 0x4195a4 ), color( 0x0066ff ) ); |
61 | 60 | camera.lookAt( 0, 1, 0 );
|
62 | 61 |
|
63 |
| - const sunLight = new THREE.DirectionalLight( 0xFFE499, 3 ); |
64 |
| - sunLight.position.set( 7, 3, 7 ); |
65 |
| - |
66 |
| - const waterAmbientLight = new THREE.HemisphereLight( 0x333366, 0x74ccf4, 3 ); |
67 |
| - const skyAmbientLight = new THREE.HemisphereLight( 0x74ccf4, 0, 1 ); |
68 |
| - |
| 62 | + const sunLight = new THREE.DirectionalLight( 0xFFE499, 2 ); |
| 63 | + sunLight.position.set( 7, 5, 7 ); |
| 64 | + sunLight.castShadow = true; |
| 65 | + sunLight.shadow.camera.zoom = 1.5; |
| 66 | + sunLight.shadow.mapSize.set( 1024, 1024 ); |
| 67 | + sunLight.shadow.bias = - 0.0001; |
69 | 68 | scene.add( sunLight );
|
70 |
| - scene.add( skyAmbientLight ); |
71 |
| - scene.add( waterAmbientLight ); |
| 69 | + |
| 70 | + const backLight = new THREE.DirectionalLight( 0x0487e2, 0.5 ); |
| 71 | + backLight.position.set( 7, - 5, 7 ); |
| 72 | + scene.add( backLight ); |
72 | 73 |
|
73 | 74 | // textures
|
74 | 75 |
|
75 | 76 | const textureLoader = new THREE.TextureLoader();
|
76 | 77 |
|
77 |
| - const floorColor = textureLoader.load( 'textures/floors/FloorsCheckerboard_S_Diffuse.jpg' ); |
| 78 | + const floorColor = await textureLoader.loadAsync( 'textures/floors/FloorsCheckerboard_S_Diffuse.jpg' ); |
78 | 79 | floorColor.wrapS = THREE.RepeatWrapping;
|
79 | 80 | floorColor.wrapT = THREE.RepeatWrapping;
|
80 | 81 | floorColor.colorSpace = THREE.SRGBColorSpace;
|
81 | 82 |
|
82 |
| - const floorNormal = textureLoader.load( 'textures/floors/FloorsCheckerboard_S_Normal.jpg' ); |
| 83 | + const floorNormal = await textureLoader.loadAsync( 'textures/floors/FloorsCheckerboard_S_Normal.jpg' ); |
83 | 84 | floorNormal.wrapS = THREE.RepeatWrapping;
|
84 | 85 | floorNormal.wrapT = THREE.RepeatWrapping;
|
| 86 | + floorNormal.repeat.set( 15, 15 ); |
| 87 | + |
| 88 | + const boxMap = await textureLoader.loadAsync( 'textures/edge3.jpg' ); |
| 89 | + boxMap.colorSpace = THREE.SRGBColorSpace; |
85 | 90 |
|
86 | 91 | // tree
|
87 | 92 |
|
88 |
| - const treeMesh = createTreeMesh(); |
| 93 | + const treeMesh = createTreeMesh( boxMap ); |
| 94 | + treeMesh.castShadow = true; |
| 95 | + treeMesh.receiveShadow = true; |
89 | 96 | scene.add( treeMesh );
|
90 | 97 |
|
91 | 98 | // floor
|
|
100 | 107 |
|
101 | 108 | const floorMaterial = new THREE.MeshPhongNodeMaterial();
|
102 | 109 | floorMaterial.colorNode = texture( floorColor, floorUV ).add( reflection );
|
| 110 | + floorMaterial.normalMap = floorNormal; |
| 111 | + floorMaterial.normalScale.set( 0.2, - 0.2 ); |
103 | 112 |
|
104 | 113 | const floor = new THREE.Mesh( new THREE.BoxGeometry( 50, .001, 50 ), floorMaterial );
|
105 |
| - floor.position.set( 0, 0, 0 ); |
| 114 | + floor.receiveShadow = true; |
106 | 115 | scene.add( floor );
|
107 | 116 |
|
108 | 117 | // renderer
|
|
111 | 120 | renderer.setPixelRatio( window.devicePixelRatio );
|
112 | 121 | renderer.setSize( window.innerWidth, window.innerHeight );
|
113 | 122 | renderer.setAnimationLoop( animate );
|
| 123 | + renderer.shadowMap.enabled = true; |
| 124 | + renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
| 125 | + renderer.toneMapping = THREE.ACESFilmicToneMapping; |
114 | 126 | document.body.appendChild( renderer.domElement );
|
115 | 127 |
|
116 | 128 | stats = new Stats();
|
|
137 | 149 | const scenePassColorBlurred = gaussianBlur( scenePassColor );
|
138 | 150 | scenePassColorBlurred.directionNode = scenePassDepth;
|
139 | 151 |
|
140 |
| - const vignette = screenUV.distance( .5 ).mul( 1.35 ).clamp().oneMinus(); |
| 152 | + const vignette = screenUV.distance( .5 ).mul( 1.25 ).clamp().oneMinus().sub( 0.2 ); |
141 | 153 |
|
142 | 154 | postProcessing = new THREE.PostProcessing( renderer );
|
143 |
| - postProcessing.outputNode = scenePassColorBlurred.mul( vignette ); |
| 155 | + postProcessing.outputNode = blendOverlay( scenePassColorBlurred, vignette ); |
144 | 156 |
|
145 |
| - // |
| 157 | + // tweens |
146 | 158 |
|
147 |
| - window.addEventListener( 'resize', onWindowResize ); |
| 159 | + new TWEEN.Tween( uniformEffector1 ) |
| 160 | + .to( { value: 1.2 }, 3000 ) |
| 161 | + .delay( 800 ) |
| 162 | + .repeat( Infinity ) |
| 163 | + .easing( TWEEN.Easing.Sinusoidal.InOut ) |
| 164 | + .start(); |
| 165 | + |
| 166 | + new TWEEN.Tween( uniformEffector2 ) |
| 167 | + .to( { value: 1.2 }, 3000 ) |
| 168 | + .repeat( Infinity ) |
| 169 | + .easing( TWEEN.Easing.Sinusoidal.InOut ) |
| 170 | + .start(); |
148 | 171 |
|
149 | 172 | //
|
150 | 173 |
|
151 |
| - startTweens(); |
| 174 | + window.addEventListener( 'resize', onWindowResize ); |
152 | 175 |
|
153 | 176 | }
|
154 | 177 |
|
|
173 | 196 |
|
174 | 197 | }
|
175 | 198 |
|
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(); |
| 199 | + function random() { |
182 | 200 |
|
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(); |
| 201 | + return ( Math.random() - 0.5 ) * 2.0; |
193 | 202 |
|
194 | 203 | }
|
195 | 204 |
|
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 | 205 |
|
214 |
| - } |
215 | 206 |
|
216 |
| - function createTreeMesh() { |
| 207 | + function createTreeMesh( boxMap ) { |
217 | 208 |
|
218 |
| - const maxSteps = 6; |
219 |
| - const angleLeft = Math.PI / 180 * 30; |
220 |
| - const angleRight = Math.PI / 180 * 40; |
221 |
| - const lengthMult = 0.88; |
| 209 | + const maxSteps = 5; |
| 210 | + const lengthMult = 0.8; |
222 | 211 |
|
223 | 212 | const positions = [];
|
224 | 213 | const normals = [];
|
|
234 | 223 |
|
235 | 224 | function createTreePart( angle, x, y, z, length, count ) {
|
236 | 225 |
|
| 226 | + if ( Math.random() > ( maxSteps / count ) * 0.25 ) return; |
| 227 | + |
237 | 228 | if ( count < maxSteps ) {
|
238 | 229 |
|
239 | 230 | const newLength = length * lengthMult;
|
|
246 | 237 | if ( size > 25 ) size = 25;
|
247 | 238 | if ( size < 10 ) size = 10;
|
248 | 239 |
|
249 |
| - size = size / 10; |
| 240 | + size = size / 100; |
250 | 241 |
|
251 |
| - const subSteps = 60; |
| 242 | + const subSteps = 200; |
252 | 243 |
|
253 | 244 | // below loop generates the instanced data for a tree part
|
254 | 245 |
|
|
270 | 261 |
|
271 | 262 | positions.push( position.x, position.y, position.z );
|
272 | 263 |
|
273 |
| - const scale = 0.25 * Math.random(); |
| 264 | + const scale = Math.random(); |
274 | 265 |
|
275 | 266 | // normal
|
276 | 267 |
|
|
279 | 270 |
|
280 | 271 | // color
|
281 | 272 |
|
282 |
| - color.setHSL( 0.55 + Math.random() * 0.05, 1.0, 0.7 + Math.random() * 0.3 ); |
| 273 | + color.setHSL( ( count / maxSteps ) * 0.5 + Math.random() * 0.05, 0.75, 0.6 + Math.random() * 0.1 ); |
283 | 274 | colors.push( color.r, color.g, color.b );
|
284 | 275 |
|
285 | 276 | // to save vertex buffers, we store the size, time and seed in a single attribute
|
|
292 | 283 |
|
293 | 284 | }
|
294 | 285 |
|
295 |
| - createTreePart( angle - angleRight, newX, newY, newZ, newLength, count + 1 ); |
296 |
| - createTreePart( angle + angleLeft, newX, newY, newZ, newLength, count + 1 ); |
| 286 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 287 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 288 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 289 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 290 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
| 291 | + createTreePart( angle + random(), newX, newY, newZ, newLength + random(), count + 1 ); |
297 | 292 |
|
298 | 293 | }
|
299 | 294 |
|
|
306 | 301 | createTreePart( angle, 0, 0, 0, 16, 0 );
|
307 | 302 |
|
308 | 303 | const geometry = new THREE.BoxGeometry();
|
309 |
| - const material = new THREE.MeshStandardNodeMaterial(); |
| 304 | + const material = new THREE.MeshStandardNodeMaterial( { map: boxMap } ); |
310 | 305 | const mesh = new THREE.Mesh( geometry, material );
|
311 | 306 | mesh.scale.setScalar( 0.05 );
|
312 | 307 | mesh.count = instanceCount;
|
|
321 | 316 |
|
322 | 317 | // TSL
|
323 | 318 |
|
324 |
| - const vVisibility = varyingProperty( 'float' ); |
325 |
| - |
326 | 319 | const instancePosition = instancedBufferAttribute( attributePosition );
|
327 | 320 | const instanceNormal = instancedBufferAttribute( attributeNormal );
|
328 | 321 | const instanceColor = instancedBufferAttribute( attributeColor );
|
329 | 322 | const instanceData = instancedBufferAttribute( attributeData );
|
330 |
| - |
| 323 | + |
331 | 324 | material.positionNode = Fn( () => {
|
332 | 325 |
|
333 | 326 | const instanceSize = instanceData.x;
|
|
342 | 335 | const dif2 = abs( instanceTime.sub( uniformEffector2 ) ).toConst();
|
343 | 336 | effect = dif2.lessThanEqual( 0.15 ).select( sub( 0.15, dif2 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), effect );
|
344 | 337 |
|
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 |
| - vVisibility.assign( uniformLife.greaterThan( instanceTime ).select( 1, 0 ) ); |
349 |
| - |
350 | 338 | // accumulate different vertex animations
|
351 | 339 |
|
352 | 340 | let animated = positionLocal.add( instancePosition ).toVar();
|
353 | 341 | const direction = positionGeometry.normalize().toConst();
|
354 | 342 |
|
355 | 343 | animated = animated.add( direction.mul( effect.add( instanceSize ) ) );
|
356 |
| - animated = animated.sub( direction.mul( scale ) ); |
| 344 | + animated = animated.sub( direction.mul( effect ) ); |
357 | 345 | animated = animated.add( instanceNormal.mul( effect.mul( 1 ) ) );
|
358 | 346 | animated = animated.add( instanceNormal.mul( abs( sin( time.add( instanceSeed.mul( 2 ) ) ).mul( 1.5 ) ) ) );
|
359 | 347 |
|
|
363 | 351 |
|
364 | 352 | material.colorNode = Fn( () => {
|
365 | 353 |
|
366 |
| - vVisibility.equal( 0 ).discard(); |
367 |
| - |
368 | 354 | return materialColor.mul( instanceColor );
|
369 | 355 |
|
370 | 356 | } )();
|
371 | 357 |
|
| 358 | + material.emissiveNode = Fn( () => { |
| 359 | + |
| 360 | + const instanceTime = instanceData.y; |
| 361 | + |
| 362 | + const dif1 = abs( instanceTime.sub( uniformEffector1 ) ).toConst(); |
| 363 | + const effect1 = dif1.lessThanEqual( 0.15 ).select( sub( 0.15, dif1 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), float( 0 ) ); |
| 364 | + |
| 365 | + const dif2 = abs( instanceTime.sub( uniformEffector2 ) ).toConst(); |
| 366 | + const effect2 = dif2.lessThanEqual( 0.15 ).select( sub( 0.15, dif2 ).mul( sub( 1.7, instanceTime ).mul( 10 ) ), effect1 ); |
| 367 | + |
| 368 | + return vec3( effect1, 0, effect2 ).mul( instanceColor ); |
| 369 | + |
| 370 | + } )(); |
| 371 | + |
372 | 372 | return mesh;
|
373 | 373 |
|
374 | 374 | }
|
|
0 commit comments