@navaramap/three-csm 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +5 -0
- package/dist/index.d.ts +197 -0
- package/dist/index.js +483 -0
- package/dist/index.umd.cjs +306 -0
- package/package.json +38 -0
- package/src/CSMHelper.ts +199 -0
- package/src/CascadedDirectionalLights.ts +80 -0
- package/src/CascadedShadowMaps.ts +475 -0
- package/src/FrustumCorners.ts +111 -0
- package/src/MaterialStates.ts +154 -0
- package/src/createFragmentShader.ts +92 -0
- package/src/index.ts +5 -0
- package/src/shaders/cascadedFadedLights.glsl +102 -0
- package/src/shaders/cascadedLights.glsl +81 -0
- package/src/shaders/cascadedLightsWithoutShadows.glsl +35 -0
- package/src/shaders/defaultLights.glsl +36 -0
- package/src/shaders/nonShadowCastingLights.glsl +18 -0
- package/src/splitFrustum.ts +59 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("three")):typeof define==`function`&&define.amd?define([`exports`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreeCSM={},e.three))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=new t.Color(16777215),r=class extends t.Object3D{mainLight;cascadedLights;direction=new t.Vector3(-1,-1,-1);constructor(e,n){super();let r=new t.DirectionalLight(e,n);r.castShadow=!0,this.mainLight=r,this.cascadedLights=[r],this.add(r),this.add(r.target)}setCount(e){e=Math.max(1,e);let t=this.cascadedLights.length;if(e===t)return this;if(e>t){let r=this.mainLight;for(let i=t;i<e;++i){let e=r.clone();e.color.copy(n),this.add(e),this.add(e.target),this.cascadedLights[i]=e}}else for(let n=e;n<t;++n){let e=this.cascadedLights[n];e.dispose(),this.remove(e.target),this.remove(e)}return this.cascadedLights.length=e,this}dispose(){for(let e=0;e<this.cascadedLights.length;++e)this.cascadedLights[e].dispose()}copy(e,t){super.copy(e,!1),this.cascadedLights.length=0,this.clear();for(let t of e.cascadedLights){let e=t.clone();this.add(e),this.add(e.target),this.cascadedLights.push(e)}return this}},i=`Invariant failed`;function a(e,t){if(!e)throw Error(i)}var o=class e{near=[new t.Vector3,new t.Vector3,new t.Vector3,new t.Vector3];far=[new t.Vector3,new t.Vector3,new t.Vector3,new t.Vector3];constructor(e,t){e!=null&&t!=null&&this.setFromCamera(e,t)}clone(){return new e().copy(this)}copy(e){for(let t=0;t<4;++t)this.near[t].copy(e.near[t]),this.far[t].copy(e.far[t]);return this}setFromCamera(e,t){let n=e.isOrthographicCamera===!0,r=e.projectionMatrixInverse;this.near[0].set(1,1,-1),this.near[1].set(1,-1,-1),this.near[2].set(-1,-1,-1),this.near[3].set(-1,1,-1);for(let e=0;e<4;++e)this.near[e].applyMatrix4(r);this.far[0].set(1,1,1),this.far[1].set(1,-1,1),this.far[2].set(-1,-1,1),this.far[3].set(-1,1,1);for(let e=0;e<4;++e){let i=this.far[e];i.applyMatrix4(r);let a=Math.abs(i.z);n?i.z*=Math.min(t/a,1):i.multiplyScalar(Math.min(t/a,1))}return this}split(t,n=[]){for(let r=0;r<t.length;++r){let i=n[r]??=new e;if(r===0)for(let e=0;e<4;++e)i.near[e].copy(this.near[e]);else for(let e=0;e<4;++e)i.near[e].lerpVectors(this.near[e],this.far[e],t[r-1]);if(r===t.length-1)for(let e=0;e<4;++e)i.far[e].copy(this.far[e]);else for(let e=0;e<4;++e)i.far[e].lerpVectors(this.near[e],this.far[e],t[r])}return n.length=t.length,n}applyMatrix4(e){for(let t=0;t<4;++t)this.near[t].applyMatrix4(e),this.far[t].applyMatrix4(e);return this}},s=`// Based on the following work:
|
|
2
|
+
// https://github.com/StrandedKitty/three-csm/tree/master/src
|
|
3
|
+
// https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
|
|
4
|
+
|
|
5
|
+
void cascadedFadedLights() {
|
|
6
|
+
directionalLight = directionalLights[0];
|
|
7
|
+
getDirectionalLightInfo(directionalLight, directLight);
|
|
8
|
+
|
|
9
|
+
float clipDepth = vViewPosition.z / (csmFar - csmNear);
|
|
10
|
+
bool lastCascade;
|
|
11
|
+
vec2 cascade;
|
|
12
|
+
float cascadeCenter;
|
|
13
|
+
float closestEdge;
|
|
14
|
+
float margin;
|
|
15
|
+
float shadow = 1.0;
|
|
16
|
+
float nextShadow = 1.0;
|
|
17
|
+
|
|
18
|
+
#pragma unroll_loop_start
|
|
19
|
+
for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
|
|
20
|
+
#if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
21
|
+
#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
|
|
22
|
+
|
|
23
|
+
lastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADE_COUNT - 1;
|
|
24
|
+
cascade = csmCascades[i];
|
|
25
|
+
cascadeCenter = (cascade.x + cascade.y) * 0.5;
|
|
26
|
+
closestEdge = clipDepth < cascadeCenter ? cascade.x : cascade.y;
|
|
27
|
+
margin = 0.25 * pow(closestEdge, 2.0);
|
|
28
|
+
cascade += margin * vec2(-0.5, 0.5);
|
|
29
|
+
|
|
30
|
+
if (clipDepth >= cascade.x && (clipDepth < cascade.y || lastCascade)) {
|
|
31
|
+
directionalLightShadow = directionalLightShadows[i];
|
|
32
|
+
float prevShadow = nextShadow;
|
|
33
|
+
nextShadow =
|
|
34
|
+
directLight.visible && receiveShadow
|
|
35
|
+
? getShadow(
|
|
36
|
+
directionalShadowMap[i],
|
|
37
|
+
directionalLightShadow.shadowMapSize,
|
|
38
|
+
directionalLightShadow.shadowIntensity,
|
|
39
|
+
directionalLightShadow.shadowBias,
|
|
40
|
+
directionalLightShadow.shadowRadius,
|
|
41
|
+
vDirectionalShadowCoord[i]
|
|
42
|
+
)
|
|
43
|
+
: 1.0;
|
|
44
|
+
|
|
45
|
+
shadow = mix(
|
|
46
|
+
prevShadow,
|
|
47
|
+
nextShadow,
|
|
48
|
+
saturate(min(clipDepth - cascade.x, cascade.y - clipDepth) / margin)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
#endif // UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
|
|
52
|
+
#endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
53
|
+
}
|
|
54
|
+
#pragma unroll_loop_end
|
|
55
|
+
|
|
56
|
+
// No loop here: all cascaded lights are in fact one light only.
|
|
57
|
+
directLight.color *= shadow;
|
|
58
|
+
RE_Direct(
|
|
59
|
+
directLight,
|
|
60
|
+
geometryPosition,
|
|
61
|
+
geometryNormal,
|
|
62
|
+
geometryViewDir,
|
|
63
|
+
geometryClearcoatNormal,
|
|
64
|
+
material,
|
|
65
|
+
reflectedLight
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// The rest of directional lights.
|
|
69
|
+
#pragma unroll_loop_start
|
|
70
|
+
for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
|
|
71
|
+
#if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
72
|
+
#if UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
|
|
73
|
+
directionalLight = directionalLights[i];
|
|
74
|
+
getDirectionalLightInfo(directionalLight, directLight);
|
|
75
|
+
|
|
76
|
+
directionalLightShadow = directionalLightShadows[i];
|
|
77
|
+
directLight.color *=
|
|
78
|
+
directLight.visible && receiveShadow
|
|
79
|
+
? getShadow(
|
|
80
|
+
directionalShadowMap[i],
|
|
81
|
+
directionalLightShadow.shadowMapSize,
|
|
82
|
+
directionalLightShadow.shadowIntensity,
|
|
83
|
+
directionalLightShadow.shadowBias,
|
|
84
|
+
directionalLightShadow.shadowRadius,
|
|
85
|
+
vDirectionalShadowCoord[i]
|
|
86
|
+
)
|
|
87
|
+
: 1.0;
|
|
88
|
+
|
|
89
|
+
RE_Direct(
|
|
90
|
+
directLight,
|
|
91
|
+
geometryPosition,
|
|
92
|
+
geometryNormal,
|
|
93
|
+
geometryViewDir,
|
|
94
|
+
geometryClearcoatNormal,
|
|
95
|
+
material,
|
|
96
|
+
reflectedLight
|
|
97
|
+
);
|
|
98
|
+
#endif // UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
|
|
99
|
+
#endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
100
|
+
}
|
|
101
|
+
#pragma unroll_loop_end
|
|
102
|
+
}
|
|
103
|
+
`,c=`// Based on the following work:
|
|
104
|
+
// https://github.com/StrandedKitty/three-csm/tree/master/src
|
|
105
|
+
// https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
|
|
106
|
+
|
|
107
|
+
void cascadedLights() {
|
|
108
|
+
directionalLight = directionalLights[0];
|
|
109
|
+
getDirectionalLightInfo(directionalLight, directLight);
|
|
110
|
+
|
|
111
|
+
float clipDepth = vViewPosition.z / (csmFar - csmNear);
|
|
112
|
+
vec2 cascade;
|
|
113
|
+
|
|
114
|
+
#pragma unroll_loop_start
|
|
115
|
+
for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
|
|
116
|
+
#if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
117
|
+
#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
|
|
118
|
+
cascade = csmCascades[i];
|
|
119
|
+
directionalLightShadow = directionalLightShadows[i];
|
|
120
|
+
if (clipDepth >= cascade.x && clipDepth < cascade.y) {
|
|
121
|
+
directLight.color *=
|
|
122
|
+
directLight.visible && receiveShadow
|
|
123
|
+
? getShadow(
|
|
124
|
+
directionalShadowMap[i],
|
|
125
|
+
directionalLightShadow.shadowMapSize,
|
|
126
|
+
directionalLightShadow.shadowIntensity,
|
|
127
|
+
directionalLightShadow.shadowBias,
|
|
128
|
+
directionalLightShadow.shadowRadius,
|
|
129
|
+
vDirectionalShadowCoord[i]
|
|
130
|
+
)
|
|
131
|
+
: 1.0;
|
|
132
|
+
}
|
|
133
|
+
#endif // UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
|
|
134
|
+
#endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
135
|
+
}
|
|
136
|
+
#pragma unroll_loop_end
|
|
137
|
+
|
|
138
|
+
// No loop here: all cascaded lights are in fact one light only.
|
|
139
|
+
RE_Direct(
|
|
140
|
+
directLight,
|
|
141
|
+
geometryPosition,
|
|
142
|
+
geometryNormal,
|
|
143
|
+
geometryViewDir,
|
|
144
|
+
geometryClearcoatNormal,
|
|
145
|
+
material,
|
|
146
|
+
reflectedLight
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// The rest of directional lights.
|
|
150
|
+
#pragma unroll_loop_start
|
|
151
|
+
for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
|
|
152
|
+
#if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
153
|
+
#if UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
|
|
154
|
+
directionalLight = directionalLights[i];
|
|
155
|
+
getDirectionalLightInfo(directionalLight, directLight);
|
|
156
|
+
|
|
157
|
+
directionalLightShadow = directionalLightShadows[i];
|
|
158
|
+
directLight.color *=
|
|
159
|
+
directLight.visible && receiveShadow
|
|
160
|
+
? getShadow(
|
|
161
|
+
directionalShadowMap[i],
|
|
162
|
+
directionalLightShadow.shadowMapSize,
|
|
163
|
+
directionalLightShadow.shadowIntensity,
|
|
164
|
+
directionalLightShadow.shadowBias,
|
|
165
|
+
directionalLightShadow.shadowRadius,
|
|
166
|
+
vDirectionalShadowCoord[i]
|
|
167
|
+
)
|
|
168
|
+
: 1.0;
|
|
169
|
+
|
|
170
|
+
RE_Direct(
|
|
171
|
+
directLight,
|
|
172
|
+
geometryPosition,
|
|
173
|
+
geometryNormal,
|
|
174
|
+
geometryViewDir,
|
|
175
|
+
geometryClearcoatNormal,
|
|
176
|
+
material,
|
|
177
|
+
reflectedLight
|
|
178
|
+
);
|
|
179
|
+
#endif // UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
|
|
180
|
+
#endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
181
|
+
}
|
|
182
|
+
#pragma unroll_loop_end
|
|
183
|
+
}
|
|
184
|
+
`,l=`// Based on the following work:
|
|
185
|
+
// https://github.com/StrandedKitty/three-csm/tree/master/src
|
|
186
|
+
// https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
|
|
187
|
+
|
|
188
|
+
void cascadedLightsWithoutShadows() {
|
|
189
|
+
// No loop here: all cascaded lights are in fact one light only.
|
|
190
|
+
getDirectionalLightInfo(directionalLights[0], directLight);
|
|
191
|
+
RE_Direct(
|
|
192
|
+
directLight,
|
|
193
|
+
geometryPosition,
|
|
194
|
+
geometryNormal,
|
|
195
|
+
geometryViewDir,
|
|
196
|
+
geometryClearcoatNormal,
|
|
197
|
+
material,
|
|
198
|
+
reflectedLight
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// The rest of directional lights.
|
|
202
|
+
#pragma unroll_loop_start
|
|
203
|
+
for (int i = CSM_CASCADE_COUNT; i < NUM_DIR_LIGHTS; i++) {
|
|
204
|
+
directionalLight = directionalLights[i];
|
|
205
|
+
getDirectionalLightInfo(directionalLight, directLight);
|
|
206
|
+
|
|
207
|
+
RE_Direct(
|
|
208
|
+
directLight,
|
|
209
|
+
geometryPosition,
|
|
210
|
+
geometryNormal,
|
|
211
|
+
geometryViewDir,
|
|
212
|
+
geometryClearcoatNormal,
|
|
213
|
+
material,
|
|
214
|
+
reflectedLight
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
#pragma unroll_loop_end
|
|
218
|
+
}
|
|
219
|
+
`,u=`// Default shader.
|
|
220
|
+
// See: https://github.com/mrdoob/three.js/blob/r169/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js#L136
|
|
221
|
+
|
|
222
|
+
void defaultLights() {
|
|
223
|
+
#pragma unroll_loop_start
|
|
224
|
+
for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
|
|
225
|
+
directionalLight = directionalLights[i];
|
|
226
|
+
getDirectionalLightInfo(directionalLight, directLight);
|
|
227
|
+
|
|
228
|
+
#if defined(USE_SHADOWMAP) && UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
|
|
229
|
+
directionalLightShadow = directionalLightShadows[i];
|
|
230
|
+
directLight.color *=
|
|
231
|
+
directLight.visible && receiveShadow
|
|
232
|
+
? getShadow(
|
|
233
|
+
directionalShadowMap[i],
|
|
234
|
+
directionalLightShadow.shadowMapSize,
|
|
235
|
+
directionalLightShadow.shadowIntensity,
|
|
236
|
+
directionalLightShadow.shadowBias,
|
|
237
|
+
directionalLightShadow.shadowRadius,
|
|
238
|
+
vDirectionalShadowCoord[i]
|
|
239
|
+
)
|
|
240
|
+
: 1.0;
|
|
241
|
+
#endif
|
|
242
|
+
|
|
243
|
+
RE_Direct(
|
|
244
|
+
directLight,
|
|
245
|
+
geometryPosition,
|
|
246
|
+
geometryNormal,
|
|
247
|
+
geometryViewDir,
|
|
248
|
+
geometryClearcoatNormal,
|
|
249
|
+
material,
|
|
250
|
+
reflectedLight
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
#pragma unroll_loop_end
|
|
254
|
+
}
|
|
255
|
+
`,d=`void nonShadowCastingLights() {
|
|
256
|
+
#pragma unroll_loop_start
|
|
257
|
+
for (int i = NUM_DIR_LIGHT_SHADOWS; i < NUM_DIR_LIGHTS; i++) {
|
|
258
|
+
directionalLight = directionalLights[i];
|
|
259
|
+
getDirectionalLightInfo(directionalLight, directLight);
|
|
260
|
+
|
|
261
|
+
RE_Direct(
|
|
262
|
+
directLight,
|
|
263
|
+
geometryPosition,
|
|
264
|
+
geometryNormal,
|
|
265
|
+
geometryViewDir,
|
|
266
|
+
geometryClearcoatNormal,
|
|
267
|
+
material,
|
|
268
|
+
reflectedLight
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
#pragma unroll_loop_end
|
|
272
|
+
}
|
|
273
|
+
`,f=`
|
|
274
|
+
#include <lights_pars_begin>
|
|
275
|
+
|
|
276
|
+
#ifdef CSM
|
|
277
|
+
uniform vec2 csmCascades[CSM_CASCADE_COUNT];
|
|
278
|
+
uniform float csmNear;
|
|
279
|
+
uniform float csmFar;
|
|
280
|
+
#endif // CSM
|
|
281
|
+
`;function p(e){return e.replace(/^\s*void\s+\w+\s*\(\s*\)\s*/m,``)}var m=`
|
|
282
|
+
#if NUM_DIR_LIGHTS > 0 && defined(RE_Direct)
|
|
283
|
+
DirectionalLight directionalLight;
|
|
284
|
+
#if defined(USE_SHADOWMAP) && NUM_DIR_LIGHT_SHADOWS > 0
|
|
285
|
+
DirectionalLightShadow directionalLightShadow;
|
|
286
|
+
#endif
|
|
287
|
+
|
|
288
|
+
#ifdef CSM
|
|
289
|
+
#ifdef USE_SHADOWMAP
|
|
290
|
+
#ifdef CSM_FADE
|
|
291
|
+
${p(s)}
|
|
292
|
+
#else
|
|
293
|
+
${p(c)}
|
|
294
|
+
#endif // CSM_FADE
|
|
295
|
+
#elif NUM_DIR_LIGHT_SHADOWS > 0
|
|
296
|
+
${p(l)}
|
|
297
|
+
#endif // USE_SHADOWMAP
|
|
298
|
+
|
|
299
|
+
#if NUM_DIR_LIGHTS > NUM_DIR_LIGHT_SHADOWS
|
|
300
|
+
${p(d)}
|
|
301
|
+
#endif
|
|
302
|
+
#else
|
|
303
|
+
${p(u)}
|
|
304
|
+
#endif // CSM
|
|
305
|
+
#endif // NUM_DIR_LIGHTS > 0 && defined(RE_Direct)
|
|
306
|
+
`;function h({source:e,startLine:t,endLine:n,replacement:r}){let i=`\n${t}\n`,a=e.indexOf(i);if(a<0)return e;let o=`\n${n}\n`,s=e.indexOf(o,a+i.length);return s<0?e:e.slice(0,a+1)+r+e.slice(s+o.length-1)}function g(e){return e.replace(`#include <lights_pars_begin>`,f).replace(`#include <lights_fragment_begin>`,h({source:t.ShaderChunk.lights_fragment_begin,startLine:`#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )`,endLine:`#endif`,replacement:m}))}var _=class{map=new Map;setup(e,t){if(this.map.has(e))return e;e.userData.defines??={},e.userData.defines.CSM=1,e.userData.defines.CSM_CASCADE_COUNT=t.cascadeCount,t.fade&&(e.userData.defines.CSM_FADE=1);let n={};this.map.set(e,n);let r=e=>{n.parameters=e;let r=t.cascades,i=t.mainCamera.near,a=Math.min(t.mainCamera.far,t.far),o=e.uniforms;o.csmCascades={value:r},o.csmNear={value:i},o.csmFar={value:a},e.fragmentShader=g(e.fragmentShader)},i=e.onBeforeCompile.bind(e),a=e.customProgramCacheKey.bind(e);Object.prototype.hasOwnProperty.call(e,`hasOwnProperty`)&&(n.originalHandler=i),e.customProgramCacheKey=()=>i.toString()+a(),e.onBeforeCompile=(...t)=>{let[n]=t;n.defines??={},Object.assign(n.defines,e.userData.defines),i.apply(e,t),r(...t)};let o=()=>{this.map.delete(e),e.removeEventListener(`dispose`,o)};return e.addEventListener(`dispose`,o),e}update(e){let t=e.cascadeCount,n=e.fade,r=e.cascades,i=e.mainCamera.near,a=Math.min(e.mainCamera.far,e.far);for(let[e,o]of this.map.entries()){let s=e.userData.defines;if(s!=null){let r=!1;s.CSM_FADE!=null!==n&&(n?s.CSM_FADE=1:delete s.CSM_FADE,r=!0),s.CSM_CASCADE_COUNT!==t&&(s.CSM_CASCADE_COUNT=t,r=!0),r&&(e.needsUpdate=!0)}let c=o.parameters?.uniforms;c!=null&&(c.csmCascades.value=r,c.csmNear.value=i,c.csmFar.value=a)}}rollback(e){let t=this.map.get(e);return t==null?e:this.rollbackToState(e,t)}rollbackToState(e,{parameters:t,originalHandler:n}){return t!=null&&(delete t.uniforms.csmCascades,delete t.uniforms.csmNear,delete t.uniforms.csmFar),n==null?delete e.onBeforeCompile:e.onBeforeCompile=n,e.userData.defines!=null&&(delete e.userData.defines.CSM,delete e.userData.defines.CSM_CASCADE_COUNT,delete e.userData.defines.CSM_FADE),e.needsUpdate=!0,this.map.delete(e),e}dispose(){for(let[e,t]of this.map)this.rollbackToState(e,t);this.map.clear()}};Math.PI/180,180/Math.PI;function v(e,t,n){return(1-n)*e+n*t}var y={uniform:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=(t+(n-t)*(r+1)/e)/n;return i.length=e,i},logarithmic:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=t*(n/t)**((r+1)/e)/n;return i.length=e,i},practical:(e,t,n,r=.5,i=[])=>{for(let a=0;a<e;++a)i[a]=v((t+(n-t)*(a+1)/e)/n,t*(n/t)**((a+1)/e)/n,r);return i.length=e,i}},b=y;function x(e,t,n,r,i,a=[]){return y[e](t,n,r,i,a)}var S=new t.Vector3,C=new t.Vector3,w=new t.Matrix4,T=new t.Matrix4,E=new o,D=new t.Box3,O={cascadeCount:4,mapSize:2048,far:5e3,mode:`practical`,lambda:.5,margin:200,fade:!0,disableLastCascadeCutoff:!1},k=class{mainCamera;needsUpdateFrusta=!0;directionalLights=new r;materialStates=new _;mainFrustum=new o;cascadedFrusta=[];splits=[];cascades=[];_far;_mode;_lambda;_margin;_fade;_disableLastCascadeCutoff;constructor(e,t){this.mainCamera=e;let{cascadeCount:n,mapSize:r,far:i,mode:a,lambda:o,margin:s,fade:c,disableLastCascadeCutoff:l}={...O,...t};this._far=i,this._mode=a,this._lambda=o,this._margin=s,this._fade=c,this._disableLastCascadeCutoff=l,this.cascadeCount=n,this.mapSize=r}dispose(){this.materialStates.dispose(),this.directionalLights.dispose()}setupMaterial(e){return this.materialStates.setup(e,this)}rollbackMaterial(e){return this.materialStates.rollback(e)}setupMaterials(e){for(let t=0;t<e.length;++t)this.materialStates.setup(e[t],this);return()=>{for(let t=0;t<e.length;++t)this.materialStates.rollback(e[t])}}updateCascades(){let e=this.cascadeCount,n=this.splits;x(this.mode,e,this.mainCamera.near,Math.min(this.mainCamera.far,this.far),this.lambda,n),this.mainFrustum.setFromCamera(this.mainCamera,this.far),this.mainFrustum.split(n,this.cascadedFrusta);let r=this.cascades;for(let i=0;i<e;++i)(r[i]??(r[i]=new t.Vector2)).set(n[i-1]??0,n[i]??0);this.disableLastCascadeCutoff&&(r[e-1].y=1/0),r.length=e}getFrustumRadius(e){let t=e.near,n=e.far,r=Math.max(n[0].distanceTo(n[2]),n[0].distanceTo(t[2]));if(this.fade){let e=this.mainCamera,t=e.near,i=Math.min(e.far,this.far),a=n[0].z/(i-t);r+=.25*a**2*(i-t)}return r*.5}updateShadowBounds(){let e=this.cascadedFrusta,t=this.directionalLights.cascadedLights;a(e.length===t.length);for(let n=0;n<e.length;++n){let e=this.getFrustumRadius(this.cascadedFrusta[n]),r=t[n].shadow.camera;r.left=-e,r.right=e,r.top=e,r.bottom=-e,r.near=0,r.far=e*2+this.margin,r.updateProjectionMatrix()}}updateFrusta(){this.updateCascades(),this.updateShadowBounds(),this.materialStates.update(this)}update(){this.needsUpdateFrusta&&(this.needsUpdateFrusta=!1,this.updateFrusta());let e=this.directionalLights,n=S.copy(e.direction).normalize(),r=w.lookAt(new t.Vector3,n,t.Object3D.DEFAULT_UP),i=T.multiplyMatrices(T.copy(r).invert(),this.mainCamera.matrixWorld),a=this.margin,o=this.mapSize,s=this.cascadedFrusta,c=e.cascadedLights;for(let e=0;e<s.length;++e){let{near:t,far:l}=E.copy(s[e]).applyMatrix4(i),u=D.makeEmpty();for(let e=0;e<4;e++)u.expandByPoint(t[e]),u.expandByPoint(l[e]);let d=u.getCenter(C);d.z=u.max.z+a;let f=c[e],{left:p,right:m,top:h,bottom:g}=f.shadow.camera,_=(m-p)/o,v=(h-g)/o;d.x=Math.round(d.x/_)*_,d.y=Math.round(d.y/v)*v,d.applyMatrix4(r),f.position.copy(d),f.target.position.copy(d),f.target.position.add(n)}}get cascadeCount(){return this.directionalLights.cascadedLights.length}set cascadeCount(e){e!==this.cascadeCount&&(this.directionalLights.setCount(e),this.needsUpdateFrusta=!0)}get mapSize(){return this.directionalLights.mainLight.shadow.mapSize.width}set mapSize(e){if(e!==this.mapSize){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n){let r=t[n].shadow;r.mapSize.width=e,r.mapSize.height=e,r.map!=null&&(r.map.dispose(),r.map=null)}}}get far(){return this._far}set far(e){e!==this._far&&(this._far=e,this.needsUpdateFrusta=!0)}get mode(){return this._mode}set mode(e){e!==this._mode&&(this._mode=e,this.needsUpdateFrusta=!0)}get lambda(){return this._lambda}set lambda(e){e!==this._lambda&&(this._lambda=e,this.needsUpdateFrusta=!0)}get margin(){return this._margin}set margin(e){e!==this._margin&&(this._margin=e,this.needsUpdateFrusta=!0)}get fade(){return this._fade}set fade(e){e!==this._fade&&(this._fade=e,this.needsUpdateFrusta=!0)}get disableLastCascadeCutoff(){return this._disableLastCascadeCutoff}set disableLastCascadeCutoff(e){e!==this._disableLastCascadeCutoff&&(this._disableLastCascadeCutoff=e,this.needsUpdateFrusta=!0)}get intensity(){return this.directionalLights.mainLight.intensity}set intensity(e){if(e!==this.intensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].intensity=e}}get shadowIntensity(){return this.directionalLights.mainLight.shadow.intensity}set shadowIntensity(e){if(e!==this.shadowIntensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.intensity=e}}get color(){return this.directionalLights.mainLight.color}set color(e){e!==this.color&&this.directionalLights.mainLight.color.copy(e)}get bias(){return this.directionalLights.mainLight.shadow.bias}set bias(e){if(e!==this.bias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.bias=e}}get normalBias(){return this.directionalLights.mainLight.shadow.normalBias}set normalBias(e){if(e!==this.normalBias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.normalBias=e}}get radius(){return this.directionalLights.mainLight.shadow.radius}set radius(e){if(e!==this.radius){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.radius=e}}get blurSamples(){return this.directionalLights.mainLight.shadow.blurSamples}set blurSamples(e){if(e!==this.blurSamples){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.blurSamples=e}}};e.CSMHelper=class extends t.Group{csm;displayFrustum=!0;displayPlanes=!0;displayShadowBounds=!0;frustumLines;cascadeLines=[];cascadePlanes=[];shadowLines=[];constructor(e){super(),this.csm=e;let n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),r=new Float32Array(24),i=new t.BufferGeometry;i.setIndex(new t.BufferAttribute(n,1)),i.setAttribute(`position`,new t.BufferAttribute(r,3,!1));let a=new t.LineSegments(i,new t.LineBasicMaterial);this.add(a),this.frustumLines=a}updateVisibility(){let e=this.displayFrustum,t=this.displayPlanes,n=this.displayShadowBounds,r=this.frustumLines,i=this.cascadeLines,a=this.cascadePlanes,o=this.shadowLines;for(let r=0,s=i.length;r<s;r++){let s=i[r],c=a[r],l=o[r];s.visible=e,c.visible=e&&t,l.visible=n}r.visible=e}update(){let e=this.csm,n=e.mainCamera,r=e.cascadeCount,i=e.mainFrustum,a=e.cascadedFrusta,o=e.directionalLights.cascadedLights,s=this.frustumLines.geometry.getAttribute(`position`),c=this.cascadeLines,l=this.cascadePlanes,u=this.shadowLines;for(this.position.copy(n.position),this.quaternion.copy(n.quaternion),this.scale.copy(n.scale),this.updateMatrixWorld(!0);c.length>r;){let e=c.pop(),t=l.pop(),n=u.pop();e&&this.remove(e),t&&this.remove(t),n&&this.remove(n)}for(;c.length<r;){let e=new t.Box3Helper(new t.Box3,new t.Color(16777215)),n=new t.MeshBasicMaterial({transparent:!0,opacity:.1,depthWrite:!1,side:t.DoubleSide}),r=new t.Mesh(new t.PlaneGeometry,n),i=new t.Group,a=new t.Box3Helper(new t.Box3,new t.Color(16776960));i.add(a),this.add(e),this.add(r),this.add(i),c.push(e),l.push(r),u.push(i)}for(let e=0;e<r;e++){let t=a[e],n=o[e].shadow.camera,r=t.far,i=c[e],s=l[e],d=u[e],f=d.children[0];i.box.min.copy(r[2]),i.box.max.copy(r[0]),i.box.max.z+=1e-4,s.position.addVectors(r[0],r[2]),s.position.multiplyScalar(.5),s.scale.subVectors(r[0],r[2]),s.scale.z=1e-4,this.remove(d),d.position.copy(n.position),d.quaternion.copy(n.quaternion),d.scale.copy(n.scale),d.updateMatrixWorld(!0),this.attach(d),f.box.min.set(n.bottom,n.left,-n.far),f.box.max.set(n.top,n.right,-n.near)}let d=i.near,f=i.far;s.setXYZ(0,f[0].x,f[0].y,f[0].z),s.setXYZ(1,f[3].x,f[3].y,f[3].z),s.setXYZ(2,f[2].x,f[2].y,f[2].z),s.setXYZ(3,f[1].x,f[1].y,f[1].z),s.setXYZ(4,d[0].x,d[0].y,d[0].z),s.setXYZ(5,d[3].x,d[3].y,d[3].z),s.setXYZ(6,d[2].x,d[2].y,d[2].z),s.setXYZ(7,d[1].x,d[1].y,d[1].z),s.needsUpdate=!0}},e.CascadedDirectionalLights=r,e.CascadedShadowMaps=k,e.cascadedShadowMapsOptionsDefaults=O,e.frustumSplitFunctions=b,e.overrideFragmentShaderForCSM=g,e.splitFrustum=x});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@navaramap/three-csm",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"main": "dist/index.umd.cjs",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"src",
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT OR Apache-2.0",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/reearth/navara.git",
|
|
16
|
+
"directory": "web/navara_three_csm"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"three": ">=0.183.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/three": "0.185.1",
|
|
26
|
+
"three": "0.185.1"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc && vite build --watch=false",
|
|
30
|
+
"build:dev": "vite build --mode development",
|
|
31
|
+
"build:dev:watch": "pnpm build:dev --watch",
|
|
32
|
+
"test": "vitest --passWithNoTests --watch=false",
|
|
33
|
+
"lint": "eslint . --fix",
|
|
34
|
+
"lint:check": "eslint .",
|
|
35
|
+
"format": "prettier --write --ignore-path ../../.gitignore --ignore-path ../../.prettierignore .",
|
|
36
|
+
"format:check": "prettier --check --ignore-path ../../.gitignore --ignore-path ../../.prettierignore ."
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/CSMHelper.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// TODO
|
|
2
|
+
|
|
3
|
+
// Based on the following work:
|
|
4
|
+
// https://github.com/StrandedKitty/three-csm/tree/master/src
|
|
5
|
+
// https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Box3,
|
|
9
|
+
Box3Helper,
|
|
10
|
+
BufferAttribute,
|
|
11
|
+
BufferGeometry,
|
|
12
|
+
Color,
|
|
13
|
+
DoubleSide,
|
|
14
|
+
Group,
|
|
15
|
+
LineBasicMaterial,
|
|
16
|
+
LineSegments,
|
|
17
|
+
Mesh,
|
|
18
|
+
MeshBasicMaterial,
|
|
19
|
+
PlaneGeometry,
|
|
20
|
+
} from "three";
|
|
21
|
+
|
|
22
|
+
import { type CascadedShadowMaps } from "./CascadedShadowMaps";
|
|
23
|
+
|
|
24
|
+
export class CSMHelper extends Group {
|
|
25
|
+
private readonly csm: CascadedShadowMaps;
|
|
26
|
+
public displayFrustum = true;
|
|
27
|
+
public displayPlanes = true;
|
|
28
|
+
public displayShadowBounds = true;
|
|
29
|
+
private frustumLines: LineSegments<BufferGeometry, LineBasicMaterial>;
|
|
30
|
+
private cascadeLines: Box3Helper[] = [];
|
|
31
|
+
private cascadePlanes: Mesh[] = [];
|
|
32
|
+
private shadowLines: Group[] = [];
|
|
33
|
+
|
|
34
|
+
public constructor(csm: CascadedShadowMaps) {
|
|
35
|
+
super();
|
|
36
|
+
this.csm = csm;
|
|
37
|
+
|
|
38
|
+
const indices = new Uint16Array([
|
|
39
|
+
0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7,
|
|
40
|
+
]);
|
|
41
|
+
const positions = new Float32Array(24);
|
|
42
|
+
const frustumGeometry = new BufferGeometry();
|
|
43
|
+
frustumGeometry.setIndex(new BufferAttribute(indices, 1));
|
|
44
|
+
frustumGeometry.setAttribute(
|
|
45
|
+
"position",
|
|
46
|
+
new BufferAttribute(positions, 3, false),
|
|
47
|
+
);
|
|
48
|
+
const frustumLines = new LineSegments(
|
|
49
|
+
frustumGeometry,
|
|
50
|
+
new LineBasicMaterial(),
|
|
51
|
+
);
|
|
52
|
+
this.add(frustumLines);
|
|
53
|
+
|
|
54
|
+
this.frustumLines = frustumLines;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public updateVisibility() {
|
|
58
|
+
const displayFrustum = this.displayFrustum;
|
|
59
|
+
const displayPlanes = this.displayPlanes;
|
|
60
|
+
const displayShadowBounds = this.displayShadowBounds;
|
|
61
|
+
|
|
62
|
+
const frustumLines = this.frustumLines;
|
|
63
|
+
const cascadeLines = this.cascadeLines;
|
|
64
|
+
const cascadePlanes = this.cascadePlanes;
|
|
65
|
+
const shadowLines = this.shadowLines;
|
|
66
|
+
for (let i = 0, l = cascadeLines.length; i < l; i++) {
|
|
67
|
+
const cascadeLine = cascadeLines[i];
|
|
68
|
+
const cascadePlane = cascadePlanes[i];
|
|
69
|
+
const shadowLineGroup = shadowLines[i];
|
|
70
|
+
|
|
71
|
+
cascadeLine.visible = displayFrustum;
|
|
72
|
+
cascadePlane.visible = displayFrustum && displayPlanes;
|
|
73
|
+
shadowLineGroup.visible = displayShadowBounds;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
frustumLines.visible = displayFrustum;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public update() {
|
|
80
|
+
const csm = this.csm;
|
|
81
|
+
const camera = csm.mainCamera;
|
|
82
|
+
const cascadeCount = csm.cascadeCount;
|
|
83
|
+
const mainFrustum = csm.mainFrustum;
|
|
84
|
+
const frusta = csm.cascadedFrusta;
|
|
85
|
+
const lights = csm.directionalLights.cascadedLights;
|
|
86
|
+
|
|
87
|
+
const frustumLines = this.frustumLines;
|
|
88
|
+
const frustumLinePositions = frustumLines.geometry.getAttribute("position");
|
|
89
|
+
const cascadeLines = this.cascadeLines;
|
|
90
|
+
const cascadePlanes = this.cascadePlanes;
|
|
91
|
+
const shadowLines = this.shadowLines;
|
|
92
|
+
|
|
93
|
+
this.position.copy(camera.position);
|
|
94
|
+
this.quaternion.copy(camera.quaternion);
|
|
95
|
+
this.scale.copy(camera.scale);
|
|
96
|
+
this.updateMatrixWorld(true);
|
|
97
|
+
|
|
98
|
+
while (cascadeLines.length > cascadeCount) {
|
|
99
|
+
const cascadeLine = cascadeLines.pop();
|
|
100
|
+
const cascadePlane = cascadePlanes.pop();
|
|
101
|
+
const shadowLine = shadowLines.pop();
|
|
102
|
+
if (cascadeLine) {
|
|
103
|
+
this.remove(cascadeLine);
|
|
104
|
+
}
|
|
105
|
+
if (cascadePlane) {
|
|
106
|
+
this.remove(cascadePlane);
|
|
107
|
+
}
|
|
108
|
+
if (shadowLine) {
|
|
109
|
+
this.remove(shadowLine);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
while (cascadeLines.length < cascadeCount) {
|
|
114
|
+
const cascadeLine = new Box3Helper(new Box3(), new Color(0xffffff));
|
|
115
|
+
const planeMat = new MeshBasicMaterial({
|
|
116
|
+
transparent: true,
|
|
117
|
+
opacity: 0.1,
|
|
118
|
+
depthWrite: false,
|
|
119
|
+
side: DoubleSide,
|
|
120
|
+
});
|
|
121
|
+
const cascadePlane = new Mesh(new PlaneGeometry(), planeMat);
|
|
122
|
+
const shadowLineGroup = new Group();
|
|
123
|
+
const shadowLine = new Box3Helper(new Box3(), new Color(0xffff00));
|
|
124
|
+
shadowLineGroup.add(shadowLine);
|
|
125
|
+
|
|
126
|
+
this.add(cascadeLine);
|
|
127
|
+
this.add(cascadePlane);
|
|
128
|
+
this.add(shadowLineGroup);
|
|
129
|
+
|
|
130
|
+
cascadeLines.push(cascadeLine);
|
|
131
|
+
cascadePlanes.push(cascadePlane);
|
|
132
|
+
shadowLines.push(shadowLineGroup);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < cascadeCount; i++) {
|
|
136
|
+
const frustum = frusta[i];
|
|
137
|
+
const light = lights[i];
|
|
138
|
+
const shadowCam = light.shadow.camera;
|
|
139
|
+
const farVerts = frustum.far;
|
|
140
|
+
|
|
141
|
+
const cascadeLine = cascadeLines[i];
|
|
142
|
+
const cascadePlane = cascadePlanes[i];
|
|
143
|
+
const shadowLineGroup = shadowLines[i];
|
|
144
|
+
const shadowLine = shadowLineGroup.children[0] as Box3Helper;
|
|
145
|
+
|
|
146
|
+
cascadeLine.box.min.copy(farVerts[2]);
|
|
147
|
+
cascadeLine.box.max.copy(farVerts[0]);
|
|
148
|
+
cascadeLine.box.max.z += 1e-4;
|
|
149
|
+
|
|
150
|
+
cascadePlane.position.addVectors(farVerts[0], farVerts[2]);
|
|
151
|
+
cascadePlane.position.multiplyScalar(0.5);
|
|
152
|
+
cascadePlane.scale.subVectors(farVerts[0], farVerts[2]);
|
|
153
|
+
cascadePlane.scale.z = 1e-4;
|
|
154
|
+
|
|
155
|
+
this.remove(shadowLineGroup);
|
|
156
|
+
shadowLineGroup.position.copy(shadowCam.position);
|
|
157
|
+
shadowLineGroup.quaternion.copy(shadowCam.quaternion);
|
|
158
|
+
shadowLineGroup.scale.copy(shadowCam.scale);
|
|
159
|
+
shadowLineGroup.updateMatrixWorld(true);
|
|
160
|
+
this.attach(shadowLineGroup);
|
|
161
|
+
|
|
162
|
+
shadowLine.box.min.set(shadowCam.bottom, shadowCam.left, -shadowCam.far);
|
|
163
|
+
shadowLine.box.max.set(shadowCam.top, shadowCam.right, -shadowCam.near);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const nearVerts = mainFrustum.near;
|
|
167
|
+
const farVerts = mainFrustum.far;
|
|
168
|
+
frustumLinePositions.setXYZ(0, farVerts[0].x, farVerts[0].y, farVerts[0].z);
|
|
169
|
+
frustumLinePositions.setXYZ(1, farVerts[3].x, farVerts[3].y, farVerts[3].z);
|
|
170
|
+
frustumLinePositions.setXYZ(2, farVerts[2].x, farVerts[2].y, farVerts[2].z);
|
|
171
|
+
frustumLinePositions.setXYZ(3, farVerts[1].x, farVerts[1].y, farVerts[1].z);
|
|
172
|
+
|
|
173
|
+
frustumLinePositions.setXYZ(
|
|
174
|
+
4,
|
|
175
|
+
nearVerts[0].x,
|
|
176
|
+
nearVerts[0].y,
|
|
177
|
+
nearVerts[0].z,
|
|
178
|
+
);
|
|
179
|
+
frustumLinePositions.setXYZ(
|
|
180
|
+
5,
|
|
181
|
+
nearVerts[3].x,
|
|
182
|
+
nearVerts[3].y,
|
|
183
|
+
nearVerts[3].z,
|
|
184
|
+
);
|
|
185
|
+
frustumLinePositions.setXYZ(
|
|
186
|
+
6,
|
|
187
|
+
nearVerts[2].x,
|
|
188
|
+
nearVerts[2].y,
|
|
189
|
+
nearVerts[2].z,
|
|
190
|
+
);
|
|
191
|
+
frustumLinePositions.setXYZ(
|
|
192
|
+
7,
|
|
193
|
+
nearVerts[1].x,
|
|
194
|
+
nearVerts[1].y,
|
|
195
|
+
nearVerts[1].z,
|
|
196
|
+
);
|
|
197
|
+
frustumLinePositions.needsUpdate = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Color,
|
|
3
|
+
DirectionalLight,
|
|
4
|
+
Object3D,
|
|
5
|
+
Vector3,
|
|
6
|
+
type ColorRepresentation,
|
|
7
|
+
} from "three";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_LIGHT_COLOR = new Color(0xffffff);
|
|
10
|
+
|
|
11
|
+
export class CascadedDirectionalLights extends Object3D {
|
|
12
|
+
readonly mainLight: DirectionalLight;
|
|
13
|
+
readonly cascadedLights: DirectionalLight[];
|
|
14
|
+
|
|
15
|
+
// For convenience and stability of the values, because CascadedShadowMaps
|
|
16
|
+
// modifies the positions of lights and their targets.
|
|
17
|
+
// i.e. position.copy(direction).multiplyScalar(-1).add(target.position)
|
|
18
|
+
direction = new Vector3(-1, -1, -1);
|
|
19
|
+
|
|
20
|
+
constructor(color?: ColorRepresentation, intensity?: number) {
|
|
21
|
+
super();
|
|
22
|
+
const light = new DirectionalLight(color, intensity);
|
|
23
|
+
light.castShadow = true;
|
|
24
|
+
this.mainLight = light;
|
|
25
|
+
this.cascadedLights = [light];
|
|
26
|
+
this.add(light);
|
|
27
|
+
this.add(light.target);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setCount(value: number): this {
|
|
31
|
+
value = Math.max(1, value);
|
|
32
|
+
const count = this.cascadedLights.length;
|
|
33
|
+
if (value === count) {
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
if (value > count) {
|
|
37
|
+
const mainLight = this.mainLight;
|
|
38
|
+
for (let i = count; i < value; ++i) {
|
|
39
|
+
const light = mainLight.clone(); // Clones the shadow parameters as well.
|
|
40
|
+
// Avoid accumulating that lighting color.
|
|
41
|
+
light.color.copy(DEFAULT_LIGHT_COLOR);
|
|
42
|
+
this.add(light);
|
|
43
|
+
this.add(light.target);
|
|
44
|
+
this.cascadedLights[i] = light;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
for (let i = value; i < count; ++i) {
|
|
48
|
+
const light = this.cascadedLights[i];
|
|
49
|
+
light.dispose();
|
|
50
|
+
this.remove(light.target);
|
|
51
|
+
this.remove(light);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
this.cascadedLights.length = value;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
dispose(): void {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
60
|
+
for (let i = 0; i < this.cascadedLights.length; ++i) {
|
|
61
|
+
this.cascadedLights[i].dispose();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
copy(source: this, _recursive?: boolean): this {
|
|
66
|
+
super.copy(source, false);
|
|
67
|
+
|
|
68
|
+
this.cascadedLights.length = 0;
|
|
69
|
+
this.clear();
|
|
70
|
+
|
|
71
|
+
for (const l of source.cascadedLights) {
|
|
72
|
+
const cloned = l.clone();
|
|
73
|
+
this.add(cloned);
|
|
74
|
+
this.add(cloned.target);
|
|
75
|
+
this.cascadedLights.push(cloned);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
}
|