@driftengine/core 4.1.3 → 4.1.5
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/CHANGELOG.json +3796 -3739
- package/README.md +1 -1
- package/dist/index.js +12 -2
- package/dist/render/backend/webgl2/renderer.d.ts +15 -0
- package/dist/render/backend/webgl2/renderer.js +28 -7
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +46 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.js +105 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +4 -0
- package/dist/render/backend/webgpu/pointShadowPass.js +10 -0
- package/dist/render/backend/webgpu/renderer.js +17 -4
- package/dist/render/depthConvention.d.ts +19 -0
- package/dist/render/depthConvention.js +21 -0
- package/dist/render/livePointShadowSet.js +8 -0
- package/dist/render/pointShadowBudget.js +59 -5
- package/dist/render/pointShadowImage.d.ts +40 -0
- package/dist/render/pointShadowImage.js +20 -0
- package/dist/render/pointShadowMap.d.ts +4 -0
- package/dist/render/pointShadowMap.js +10 -0
- package/dist/render/pointShadowSystem.js +9 -0
- package/dist/render/renderQuality.d.ts +27 -4
- package/dist/render/renderQuality.js +1 -1
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +104 -10
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +84 -46
- package/dist/render/shaders/flat/preamble.js +70 -9
- package/dist/render/shaders/generated/flat.wgsl.d.ts +16 -16
- package/dist/render/shaders/generated/flat.wgsl.js +32 -32
- package/dist/render/shaders/sky.d.ts +10 -6
- package/dist/render/shaders/sky.js +35 -2
- package/dist/render/textLayout.d.ts +58 -0
- package/dist/render/textLayout.js +83 -0
- package/dist/render/textRenderer.js +7 -3
- package/package.json +6 -6
- package/src/index.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +28 -7
- package/src/render/backend/webgpu/gpuDrivenPass.ts +105 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +13 -0
- package/src/render/backend/webgpu/renderer.ts +26 -4
- package/src/render/depthConvention.ts +22 -0
- package/src/render/livePointShadowSet.ts +7 -0
- package/src/render/pointShadowBudget.ts +58 -5
- package/src/render/pointShadowImage.ts +51 -0
- package/src/render/pointShadowMap.ts +13 -0
- package/src/render/pointShadowSystem.ts +8 -0
- package/src/render/renderQuality.ts +28 -5
- package/src/render/shaders/flat/main.ts +104 -10
- package/src/render/shaders/flat/pointShadow.ts +84 -46
- package/src/render/shaders/flat/preamble.ts +70 -9
- package/src/render/shaders/generated/flat.wgsl.ts +32 -32
- package/src/render/shaders/sky.ts +37 -2
- package/src/render/textLayout.ts +88 -0
- package/src/render/textRenderer.ts +22 -3
|
@@ -15,7 +15,40 @@
|
|
|
15
15
|
import { MAX_SHADOW_FILTER_TAPS } from '../../renderQuality.ts';
|
|
16
16
|
import { OUTPUT_TRANSFORM_GLSL } from '../outputTransform.ts';
|
|
17
17
|
|
|
18
|
-
export const POINTSHADOW_GLSL =
|
|
18
|
+
export const POINTSHADOW_GLSL = `/**
|
|
19
|
+
* How much of one tap's occlusion survives its own penumbra.
|
|
20
|
+
*
|
|
21
|
+
* **How much of a shadow survives, as well as how soft it is.** A penumbra does not merely blur
|
|
22
|
+
* with distance from its caster, it *takes over*: far enough from the occluder, relative to how
|
|
23
|
+
* big the source is, every point on the receiver can see part of the flame and there is no umbra
|
|
24
|
+
* left at all. The shadow is gone, not blurry. Without it, two columns lit by a brazier threw
|
|
25
|
+
* full-strength edges fifteen metres across the terrace, and the gap between them read as a
|
|
26
|
+
* spotlight cone with straight sides rather than as a fire.
|
|
27
|
+
*
|
|
28
|
+
* **Per tap, and it was once for the whole filter, taken from the centre tap alone.** That is a
|
|
29
|
+
* blocker search of one sample standing in for twelve, and it is right wherever the taps agree —
|
|
30
|
+
* inside an umbra they all find the same occluder, so this returns exactly what the shared value
|
|
31
|
+
* did, and the umbra is unchanged to the bit. It is wrong exactly where they disagree, which is
|
|
32
|
+
* the silhouette: there the centre ray *misses*, so the search reports no occluder, the spread is
|
|
33
|
+
* zero and the fade is switched off — while the taps that do land on the caster then draw at full
|
|
34
|
+
* strength. What that paints is a hard one-texel line around a shadow whose interior has
|
|
35
|
+
* correctly faded to nothing.
|
|
36
|
+
*
|
|
37
|
+
* Reported as shadows "faded out but NOT shadow outlines, generating long lines around the
|
|
38
|
+
* world", and on a brazier as a bare square outline lying on the ground with no shadow inside it.
|
|
39
|
+
*
|
|
40
|
+
* **What it costs** is two multiplies and a divide per occluded tap, against a tap that has
|
|
41
|
+
* already paid for a texture fetch. **What would make it wrong** is a filter wide enough that one
|
|
42
|
+
* tap's occluder is not the occluder a neighbouring tap is estimating for, which is the same
|
|
43
|
+
* limit the shared value had and is what \`MAX_FILTER_RADIUS\` bounds.
|
|
44
|
+
*/
|
|
45
|
+
float tapStrength(float receiverDistance, float occluderDistance, float sourceRadius) {
|
|
46
|
+
float spread = max(receiverDistance - occluderDistance, 0.0) / max(occluderDistance, 0.05);
|
|
47
|
+
float penumbra = clamp(sourceRadius * spread * PENUMBRA_FADE, 0.0, 1.0);
|
|
48
|
+
return 1.0 - penumbra * penumbra;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
float pointShadow(
|
|
19
52
|
highp sampler2DArray maps,
|
|
20
53
|
float layer,
|
|
21
54
|
vec3 toFrag,
|
|
@@ -98,26 +131,8 @@ export const POINTSHADOW_GLSL = `float pointShadow(
|
|
|
98
131
|
float radius = clamp(sourceRadius * spread, 0.01, MAX_FILTER_RADIUS) + 0.012;
|
|
99
132
|
|
|
100
133
|
/*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* This is the half that was missing, and it is why widening the filter never
|
|
104
|
-
* fixed anything. A penumbra does not merely blur with distance from its
|
|
105
|
-
* caster — it *takes over*. Far enough from the occluder, relative to how big
|
|
106
|
-
* the source is, every point on the receiver can see part of the flame and
|
|
107
|
-
* there is no umbra left at all: the shadow is gone, not blurry.
|
|
108
|
-
*
|
|
109
|
-
* Without it, two columns lit by a brazier threw full-strength edges fifteen
|
|
110
|
-
* metres across the terrace, and the gap between them read as a spotlight cone
|
|
111
|
-
* with straight sides rather than as a fire. Three attempts in, the note that
|
|
112
|
-
* unlocked it was that the pool should still fade naturally at its rim — a
|
|
113
|
-
* description of the falloff, not of the blur.
|
|
114
|
-
*/
|
|
115
|
-
float penumbra = clamp(sourceRadius * spread * PENUMBRA_FADE, 0.0, 1.0);
|
|
116
|
-
float strength = 1.0 - penumbra * penumbra;
|
|
117
|
-
|
|
118
|
-
/*
|
|
119
|
-
* Taps are placed on a disk *perpendicular to the light ray*, rotated per
|
|
120
|
-
* fragment.
|
|
134
|
+
* Taps are placed on a disk *perpendicular to the light ray*, turned by half a golden
|
|
135
|
+
* angle on every other column of pixels.
|
|
121
136
|
*
|
|
122
137
|
* Offsetting the ray by fixed 3D vectors was the earlier approach and it has
|
|
123
138
|
* two faults that only show on a wide filter. The offsets are not
|
|
@@ -127,8 +142,33 @@ export const POINTSHADOW_GLSL = `float pointShadow(
|
|
|
127
142
|
* tap pattern — reported, on a brazier behind a rank of columns, as strange
|
|
128
143
|
* straight lines across the light pool.
|
|
129
144
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
145
|
+
* **The cure for that was a per-fragment hash, and it is what made the penumbra
|
|
146
|
+
* stipple.** Twelve taps are a coarse estimate of how much of the emitter this point can
|
|
147
|
+
* see; turning them by an unrelated angle at every pixel makes each pixel's error
|
|
148
|
+
* independent of its neighbour's, which is the definition of noise. Something has to
|
|
149
|
+
* average that back out, and this engine's lit pass is the last thing to touch the frame —
|
|
150
|
+
* there is no blur and no temporal resolve behind it, so the noise reached the screen. Two
|
|
151
|
+
* games reported the same coarse cross-hatch across a light pool.
|
|
152
|
+
*
|
|
153
|
+
* **So the turn comes from a two-pixel tile instead, and the pair is averaged afterwards.**
|
|
154
|
+
* A pixel's own x parity picks one of two tap sets half a golden angle apart, and
|
|
155
|
+
* \`main\` folds the pair together once the light loop is over — see the resolve there. The
|
|
156
|
+
* pair's twenty-four taps are the filter; each pixel pays for twelve of them.
|
|
157
|
+
*
|
|
158
|
+
* **A *column* pair rather than a 2x2 square, and that was measured rather than assumed.**
|
|
159
|
+
* Four turns on a square tile, resolved with both derivatives, came back at 3.88 against this
|
|
160
|
+
* pair's 2.46 on the same build — because the second derivative hands the lower row of a quad
|
|
161
|
+
* the upper row's difference on hardware that answers a coarse derivative, and with four turns
|
|
162
|
+
* that is the wrong difference. Two turns keyed on the column alone make it the right one,
|
|
163
|
+
* which is why the tile's shape and the resolve are one decision rather than two.
|
|
164
|
+
*
|
|
165
|
+
* **Neither half works alone**, which is the same pairing \`ambientOcclusion.ts\` describes
|
|
166
|
+
* between its rotation tile and its blur. Measured on \`demo/dev/pointshadow.html\` at
|
|
167
|
+
* \`?exposure=6\`, as mean absolute luminance difference to the right and down neighbour over
|
|
168
|
+
* the mover's penumbra, on WebGL2: **4.60** for the per-fragment hash this replaces, **2.88**
|
|
169
|
+
* for this tile with no resolve — and a one-pixel vertical comb in the picture, which is the
|
|
170
|
+
* tile itself on screen — against **2.37** for the pair resolved. A frame with the shadow
|
|
171
|
+
* switched off measures 0.81, and WebGPU answers 4.56 and 2.33 for the same two builds.
|
|
132
172
|
*
|
|
133
173
|
* **Unchanged by the move to an octahedral map, and that is the point.** The offsets are
|
|
134
174
|
* angular perturbations of a direction, so every tap is still a direction and octEncode is
|
|
@@ -139,17 +179,19 @@ export const POINTSHADOW_GLSL = `float pointShadow(
|
|
|
139
179
|
vec3 up = abs(fwd.y) < 0.9 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
|
140
180
|
vec3 tu = normalize(cross(up, fwd));
|
|
141
181
|
vec3 tv = cross(fwd, tu);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
182
|
+
/*
|
|
183
|
+
* Integer arithmetic, because this is a lattice: a shader compiler is free to implement
|
|
184
|
+
* \`mod(x, 2.0)\` as a multiply by a reciprocal, and a GPU here answers 3 for \`3.0 % 3.0\`.
|
|
185
|
+
* \`gl_FragCoord.x\` is the pixel's centre, so truncating it is the pixel's own column.
|
|
186
|
+
*/
|
|
187
|
+
float angle = float(int(gl_FragCoord.x) & 1) * PCF_PAIR_TURN;
|
|
146
188
|
float ca = cos(angle);
|
|
147
189
|
float sa = sin(angle);
|
|
148
190
|
|
|
149
191
|
float lit = 0.0;
|
|
150
192
|
for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
|
|
151
193
|
if (i >= uShadowFilterTaps) break;
|
|
152
|
-
vec2 disk = PCF_OFFSETS[i]
|
|
194
|
+
vec2 disk = PCF_OFFSETS[i];
|
|
153
195
|
vec2 turned = vec2(disk.x * ca - disk.y * sa, disk.x * sa + disk.y * ca);
|
|
154
196
|
// Perpendicular to the ray, and *not* scaled by its length: the offsets are
|
|
155
197
|
// an angular perturbation of a direction, which is what the encoding takes.
|
|
@@ -164,14 +206,13 @@ export const POINTSHADOW_GLSL = `float pointShadow(
|
|
|
164
206
|
if (dist - bias > storedDistance) {
|
|
165
207
|
// A point shadow cannot outlive the finite light that casts it, so its
|
|
166
208
|
// occlusion fades smoothly before the light's far plane.
|
|
167
|
-
|
|
209
|
+
float reach = shadowReach(max(dist - storedDistance, 0.0), far);
|
|
210
|
+
lit += 1.0 - reach * tapStrength(dist, storedDistance, sourceRadius);
|
|
168
211
|
} else {
|
|
169
212
|
lit += 1.0;
|
|
170
213
|
}
|
|
171
214
|
}
|
|
172
|
-
|
|
173
|
-
// Fade the whole shadow toward lit as the penumbra swallows it.
|
|
174
|
-
return mix(1.0, shaded, strength);
|
|
215
|
+
return lit / float(max(uShadowFilterTaps, 1));
|
|
175
216
|
}
|
|
176
217
|
|
|
177
218
|
/**
|
|
@@ -262,25 +303,20 @@ float areaShadow(
|
|
|
262
303
|
float widthX = min(halfX * spread, MAX_FILTER_RADIUS) + 0.012;
|
|
263
304
|
float widthY = min(halfY * spread, MAX_FILTER_RADIUS) + 0.012;
|
|
264
305
|
|
|
265
|
-
/* The narrower half extent, for the reason in the header: it is what keeps a strip's umbra. */
|
|
266
|
-
float penumbra = clamp(min(halfX, halfY) * spread * PENUMBRA_FADE, 0.0, 1.0);
|
|
267
|
-
float strength = 1.0 - penumbra * penumbra;
|
|
268
|
-
|
|
269
306
|
/*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
307
|
+
* Turned inside the unit disk *before* the ellipse maps it, so the half-turn that separates
|
|
308
|
+
* the two halves of a pixel pair survives the anisotropy. The same two-pixel tile the round
|
|
309
|
+
* filter uses and for the same reasons — see \`pointShadow\`, which carries the measurement;
|
|
310
|
+
* a rectangle's shadow stippled exactly as a lamp's did, because it is the same twelve taps.
|
|
274
311
|
*/
|
|
275
|
-
float angle =
|
|
276
|
-
* 6.28318530718;
|
|
312
|
+
float angle = float(int(gl_FragCoord.x) & 1) * PCF_PAIR_TURN;
|
|
277
313
|
float ca = cos(angle);
|
|
278
314
|
float sa = sin(angle);
|
|
279
315
|
|
|
280
316
|
float lit = 0.0;
|
|
281
317
|
for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
|
|
282
318
|
if (i >= uShadowFilterTaps) break;
|
|
283
|
-
vec2 disk = PCF_OFFSETS[i]
|
|
319
|
+
vec2 disk = PCF_OFFSETS[i];
|
|
284
320
|
vec2 turned = vec2(disk.x * ca - disk.y * sa, disk.x * sa + disk.y * ca);
|
|
285
321
|
vec3 sampleDir = toFrag + alongX * (turned.x * widthX) + alongY * (turned.y * widthY);
|
|
286
322
|
float stored = textureLod(maps, vec3(octEncode(sampleDir), layer), 0.0).r;
|
|
@@ -292,13 +328,15 @@ float areaShadow(
|
|
|
292
328
|
float storedDistance = stored * far;
|
|
293
329
|
if (dist - bias > storedDistance) {
|
|
294
330
|
/* An area light's occlusion cannot outlive the map that carries it either. */
|
|
295
|
-
|
|
331
|
+
float reach = shadowReach(max(dist - storedDistance, 0.0), far);
|
|
332
|
+
/* The narrower half extent, for the reason in the header: it is what keeps a strip's
|
|
333
|
+
umbra. Per tap, for the reason \`tapStrength\` gives. */
|
|
334
|
+
lit += 1.0 - reach * tapStrength(dist, storedDistance, min(halfX, halfY));
|
|
296
335
|
} else {
|
|
297
336
|
lit += 1.0;
|
|
298
337
|
}
|
|
299
338
|
}
|
|
300
|
-
|
|
301
|
-
return mix(1.0, shaded, strength);
|
|
339
|
+
return lit / float(max(uShadowFilterTaps, 1));
|
|
302
340
|
}
|
|
303
341
|
#endif
|
|
304
342
|
|
|
@@ -803,7 +803,30 @@ uniform float uAreaLightTwoSided[MAX_AREA_LIGHTS];
|
|
|
803
803
|
#if POINT_SHADOWS
|
|
804
804
|
/** Which array layer holds this light's shadow, or -1 for none. */
|
|
805
805
|
uniform int uPointShadowLayer[MAX_LIGHTS];
|
|
806
|
-
|
|
806
|
+
/*
|
|
807
|
+
* **The projection each map was rendered under: \`xyz\` where from, \`w\` how far.**
|
|
808
|
+
*
|
|
809
|
+
* The origin is the half that is new and it is **not** the light's position. A light may drift
|
|
810
|
+
* \`pointShadowRebakeDistance\` from its image before the image is re-rendered, because
|
|
811
|
+
* re-baking six faces of the static world on every frame of a flicker is what made a brazier
|
|
812
|
+
* the most expensive object in a scene. The ray below is built from this point rather than from
|
|
813
|
+
* \`uLightPos\`, so it is the ray the picture was drawn along and the drift costs nothing.
|
|
814
|
+
*
|
|
815
|
+
* **It rides with the far plane because a row is a row.** A default-block \`float[N]\` spends a
|
|
816
|
+
* whole uniform vector per element and uses one of its four components, so the far plane was
|
|
817
|
+
* already paying for three floats it threw away — see \`uniformVectorBudget.ts\`. Declared as a
|
|
818
|
+
* separate \`vec3\` array this would have cost sixteen more rows on each of the two sets, and
|
|
819
|
+
* the ladder answers a 256-vector phone by halving \`MAX_LIGHTS\`: the measured price of a
|
|
820
|
+
* separate array was **eight lights becoming four**. Folded in, the whole fix is free.
|
|
821
|
+
*
|
|
822
|
+
* **What it gives up** is that the shadow stops tracking the flame *within* the tolerance — a
|
|
823
|
+
* flicker no longer nudges the shadow it casts, because the image it is read from did not move
|
|
824
|
+
* either. That is the trade the tolerance was always making, now made honestly instead of by
|
|
825
|
+
* reading a picture from the wrong place. **What would make it wrong** is a tolerance large
|
|
826
|
+
* enough that a shadow visibly lags its light, which is a reason to lower
|
|
827
|
+
* \`pointShadowRebakeDistance\` rather than to sample from a point the image cannot answer for.
|
|
828
|
+
*/
|
|
829
|
+
uniform vec4 uPointShadowProjection[MAX_LIGHTS];
|
|
807
830
|
uniform float uPointShadowNear[MAX_LIGHTS];
|
|
808
831
|
/** Emitter radius per light — drives how soft its shadows are. */
|
|
809
832
|
uniform float uPointShadowSize[MAX_LIGHTS];
|
|
@@ -834,7 +857,8 @@ uniform highp sampler2DArray uPointShadows; // wgsl:share shadow
|
|
|
834
857
|
* light owns at most one of the two at a time.
|
|
835
858
|
*/
|
|
836
859
|
uniform int uLivePointShadowLayer[MAX_LIGHTS];
|
|
837
|
-
|
|
860
|
+
/** The live pair's own projection, by the same rule. See \`uPointShadowProjection\`. */
|
|
861
|
+
uniform vec4 uLivePointShadowProjection[MAX_LIGHTS];
|
|
838
862
|
uniform float uLivePointShadowNear[MAX_LIGHTS];
|
|
839
863
|
uniform float uLivePointShadowSize[MAX_LIGHTS];
|
|
840
864
|
uniform float uLivePointShadowWeight[MAX_LIGHTS];
|
|
@@ -883,16 +907,53 @@ uniform float uLiveAreaShadowWeight[MAX_AREA_LIGHTS];
|
|
|
883
907
|
* where a bias means what it looks like it means.
|
|
884
908
|
*/
|
|
885
909
|
/**
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
910
|
+
* Twelve places on a disk across the light ray, for filtering an omnidirectional shadow.
|
|
911
|
+
*
|
|
912
|
+
* **These were eight, and the count is why a penumbra was stippled.** The list used to be the
|
|
913
|
+
* eight corners and six faces of a cube, carried as \`vec3\` from the era when a tap offset a
|
|
914
|
+
* direction by a 3D vector. Nothing has read the third component since the map became
|
|
915
|
+
* octahedral — both filters take \`.xy\` — and flattening a cube's corners onto a plane makes
|
|
916
|
+
* pairs of them coincide: \`(1, 1, 1)\` and \`(1, 1, -1)\` are one 2D offset, four times over. So
|
|
917
|
+
* twelve taps asked eight questions and answered four of them twice, and every one of the eight
|
|
918
|
+
* sat on the rim at radius 1 or 1.414 with nothing in between. A coverage estimate from a ring
|
|
919
|
+
* moves in steps of two twelfths as the penumbra's edge sweeps a doubled pair across it, which
|
|
920
|
+
* is the size of the speckle that was reported.
|
|
921
|
+
*
|
|
922
|
+
* **The replacement is the golden-angle disk this repository already uses for ambient
|
|
923
|
+
* occlusion** — angle \`i\` turns of 137.5 degrees, radius \`sqrt((k + 0.5) / 12)\` — so the taps
|
|
924
|
+
* are spread evenly over the area rather than crowded on the rim or at the centre, and no two
|
|
925
|
+
* coincide. The nearest pair is 0.355 apart where the old set had a pair at zero.
|
|
926
|
+
*
|
|
927
|
+
* **The radii are dealt round rather than taken in order, and that is what makes a short filter
|
|
928
|
+
* work.** \`shadowFilterTaps\` is 4, 8 or 12 and the loop below simply stops early, so every
|
|
929
|
+
* prefix of this list is a filter somebody ships: in spiral order the first four taps would be
|
|
930
|
+
* the four innermost and a low profile would quietly get a filter a third of the width it asked
|
|
931
|
+
* for. Interleaved, the first four already span 0.21 to 0.91 of the disk's reach and sit around
|
|
932
|
+
* its centre rather than to one side, which is a small filter rather than a narrow one.
|
|
933
|
+
*
|
|
934
|
+
* **What it gives up** is a little softness. The old set put two thirds of its taps at the
|
|
935
|
+
* outermost radius, so it read the penumbra's widest part twice as often as an even disk does
|
|
936
|
+
* and drew a shadow edge softer than the emitter's size calls for; an even disk is the honest
|
|
937
|
+
* estimate and is very slightly crisper. **What would make it wrong** is a caller wanting the
|
|
938
|
+
* filter's *reach* changed — that is \`MAX_FILTER_RADIUS\`, and the outermost tap here is 1.384
|
|
939
|
+
* against the old 1.414 precisely so this change does not move it.
|
|
889
940
|
*/
|
|
890
941
|
#if POINT_SHADOWS
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
942
|
+
const vec2 PCF_OFFSETS[${MAX_SHADOW_FILTER_TAPS}] = vec2[${MAX_SHADOW_FILTER_TAPS}](
|
|
943
|
+
vec2( 0.289, 0.000), vec2(-0.767, 0.703), vec2( 0.067, -0.761), vec2( 0.766, 0.999),
|
|
944
|
+
vec2(-0.492, -0.087), vec2( 0.943, -0.600), vec2(-0.225, 0.836), vec2(-0.610, -1.174),
|
|
945
|
+
vec2( 0.606, 0.221), vec2(-1.100, 0.454), vec2( 0.406, -0.867), vec2( 0.414, 1.321)
|
|
895
946
|
);
|
|
947
|
+
/**
|
|
948
|
+
* The angle between the two tap sets a pair of neighbouring pixels uses. See \`pointShadow\`.
|
|
949
|
+
*
|
|
950
|
+
* Half the golden angle, because the offsets above are a golden-angle spiral: turning the whole
|
|
951
|
+
* set by half a step drops the second pixel's taps between the first pixel's arms rather than
|
|
952
|
+
* on top of them. Measured on the pair, the closest two of the twenty-four are 0.305 apart,
|
|
953
|
+
* against 0.175 for a half turn and 0.233 for a whole golden angle — so this is the choice that
|
|
954
|
+
* makes the pair's combined filter as even as the twelve it is built from.
|
|
955
|
+
*/
|
|
956
|
+
const float PCF_PAIR_TURN = 1.19998161;
|
|
896
957
|
#endif
|
|
897
958
|
|
|
898
959
|
/** A finite point-light shadow loses strength with the light that casts it. */`;
|