TileShadowNodeHelper.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { Group, NodeMaterial, Mesh, PlaneGeometry, DoubleSide, CameraHelper } from 'three/webgpu';
  2. import { Fn, vec4, vec3, texture, uv, positionLocal, vec2, float, screenSize } from 'three/tsl';
  3. /**
  4. * Helper class to manage and display debug visuals for TileShadowNode.
  5. *
  6. * @augments Group
  7. * @three_import import { TileShadowNodeHelper } from 'three/addons/tsl/shadows/TileShadowNodeHelper.js';
  8. */
  9. class TileShadowNodeHelper extends Group {
  10. /**
  11. * @param {TileShadowNode} tileShadowNode The TileShadowNode instance to debug.
  12. */
  13. constructor( tileShadowNode ) {
  14. super();
  15. if ( ! tileShadowNode ) {
  16. throw new Error( 'TileShadowNode instance is required for TileShadowNodeHelper.' );
  17. }
  18. this.tileShadowNode = tileShadowNode;
  19. this.config = tileShadowNode.config;
  20. this.tiles = tileShadowNode.tiles;
  21. this._debugMeshes = [];
  22. this._shadowCamHelpers = [];
  23. this.initialized = false;
  24. }
  25. /**
  26. * Initializes the debug displays (planes and camera helpers).
  27. * Should be called after TileShadowNode has initialized its lights and shadow nodes.
  28. */
  29. init() {
  30. if ( this.tileShadowNode._shadowNodes.length !== this.tiles.length ) {
  31. console.error( 'Cannot initialize TileShadowNodeHelper: Shadow nodes not ready or mismatch count.' );
  32. return;
  33. }
  34. const tilesX = this.config.tilesX;
  35. const tilesY = this.config.tilesY;
  36. // Clear previous helpers if any (e.g., during a re-init)
  37. this.dispose();
  38. // Create a display for each shadow map tile
  39. for ( let i = 0; i < this.tiles.length; i ++ ) {
  40. // Create display plane
  41. const display = new Mesh( new PlaneGeometry( 1, 1 ), new NodeMaterial() );
  42. display.renderOrder = 9999999; // Ensure they appear on top
  43. display.material.transparent = true;
  44. display.frustumCulled = false;
  45. display.side = DoubleSide;
  46. display.material.depthTest = false; // Disable depth testing
  47. display.material.depthWrite = false; // Disable depth writing
  48. const col = i % tilesX;
  49. const row = Math.floor( i / tilesX );
  50. // Vertex shader logic for positioning the debug quad
  51. display.material.vertexNode = Fn( () => {
  52. const aspectRatio = screenSize.x.div( screenSize.y );
  53. const maxTiles = Math.max( tilesX, tilesY );
  54. const displaySize = float( 0.8 / maxTiles ); // Size adapts to number of tiles
  55. const margin = float( 0.01 );
  56. const cornerOffset = float( 0.05 );
  57. // Position tiles left-to-right, top-to-bottom
  58. const xBase = float( - 1.0 ).add( cornerOffset ).add(
  59. displaySize.div( 2 ).div( aspectRatio )
  60. ).add( float( col ).mul( displaySize.div( aspectRatio ).add( margin ) ) );
  61. const yBase = float( 1.0 ).sub( cornerOffset ).sub(
  62. displaySize.div( 2 )
  63. ).sub( float( row ).mul( displaySize.add( margin ) ) );
  64. const scaledPos = vec2(
  65. positionLocal.x.mul( displaySize.div( aspectRatio ) ),
  66. positionLocal.y.mul( displaySize )
  67. );
  68. const finalPos = vec2(
  69. scaledPos.x.add( xBase ),
  70. scaledPos.y.add( yBase )
  71. );
  72. return vec4( finalPos.x, finalPos.y, 0.0, 1.0 );
  73. } )();
  74. display.material.outputNode = Fn( () => {
  75. // Ensure shadowMap and depthTexture are available
  76. if ( ! this.tileShadowNode.shadowMap || ! this.tileShadowNode.shadowMap.depthTexture ) {
  77. return vec4( 1, 0, 1, 1 ); // Magenta error color
  78. }
  79. const sampledDepth = texture( this.tileShadowNode.shadowMap.depthTexture )
  80. .sample( uv().flipY() )
  81. .depth( float( i ) ) // Sample correct layer
  82. .compare( 0.9 ); // Example comparison value
  83. // Simple tint based on index for visual distinction
  84. const r = float( 0.5 + ( i % 3 ) * 0.16 );
  85. const g = float( 0.5 + ( i % 2 ) * 0.25 );
  86. const b = float( 0.7 + ( i % 4 ) * 0.075 );
  87. return vec4(
  88. vec3( r, g, b )
  89. .mul( sampledDepth )
  90. .saturate()
  91. .rgb,
  92. 1.0
  93. );
  94. } )();
  95. this.add( display );
  96. this._debugMeshes.push( display );
  97. if ( this.tileShadowNode._shadowNodes[ i ] && this.tileShadowNode._shadowNodes[ i ].shadow ) {
  98. const camHelper = new CameraHelper( this.tileShadowNode._shadowNodes[ i ].shadow.camera );
  99. camHelper.fog = false;
  100. this.add( camHelper );
  101. this._shadowCamHelpers.push( camHelper );
  102. } else {
  103. console.warn( `TileShadowNodeHelper: Could not create CameraHelper for tile index ${i}. Shadow node or camera missing.` );
  104. this._shadowCamHelpers.push( null );
  105. }
  106. }
  107. this.initialized = true;
  108. }
  109. /**
  110. * Updates the debug visuals (specifically camera helpers).
  111. * Should be called within TileShadowNode's update method.
  112. */
  113. update() {
  114. if ( this.initialized === false ) {
  115. this.init();
  116. }
  117. for ( const helper of this._shadowCamHelpers ) {
  118. if ( helper ) {
  119. helper.update(); // Update CameraHelper matrices
  120. helper.updateMatrixWorld( true ); // Ensure world matrix is current
  121. }
  122. }
  123. }
  124. /**
  125. * Removes all debug objects (planes and helpers) from the scene.
  126. */
  127. dispose() {
  128. if ( this.scene ) {
  129. for ( const mesh of this._debugMeshes ) {
  130. mesh.geometry.dispose();
  131. mesh.material.dispose();
  132. this.scene.remove( mesh );
  133. }
  134. for ( const helper of this._shadowCamHelpers ) {
  135. if ( helper ) {
  136. this.scene.remove( helper );
  137. }
  138. }
  139. }
  140. this._debugMeshes = [];
  141. this._shadowCamHelpers = [];
  142. }
  143. }
  144. export { TileShadowNodeHelper };