three-csm.module.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import { Vector3, ShaderChunk, DirectionalLight, Vector2, LineBasicMaterial, Object3D, Geometry, Line } from '../../../build/three.module.js';
  2. class FrustumVertex {
  3. constructor(x, y, z) {
  4. this.x = x || 0;
  5. this.y = y || 0;
  6. this.z = z || 0;
  7. }
  8. fromLerp(v1, v2, amount) {
  9. this.x = (1 - amount) * v1.x + amount * v2.x;
  10. this.y = (1 - amount) * v1.y + amount * v2.y;
  11. this.z = (1 - amount) * v1.z + amount * v2.z;
  12. return this;
  13. }
  14. }
  15. function toRad(degrees) {
  16. return degrees * Math.PI / 180;
  17. }
  18. class Frustum {
  19. constructor(data) {
  20. data = data || {};
  21. this.fov = data.fov || 70;
  22. this.near = data.near || 0.1;
  23. this.far = data.far || 1000;
  24. this.aspect = data.aspect || 1;
  25. this.vertices = {
  26. near: [],
  27. far: []
  28. };
  29. }
  30. getViewSpaceVertices() {
  31. this.nearPlaneY = this.near * Math.tan(toRad(this.fov / 2));
  32. this.nearPlaneX = this.aspect * this.nearPlaneY;
  33. this.farPlaneY = this.far * Math.tan(toRad(this.fov / 2));
  34. this.farPlaneX = this.aspect * this.farPlaneY;
  35. // 3 --- 0 vertices.near/far order
  36. // | |
  37. // 2 --- 1
  38. this.vertices.near.push(
  39. new FrustumVertex(this.nearPlaneX, this.nearPlaneY, -this.near),
  40. new FrustumVertex(this.nearPlaneX, -this.nearPlaneY, -this.near),
  41. new FrustumVertex(-this.nearPlaneX, -this.nearPlaneY, -this.near),
  42. new FrustumVertex(-this.nearPlaneX, this.nearPlaneY, -this.near)
  43. );
  44. this.vertices.far.push(
  45. new FrustumVertex(this.farPlaneX, this.farPlaneY, -this.far),
  46. new FrustumVertex(this.farPlaneX, -this.farPlaneY, -this.far),
  47. new FrustumVertex(-this.farPlaneX, -this.farPlaneY, -this.far),
  48. new FrustumVertex(-this.farPlaneX, this.farPlaneY, -this.far)
  49. );
  50. return this.vertices;
  51. }
  52. split(breaks) {
  53. const result = [];
  54. for(let i = 0; i < breaks.length; i++) {
  55. const cascade = new Frustum();
  56. if(i === 0) {
  57. cascade.vertices.near = this.vertices.near;
  58. } else {
  59. for(let j = 0; j < 4; j++) {
  60. cascade.vertices.near.push(new FrustumVertex().fromLerp(this.vertices.near[j], this.vertices.far[j], breaks[i - 1]));
  61. }
  62. }
  63. if(i === breaks - 1) {
  64. cascade.vertices.far = this.vertices.far;
  65. } else {
  66. for(let j = 0; j < 4; j++) {
  67. cascade.vertices.far.push(new FrustumVertex().fromLerp(this.vertices.near[j], this.vertices.far[j], breaks[i]));
  68. }
  69. }
  70. result.push(cascade);
  71. }
  72. return result;
  73. }
  74. toSpace(cameraMatrix) {
  75. const result = new Frustum();
  76. const point = new Vector3();
  77. for(var i = 0; i < 4; i++) {
  78. point.set(this.vertices.near[i].x, this.vertices.near[i].y, this.vertices.near[i].z);
  79. point.applyMatrix4(cameraMatrix);
  80. result.vertices.near.push(new FrustumVertex(point.x, point.y, point.z));
  81. point.set(this.vertices.far[i].x, this.vertices.far[i].y, this.vertices.far[i].z);
  82. point.applyMatrix4(cameraMatrix);
  83. result.vertices.far.push(new FrustumVertex(point.x, point.y, point.z));
  84. }
  85. return result;
  86. }
  87. }
  88. class FrustumBoundingBox {
  89. constructor() {
  90. this.min = {
  91. x: 0,
  92. y: 0,
  93. z: 0
  94. };
  95. this.max = {
  96. x: 0,
  97. y: 0,
  98. z: 0
  99. };
  100. }
  101. fromFrustum(frustum) {
  102. const vertices = [];
  103. for(let i = 0; i < 4; i++) {
  104. vertices.push(frustum.vertices.near[i]);
  105. vertices.push(frustum.vertices.far[i]);
  106. }
  107. this.min = {
  108. x: vertices[0].x,
  109. y: vertices[0].y,
  110. z: vertices[0].z
  111. };
  112. this.max = {
  113. x: vertices[0].x,
  114. y: vertices[0].y,
  115. z: vertices[0].z
  116. };
  117. for(let i = 1; i < 8; i++) {
  118. this.min.x = Math.min(this.min.x, vertices[i].x);
  119. this.min.y = Math.min(this.min.y, vertices[i].y);
  120. this.min.z = Math.min(this.min.z, vertices[i].z);
  121. this.max.x = Math.max(this.max.x, vertices[i].x);
  122. this.max.y = Math.max(this.max.y, vertices[i].y);
  123. this.max.z = Math.max(this.max.z, vertices[i].z);
  124. }
  125. return this;
  126. }
  127. getSize() {
  128. this.size = {
  129. x: this.max.x - this.min.x,
  130. y: this.max.y - this.min.y,
  131. z: this.max.z - this.min.z
  132. };
  133. return this.size;
  134. }
  135. getCenter(margin) {
  136. this.center = {
  137. x: (this.max.x + this.min.x) / 2,
  138. y: (this.max.y + this.min.y) / 2,
  139. z: this.max.z + margin
  140. };
  141. return this.center;
  142. }
  143. }
  144. var Shader = {
  145. lights_fragment_begin: `
  146. GeometricContext geometry;
  147. geometry.position = - vViewPosition;
  148. geometry.normal = normal;
  149. geometry.viewDir = normalize( vViewPosition );
  150. #ifdef CLEARCOAT
  151. geometry.clearcoatNormal = clearcoatNormal;
  152. #endif
  153. IncidentLight directLight;
  154. #if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )
  155. PointLight pointLight;
  156. #pragma unroll_loop
  157. for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {
  158. pointLight = pointLights[ i ];
  159. getPointDirectLightIrradiance( pointLight, geometry, directLight );
  160. #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )
  161. directLight.color *= all( bvec3( pointLight.shadow, directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;
  162. #endif
  163. RE_Direct( directLight, geometry, material, reflectedLight );
  164. }
  165. #endif
  166. #if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )
  167. SpotLight spotLight;
  168. #pragma unroll_loop
  169. for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {
  170. spotLight = spotLights[ i ];
  171. getSpotDirectLightIrradiance( spotLight, geometry, directLight );
  172. #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
  173. directLight.color *= all( bvec3( spotLight.shadow, directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;
  174. #endif
  175. RE_Direct( directLight, geometry, material, reflectedLight );
  176. }
  177. #endif
  178. #if ( NUM_DIR_LIGHTS > 0) && defined( RE_Direct ) && defined( USE_CSM ) && defined( CSM_CASCADES )
  179. DirectionalLight directionalLight;
  180. float linearDepth = (vViewPosition.z) / (shadowFar - cameraNear);
  181. #pragma unroll_loop
  182. for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
  183. directionalLight = directionalLights[ i ];
  184. getDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );
  185. #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
  186. if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= all( bvec3( directionalLight.shadow, directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
  187. #endif
  188. if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometry, material, reflectedLight );
  189. }
  190. #endif
  191. #if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && !defined( USE_CSM ) && !defined( CSM_CASCADES )
  192. DirectionalLight directionalLight;
  193. #pragma unroll_loop
  194. for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
  195. directionalLight = directionalLights[ i ];
  196. getDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );
  197. #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
  198. directLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
  199. #endif
  200. RE_Direct( directLight, geometry, material, reflectedLight );
  201. }
  202. #endif
  203. #if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )
  204. RectAreaLight rectAreaLight;
  205. #pragma unroll_loop
  206. for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {
  207. rectAreaLight = rectAreaLights[ i ];
  208. RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );
  209. }
  210. #endif
  211. #if defined( RE_IndirectDiffuse )
  212. vec3 iblIrradiance = vec3( 0.0 );
  213. vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
  214. irradiance += getLightProbeIrradiance( lightProbe, geometry );
  215. #if ( NUM_HEMI_LIGHTS > 0 )
  216. #pragma unroll_loop
  217. for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
  218. irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );
  219. }
  220. #endif
  221. #endif
  222. #if defined( RE_IndirectSpecular )
  223. vec3 radiance = vec3( 0.0 );
  224. vec3 clearcoatRadiance = vec3( 0.0 );
  225. #endif
  226. `,
  227. lights_pars_begin: `
  228. #if defined( USE_CSM ) && defined( CSM_CASCADES )
  229. uniform vec2 CSM_cascades[CSM_CASCADES];
  230. uniform float cameraNear;
  231. uniform float shadowFar;
  232. #endif
  233. ` + ShaderChunk.lights_pars_begin
  234. };
  235. class CSM {
  236. constructor(data) {
  237. data = data || {};
  238. this.camera = data.camera;
  239. this.parent = data.parent;
  240. this.fov = data.fov || this.camera.fov;
  241. this.near = this.camera.near;
  242. this.far = data.far || this.camera.far;
  243. this.aspect = data.aspect || this.camera.aspect;
  244. this.cascades = data.cascades || 3;
  245. this.mode = data.mode || 'practical';
  246. this.shadowMapSize = data.shadowMapSize || 2048;
  247. this.shadowBias = data.shadowBias || 0.000001;
  248. this.lightDirection = data.lightDirection || new Vector3(1, -1, 1).normalize();
  249. this.lightIntensity = data.lightIntensity || 1;
  250. this.lightNear = data.lightNear || 1;
  251. this.lightFar = data.lightFar || 2000;
  252. this.lightMargin = data.lightMargin || 200;
  253. this.customSplitsCallback = data.customSplitsCallback;
  254. this.lights = [];
  255. this.materials = [];
  256. this.createLights();
  257. this.getBreaks();
  258. this.initCascades();
  259. this.injectInclude();
  260. }
  261. createLights() {
  262. for(let i = 0; i < this.cascades; i++) {
  263. const light = new DirectionalLight(0xffffff, this.lightIntensity);
  264. light.castShadow = true;
  265. light.shadow.mapSize.width = this.shadowMapSize;
  266. light.shadow.mapSize.height = this.shadowMapSize;
  267. light.shadow.camera.near = this.lightNear;
  268. light.shadow.camera.far = this.lightFar;
  269. light.shadow.bias = this.shadowBias;
  270. this.parent.add(light);
  271. this.parent.add(light.target);
  272. this.lights.push(light);
  273. }
  274. }
  275. initCascades() {
  276. this.mainFrustum = new Frustum({
  277. fov: this.fov,
  278. near: this.near,
  279. far: this.far,
  280. aspect: this.aspect
  281. });
  282. this.mainFrustum.getViewSpaceVertices();
  283. this.frustums = this.mainFrustum.split(this.breaks);
  284. }
  285. getBreaks() {
  286. this.breaks = [];
  287. switch (this.mode) {
  288. case 'uniform':
  289. this.breaks = uniformSplit(this.cascades, this.near, this.far);
  290. break;
  291. case 'logarithmic':
  292. this.breaks = logarithmicSplit(this.cascades, this.near, this.far);
  293. break;
  294. case 'practical':
  295. this.breaks = practicalSplit(this.cascades, this.near, this.far, 0.5);
  296. break;
  297. case 'custom':
  298. if(this.customSplitsCallback === undefined) console.error('CSM: Custom split scheme callback not defined.');
  299. this.breaks = this.customSplitsCallback(this.cascades, this.near, this.far);
  300. break;
  301. }
  302. function uniformSplit(amount, near, far) {
  303. const r = [];
  304. for(let i = 1; i < amount; i++) {
  305. r.push((near + (far - near) * i / amount) / far);
  306. }
  307. r.push(1);
  308. return r;
  309. }
  310. function logarithmicSplit(amount, near, far) {
  311. const r = [];
  312. for(let i = 1; i < amount; i++) {
  313. r.push((near * (far / near) ** (i / amount)) / far);
  314. }
  315. r.push(1);
  316. return r;
  317. }
  318. function practicalSplit(amount, near, far, lambda) {
  319. const log = logarithmicSplit(amount, near, far);
  320. const uni = uniformSplit(amount, near, far);
  321. const r = [];
  322. for(let i = 1; i < amount; i++) {
  323. r.push(lambda * log[i - 1] + (1 - lambda) * uni[i - 1]);
  324. }
  325. r.push(1);
  326. return r;
  327. }
  328. }
  329. update(cameraMatrix) {
  330. for(let i = 0; i < this.frustums.length; i++) {
  331. const worldSpaceFrustum = this.frustums[i].toSpace(cameraMatrix);
  332. const light = this.lights[i];
  333. const lightSpaceFrustum = worldSpaceFrustum.toSpace(light.shadow.camera.matrixWorldInverse);
  334. light.shadow.camera.updateMatrixWorld(true);
  335. const bbox = new FrustumBoundingBox().fromFrustum(lightSpaceFrustum);
  336. bbox.getSize();
  337. bbox.getCenter(this.lightMargin);
  338. const squaredBBWidth = Math.max(bbox.size.x, bbox.size.y);
  339. let center = new Vector3(bbox.center.x, bbox.center.y, bbox.center.z);
  340. center.applyMatrix4(light.shadow.camera.matrixWorld);
  341. light.shadow.camera.left = -squaredBBWidth / 2;
  342. light.shadow.camera.right = squaredBBWidth / 2;
  343. light.shadow.camera.top = squaredBBWidth / 2;
  344. light.shadow.camera.bottom = -squaredBBWidth / 2;
  345. light.position.copy(center);
  346. light.target.position.copy(center);
  347. light.target.position.x += this.lightDirection.x;
  348. light.target.position.y += this.lightDirection.y;
  349. light.target.position.z += this.lightDirection.z;
  350. light.shadow.camera.updateProjectionMatrix();
  351. light.shadow.camera.updateMatrixWorld();
  352. }
  353. }
  354. injectInclude() {
  355. ShaderChunk.lights_fragment_begin = Shader.lights_fragment_begin;
  356. ShaderChunk.lights_pars_begin = Shader.lights_pars_begin;
  357. }
  358. setupMaterial(material) {
  359. material.defines = material.defines || {};
  360. material.defines.USE_CSM = 1;
  361. material.defines.CSM_CASCADES = this.cascades;
  362. const breaksVec2 = [];
  363. for(let i = 0; i < this.cascades; i++) {
  364. let amount = this.breaks[i];
  365. let prev = this.breaks[i - 1] || 0;
  366. breaksVec2.push(new Vector2(prev, amount));
  367. }
  368. const self = this;
  369. material.onBeforeCompile = function (shader) {
  370. shader.uniforms.CSM_cascades = {value: breaksVec2};
  371. shader.uniforms.cameraNear = {value: self.camera.near};
  372. shader.uniforms.shadowFar = {value: self.far};
  373. self.materials.push(shader);
  374. };
  375. }
  376. updateUniforms() {
  377. for(let i = 0; i < this.materials.length; i++) {
  378. this.materials[i].uniforms.CSM_cascades.value = this.getExtendedBreaks();
  379. this.materials[i].uniforms.cameraNear.value = this.camera.near;
  380. this.materials[i].uniforms.shadowFar.value = this.far;
  381. }
  382. }
  383. getExtendedBreaks() {
  384. let breaksVec2 = [];
  385. for(let i = 0; i < this.cascades; i++) {
  386. let amount = this.breaks[i];
  387. let prev = this.breaks[i - 1] || 0;
  388. breaksVec2.push(new Vector2(prev, amount));
  389. }
  390. return breaksVec2;
  391. }
  392. setAspect(aspect) {
  393. this.aspect = aspect;
  394. this.initCascades();
  395. }
  396. updateFrustums() {
  397. this.getBreaks();
  398. this.initCascades();
  399. this.updateUniforms();
  400. }
  401. helper(cameraMatrix) {
  402. let frustum;
  403. let geometry;
  404. const material = new LineBasicMaterial({color: 0xffffff});
  405. const object = new Object3D();
  406. for(let i = 0; i < this.frustums.length; i++) {
  407. frustum = this.frustums[i].toSpace(cameraMatrix);
  408. geometry = new Geometry();
  409. for(let i = 0; i < 5; i++) {
  410. const point = frustum.vertices.near[i === 4 ? 0 : i];
  411. geometry.vertices.push(new Vector3(point.x, point.y, point.z));
  412. }
  413. object.add(new Line(geometry, material));
  414. geometry = new Geometry();
  415. for(let i = 0; i < 5; i++) {
  416. const point = frustum.vertices.far[i === 4 ? 0 : i];
  417. geometry.vertices.push(new Vector3(point.x, point.y, point.z));
  418. }
  419. object.add(new Line(geometry, material));
  420. for(let i = 0; i < 4; i++) {
  421. geometry = new Geometry();
  422. const near = frustum.vertices.near[i];
  423. const far = frustum.vertices.far[i];
  424. geometry.vertices.push(new Vector3(near.x, near.y, near.z));
  425. geometry.vertices.push(new Vector3(far.x, far.y, far.z));
  426. object.add(new Line(geometry, material));
  427. }
  428. }
  429. return object;
  430. }
  431. remove() {
  432. for(let i = 0; i < this.lights.length; i++) {
  433. this.parent.remove(this.lights[i]);
  434. }
  435. }
  436. }
  437. export default CSM;