DynamicLightsNode.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import { LightsNode, NodeUtils, warn } from 'three/webgpu';
  2. import { nodeObject } from 'three/tsl';
  3. import AmbientLightDataNode from './data/AmbientLightDataNode.js';
  4. import DirectionalLightDataNode from './data/DirectionalLightDataNode.js';
  5. import PointLightDataNode from './data/PointLightDataNode.js';
  6. import SpotLightDataNode from './data/SpotLightDataNode.js';
  7. import HemisphereLightDataNode from './data/HemisphereLightDataNode.js';
  8. const _lightNodeRef = /*@__PURE__*/ new WeakMap();
  9. const _hashData = [];
  10. const _lightTypeToDataNode = {
  11. AmbientLight: AmbientLightDataNode,
  12. DirectionalLight: DirectionalLightDataNode,
  13. PointLight: PointLightDataNode,
  14. SpotLight: SpotLightDataNode,
  15. HemisphereLight: HemisphereLightDataNode
  16. };
  17. const _lightTypeToMaxProp = {
  18. DirectionalLight: 'maxDirectionalLights',
  19. PointLight: 'maxPointLights',
  20. SpotLight: 'maxSpotLights',
  21. HemisphereLight: 'maxHemisphereLights'
  22. };
  23. const sortLights = ( lights ) => lights.sort( ( a, b ) => a.id - b.id );
  24. const isSpecialSpotLight = ( light ) => {
  25. return light.isSpotLight === true && ( light.map !== null || light.colorNode !== undefined );
  26. };
  27. const canBatchLight = ( light ) => {
  28. return light.isNode !== true &&
  29. light.castShadow !== true &&
  30. isSpecialSpotLight( light ) === false &&
  31. _lightTypeToDataNode[ light.constructor.name ] !== undefined;
  32. };
  33. const getOrCreateLightNode = ( light, nodeLibrary ) => {
  34. const lightNodeClass = nodeLibrary.getLightNodeClass( light.constructor );
  35. if ( lightNodeClass === null ) {
  36. warn( `DynamicLightsNode: Light node not found for ${ light.constructor.name }.` );
  37. return null;
  38. }
  39. if ( _lightNodeRef.has( light ) === false ) {
  40. _lightNodeRef.set( light, new lightNodeClass( light ) );
  41. }
  42. return _lightNodeRef.get( light );
  43. };
  44. /**
  45. * A custom version of `LightsNode` that batches supported analytic lights into
  46. * uniform arrays and loops.
  47. *
  48. * Unsupported lights, node lights, shadow-casting lights, and projected spot
  49. * lights keep the default per-light path.
  50. *
  51. * @augments LightsNode
  52. * @three_import import { DynamicLightsNode } from 'three/addons/tsl/lighting/DynamicLightsNode.js';
  53. */
  54. class DynamicLightsNode extends LightsNode {
  55. static get type() {
  56. return 'DynamicLightsNode';
  57. }
  58. /**
  59. * Constructs a new dynamic lights node.
  60. *
  61. * @param {Object} [options={}] - Dynamic lighting configuration.
  62. * @param {number} [options.maxDirectionalLights=8] - Maximum number of batched directional lights.
  63. * @param {number} [options.maxPointLights=16] - Maximum number of batched point lights.
  64. * @param {number} [options.maxSpotLights=16] - Maximum number of batched spot lights.
  65. * @param {number} [options.maxHemisphereLights=4] - Maximum number of batched hemisphere lights.
  66. */
  67. constructor( options = {} ) {
  68. super();
  69. this.maxDirectionalLights = options.maxDirectionalLights !== undefined ? options.maxDirectionalLights : 8;
  70. this.maxPointLights = options.maxPointLights !== undefined ? options.maxPointLights : 16;
  71. this.maxSpotLights = options.maxSpotLights !== undefined ? options.maxSpotLights : 16;
  72. this.maxHemisphereLights = options.maxHemisphereLights !== undefined ? options.maxHemisphereLights : 4;
  73. this._dataNodes = new Map();
  74. }
  75. customCacheKey() {
  76. const typeSet = new Set();
  77. for ( let i = 0; i < this._lights.length; i ++ ) {
  78. const light = this._lights[ i ];
  79. if ( canBatchLight( light ) ) {
  80. typeSet.add( light.constructor.name );
  81. } else {
  82. _hashData.push( light.id );
  83. _hashData.push( light.castShadow ? 1 : 0 );
  84. if ( light.isSpotLight === true ) {
  85. const hashMap = light.map !== null ? light.map.id : - 1;
  86. const hashColorNode = light.colorNode ? light.colorNode.getCacheKey() : - 1;
  87. _hashData.push( hashMap, hashColorNode );
  88. }
  89. }
  90. }
  91. for ( const typeName of this._dataNodes.keys() ) {
  92. typeSet.add( typeName );
  93. }
  94. for ( const typeName of [ ...typeSet ].sort() ) {
  95. _hashData.push( NodeUtils.hashString( typeName ) );
  96. }
  97. const cacheKey = NodeUtils.hashArray( _hashData );
  98. _hashData.length = 0;
  99. return cacheKey;
  100. }
  101. setupLightsNode( builder ) {
  102. const lightNodes = [];
  103. const lightsByType = new Map();
  104. const lights = sortLights( this._lights );
  105. const nodeLibrary = builder.renderer.library;
  106. for ( const light of lights ) {
  107. if ( light.isNode === true ) {
  108. lightNodes.push( nodeObject( light ) );
  109. continue;
  110. }
  111. if ( canBatchLight( light ) ) {
  112. const typeName = light.constructor.name;
  113. const typeLights = lightsByType.get( typeName );
  114. if ( typeLights === undefined ) {
  115. lightsByType.set( typeName, [ light ] );
  116. } else {
  117. typeLights.push( light );
  118. }
  119. continue;
  120. }
  121. const lightNode = getOrCreateLightNode( light, nodeLibrary );
  122. if ( lightNode !== null ) {
  123. lightNodes.push( lightNode );
  124. }
  125. }
  126. for ( const [ typeName, typeLights ] of lightsByType ) {
  127. let dataNode = this._dataNodes.get( typeName );
  128. if ( dataNode === undefined ) {
  129. const DataNodeClass = _lightTypeToDataNode[ typeName ];
  130. const maxProp = _lightTypeToMaxProp[ typeName ];
  131. const maxCount = maxProp !== undefined ? this[ maxProp ] : undefined;
  132. dataNode = maxCount !== undefined ? new DataNodeClass( maxCount ) : new DataNodeClass();
  133. this._dataNodes.set( typeName, dataNode );
  134. }
  135. dataNode.setLights( typeLights );
  136. lightNodes.push( dataNode );
  137. }
  138. for ( const [ typeName, dataNode ] of this._dataNodes ) {
  139. if ( lightsByType.has( typeName ) === false ) {
  140. dataNode.setLights( [] );
  141. lightNodes.push( dataNode );
  142. }
  143. }
  144. this._lightNodes = lightNodes;
  145. }
  146. setLights( lights ) {
  147. super.setLights( lights );
  148. if ( this._dataNodes.size > 0 ) {
  149. this._updateDataNodeLights( lights );
  150. }
  151. return this;
  152. }
  153. _updateDataNodeLights( lights ) {
  154. const lightsByType = new Map();
  155. for ( const light of lights ) {
  156. if ( canBatchLight( light ) === false ) continue;
  157. const typeName = light.constructor.name;
  158. const typeLights = lightsByType.get( typeName );
  159. if ( typeLights === undefined ) {
  160. lightsByType.set( typeName, [ light ] );
  161. } else {
  162. typeLights.push( light );
  163. }
  164. }
  165. for ( const [ typeName, dataNode ] of this._dataNodes ) {
  166. dataNode.setLights( lightsByType.get( typeName ) || [] );
  167. }
  168. }
  169. get hasLights() {
  170. return super.hasLights || this._dataNodes.size > 0;
  171. }
  172. }
  173. export default DynamicLightsNode;
  174. /**
  175. * TSL function that creates a dynamic lights node.
  176. *
  177. * @tsl
  178. * @function
  179. * @param {Object} [options={}] - Dynamic lighting configuration.
  180. * @return {DynamicLightsNode} The created dynamic lights node.
  181. */
  182. export const dynamicLights = ( options = {} ) => new DynamicLightsNode( options );
粤ICP备19079148号