@woosh/meep-engine 2.138.13 → 2.138.15

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.
Files changed (56) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/package.json +1 -1
  3. package/samples/terrain/from_image_2.js +2 -10
  4. package/src/engine/asset/loaders/image/ImageDecoderWorker.js +1 -1
  5. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
  6. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +6 -1
  7. package/src/engine/asset/loaders/image/png/PNGReader.d.ts +1 -99
  8. package/src/engine/asset/loaders/image/png/PNGReader.d.ts.map +1 -1
  9. package/src/engine/asset/loaders/image/png/PNGReader.js +31 -420
  10. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.d.ts +12 -0
  11. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.d.ts.map +1 -0
  12. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.js +53 -0
  13. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.d.ts +10 -0
  14. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.d.ts.map +1 -0
  15. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.js +42 -0
  16. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.d.ts +18 -0
  17. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.d.ts.map +1 -0
  18. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -0
  19. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts +17 -0
  20. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts.map +1 -0
  21. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -0
  22. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.d.ts +17 -0
  23. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.d.ts.map +1 -0
  24. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -0
  25. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts +15 -0
  26. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts.map +1 -0
  27. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -0
  28. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts +16 -0
  29. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts.map +1 -0
  30. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -0
  31. package/src/engine/asset/loaders/image/png/inflate.d.ts +7 -0
  32. package/src/engine/asset/loaders/image/png/inflate.d.ts.map +1 -0
  33. package/src/engine/asset/loaders/image/png/inflate.js +20 -0
  34. package/src/engine/ecs/terrain/ecs/Terrain.js +1 -1
  35. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.d.ts.map +1 -1
  36. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +32 -9
  37. package/src/engine/graphics/impostors/octahedral/prototypeBaker.js +202 -8
  38. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.d.ts +10 -0
  39. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.d.ts.map +1 -0
  40. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.js +395 -0
  41. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.d.ts +14 -0
  42. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.d.ts.map +1 -0
  43. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.js +757 -0
  44. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.d.ts +13 -0
  45. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.d.ts.map +1 -0
  46. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.js +380 -0
  47. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.d.ts +6 -0
  48. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.d.ts.map +1 -0
  49. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.js +406 -0
  50. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.d.ts.map +1 -1
  51. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.js +8 -1
  52. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.d.ts +14 -0
  53. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.d.ts.map +1 -0
  54. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.js +356 -0
  55. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.d.ts.map +1 -1
  56. package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.js +4 -1
@@ -0,0 +1,757 @@
1
+ import {
2
+ Color,
3
+ ShaderMaterial,
4
+ UniformsLib,
5
+ UniformsUtils,
6
+ Vector3
7
+ } from "three";
8
+
9
+ // Lit variant of ImpostorShaderV0 — runs three.js's STANDARD (physical) shading
10
+ // model so the impostor responds to scene lights and shadow maps.
11
+ //
12
+ // What this shader does differently from V0:
13
+ // - The atlas baked-view-space normal is reconstructed back to object
14
+ // space per-corner before blending. We need the actual surface normal
15
+ // in view space for the lighting equations; V0 didn't care.
16
+ // - We integrate three.js's lighting chunks (lights_fragment_begin /
17
+ // lights_physical_fragment / shadowmap_* etc.) instead of just writing
18
+ // out the sampled colour.
19
+ // - We don't write gl_FragDepth — the card sits at the bounding sphere
20
+ // centre, so depth from gl_Position is close enough for the lit
21
+ // impostor's own occlusion. (Receiving shadows needs vDirectionalShadowCoord
22
+ // etc., which we compute in the vertex shader from worldPosition.)
23
+ //
24
+ // Implementation choice: ShaderMaterial (not Raw) so three.js auto-injects
25
+ // position/normal/uv attributes, modelMatrix/viewMatrix uniforms, and the
26
+ // light/shadow uniform block, and so the chunk system Just Works. GLSL1-style
27
+ // (varying/attribute/texture2D) so the included three.js chunks compile
28
+ // without per-include rewrites; on WebGL2 three.js #defines these to their
29
+ // GLSL3 equivalents automatically.
30
+ const shader_vx = `
31
+ #define STANDARD
32
+
33
+ varying vec3 vViewPosition;
34
+ varying vec2 vImpUv;
35
+
36
+ // Card position and normal in OBJECT space — the fragment shader uses
37
+ // these (plus the atlas depth at this fragment's parallax-shifted UV)
38
+ // to reconstruct the actual surface position in WORLD space. Once we
39
+ // have that, the shadow coord we hand to getShadow() reflects where
40
+ // the pixel REALLY is rather than where its card-plane proxy is, so
41
+ // the lit impostor's shadow-receive agrees with the caster's
42
+ // gl_FragDepth and self-shadow goes away.
43
+ varying vec3 vCardPos_OS;
44
+ flat varying vec3 vCardNormal_OS;
45
+
46
+ flat varying vec2 vGridFloor;
47
+ flat varying vec4 vWeights;
48
+ flat varying vec3 vImpTangent;
49
+ flat varying vec3 vImpBinormal;
50
+ flat varying vec3 vImpNormal;
51
+ flat varying vec3 vRay00;
52
+ flat varying vec3 vRay10;
53
+ flat varying vec3 vRay01;
54
+ flat varying vec3 vRay11;
55
+ flat varying vec4 vFrameXform00;
56
+ flat varying vec4 vFrameXform10;
57
+ flat varying vec4 vFrameXform01;
58
+ flat varying vec4 vFrameXform11;
59
+
60
+ uniform vec3 uOffset;
61
+ uniform float uRadius;
62
+ uniform float uFrames;
63
+ uniform bool uIsFullSphere;
64
+
65
+ #include <common>
66
+ #include <fog_pars_vertex>
67
+ #include <shadowmap_pars_vertex>
68
+ #include <logdepthbuf_pars_vertex>
69
+ #include <clipping_planes_pars_vertex>
70
+
71
+ vec2 VecToSphereOct(vec3 pivotToCamera)
72
+ {
73
+ vec3 octant = sign(pivotToCamera);
74
+ float sum = dot(pivotToCamera, octant);
75
+ vec3 octahedron = pivotToCamera / sum;
76
+ if (octahedron.y < 0.0) {
77
+ vec3 absolute = abs(octahedron);
78
+ octahedron.xz = octant.xz * vec2(1.0 - absolute.z, 1.0 - absolute.x);
79
+ }
80
+ return octahedron.xz;
81
+ }
82
+
83
+ vec2 VecToHemiSphereOct(vec3 v)
84
+ {
85
+ v.y = max(v.y, 0.001);
86
+ v = normalize(v);
87
+ vec3 octant = sign(v);
88
+ float sum = dot(v, octant);
89
+ vec3 octahedron = v / sum;
90
+ return vec2(
91
+ octahedron.x + octahedron.z,
92
+ octahedron.z - octahedron.x
93
+ );
94
+ }
95
+
96
+ vec2 VectorToGrid(vec3 v)
97
+ {
98
+ return uIsFullSphere ? VecToSphereOct(v) : VecToHemiSphereOct(v);
99
+ }
100
+
101
+ vec3 OctaSphereDec(vec2 coord)
102
+ {
103
+ coord = (coord - 0.5) * 2.0;
104
+ vec3 p = vec3(coord.x, 0.0, coord.y);
105
+ vec2 a = abs(p.xz);
106
+ p.y = 1.0 - a.x - a.y;
107
+ if (p.y < 0.0) {
108
+ p.xz = sign(p.xz) * vec2(1.0 - a.y, 1.0 - a.x);
109
+ }
110
+ return p;
111
+ }
112
+
113
+ vec3 OctaHemiSphereDec(vec2 coord)
114
+ {
115
+ vec3 p = vec3(coord.x - coord.y, 0.0, -1.0 + coord.x + coord.y);
116
+ vec2 a = abs(p.xz);
117
+ p.y = 1.0 - a.x - a.y;
118
+ return p;
119
+ }
120
+
121
+ vec3 GridToVector(vec2 coord)
122
+ {
123
+ return uIsFullSphere ? OctaSphereDec(coord) : OctaHemiSphereDec(coord);
124
+ }
125
+
126
+ vec3 FrameToRay(vec2 frame, vec2 framesMinusOne)
127
+ {
128
+ vec2 f = clamp(frame / framesMinusOne, 0.0, 1.0);
129
+ return normalize(GridToVector(f));
130
+ }
131
+
132
+ vec4 ComputeFrameXform(vec3 D_frame, vec3 tangent_OS, vec3 binormal_OS)
133
+ {
134
+ vec3 D = abs(D_frame.y) > 0.99999
135
+ ? normalize(D_frame + vec3(0.0, 0.0, 0.0001))
136
+ : D_frame;
137
+ vec3 bake_right = normalize(cross(vec3(0.0, 1.0, 0.0), D));
138
+ vec3 bake_up = cross(D, bake_right);
139
+ return vec4(
140
+ dot(tangent_OS, bake_right),
141
+ dot(binormal_OS, bake_right),
142
+ dot(tangent_OS, bake_up),
143
+ dot(binormal_OS, bake_up)
144
+ );
145
+ }
146
+
147
+ vec4 BilinearWeights(vec2 frac_uv)
148
+ {
149
+ vec2 omuv = vec2(1.0) - frac_uv;
150
+ return vec4(
151
+ omuv.x * omuv.y,
152
+ frac_uv.x * omuv.y,
153
+ omuv.x * frac_uv.y,
154
+ frac_uv.x * frac_uv.y
155
+ );
156
+ }
157
+
158
+ void main() {
159
+ vImpUv = uv;
160
+
161
+ vec3 cameraPos_OS = (inverse(modelViewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
162
+ // Atlas lookup must be relative to the bounding-sphere centre
163
+ // (uOffset), since the bake's D_frame is measured from the centre,
164
+ // not from the object origin.
165
+ vec3 pivotToCameraRay = normalize(cameraPos_OS - uOffset);
166
+
167
+ vec2 framesMinusOne = vec2(uFrames - 1.0);
168
+ vec2 octahedral_uv = clamp(VectorToGrid(pivotToCameraRay) * 0.5 + 0.5, 0.0, 1.0);
169
+ vec2 grid = octahedral_uv * framesMinusOne;
170
+ vec2 gridFloor = min(floor(grid), framesMinusOne - 1.0);
171
+ vec4 weights = BilinearWeights(grid - gridFloor);
172
+
173
+ vGridFloor = gridFloor;
174
+ vWeights = weights;
175
+
176
+ vec3 ray00 = FrameToRay(gridFloor + vec2(0.0, 0.0), framesMinusOne);
177
+ vec3 ray10 = FrameToRay(gridFloor + vec2(1.0, 0.0), framesMinusOne);
178
+ vec3 ray01 = FrameToRay(gridFloor + vec2(0.0, 1.0), framesMinusOne);
179
+ vec3 ray11 = FrameToRay(gridFloor + vec2(1.0, 1.0), framesMinusOne);
180
+
181
+ // Object-space corner ray directions — needed by the fragment shader
182
+ // to reconstruct each frame's bake_right / bake_up basis and decode
183
+ // the atlas normal back to object space.
184
+ vRay00 = ray00;
185
+ vRay10 = ray10;
186
+ vRay01 = ray01;
187
+ vRay11 = ray11;
188
+
189
+ vec3 projectedRay = normalize(
190
+ ray00 * weights.x +
191
+ ray10 * weights.y +
192
+ ray01 * weights.z +
193
+ ray11 * weights.w
194
+ );
195
+
196
+ vec3 normal_OS = projectedRay;
197
+ vec3 up_OS = abs(normal_OS.y) > 0.999
198
+ ? vec3(0.0, 0.0, -1.0)
199
+ : vec3(0.0, 1.0, 0.0);
200
+ vec3 tangent_OS = normalize(cross(up_OS, normal_OS));
201
+ vec3 binormal_OS = cross(normal_OS, tangent_OS);
202
+
203
+ float card_diameter = uRadius * 2.0;
204
+ vec3 pos_OS = uOffset
205
+ + position.x * card_diameter * tangent_OS
206
+ + position.y * card_diameter * binormal_OS;
207
+
208
+ vCardPos_OS = pos_OS;
209
+ vCardNormal_OS = normal_OS;
210
+
211
+ vec4 mvPosition = modelViewMatrix * vec4(pos_OS, 1.0);
212
+ gl_Position = projectionMatrix * mvPosition;
213
+ vViewPosition = -mvPosition.xyz;
214
+
215
+ mat3 m3 = mat3(modelViewMatrix);
216
+ vImpTangent = normalize(m3 * tangent_OS);
217
+ vImpBinormal = normalize(m3 * binormal_OS);
218
+ vImpNormal = normalize(m3 * normal_OS);
219
+
220
+ vFrameXform00 = ComputeFrameXform(ray00, tangent_OS, binormal_OS);
221
+ vFrameXform10 = ComputeFrameXform(ray10, tangent_OS, binormal_OS);
222
+ vFrameXform01 = ComputeFrameXform(ray01, tangent_OS, binormal_OS);
223
+ vFrameXform11 = ComputeFrameXform(ray11, tangent_OS, binormal_OS);
224
+
225
+ // Quantities the three.js chunks expect to be in scope.
226
+ // - 'worldPosition' is consumed by <shadowmap_vertex> to build the
227
+ // per-light shadow projection coords.
228
+ // - 'transformedNormal' is consumed by the same chunk for the
229
+ // shadow-acne normal-bias offset; it should be the view-space
230
+ // surface normal.
231
+ vec4 worldPosition = modelMatrix * vec4(pos_OS, 1.0);
232
+ vec3 transformedNormal = vImpNormal;
233
+
234
+ #include <logdepthbuf_vertex>
235
+ #include <clipping_planes_vertex>
236
+ #include <shadowmap_vertex>
237
+ #include <fog_vertex>
238
+ }
239
+ `;
240
+ const shader_fg = `
241
+ #define STANDARD
242
+
243
+ uniform vec3 diffuse;
244
+ uniform vec3 emissive;
245
+ uniform float roughness;
246
+ uniform float metalness;
247
+ uniform float opacity;
248
+
249
+ varying vec3 vViewPosition;
250
+ varying vec2 vImpUv;
251
+
252
+ varying vec3 vCardPos_OS;
253
+ flat varying vec3 vCardNormal_OS;
254
+
255
+ flat varying vec2 vGridFloor;
256
+ flat varying vec4 vWeights;
257
+ flat varying vec3 vImpTangent;
258
+ flat varying vec3 vImpBinormal;
259
+ flat varying vec3 vImpNormal;
260
+ flat varying vec3 vRay00;
261
+ flat varying vec3 vRay10;
262
+ flat varying vec3 vRay01;
263
+ flat varying vec3 vRay11;
264
+ flat varying vec4 vFrameXform00;
265
+ flat varying vec4 vFrameXform10;
266
+ flat varying vec4 vFrameXform01;
267
+ flat varying vec4 vFrameXform11;
268
+
269
+ uniform sampler2D tBase;
270
+ uniform sampler2D tGeometry;
271
+ uniform sampler2D tMaterial;
272
+ uniform float uFrames;
273
+ uniform float uRadius;
274
+ uniform float uDepthScale;
275
+
276
+ // Needed in the fragment by the light-direction parallax block below
277
+ // (it has to compute its own octahedral grid lookup for the caster's
278
+ // sampling direction, not the camera's).
279
+ uniform vec3 uOffset;
280
+ uniform bool uIsFullSphere;
281
+
282
+ // three.js auto-injects normalMatrix in the VERTEX prefix only; in the
283
+ // fragment we have to declare it ourselves. WebGLRenderer.js still
284
+ // binds it from object.normalMatrix when the program exposes the
285
+ // location, so this is enough to transform our atlas-reconstructed
286
+ // object-space normal into view space.
287
+ uniform mat3 normalMatrix;
288
+
289
+ // Same story for modelMatrix. We need it to put the parallax surface
290
+ // into world space for the shadow-coord recomputation.
291
+ uniform mat4 modelMatrix;
292
+
293
+ #include <common>
294
+ #include <packing>
295
+ #include <dithering_pars_fragment>
296
+ #include <color_pars_fragment>
297
+ #include <bsdfs>
298
+ #include <cube_uv_reflection_fragment>
299
+ #include <envmap_common_pars_fragment>
300
+ #include <envmap_physical_pars_fragment>
301
+ #include <fog_pars_fragment>
302
+ #include <lights_pars_begin>
303
+ #include <lights_physical_pars_fragment>
304
+ #include <shadowmap_pars_fragment>
305
+ #include <logdepthbuf_pars_fragment>
306
+ #include <clipping_planes_pars_fragment>
307
+
308
+ // directionalShadowMatrix[] is declared in <shadowmap_pars_VERTEX> but
309
+ // not in the fragment-side pars chunk; declaring it here lets
310
+ // three.js's WebGLLights bind it (it's part of UniformsLib.lights
311
+ // which got merged into our material's uniforms via lights: true).
312
+ #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
313
+ uniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];
314
+ #endif
315
+
316
+ // Redirect the chunk-side reference to vDirectionalShadowCoord[i]
317
+ // into our per-fragment customShadowCoord array. The varying with the
318
+ // original name still exists (declared above by shadowmap_pars_fragment)
319
+ // and the vertex shader still writes to it — we just don't read it.
320
+ // We have to put the #define AFTER the pars include so the chunk's
321
+ // own "varying vec4 vDirectionalShadowCoord[...]" declaration isn't
322
+ // affected by the macro.
323
+ #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
324
+ #define vDirectionalShadowCoord customShadowCoord
325
+ #endif
326
+
327
+ // ---- octahedral helpers duplicated from the vertex shader ----
328
+ // We need them in the fragment too because the shadow-coord block
329
+ // below recomputes the caster's parallax surface (with light-direction
330
+ // octahedral lookup) per-fragment. The vertex flat-varyings are for
331
+ // the CAMERA direction; here we need the same math evaluated for the
332
+ // LIGHT direction.
333
+
334
+ vec2 VecToSphereOct(vec3 pivotToCamera)
335
+ {
336
+ vec3 octant = sign(pivotToCamera);
337
+ float sum = dot(pivotToCamera, octant);
338
+ vec3 octahedron = pivotToCamera / sum;
339
+ if (octahedron.y < 0.0) {
340
+ vec3 absolute = abs(octahedron);
341
+ octahedron.xz = octant.xz * vec2(1.0 - absolute.z, 1.0 - absolute.x);
342
+ }
343
+ return octahedron.xz;
344
+ }
345
+
346
+ vec2 VecToHemiSphereOct(vec3 v)
347
+ {
348
+ v.y = max(v.y, 0.001);
349
+ v = normalize(v);
350
+ vec3 octant = sign(v);
351
+ float sum = dot(v, octant);
352
+ vec3 octahedron = v / sum;
353
+ return vec2(
354
+ octahedron.x + octahedron.z,
355
+ octahedron.z - octahedron.x
356
+ );
357
+ }
358
+
359
+ vec2 VectorToGrid(vec3 v)
360
+ {
361
+ return uIsFullSphere ? VecToSphereOct(v) : VecToHemiSphereOct(v);
362
+ }
363
+
364
+ vec3 OctaSphereDec(vec2 coord)
365
+ {
366
+ coord = (coord - 0.5) * 2.0;
367
+ vec3 p = vec3(coord.x, 0.0, coord.y);
368
+ vec2 a = abs(p.xz);
369
+ p.y = 1.0 - a.x - a.y;
370
+ if (p.y < 0.0) {
371
+ p.xz = sign(p.xz) * vec2(1.0 - a.y, 1.0 - a.x);
372
+ }
373
+ return p;
374
+ }
375
+
376
+ vec3 OctaHemiSphereDec(vec2 coord)
377
+ {
378
+ vec3 p = vec3(coord.x - coord.y, 0.0, -1.0 + coord.x + coord.y);
379
+ vec2 a = abs(p.xz);
380
+ p.y = 1.0 - a.x - a.y;
381
+ return p;
382
+ }
383
+
384
+ vec3 GridToVector(vec2 coord)
385
+ {
386
+ return uIsFullSphere ? OctaSphereDec(coord) : OctaHemiSphereDec(coord);
387
+ }
388
+
389
+ vec3 FrameToRay(vec2 frame, vec2 framesMinusOne)
390
+ {
391
+ vec2 f = clamp(frame / framesMinusOne, 0.0, 1.0);
392
+ return normalize(GridToVector(f));
393
+ }
394
+
395
+ vec4 ComputeFrameXform(vec3 D_frame, vec3 tangent_OS, vec3 binormal_OS)
396
+ {
397
+ vec3 D = abs(D_frame.y) > 0.99999
398
+ ? normalize(D_frame + vec3(0.0, 0.0, 0.0001))
399
+ : D_frame;
400
+ vec3 bake_right = normalize(cross(vec3(0.0, 1.0, 0.0), D));
401
+ vec3 bake_up = cross(D, bake_right);
402
+ return vec4(
403
+ dot(tangent_OS, bake_right),
404
+ dot(binormal_OS, bake_right),
405
+ dot(tangent_OS, bake_up),
406
+ dot(binormal_OS, bake_up)
407
+ );
408
+ }
409
+
410
+ vec4 BilinearWeights(vec2 frac_uv)
411
+ {
412
+ vec2 omuv = vec2(1.0) - frac_uv;
413
+ return vec4(
414
+ omuv.x * omuv.y,
415
+ frac_uv.x * omuv.y,
416
+ omuv.x * frac_uv.y,
417
+ frac_uv.x * frac_uv.y
418
+ );
419
+ }
420
+
421
+ vec2 apply_frame_xform(vec2 card_uv, vec4 xform)
422
+ {
423
+ vec2 c = card_uv - 0.5;
424
+ return vec2(
425
+ xform.x * c.x + xform.y * c.y,
426
+ xform.z * c.x + xform.w * c.y
427
+ ) + 0.5;
428
+ }
429
+
430
+ // Sample one corner's atlas tile after rotating the card UV into that
431
+ // frame's bake-camera basis. Returns the diffuse and geometry+ORM
432
+ // texels along with the per-frame atlas UV (so the caller can do
433
+ // parallax in two passes — first depth, then a shifted re-sample).
434
+ void corner_atlas_uv(
435
+ vec2 card_uv, vec2 gridFloor, vec2 corner_offset,
436
+ vec4 xform, out vec2 atlas_uv
437
+ ) {
438
+ vec2 frame_size = vec2(1.0 / uFrames);
439
+ vec2 uv = clamp(apply_frame_xform(card_uv, xform), 0.0, 1.0);
440
+ atlas_uv = (gridFloor + corner_offset + uv) * frame_size;
441
+ }
442
+
443
+ // Decode the atlas-encoded normal (stored in bake-camera view space)
444
+ // back to OBJECT-LOCAL space. Inverse of the (right, up, D_frame)
445
+ // basis the baker used. See BakeShaderStandard.js for the encode side.
446
+ vec3 decode_normal_OS(vec3 encoded, vec3 D_frame_OS)
447
+ {
448
+ vec3 n_bv = encoded * 2.0 - 1.0;
449
+ vec3 D = abs(D_frame_OS.y) > 0.99999
450
+ ? normalize(D_frame_OS + vec3(0.0, 0.0, 0.0001))
451
+ : D_frame_OS;
452
+ vec3 bake_right = normalize(cross(vec3(0.0, 1.0, 0.0), D));
453
+ vec3 bake_up = cross(D, bake_right);
454
+ return bake_right * n_bv.x + bake_up * n_bv.y + D * n_bv.z;
455
+ }
456
+
457
+ void main() {
458
+
459
+ #include <clipping_planes_fragment>
460
+
461
+ // ---- pass 1: blended depth (for parallax shift) ----
462
+ vec2 base_uv = vImpUv;
463
+
464
+ vec2 atlas_uv00, atlas_uv10, atlas_uv01, atlas_uv11;
465
+ corner_atlas_uv(base_uv, vGridFloor, vec2(0.0, 0.0), vFrameXform00, atlas_uv00);
466
+ corner_atlas_uv(base_uv, vGridFloor, vec2(1.0, 0.0), vFrameXform10, atlas_uv10);
467
+ corner_atlas_uv(base_uv, vGridFloor, vec2(0.0, 1.0), vFrameXform01, atlas_uv01);
468
+ corner_atlas_uv(base_uv, vGridFloor, vec2(1.0, 1.0), vFrameXform11, atlas_uv11);
469
+
470
+ float d00 = texture2D(tGeometry, atlas_uv00).a;
471
+ float d10 = texture2D(tGeometry, atlas_uv10).a;
472
+ float d01 = texture2D(tGeometry, atlas_uv01).a;
473
+ float d11 = texture2D(tGeometry, atlas_uv11).a;
474
+ float blended_depth =
475
+ d00 * vWeights.x + d10 * vWeights.y +
476
+ d01 * vWeights.z + d11 * vWeights.w;
477
+
478
+ // Card-tangent-space view direction; same trick as V0 — the card
479
+ // is perpendicular to the projected ray (= vImpNormal), so projecting
480
+ // -vViewPosition onto (vImpTangent, vImpBinormal, vImpNormal) gives the
481
+ // view direction in the card's frame.
482
+ vec3 view_dir_view = normalize(vViewPosition);
483
+ vec3 view_dir = vec3(
484
+ dot(vImpTangent, view_dir_view),
485
+ dot(vImpBinormal, view_dir_view),
486
+ dot(vImpNormal, view_dir_view)
487
+ );
488
+
489
+ base_uv += (view_dir.xy / view_dir.z) * (blended_depth - 0.5) * uDepthScale;
490
+ base_uv = clamp(base_uv, 0.0, 1.0);
491
+
492
+ // ---- pass 2: full atlas sample at parallax-shifted UV ----
493
+ corner_atlas_uv(base_uv, vGridFloor, vec2(0.0, 0.0), vFrameXform00, atlas_uv00);
494
+ corner_atlas_uv(base_uv, vGridFloor, vec2(1.0, 0.0), vFrameXform10, atlas_uv10);
495
+ corner_atlas_uv(base_uv, vGridFloor, vec2(0.0, 1.0), vFrameXform01, atlas_uv01);
496
+ corner_atlas_uv(base_uv, vGridFloor, vec2(1.0, 1.0), vFrameXform11, atlas_uv11);
497
+
498
+ vec4 b00 = texture2D(tBase, atlas_uv00);
499
+ vec4 b10 = texture2D(tBase, atlas_uv10);
500
+ vec4 b01 = texture2D(tBase, atlas_uv01);
501
+ vec4 b11 = texture2D(tBase, atlas_uv11);
502
+ vec4 albedo =
503
+ b00 * vWeights.x + b10 * vWeights.y +
504
+ b01 * vWeights.z + b11 * vWeights.w;
505
+
506
+ if (albedo.a <= 0.5) {
507
+ discard;
508
+ }
509
+
510
+ // sRGB -> linear. The GLTFLoader marks color textures as
511
+ // sRGBEncoding, but BakeShaderStandard is a RawShaderMaterial and
512
+ // sampled them with a plain texture() call (no auto-conversion
513
+ // through <map_fragment>), so the bake atlas holds the raw sRGB
514
+ // bytes labelled LinearEncoding. The truth mesh's
515
+ // MeshStandardMaterial decodes its map in the shader before
516
+ // running PBR; we have to do the same here or the lighting math
517
+ // operates on gamma-compressed values and the result looks
518
+ // washed-out / too bright in the mid-tones relative to the
519
+ // truth. (sRGBToLinear is provided by three.js's fragment prefix
520
+ // via <encodings_pars_fragment>, which is auto-included.)
521
+ albedo.rgb = sRGBToLinear(vec4(albedo.rgb, 1.0)).rgb;
522
+
523
+ vec4 g00 = texture2D(tGeometry, atlas_uv00);
524
+ vec4 g10 = texture2D(tGeometry, atlas_uv10);
525
+ vec4 g01 = texture2D(tGeometry, atlas_uv01);
526
+ vec4 g11 = texture2D(tGeometry, atlas_uv11);
527
+
528
+ // Decode each corner's normal in its own bake basis, then blend.
529
+ // Blending the encoded RGB directly would mix incompatible bases
530
+ // (a normal pointing "right" in frame 00 is unrelated to the
531
+ // same RGB in frame 11).
532
+ vec3 n_OS_00 = decode_normal_OS(g00.rgb, vRay00);
533
+ vec3 n_OS_10 = decode_normal_OS(g10.rgb, vRay10);
534
+ vec3 n_OS_01 = decode_normal_OS(g01.rgb, vRay01);
535
+ vec3 n_OS_11 = decode_normal_OS(g11.rgb, vRay11);
536
+ vec3 normal_OS = normalize(
537
+ n_OS_00 * vWeights.x + n_OS_10 * vWeights.y +
538
+ n_OS_01 * vWeights.z + n_OS_11 * vWeights.w
539
+ );
540
+
541
+ vec4 m00 = texture2D(tMaterial, atlas_uv00);
542
+ vec4 m10 = texture2D(tMaterial, atlas_uv10);
543
+ vec4 m01 = texture2D(tMaterial, atlas_uv01);
544
+ vec4 m11 = texture2D(tMaterial, atlas_uv11);
545
+ vec4 orm =
546
+ m00 * vWeights.x + m10 * vWeights.y +
547
+ m01 * vWeights.z + m11 * vWeights.w;
548
+
549
+ // ---- parallax surface in world space ----
550
+ // Use the post-parallax depth (we just sampled g00..g11 at the
551
+ // shifted UV — their alpha is the depth at that surface point).
552
+ // Same convention as in the bake/depth shader: depth=1 -> +radius
553
+ // along the card normal, 0.5 -> on the card plane, 0 -> -radius.
554
+ float surface_depth_blended =
555
+ g00.a * vWeights.x + g10.a * vWeights.y +
556
+ g01.a * vWeights.z + g11.a * vWeights.w;
557
+ float height_OS = (surface_depth_blended - 0.5) * 2.0 * uRadius;
558
+ vec3 surface_OS_pos = vCardPos_OS + height_OS * vCardNormal_OS;
559
+ vec4 worldPosition_surface = modelMatrix * vec4(surface_OS_pos, 1.0);
560
+
561
+ // ---- standard three.js lit pipeline ----
562
+ // The chunks below expect these names to already be defined in
563
+ // scope: diffuseColor (vec4), reflectedLight, totalEmissiveRadiance,
564
+ // roughnessFactor, metalnessFactor, normal, geometryNormal.
565
+ vec4 diffuseColor = vec4(albedo.rgb * diffuse, 1.0);
566
+ ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
567
+
568
+ float occlusionFactor = orm.r;
569
+ float roughnessFactor = roughness * orm.g;
570
+ float metalnessFactor = metalness * orm.b;
571
+
572
+ vec3 totalEmissiveRadiance = emissive;
573
+
574
+ vec3 normal = normalize(normalMatrix * normal_OS);
575
+ vec3 geometryNormal = normal;
576
+
577
+ // Recompute the directional-light shadow coord per-fragment.
578
+ //
579
+ // The naive approach — feed worldPosition_surface (which uses
580
+ // CAMERA-direction parallax) straight to directionalShadowMatrix —
581
+ // doesn't work because the CASTER's gl_FragDepth uses LIGHT-
582
+ // direction parallax. Single-step parallax along two different
583
+ // directions converges to two different surface points on a
584
+ // sloped height-field, so the receiver and caster disagree on
585
+ // "where this pixel actually is in world space" and the
586
+ // comparison creates self-shadow along the boundary.
587
+ //
588
+ // The fix is to do a SECOND parallax inside the lit shader that
589
+ // mirrors the caster: light-perpendicular caster card geometry,
590
+ // light-direction octahedral grid, and a sample at the caster's
591
+ // UV. The world-space surface that comes out matches what
592
+ // ImpostorShaderDepthV0 wrote at the same light_xy, so the
593
+ // receiver/caster depths line up.
594
+ //
595
+ // (We keep the camera-direction parallax surface for the rest of
596
+ // the lit pipeline — it's what the eye actually sees through this
597
+ // card pixel.)
598
+ //
599
+ // The vDirectionalShadowCoord macro defined above redirects the
600
+ // chunk's reference into this array.
601
+ #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
602
+ vec4 customShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];
603
+ {
604
+ vec3 shadowWorldNormal_lit = normalize( mat3( modelMatrix ) * normal_OS );
605
+
606
+ for ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {
607
+
608
+ // ---- step 1: light direction in OBJECT space ----
609
+ // directionalLights[i].direction is "to-light" in VIEW
610
+ // space; bring it back to world then into OS. modelMatrix
611
+ // is uniform-scale + rotation for our impostor entities,
612
+ // so a plain inverseTransformDirection works.
613
+ vec3 light_dir_view = normalize( directionalLights[ i ].direction );
614
+ vec3 light_dir_world = inverseTransformDirection( light_dir_view, viewMatrix );
615
+ vec3 light_dir_OS = inverseTransformDirection( light_dir_world, modelMatrix );
616
+
617
+ // ---- step 2: caster card basis (perpendicular to light) ----
618
+ vec3 up_OS_L = abs( light_dir_OS.y ) > 0.999
619
+ ? vec3( 0.0, 0.0, -1.0 )
620
+ : vec3( 0.0, 1.0, 0.0 );
621
+ vec3 tan_OS_L = normalize( cross( up_OS_L, light_dir_OS ) );
622
+ vec3 bin_OS_L = cross( light_dir_OS, tan_OS_L );
623
+
624
+ // ---- step 3: project the visible's parallax surface
625
+ // onto the caster card plane to find the
626
+ // caster-UV at this fragment ----
627
+ vec3 surf_OS = surface_OS_pos;
628
+ vec3 from_center = surf_OS - uOffset;
629
+ float u_caster = dot( from_center, tan_OS_L );
630
+ float v_caster = dot( from_center, bin_OS_L );
631
+ vec2 caster_card_uv = vec2( u_caster, v_caster ) / ( 2.0 * uRadius ) + 0.5;
632
+ caster_card_uv = clamp( caster_card_uv, 0.0, 1.0 );
633
+
634
+ // ---- step 4: atlas lookup for the light direction ----
635
+ vec2 fMinusOne_L = vec2( uFrames - 1.0 );
636
+ vec2 oct_uv_L = clamp(
637
+ VectorToGrid( light_dir_OS ) * 0.5 + 0.5,
638
+ 0.0, 1.0
639
+ );
640
+ vec2 grid_L = oct_uv_L * fMinusOne_L;
641
+ vec2 gridFloor_L = min( floor( grid_L ), fMinusOne_L - 1.0 );
642
+ vec4 weights_L = BilinearWeights( grid_L - gridFloor_L );
643
+
644
+ vec3 ray00_L = FrameToRay( gridFloor_L + vec2( 0.0, 0.0 ), fMinusOne_L );
645
+ vec3 ray10_L = FrameToRay( gridFloor_L + vec2( 1.0, 0.0 ), fMinusOne_L );
646
+ vec3 ray01_L = FrameToRay( gridFloor_L + vec2( 0.0, 1.0 ), fMinusOne_L );
647
+ vec3 ray11_L = FrameToRay( gridFloor_L + vec2( 1.0, 1.0 ), fMinusOne_L );
648
+
649
+ vec4 xform00_L = ComputeFrameXform( ray00_L, tan_OS_L, bin_OS_L );
650
+ vec4 xform10_L = ComputeFrameXform( ray10_L, tan_OS_L, bin_OS_L );
651
+ vec4 xform01_L = ComputeFrameXform( ray01_L, tan_OS_L, bin_OS_L );
652
+ vec4 xform11_L = ComputeFrameXform( ray11_L, tan_OS_L, bin_OS_L );
653
+
654
+ // ---- step 5: sample the depth atlas at the caster UV.
655
+ // Caster card is perpendicular to light, so
656
+ // its view_dir.z == 1 and parallax shift is
657
+ // zero — same as in ImpostorShaderDepthV0. ----
658
+ vec2 fs_L = vec2( 1.0 / uFrames );
659
+ vec2 uv00_L = clamp( apply_frame_xform( caster_card_uv, xform00_L ), 0.0, 1.0 );
660
+ vec2 uv10_L = clamp( apply_frame_xform( caster_card_uv, xform10_L ), 0.0, 1.0 );
661
+ vec2 uv01_L = clamp( apply_frame_xform( caster_card_uv, xform01_L ), 0.0, 1.0 );
662
+ vec2 uv11_L = clamp( apply_frame_xform( caster_card_uv, xform11_L ), 0.0, 1.0 );
663
+
664
+ float d00_L = texture2D( tGeometry, ( gridFloor_L + vec2( 0.0, 0.0 ) + uv00_L ) * fs_L ).a;
665
+ float d10_L = texture2D( tGeometry, ( gridFloor_L + vec2( 1.0, 0.0 ) + uv10_L ) * fs_L ).a;
666
+ float d01_L = texture2D( tGeometry, ( gridFloor_L + vec2( 0.0, 1.0 ) + uv01_L ) * fs_L ).a;
667
+ float d11_L = texture2D( tGeometry, ( gridFloor_L + vec2( 1.0, 1.0 ) + uv11_L ) * fs_L ).a;
668
+ float depth_L =
669
+ d00_L * weights_L.x + d10_L * weights_L.y +
670
+ d01_L * weights_L.z + d11_L * weights_L.w;
671
+
672
+ // ---- step 6: build the surface position the CASTER
673
+ // would have written at this light_xy, then
674
+ // project through directionalShadowMatrix. ----
675
+ float height_OS_L = ( depth_L - 0.5 ) * 2.0 * uRadius;
676
+ vec3 caster_card_pos_OS =
677
+ uOffset + u_caster * tan_OS_L + v_caster * bin_OS_L;
678
+ vec3 surface_OS_L = caster_card_pos_OS + height_OS_L * light_dir_OS;
679
+ vec4 worldPosition_surface_L =
680
+ modelMatrix * vec4( surface_OS_L, 1.0 );
681
+
682
+ vec4 shadowWorldPosition = worldPosition_surface_L
683
+ + vec4( shadowWorldNormal_lit * directionalLightShadows[ i ].shadowNormalBias, 0.0 );
684
+ customShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;
685
+ }
686
+ }
687
+ #endif
688
+
689
+ #include <logdepthbuf_fragment>
690
+
691
+ #include <lights_physical_fragment>
692
+ #include <lights_fragment_begin>
693
+ #include <lights_fragment_maps>
694
+ #include <lights_fragment_end>
695
+
696
+ // Apply the atlas's baked AO directly to direct + indirect diffuse;
697
+ // standard <aomap_fragment> only touches indirect, but the impostor
698
+ // bake doesn't separate them.
699
+ reflectedLight.directDiffuse *= occlusionFactor;
700
+ reflectedLight.indirectDiffuse *= occlusionFactor;
701
+ reflectedLight.indirectSpecular *= occlusionFactor;
702
+
703
+ vec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;
704
+ vec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;
705
+ vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;
706
+
707
+ gl_FragColor = vec4(outgoingLight, 1.0);
708
+
709
+ #include <tonemapping_fragment>
710
+ #include <encodings_fragment>
711
+ #include <fog_fragment>
712
+ #include <premultiplied_alpha_fragment>
713
+ #include <dithering_fragment>
714
+ }
715
+ `;
716
+
717
+ /**
718
+ * PBR-lit impostor material — applies three.js's standard (physical)
719
+ * lighting model to a baked impostor atlas. Casts/receives shadows like
720
+ * any MeshStandardMaterial-backed mesh.
721
+ *
722
+ * Same uniform names as ImpostorShaderV0 (tBase / tGeometry / tMaterial /
723
+ * uFrames / uRadius / uOffset / uIsFullSphere / uDepthScale) plus the
724
+ * standard PBR knobs (diffuse / emissive / roughness / metalness / opacity).
725
+ */
726
+ export class ImpostorShaderLitV0 extends ShaderMaterial {
727
+ constructor() {
728
+ const uniforms = UniformsUtils.merge([
729
+ UniformsLib.common,
730
+ UniformsLib.fog,
731
+ UniformsLib.lights,
732
+ {
733
+ roughness: { value: 1.0 },
734
+ metalness: { value: 0.0 },
735
+ emissive: { value: new Color(0x000000) }
736
+ },
737
+ {
738
+ tBase: { value: null },
739
+ tGeometry: { value: null },
740
+ tMaterial: { value: null },
741
+ uFrames: { value: 0 },
742
+ uRadius: { value: 0 },
743
+ uOffset: { value: new Vector3(0, 0, 0) },
744
+ uIsFullSphere: { value: false },
745
+ uDepthScale: { value: 0.5 }
746
+ }
747
+ ]);
748
+
749
+ super({
750
+ uniforms,
751
+ vertexShader: shader_vx,
752
+ fragmentShader: shader_fg,
753
+ lights: true,
754
+ fog: true
755
+ });
756
+ }
757
+ }