TiledLightsNode.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. import { DataTexture, FloatType, RGBAFormat, Vector2, Vector3, LightsNode, NodeUpdateType } from 'three/webgpu';
  2. import {
  3. attributeArray, nodeProxy, int, float, vec2, ivec2, ivec4, uniform, Break, Loop, positionView,
  4. Fn, If, Return, textureLoad, instanceIndex, screenCoordinate, directPointLight
  5. } from 'three/tsl';
  6. export const circleIntersectsAABB = /*@__PURE__*/ Fn( ( [ circleCenter, radius, minBounds, maxBounds ] ) => {
  7. // Find the closest point on the AABB to the circle's center using method chaining
  8. const closestX = minBounds.x.max( circleCenter.x.min( maxBounds.x ) );
  9. const closestY = minBounds.y.max( circleCenter.y.min( maxBounds.y ) );
  10. // Compute the distance between the circle's center and the closest point
  11. const distX = circleCenter.x.sub( closestX );
  12. const distY = circleCenter.y.sub( closestY );
  13. // Calculate the squared distance
  14. const distSquared = distX.mul( distX ).add( distY.mul( distY ) );
  15. return distSquared.lessThanEqual( radius.mul( radius ) );
  16. } ).setLayout( {
  17. name: 'circleIntersectsAABB',
  18. type: 'bool',
  19. inputs: [
  20. { name: 'circleCenter', type: 'vec2' },
  21. { name: 'radius', type: 'float' },
  22. { name: 'minBounds', type: 'vec2' },
  23. { name: 'maxBounds', type: 'vec2' }
  24. ]
  25. } );
  26. const _vector3 = /*@__PURE__*/ new Vector3();
  27. const _size = /*@__PURE__*/ new Vector2();
  28. class TiledLightsNode extends LightsNode {
  29. static get type() {
  30. return 'TiledLightsNode';
  31. }
  32. constructor( maxLights = 1024, tileSize = 32 ) {
  33. super();
  34. this.materialLights = [];
  35. this.tiledLights = [];
  36. this.maxLights = maxLights;
  37. this.tileSize = tileSize;
  38. this._bufferSize = null;
  39. this._lightIndexes = null;
  40. this._screenTileIndex = null;
  41. this._compute = null;
  42. this._lightsTexture = null;
  43. this._lightsCount = uniform( 0, 'int' );
  44. this._tileLightCount = 8;
  45. this._screenSize = uniform( new Vector2() );
  46. this._cameraProjectionMatrix = uniform( 'mat4' );
  47. this._cameraViewMatrix = uniform( 'mat4' );
  48. this.updateBeforeType = NodeUpdateType.RENDER;
  49. }
  50. customCacheKey() {
  51. return this._compute.getCacheKey() + super.customCacheKey();
  52. }
  53. updateLightsTexture() {
  54. const { _lightsTexture: lightsTexture, tiledLights } = this;
  55. const data = lightsTexture.image.data;
  56. const lineSize = lightsTexture.image.width * 4;
  57. this._lightsCount.value = tiledLights.length;
  58. for ( let i = 0; i < tiledLights.length; i ++ ) {
  59. const light = tiledLights[ i ];
  60. // world position
  61. _vector3.setFromMatrixPosition( light.matrixWorld );
  62. // store data
  63. const offset = i * 4;
  64. data[ offset + 0 ] = _vector3.x;
  65. data[ offset + 1 ] = _vector3.y;
  66. data[ offset + 2 ] = _vector3.z;
  67. data[ offset + 3 ] = light.distance;
  68. data[ lineSize + offset + 0 ] = light.color.r * light.intensity;
  69. data[ lineSize + offset + 1 ] = light.color.g * light.intensity;
  70. data[ lineSize + offset + 2 ] = light.color.b * light.intensity;
  71. data[ lineSize + offset + 3 ] = light.decay;
  72. }
  73. lightsTexture.needsUpdate = true;
  74. }
  75. updateBefore( frame ) {
  76. const { renderer, camera } = frame;
  77. this.updateProgram( renderer );
  78. this.updateLightsTexture( camera );
  79. this._cameraProjectionMatrix.value = camera.projectionMatrix;
  80. this._cameraViewMatrix.value = camera.matrixWorldInverse;
  81. renderer.getDrawingBufferSize( _size );
  82. this._screenSize.value.copy( _size );
  83. renderer.compute( this._compute );
  84. }
  85. setLights( lights ) {
  86. const { tiledLights, materialLights } = this;
  87. let materialindex = 0;
  88. let tiledIndex = 0;
  89. for ( const light of lights ) {
  90. if ( light.isPointLight === true ) {
  91. tiledLights[ tiledIndex ++ ] = light;
  92. } else {
  93. materialLights[ materialindex ++ ] = light;
  94. }
  95. }
  96. materialLights.length = materialindex;
  97. tiledLights.length = tiledIndex;
  98. return super.setLights( materialLights );
  99. }
  100. getBlock( block = 0 ) {
  101. return this._lightIndexes.element( this._screenTileIndex.mul( int( 2 ).add( int( block ) ) ) );
  102. }
  103. getTile( element ) {
  104. element = int( element );
  105. const stride = int( 4 );
  106. const tileOffset = element.div( stride );
  107. const tileIndex = this._screenTileIndex.mul( int( 2 ) ).add( tileOffset );
  108. return this._lightIndexes.element( tileIndex ).element( element.modInt( stride ) );
  109. }
  110. getLightData( index ) {
  111. index = int( index );
  112. const dataA = textureLoad( this._lightsTexture, ivec2( index, 0 ) );
  113. const dataB = textureLoad( this._lightsTexture, ivec2( index, 1 ) );
  114. const position = dataA.xyz;
  115. const viewPosition = this._cameraViewMatrix.mul( position );
  116. const distance = dataA.w;
  117. const color = dataB.rgb;
  118. const decay = dataB.w;
  119. return {
  120. position,
  121. viewPosition,
  122. distance,
  123. color,
  124. decay
  125. };
  126. }
  127. setupLights( builder, lightNodes ) {
  128. this.updateProgram( builder.renderer );
  129. //
  130. const lightingModel = builder.context.reflectedLight;
  131. // force declaration order, before of the loop
  132. lightingModel.directDiffuse.append();
  133. lightingModel.directSpecular.append();
  134. super.setupLights( builder, lightNodes );
  135. Fn( () => {
  136. Loop( this._tileLightCount, ( { i } ) => {
  137. const lightIndex = this.getTile( i );
  138. If( lightIndex.equal( int( 0 ) ), () => {
  139. Break();
  140. } );
  141. const { color, decay, viewPosition, distance } = this.getLightData( lightIndex.sub( 1 ) );
  142. builder.lightsNode.setupDirectLight( builder, this, directPointLight( {
  143. color,
  144. lightVector: viewPosition.sub( positionView ),
  145. cutoffDistance: distance,
  146. decayExponent: decay
  147. } ) );
  148. } );
  149. } )().append();
  150. }
  151. getBufferFitSize( value ) {
  152. const multiple = this.tileSize;
  153. return Math.ceil( value / multiple ) * multiple;
  154. }
  155. setSize( width, height ) {
  156. width = this.getBufferFitSize( width );
  157. height = this.getBufferFitSize( height );
  158. if ( ! this._bufferSize || this._bufferSize.width !== width || this._bufferSize.height !== height ) {
  159. this.create( width, height );
  160. }
  161. return this;
  162. }
  163. updateProgram( renderer ) {
  164. renderer.getDrawingBufferSize( _size );
  165. const width = this.getBufferFitSize( _size.width );
  166. const height = this.getBufferFitSize( _size.height );
  167. if ( this._bufferSize === null ) {
  168. this.create( width, height );
  169. } else if ( this._bufferSize.width !== width || this._bufferSize.height !== height ) {
  170. this.create( width, height );
  171. }
  172. }
  173. create( width, height ) {
  174. const { tileSize, maxLights } = this;
  175. const bufferSize = new Vector2( width, height );
  176. const lineSize = Math.floor( bufferSize.width / tileSize );
  177. const count = Math.floor( ( bufferSize.width * bufferSize.height ) / tileSize );
  178. // buffers
  179. const lightsData = new Float32Array( maxLights * 4 * 2 ); // 2048 lights, 4 elements(rgba), 2 components, 1 component per line (position, distance, color, decay)
  180. const lightsTexture = new DataTexture( lightsData, lightsData.length / 8, 2, RGBAFormat, FloatType );
  181. const lightIndexesArray = new Int32Array( count * 4 * 2 );
  182. const lightIndexes = attributeArray( lightIndexesArray, 'ivec4' ).label( 'lightIndexes' );
  183. // compute
  184. const getBlock = ( index ) => {
  185. const tileIndex = instanceIndex.mul( int( 2 ) ).add( int( index ) );
  186. return lightIndexes.element( tileIndex );
  187. };
  188. const getTile = ( elementIndex ) => {
  189. elementIndex = int( elementIndex );
  190. const stride = int( 4 );
  191. const tileOffset = elementIndex.div( stride );
  192. const tileIndex = instanceIndex.mul( int( 2 ) ).add( tileOffset );
  193. return lightIndexes.element( tileIndex ).element( elementIndex.modInt( stride ) );
  194. };
  195. const compute = Fn( () => {
  196. const { _cameraProjectionMatrix: cameraProjectionMatrix, _bufferSize: bufferSize, _screenSize: screenSize } = this;
  197. const tiledBufferSize = bufferSize.clone().divideScalar( tileSize ).floor();
  198. const tileScreen = vec2(
  199. instanceIndex.modInt( tiledBufferSize.width ),
  200. instanceIndex.div( tiledBufferSize.width )
  201. ).mul( tileSize ).div( screenSize );
  202. const blockSize = float( tileSize ).div( screenSize );
  203. const minBounds = tileScreen;
  204. const maxBounds = minBounds.add( blockSize );
  205. const index = int( 0 ).toVar();
  206. getBlock( 0 ).assign( ivec4( 0 ) );
  207. getBlock( 1 ).assign( ivec4( 0 ) );
  208. Loop( this.maxLights, ( { i } ) => {
  209. If( index.greaterThanEqual( this._tileLightCount ).or( int( i ).greaterThanEqual( int( this._lightsCount ) ) ), () => {
  210. Return();
  211. } );
  212. const { viewPosition, distance } = this.getLightData( i );
  213. const projectedPosition = cameraProjectionMatrix.mul( viewPosition );
  214. const ndc = projectedPosition.div( projectedPosition.w );
  215. const screenPosition = ndc.xy.mul( 0.5 ).add( 0.5 ).flipY();
  216. const distanceFromCamera = viewPosition.z;
  217. const pointRadius = distance.div( distanceFromCamera );
  218. If( circleIntersectsAABB( screenPosition, pointRadius, minBounds, maxBounds ), () => {
  219. getTile( index ).assign( i.add( int( 1 ) ) );
  220. index.addAssign( int( 1 ) );
  221. } );
  222. } );
  223. } )().compute( count );
  224. // screen coordinate lighting indexes
  225. const screenTile = screenCoordinate.div( tileSize ).floor().toVar();
  226. const screenTileIndex = screenTile.x.add( screenTile.y.mul( lineSize ) );
  227. // assigns
  228. this._bufferSize = bufferSize;
  229. this._lightIndexes = lightIndexes;
  230. this._screenTileIndex = screenTileIndex;
  231. this._compute = compute;
  232. this._lightsTexture = lightsTexture;
  233. }
  234. get hasLights() {
  235. return super.hasLights || this.tiledLights.length > 0;
  236. }
  237. }
  238. export default TiledLightsNode;
  239. export const tiledLights = /*@__PURE__*/ nodeProxy( TiledLightsNode );
粤ICP备19079148号