CSMShadowNode.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import {
  2. Vector2,
  3. Vector3,
  4. MathUtils,
  5. Matrix4,
  6. Box3,
  7. Object3D,
  8. WebGLCoordinateSystem,
  9. ShadowBaseNode
  10. } from 'three/webgpu';
  11. import { CSMFrustum } from './CSMFrustum.js';
  12. import { viewZToOrthographicDepth, reference, uniform, float, vec4, vec2, If, Fn, min, renderGroup, positionView, shadow } from 'three/tsl';
  13. const _cameraToLightMatrix = new Matrix4();
  14. const _lightSpaceFrustum = new CSMFrustum();
  15. const _center = new Vector3();
  16. const _bbox = new Box3();
  17. const _uniformArray = [];
  18. const _logArray = [];
  19. const _lightDirection = new Vector3();
  20. const _lightOrientationMatrix = new Matrix4();
  21. const _lightOrientationMatrixInverse = new Matrix4();
  22. const _up = new Vector3( 0, 1, 0 );
  23. class LwLight extends Object3D {
  24. constructor() {
  25. super();
  26. this.target = new Object3D();
  27. }
  28. }
  29. class CSMShadowNode extends ShadowBaseNode {
  30. constructor( light, data = {} ) {
  31. super( light );
  32. this.camera = null;
  33. this.cascades = data.cascades || 3;
  34. this.maxFar = data.maxFar || 100000;
  35. this.mode = data.mode || 'practical';
  36. this.lightMargin = data.lightMargin || 200;
  37. this.customSplitsCallback = data.customSplitsCallback;
  38. this.fade = false;
  39. this.breaks = [];
  40. this._cascades = [];
  41. this.mainFrustum = null;
  42. this.frustums = [];
  43. this.lights = [];
  44. this._shadowNodes = [];
  45. }
  46. init( { camera, renderer } ) {
  47. this.camera = camera;
  48. const data = { webGL: renderer.coordinateSystem === WebGLCoordinateSystem };
  49. this.mainFrustum = new CSMFrustum( data );
  50. const light = this.light;
  51. const parent = light.parent;
  52. for ( let i = 0; i < this.cascades; i ++ ) {
  53. const lwLight = new LwLight();
  54. lwLight.castShadow = true;
  55. const lShadow = light.shadow.clone();
  56. lShadow.bias = lShadow.bias * ( i + 1 );
  57. this.lights.push( lwLight );
  58. parent.add( lwLight );
  59. parent.add( lwLight.target );
  60. lwLight.shadow = lShadow;
  61. this._shadowNodes.push( shadow( lwLight, lShadow ) );
  62. this._cascades.push( new Vector2() );
  63. }
  64. this.updateFrustums();
  65. }
  66. initCascades() {
  67. const camera = this.camera;
  68. camera.updateProjectionMatrix();
  69. this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar );
  70. this.mainFrustum.split( this.breaks, this.frustums );
  71. }
  72. getBreaks() {
  73. const camera = this.camera;
  74. const far = Math.min( camera.far, this.maxFar );
  75. this.breaks.length = 0;
  76. switch ( this.mode ) {
  77. case 'uniform':
  78. uniformSplit( this.cascades, camera.near, far, this.breaks );
  79. break;
  80. case 'logarithmic':
  81. logarithmicSplit( this.cascades, camera.near, far, this.breaks );
  82. break;
  83. case 'practical':
  84. practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks );
  85. break;
  86. case 'custom':
  87. if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' );
  88. this.customSplitsCallback( this.cascades, camera.near, far, this.breaks );
  89. break;
  90. }
  91. function uniformSplit( amount, near, far, target ) {
  92. for ( let i = 1; i < amount; i ++ ) {
  93. target.push( ( near + ( far - near ) * i / amount ) / far );
  94. }
  95. target.push( 1 );
  96. }
  97. function logarithmicSplit( amount, near, far, target ) {
  98. for ( let i = 1; i < amount; i ++ ) {
  99. target.push( ( near * ( far / near ) ** ( i / amount ) ) / far );
  100. }
  101. target.push( 1 );
  102. }
  103. function practicalSplit( amount, near, far, lambda, target ) {
  104. _uniformArray.length = 0;
  105. _logArray.length = 0;
  106. logarithmicSplit( amount, near, far, _logArray );
  107. uniformSplit( amount, near, far, _uniformArray );
  108. for ( let i = 1; i < amount; i ++ ) {
  109. target.push( MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) );
  110. }
  111. target.push( 1 );
  112. }
  113. }
  114. setLightBreaks() {
  115. for ( let i = 0, l = this.cascades; i < l; i ++ ) {
  116. const amount = this.breaks[ i ];
  117. const prev = this.breaks[ i - 1 ] || 0;
  118. this._cascades[ i ].set( prev, amount );
  119. }
  120. }
  121. updateShadowBounds() {
  122. const frustums = this.frustums;
  123. for ( let i = 0; i < frustums.length; i ++ ) {
  124. const shadowCam = this.lights[ i ].shadow.camera;
  125. const frustum = this.frustums[ i ];
  126. // Get the two points that represent that furthest points on the frustum assuming
  127. // that's either the diagonal across the far plane or the diagonal across the whole
  128. // frustum itself.
  129. const nearVerts = frustum.vertices.near;
  130. const farVerts = frustum.vertices.far;
  131. const point1 = farVerts[ 0 ];
  132. let point2;
  133. if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) {
  134. point2 = farVerts[ 2 ];
  135. } else {
  136. point2 = nearVerts[ 2 ];
  137. }
  138. let squaredBBWidth = point1.distanceTo( point2 );
  139. if ( this.fade ) {
  140. // expand the shadow extents by the fade margin if fade is enabled.
  141. const camera = this.camera;
  142. const far = Math.max( camera.far, this.maxFar );
  143. const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near );
  144. const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near );
  145. squaredBBWidth += margin;
  146. }
  147. shadowCam.left = - squaredBBWidth / 2;
  148. shadowCam.right = squaredBBWidth / 2;
  149. shadowCam.top = squaredBBWidth / 2;
  150. shadowCam.bottom = - squaredBBWidth / 2;
  151. shadowCam.updateProjectionMatrix();
  152. }
  153. }
  154. updateFrustums() {
  155. this.getBreaks();
  156. this.initCascades();
  157. this.updateShadowBounds();
  158. this.setLightBreaks();
  159. }
  160. setupFade() {
  161. const cameraNear = reference( 'camera.near', 'float', this ).setGroup( renderGroup );
  162. const cascades = reference( '_cascades', 'vec2', this ).setGroup( renderGroup ).label( 'cascades' );
  163. const shadowFar = uniform( 'float' ).setGroup( renderGroup ).label( 'shadowFar' )
  164. .onRenderUpdate( () => Math.min( this.maxFar, this.camera.far ) );
  165. const linearDepth = viewZToOrthographicDepth( positionView.z, cameraNear, shadowFar ).toVar( 'linearDepth' );
  166. const lastCascade = this.cascades - 1;
  167. return Fn( ( builder ) => {
  168. this.setupShadowPosition( builder );
  169. const ret = vec4( 1, 1, 1, 1 ).toVar( 'shadowValue' );
  170. const cascade = vec2().toVar( 'cascade' );
  171. const cascadeCenter = float().toVar( 'cascadeCenter' );
  172. const margin = float().toVar( 'margin' );
  173. const csmX = float().toVar( 'csmX' );
  174. const csmY = float().toVar( 'csmY' );
  175. for ( let i = 0; i < this.cascades; i ++ ) {
  176. const isLastCascade = i === lastCascade;
  177. cascade.assign( cascades.element( i ) );
  178. cascadeCenter.assign( cascade.x.add( cascade.y ).div( 2.0 ) );
  179. const closestEdge = linearDepth.lessThan( cascadeCenter ).select( cascade.x, cascade.y );
  180. margin.assign( float( 0.25 ).mul( closestEdge.pow( 2.0 ) ) );
  181. csmX.assign( cascade.x.sub( margin.div( 2.0 ) ) );
  182. if ( isLastCascade ) {
  183. csmY.assign( cascade.y );
  184. } else {
  185. csmY.assign( cascade.y.add( margin.div( 2.0 ) ) );
  186. }
  187. const inRange = linearDepth.greaterThanEqual( csmX ).and( linearDepth.lessThanEqual( csmY ) );
  188. If( inRange, () => {
  189. const dist = min( linearDepth.sub( csmX ), csmY.sub( linearDepth ) ).toVar();
  190. let ratio = dist.div( margin ).clamp( 0.0, 1.0 );
  191. if ( i === 0 ) {
  192. // don't fade at nearest edge
  193. ratio = linearDepth.greaterThan( cascadeCenter ).select( ratio, 1 );
  194. }
  195. ret.subAssign( this._shadowNodes[ i ].oneMinus().mul( ratio ) );
  196. } );
  197. }
  198. return ret;
  199. } )();
  200. }
  201. setupStandard() {
  202. const cameraNear = reference( 'camera.near', 'float', this ).setGroup( renderGroup );
  203. const cascades = reference( '_cascades', 'vec2', this ).setGroup( renderGroup ).label( 'cascades' );
  204. const shadowFar = uniform( 'float' ).setGroup( renderGroup ).label( 'shadowFar' )
  205. .onRenderUpdate( () => Math.min( this.maxFar, this.camera.far ) );
  206. const linearDepth = viewZToOrthographicDepth( positionView.z, cameraNear, shadowFar ).toVar( 'linearDepth' );
  207. return Fn( ( builder ) => {
  208. this.setupShadowPosition( builder );
  209. const ret = vec4( 1, 1, 1, 1 ).toVar( 'shadowValue' );
  210. const cascade = vec2().toVar( 'cascade' );
  211. for ( let i = 0; i < this.cascades; i ++ ) {
  212. cascade.assign( cascades.element( i ) );
  213. If( linearDepth.greaterThanEqual( cascade.x ).and( linearDepth.lessThanEqual( cascade.y ) ), () => {
  214. ret.assign( this._shadowNodes[ i ] );
  215. } );
  216. }
  217. return ret;
  218. } )();
  219. }
  220. setup( builder ) {
  221. if ( this.camera === null ) this.init( builder );
  222. return this.fade === true ? this.setupFade() : this.setupStandard();
  223. }
  224. updateBefore( /*builder*/ ) {
  225. const light = this.light;
  226. const camera = this.camera;
  227. const frustums = this.frustums;
  228. _lightDirection.subVectors( light.target.position, light.position ).normalize();
  229. // for each frustum we need to find its min-max box aligned with the light orientation
  230. // the position in _lightOrientationMatrix does not matter, as we transform there and back
  231. _lightOrientationMatrix.lookAt( light.position, light.target.position, _up );
  232. _lightOrientationMatrixInverse.copy( _lightOrientationMatrix ).invert();
  233. for ( let i = 0; i < frustums.length; i ++ ) {
  234. const lwLight = this.lights[ i ];
  235. const shadow = lwLight.shadow;
  236. const shadowCam = shadow.camera;
  237. const texelWidth = ( shadowCam.right - shadowCam.left ) / shadow.mapSize.width;
  238. const texelHeight = ( shadowCam.top - shadowCam.bottom ) / shadow.mapSize.height;
  239. _cameraToLightMatrix.multiplyMatrices( _lightOrientationMatrixInverse, camera.matrixWorld );
  240. frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum );
  241. const nearVerts = _lightSpaceFrustum.vertices.near;
  242. const farVerts = _lightSpaceFrustum.vertices.far;
  243. _bbox.makeEmpty();
  244. for ( let j = 0; j < 4; j ++ ) {
  245. _bbox.expandByPoint( nearVerts[ j ] );
  246. _bbox.expandByPoint( farVerts[ j ] );
  247. }
  248. _bbox.getCenter( _center );
  249. _center.z = _bbox.max.z + this.lightMargin;
  250. _center.x = Math.floor( _center.x / texelWidth ) * texelWidth;
  251. _center.y = Math.floor( _center.y / texelHeight ) * texelHeight;
  252. _center.applyMatrix4( _lightOrientationMatrix );
  253. lwLight.position.copy( _center );
  254. lwLight.target.position.copy( _center );
  255. lwLight.target.position.add( _lightDirection );
  256. }
  257. }
  258. dispose() {
  259. for ( let i = 0; i < this.lights.length; i ++ ) {
  260. const light = this.lights[ i ];
  261. const parent = light.parent;
  262. parent.remove( light.target );
  263. parent.remove( light );
  264. }
  265. super.dispose();
  266. }
  267. }
  268. export { CSMShadowNode };
粤ICP备19079148号