|
35 | 35 | <script type="module"> |
36 | 36 |
|
37 | 37 | import * as THREE from 'three/webgpu'; |
38 | | - import { uniform, varying, vec4, add, sub, max, dot, sin, mat3, uint, negate, instancedArray, cameraProjectionMatrix, cameraViewMatrix, positionLocal, modelWorldMatrix, sqrt, float, Fn, If, cos, Loop, Continue, normalize, instanceIndex, length, vertexIndex } from 'three/tsl'; |
39 | | - |
| 38 | + import { uniform, varying, vec4, add, abs, sub, max, dot, sin, mat3, uint, negate, instancedArray, cameraProjectionMatrix, cameraViewMatrix, positionLocal, modelWorldMatrix, sqrt, float, Fn, If, cos, Loop, Continue, normalize, instanceIndex, length, vertexIndex } from 'three/tsl'; |
40 | 39 | import { Inspector } from 'three/addons/inspector/Inspector.js'; |
| 40 | + import { PrefixSum } from 'three/addons/gpgpu/PrefixSum.js'; |
41 | 41 |
|
42 | 42 | import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; |
43 | 43 |
|
|
49 | 49 | let last = performance.now(); |
50 | 50 |
|
51 | 51 | let pointer, raycaster; |
52 | | - let computeVelocity, computePosition, effectController; |
| 52 | + let computeVelocity, computePosition, effectController, computeHash; |
53 | 53 |
|
54 | 54 | const BIRDS = 16384; |
| 55 | + const TABLE_SIZE = BIRDS * 2; |
55 | 56 | const SPEED_LIMIT = 9.0; |
56 | 57 | const BOUNDS = 800, BOUNDS_HALF = BOUNDS / 2; |
57 | 58 |
|
|
175 | 176 | const velocityArray = new Float32Array( BIRDS * 3 ); |
176 | 177 | const phaseArray = new Float32Array( BIRDS ); |
177 | 178 |
|
| 179 | + |
| 180 | + |
178 | 181 | for ( let i = 0; i < BIRDS; i ++ ) { |
179 | 182 |
|
180 | 183 | const posX = Math.random() * BOUNDS - BOUNDS_HALF; |
|
287 | 290 |
|
288 | 291 | // Define GPU Compute shaders. |
289 | 292 | // Shaders are computationally identical to their GLSL counterparts outside of texture destructuring. |
| 293 | + const intCoord = Fn( ( [ x, spacing ] ) => { |
| 294 | + |
| 295 | + return floor( x.div( spacing ) ); |
| 296 | + |
| 297 | + } ).setLayout( { |
| 298 | + name: 'intCoord', |
| 299 | + type: 'int', |
| 300 | + inputs: [ |
| 301 | + { name: 'x', type: 'float' }, |
| 302 | + { name: 'spacing', type: 'float' } |
| 303 | + ] |
| 304 | + } ); |
| 305 | + |
| 306 | + const hash3DFloatCoord = Fn( ( [ v ] ) => { |
| 307 | + |
| 308 | + const x = intCoord( v.x ); |
| 309 | + const y = intCoord( v.y ); |
| 310 | + const z = intCoord( v.z ); |
| 311 | + |
| 312 | + const xMul = x.mul( 92837111 ); |
| 313 | + const yMul = y.mul( 689287499 ); |
| 314 | + const zMul = z.mul( 283923481 ); |
| 315 | + |
| 316 | + const h = xMul.pow( yMul ).pow( zMul ); |
| 317 | + |
| 318 | + return abs( h ).modInt( TABLE_SIZE ); |
| 319 | + |
| 320 | + } ).setLayout( { |
| 321 | + name: 'hash3DCoord', |
| 322 | + type: 'int', |
| 323 | + inputs: [ |
| 324 | + { name: 'v', type: 'vec3' }, |
| 325 | + { name: 'spacing', type: 'float' } |
| 326 | + ] |
| 327 | + } ); |
| 328 | + |
| 329 | + const hash3DIntCoord = Fn( ( [ x, y, z ] ) => { |
| 330 | + |
| 331 | + const xMul = x.mul( 92837111 ); |
| 332 | + const yMul = y.mul( 689287499 ); |
| 333 | + const zMul = z.mul( 283923481 ); |
| 334 | + |
| 335 | + const h = xMul.pow( yMul ).pow( zMul ); |
| 336 | + |
| 337 | + return abs( h ).modInt( TABLE_SIZE ); |
| 338 | + |
| 339 | + } ).setLayout( { |
| 340 | + name: 'hash3DIntCoord', |
| 341 | + type: 'int', |
| 342 | + inputs: [ |
| 343 | + { name: 'x', type: 'int' }, |
| 344 | + { name: 'y', type: 'int'} |
| 345 | + { name: 'z', type: 'int'} |
| 346 | + ] |
| 347 | + } ); |
| 348 | + |
| 349 | + class Hash { |
| 350 | + |
| 351 | + constructor( renderer, spacing, maxNumObjects ) { |
| 352 | + |
| 353 | + this.spacing = spacing; |
| 354 | + this.tableSize = maxNumObjects * 2; |
| 355 | + this.cellStartArrayBuffer = new Int32Array( this.tableSize + 1 ); |
| 356 | + this.cellStartStorage = instancedArray( this.cellStartArrayBuffer, 'int' ); |
| 357 | + this.cellEntriesArrayBuffer = new Int32Array( maxNumObjects ); |
| 358 | + this.cellEntriesStorage = instancedArray( this.cellEntriesArrayBuffer, 'int' ); |
| 359 | + this.queryIdsArrayBuffer = new Int32Array( maxNumObjects ); |
| 360 | + this.queryIdsStorage = instancedArray( this.queryIdsArrayBuffer, 'int' ); |
| 361 | + this.querySize = 0; |
| 362 | + |
| 363 | + // TODO: Check if faster to replace with .fill() operation |
| 364 | + this.resetFn = this._createResetFn( maxNumObjects ); |
| 365 | + this.calculateHashFn = this._createCalculateHashFn( maxNumObjects ); |
| 366 | + this.prefixSumModule = new PrefixSum( renderer, [ 1, 2, 3, 4, 5, 6, 7, 8 ], 'uint' ); |
| 367 | + this.calculateCellEntriesFn = this._createCalculateCellEntriesFn( maxNumObjects ); |
| 368 | + |
| 369 | + } |
| 370 | + |
| 371 | + _createResetFn( maxNumObjects ) { |
| 372 | + |
| 373 | + const fnDef = Fn( () => { |
| 374 | + |
| 375 | + const { cellStartStorage, cellEntriesStorage } = this; |
| 376 | + |
| 377 | + cellStartStorage.element( instanceIndex ).assign( 0 ); |
| 378 | + cellEntriesStorage.element( instanceIndex ).assign( 0 ); |
| 379 | + |
| 380 | + } )().compute( maxNumObjects ); |
| 381 | + |
| 382 | + return fnDef; |
| 383 | + |
| 384 | + } |
| 385 | + |
| 386 | + _createCalculateHashFn( maxNumObjects ) { |
| 387 | + |
| 388 | + const fnDef = Fn( () => { |
| 389 | + |
| 390 | + const { cellStartStorage } = this; |
| 391 | + |
| 392 | + const hashedCoord = hash3DFloatCoord( positionStorage.element( instanceIndex ) ); |
| 393 | + cellStartStorage.element( hashedCoord ).addAssign( 1 ); |
| 394 | + |
| 395 | + } )().compute( maxNumObjects ); |
| 396 | + |
| 397 | + return fnDef; |
| 398 | + |
| 399 | + } |
| 400 | + |
| 401 | + _createCalculateCellEntriesFn( maxNumObjects ) { |
| 402 | + |
| 403 | + const fnDef = Fn( () => { |
| 404 | + |
| 405 | + const { cellStartStorage, cellEntriesStorage } = this; |
| 406 | + |
| 407 | + const hashedCoord = hash3DCoord( positionStorage.element( instanceIndex ) ); |
| 408 | + cellStartStorage.element( hashedCoord ).subAssign( 1 ); |
| 409 | + |
| 410 | + // TODO: Prevent errors from two threads substracting the same cellStart entry at the same time |
| 411 | + const cellStart = cellStartStorage.element( hashedCoord ).toVar(); |
| 412 | + cellEntriesStorage.element( cellStart ).assign( instanceIndex ); |
| 413 | + |
| 414 | + } )().compute( maxNumObjects ); |
| 415 | + |
| 416 | + return fnDef; |
| 417 | + |
| 418 | + } |
| 419 | + |
| 420 | + query() { |
| 421 | + |
| 422 | + const fnDef = Fn( ( [ position, maxDist ] ) => { |
| 423 | + |
| 424 | + const {queryIdsStorage, cellEntriesStorage} = this; |
| 425 | + |
| 426 | + const x0 = intCoord( position.x.sub( maxDist ) ); |
| 427 | + const y0 = intCoord( position.y.sub( maxDist ) ); |
| 428 | + const z0 = intCoord( position.z.sub( maxDist ) ); |
| 429 | + |
| 430 | + const x1 = intCoord( position.x.sub( maxDist ) ); |
| 431 | + const y1 = intCoord( position.y.sub( maxDist ) ); |
| 432 | + const z1 = intCoord( position.z.sub( maxDist ) ); |
| 433 | + |
| 434 | + const xi = uint( x0 ).toVar(); |
| 435 | + const yi = uint( y0 ).toVar(); |
| 436 | + const zi = uint( z0 ).toVar(); |
| 437 | + |
| 438 | + const querySize = uint(0).toVar(); |
| 439 | + |
| 440 | + Loop( xi.lessThanEqual( x1 ), () => { |
| 441 | + |
| 442 | + Loop( yi.lessThanEqual( y1 ), () => { |
| 443 | + |
| 444 | + Loop( zi.lessThanEqual( z1 ), () => { |
| 445 | + |
| 446 | + const hashedCoord = hash3DIntCoord(xi, yi, zi); |
| 447 | + const start = cellStartStorage.element(hashedCoord); |
| 448 | + const end = cellStartStorage.element(hashedCoord.add(1)) |
| 449 | + |
| 450 | + Loop({start: start, end: end, condition: '<', type: 'int'}, ({i}) => { |
| 451 | + |
| 452 | + queryIdsStorage.element(querySize).assign(cellEntriesStorage.element(i)) |
| 453 | + querySize.addAssign(1) |
| 454 | + |
| 455 | + }) |
| 456 | + |
| 457 | + zi.addAssign( 1 ); |
| 458 | + |
| 459 | + } ); |
| 460 | + |
| 461 | + yi.addAssign( 1 ); |
| 462 | + |
| 463 | + } ); |
| 464 | + |
| 465 | + xi.addAssign( 1 ); |
| 466 | + |
| 467 | + } ); |
| 468 | + |
| 469 | + } ).setLayout( { |
| 470 | + name: 'queryPosition', |
| 471 | + type: 'float', |
| 472 | + inputs: [ |
| 473 | + { name: 'pos', type: 'vec3' }, |
| 474 | + { name: 'maxDist', type: 'float' } |
| 475 | + ] |
| 476 | + } ); |
| 477 | + |
| 478 | + return fnDef; |
| 479 | + |
| 480 | + } |
| 481 | + |
| 482 | + } |
290 | 483 |
|
291 | 484 | computeVelocity = Fn( () => { |
292 | 485 |
|
|
0 commit comments