SpotLightDataNode.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { Color, Node, Vector3, Vector4 } from 'three/webgpu';
  2. import { Loop, NodeUpdateType, getDistanceAttenuation, positionView, renderGroup, smoothstep, uniform, uniformArray, vec3 } from 'three/tsl';
  3. const _lightPosition = /*@__PURE__*/ new Vector3();
  4. const _targetPosition = /*@__PURE__*/ new Vector3();
  5. const warn = ( message ) => {
  6. console.warn( `THREE.SpotLightDataNode: ${ message }` );
  7. };
  8. /**
  9. * Batched data node for simple spot lights in dynamic lighting mode.
  10. *
  11. * Projected spot lights keep the default per-light path.
  12. *
  13. * @augments Node
  14. */
  15. class SpotLightDataNode extends Node {
  16. static get type() {
  17. return 'SpotLightDataNode';
  18. }
  19. constructor( maxCount = 16 ) {
  20. super();
  21. this.maxCount = maxCount;
  22. this._lights = [];
  23. this._colors = [];
  24. this._positionsAndCutoff = [];
  25. this._directionsAndDecay = [];
  26. this._cones = [];
  27. for ( let i = 0; i < maxCount; i ++ ) {
  28. this._colors.push( new Color() );
  29. this._positionsAndCutoff.push( new Vector4() );
  30. this._directionsAndDecay.push( new Vector4() );
  31. this._cones.push( new Vector4() );
  32. }
  33. this.colorsNode = uniformArray( this._colors, 'color' ).setGroup( renderGroup );
  34. this.positionsAndCutoffNode = uniformArray( this._positionsAndCutoff, 'vec4' ).setGroup( renderGroup );
  35. this.directionsAndDecayNode = uniformArray( this._directionsAndDecay, 'vec4' ).setGroup( renderGroup );
  36. this.conesNode = uniformArray( this._cones, 'vec4' ).setGroup( renderGroup );
  37. this.countNode = uniform( 0, 'int' ).setGroup( renderGroup );
  38. this.updateType = NodeUpdateType.RENDER;
  39. }
  40. setLights( lights ) {
  41. if ( lights.length > this.maxCount ) {
  42. warn( `${ lights.length } lights exceed the configured max of ${ this.maxCount }. Excess lights are ignored.` );
  43. }
  44. this._lights = lights;
  45. return this;
  46. }
  47. update( { camera } ) {
  48. const count = Math.min( this._lights.length, this.maxCount );
  49. this.countNode.value = count;
  50. for ( let i = 0; i < count; i ++ ) {
  51. const light = this._lights[ i ];
  52. this._colors[ i ].copy( light.color ).multiplyScalar( light.intensity );
  53. _lightPosition.setFromMatrixPosition( light.matrixWorld );
  54. _lightPosition.applyMatrix4( camera.matrixWorldInverse );
  55. const positionAndCutoff = this._positionsAndCutoff[ i ];
  56. positionAndCutoff.x = _lightPosition.x;
  57. positionAndCutoff.y = _lightPosition.y;
  58. positionAndCutoff.z = _lightPosition.z;
  59. positionAndCutoff.w = light.distance;
  60. _lightPosition.setFromMatrixPosition( light.matrixWorld );
  61. _targetPosition.setFromMatrixPosition( light.target.matrixWorld );
  62. _lightPosition.sub( _targetPosition ).transformDirection( camera.matrixWorldInverse );
  63. const directionAndDecay = this._directionsAndDecay[ i ];
  64. directionAndDecay.x = _lightPosition.x;
  65. directionAndDecay.y = _lightPosition.y;
  66. directionAndDecay.z = _lightPosition.z;
  67. directionAndDecay.w = light.decay;
  68. const cone = this._cones[ i ];
  69. cone.x = Math.cos( light.angle );
  70. cone.y = Math.cos( light.angle * ( 1 - light.penumbra ) );
  71. }
  72. }
  73. setup( builder ) {
  74. const surfacePosition = builder.context.positionView || positionView;
  75. const { lightingModel, reflectedLight } = builder.context;
  76. const dynDiffuse = vec3( 0 ).toVar( 'dynSpotDiffuse' );
  77. const dynSpecular = vec3( 0 ).toVar( 'dynSpotSpecular' );
  78. Loop( this.countNode, ( { i } ) => {
  79. const positionAndCutoff = this.positionsAndCutoffNode.element( i );
  80. const lightViewPosition = positionAndCutoff.xyz;
  81. const cutoffDistance = positionAndCutoff.w;
  82. const directionAndDecay = this.directionsAndDecayNode.element( i );
  83. const spotDirection = directionAndDecay.xyz;
  84. const decayExponent = directionAndDecay.w;
  85. const cone = this.conesNode.element( i );
  86. const coneCos = cone.x;
  87. const penumbraCos = cone.y;
  88. const lightVector = lightViewPosition.sub( surfacePosition ).toVar();
  89. const lightDirection = lightVector.normalize().toVar();
  90. const lightDistance = lightVector.length();
  91. const angleCos = lightDirection.dot( spotDirection );
  92. const spotAttenuation = smoothstep( coneCos, penumbraCos, angleCos );
  93. const distanceAttenuation = getDistanceAttenuation( {
  94. lightDistance,
  95. cutoffDistance,
  96. decayExponent
  97. } );
  98. const lightColor = this.colorsNode.element( i ).mul( spotAttenuation ).mul( distanceAttenuation ).toVar();
  99. lightingModel.direct( {
  100. lightDirection,
  101. lightColor,
  102. lightNode: { light: {}, shadowNode: null },
  103. reflectedLight: { directDiffuse: dynDiffuse, directSpecular: dynSpecular }
  104. }, builder );
  105. } );
  106. reflectedLight.directDiffuse.addAssign( dynDiffuse );
  107. reflectedLight.directSpecular.addAssign( dynSpecular );
  108. }
  109. }
  110. export default SpotLightDataNode;
粤ICP备19079148号