TiledLightsNode.js 9.9 KB

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