CSM.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. ( function () {
  2. const _cameraToLightMatrix = new THREE.Matrix4();
  3. const _lightSpaceFrustum = new THREE.CSMFrustum();
  4. const _center = new THREE.Vector3();
  5. const _bbox = new THREE.Box3();
  6. const _uniformArray = [];
  7. const _logArray = [];
  8. class CSM {
  9. constructor( data ) {
  10. data = data || {};
  11. this.camera = data.camera;
  12. this.parent = data.parent;
  13. this.cascades = data.cascades || 3;
  14. this.maxFar = data.maxFar || 100000;
  15. this.mode = data.mode || 'practical';
  16. this.shadowMapSize = data.shadowMapSize || 2048;
  17. this.shadowBias = data.shadowBias || 0.000001;
  18. this.lightDirection = data.lightDirection || new THREE.Vector3( 1, - 1, 1 ).normalize();
  19. this.lightIntensity = data.lightIntensity || 1;
  20. this.lightNear = data.lightNear || 1;
  21. this.lightFar = data.lightFar || 2000;
  22. this.lightMargin = data.lightMargin || 200;
  23. this.customSplitsCallback = data.customSplitsCallback;
  24. this.fade = false;
  25. this.mainFrustum = new THREE.CSMFrustum();
  26. this.frustums = [];
  27. this.breaks = [];
  28. this.lights = [];
  29. this.shaders = new Map();
  30. this.createLights();
  31. this.updateFrustums();
  32. this.injectInclude();
  33. }
  34. createLights() {
  35. for ( let i = 0; i < this.cascades; i ++ ) {
  36. const light = new THREE.DirectionalLight( 0xffffff, this.lightIntensity );
  37. light.castShadow = true;
  38. light.shadow.mapSize.width = this.shadowMapSize;
  39. light.shadow.mapSize.height = this.shadowMapSize;
  40. light.shadow.camera.near = this.lightNear;
  41. light.shadow.camera.far = this.lightFar;
  42. light.shadow.bias = this.shadowBias;
  43. this.parent.add( light );
  44. this.parent.add( light.target );
  45. this.lights.push( light );
  46. }
  47. }
  48. initCascades() {
  49. const camera = this.camera;
  50. camera.updateProjectionMatrix();
  51. this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar );
  52. this.mainFrustum.split( this.breaks, this.frustums );
  53. }
  54. updateShadowBounds() {
  55. const frustums = this.frustums;
  56. for ( let i = 0; i < frustums.length; i ++ ) {
  57. const light = this.lights[ i ];
  58. const shadowCam = light.shadow.camera;
  59. const frustum = this.frustums[ i ];
  60. // Get the two points that represent that furthest points on the frustum assuming
  61. // that's either the diagonal across the far plane or the diagonal across the whole
  62. // frustum itself.
  63. const nearVerts = frustum.vertices.near;
  64. const farVerts = frustum.vertices.far;
  65. const point1 = farVerts[ 0 ];
  66. let point2;
  67. if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) {
  68. point2 = farVerts[ 2 ];
  69. } else {
  70. point2 = nearVerts[ 2 ];
  71. }
  72. let squaredBBWidth = point1.distanceTo( point2 );
  73. if ( this.fade ) {
  74. // expand the shadow extents by the fade margin if fade is enabled.
  75. const camera = this.camera;
  76. const far = Math.max( camera.far, this.maxFar );
  77. const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near );
  78. const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near );
  79. squaredBBWidth += margin;
  80. }
  81. shadowCam.left = - squaredBBWidth / 2;
  82. shadowCam.right = squaredBBWidth / 2;
  83. shadowCam.top = squaredBBWidth / 2;
  84. shadowCam.bottom = - squaredBBWidth / 2;
  85. shadowCam.updateProjectionMatrix();
  86. }
  87. }
  88. getBreaks() {
  89. const camera = this.camera;
  90. const far = Math.min( camera.far, this.maxFar );
  91. this.breaks.length = 0;
  92. switch ( this.mode ) {
  93. case 'uniform':
  94. uniformSplit( this.cascades, camera.near, far, this.breaks );
  95. break;
  96. case 'logarithmic':
  97. logarithmicSplit( this.cascades, camera.near, far, this.breaks );
  98. break;
  99. case 'practical':
  100. practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks );
  101. break;
  102. case 'custom':
  103. if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' );
  104. this.customSplitsCallback( this.cascades, camera.near, far, this.breaks );
  105. break;
  106. }
  107. function uniformSplit( amount, near, far, target ) {
  108. for ( let i = 1; i < amount; i ++ ) {
  109. target.push( ( near + ( far - near ) * i / amount ) / far );
  110. }
  111. target.push( 1 );
  112. }
  113. function logarithmicSplit( amount, near, far, target ) {
  114. for ( let i = 1; i < amount; i ++ ) {
  115. target.push( near * ( far / near ) ** ( i / amount ) / far );
  116. }
  117. target.push( 1 );
  118. }
  119. function practicalSplit( amount, near, far, lambda, target ) {
  120. _uniformArray.length = 0;
  121. _logArray.length = 0;
  122. logarithmicSplit( amount, near, far, _logArray );
  123. uniformSplit( amount, near, far, _uniformArray );
  124. for ( let i = 1; i < amount; i ++ ) {
  125. target.push( THREE.MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) );
  126. }
  127. target.push( 1 );
  128. }
  129. }
  130. update() {
  131. const camera = this.camera;
  132. const frustums = this.frustums;
  133. for ( let i = 0; i < frustums.length; i ++ ) {
  134. const light = this.lights[ i ];
  135. const shadowCam = light.shadow.camera;
  136. const texelWidth = ( shadowCam.right - shadowCam.left ) / this.shadowMapSize;
  137. const texelHeight = ( shadowCam.top - shadowCam.bottom ) / this.shadowMapSize;
  138. light.shadow.camera.updateMatrixWorld( true );
  139. _cameraToLightMatrix.multiplyMatrices( light.shadow.camera.matrixWorldInverse, camera.matrixWorld );
  140. frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum );
  141. const nearVerts = _lightSpaceFrustum.vertices.near;
  142. const farVerts = _lightSpaceFrustum.vertices.far;
  143. _bbox.makeEmpty();
  144. for ( let j = 0; j < 4; j ++ ) {
  145. _bbox.expandByPoint( nearVerts[ j ] );
  146. _bbox.expandByPoint( farVerts[ j ] );
  147. }
  148. _bbox.getCenter( _center );
  149. _center.z = _bbox.max.z + this.lightMargin;
  150. _center.x = Math.floor( _center.x / texelWidth ) * texelWidth;
  151. _center.y = Math.floor( _center.y / texelHeight ) * texelHeight;
  152. _center.applyMatrix4( light.shadow.camera.matrixWorld );
  153. light.position.copy( _center );
  154. light.target.position.copy( _center );
  155. light.target.position.x += this.lightDirection.x;
  156. light.target.position.y += this.lightDirection.y;
  157. light.target.position.z += this.lightDirection.z;
  158. }
  159. }
  160. injectInclude() {
  161. THREE.ShaderChunk.lights_fragment_begin = THREE.CSMShader.lights_fragment_begin;
  162. THREE.ShaderChunk.lights_pars_begin = THREE.CSMShader.lights_pars_begin;
  163. }
  164. setupMaterial( material ) {
  165. material.defines = material.defines || {};
  166. material.defines.USE_CSM = 1;
  167. material.defines.CSM_CASCADES = this.cascades;
  168. if ( this.fade ) {
  169. material.defines.CSM_FADE = '';
  170. }
  171. const breaksVec2 = [];
  172. const scope = this;
  173. const shaders = this.shaders;
  174. material.onBeforeCompile = function ( shader ) {
  175. const far = Math.min( scope.camera.far, scope.maxFar );
  176. scope.getExtendedBreaks( breaksVec2 );
  177. shader.uniforms.CSM_cascades = {
  178. value: breaksVec2
  179. };
  180. shader.uniforms.cameraNear = {
  181. value: scope.camera.near
  182. };
  183. shader.uniforms.shadowFar = {
  184. value: far
  185. };
  186. shaders.set( material, shader );
  187. };
  188. shaders.set( material, null );
  189. }
  190. updateUniforms() {
  191. const far = Math.min( this.camera.far, this.maxFar );
  192. const shaders = this.shaders;
  193. shaders.forEach( function ( shader, material ) {
  194. if ( shader !== null ) {
  195. const uniforms = shader.uniforms;
  196. this.getExtendedBreaks( uniforms.CSM_cascades.value );
  197. uniforms.cameraNear.value = this.camera.near;
  198. uniforms.shadowFar.value = far;
  199. }
  200. if ( ! this.fade && 'CSM_FADE' in material.defines ) {
  201. delete material.defines.CSM_FADE;
  202. material.needsUpdate = true;
  203. } else if ( this.fade && ! ( 'CSM_FADE' in material.defines ) ) {
  204. material.defines.CSM_FADE = '';
  205. material.needsUpdate = true;
  206. }
  207. }, this );
  208. }
  209. getExtendedBreaks( target ) {
  210. while ( target.length < this.breaks.length ) {
  211. target.push( new THREE.Vector2() );
  212. }
  213. target.length = this.breaks.length;
  214. for ( let i = 0; i < this.cascades; i ++ ) {
  215. const amount = this.breaks[ i ];
  216. const prev = this.breaks[ i - 1 ] || 0;
  217. target[ i ].x = prev;
  218. target[ i ].y = amount;
  219. }
  220. }
  221. updateFrustums() {
  222. this.getBreaks();
  223. this.initCascades();
  224. this.updateShadowBounds();
  225. this.updateUniforms();
  226. }
  227. remove() {
  228. for ( let i = 0; i < this.lights.length; i ++ ) {
  229. this.parent.remove( this.lights[ i ].target );
  230. this.parent.remove( this.lights[ i ] );
  231. }
  232. }
  233. dispose() {
  234. const shaders = this.shaders;
  235. shaders.forEach( function ( shader, material ) {
  236. delete material.onBeforeCompile;
  237. delete material.defines.USE_CSM;
  238. delete material.defines.CSM_CASCADES;
  239. delete material.defines.CSM_FADE;
  240. if ( shader !== null ) {
  241. delete shader.uniforms.CSM_cascades;
  242. delete shader.uniforms.cameraNear;
  243. delete shader.uniforms.shadowFar;
  244. }
  245. material.needsUpdate = true;
  246. } );
  247. shaders.clear();
  248. }
  249. }
  250. THREE.CSM = CSM;
  251. } )();