@woosh/meep-engine 2.138.12 → 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.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/editor/actions/concrete/PatchTerrainTextureAction.js +0 -2
- package/package.json +1 -1
- package/samples/terrain/from_image_2.js +2 -10
- package/src/engine/asset/loaders/image/ImageDecoderWorker.js +1 -1
- package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +6 -1
- package/src/engine/asset/loaders/image/png/PNGReader.d.ts +1 -99
- package/src/engine/asset/loaders/image/png/PNGReader.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/PNGReader.js +31 -420
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.d.ts +12 -0
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.js +53 -0
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.d.ts +10 -0
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.js +42 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.d.ts +18 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts +17 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.d.ts +17 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts +15 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts +16 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -0
- package/src/engine/asset/loaders/image/png/inflate.d.ts +7 -0
- package/src/engine/asset/loaders/image/png/inflate.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/inflate.js +20 -0
- package/src/engine/ecs/terrain/ecs/Terrain.js +1 -1
- package/src/engine/ecs/terrain/ecs/splat/SplatMapping.d.ts +0 -7
- package/src/engine/ecs/terrain/ecs/splat/SplatMapping.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +36 -19
- package/src/engine/graphics/impostors/octahedral/prototypeBaker.js +202 -8
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.d.ts +10 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.d.ts.map +1 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.js +395 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.d.ts +14 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.d.ts.map +1 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.js +757 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.d.ts +13 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.d.ts.map +1 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.js +380 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.d.ts +6 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.d.ts.map +1 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.js +406 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.js +8 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.d.ts +14 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.d.ts.map +1 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.js +356 -0
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.js +4 -1
|
@@ -22,7 +22,12 @@ import { ShadedGeometryFlags } from "../../ecs/mesh-v2/ShadedGeometryFlags.js";
|
|
|
22
22
|
import { ShadedGeometrySystem } from "../../ecs/mesh-v2/ShadedGeometrySystem.js";
|
|
23
23
|
import { ImpostorBaker } from "./ImpostorBaker.js";
|
|
24
24
|
import { ImpostorCaptureType } from "./ImpostorCaptureType.js";
|
|
25
|
+
import { ImpostorShaderDepthV0 } from "./shader/ImpostorShaderDepthV0.js";
|
|
26
|
+
import { ImpostorShaderLitV0 } from "./shader/ImpostorShaderLitV0.js";
|
|
27
|
+
import { ImpostorShaderNormalsV0 } from "./shader/ImpostorShaderNormalsV0.js";
|
|
28
|
+
import { ImpostorShaderPerPixelV0 } from "./shader/ImpostorShaderPerPixelV0.js";
|
|
25
29
|
import { ImpostorShaderV0 } from "./shader/ImpostorShaderV0.js";
|
|
30
|
+
import { ImpostorShaderViewportDepthV0 } from "./shader/ImpostorShaderViewportDepthV0.js";
|
|
26
31
|
import { ImpostorShaderWireframeV0 } from "./shader/ImpostorShaderWireframeV0.js";
|
|
27
32
|
import { build_geometry_from_cutout_shape } from "./util/build_geometry_from_cutout_shape.js";
|
|
28
33
|
import { load_mesh_for_bake } from "./util/load_mesh_for_bake.js";
|
|
@@ -42,6 +47,13 @@ async function main(engine) {
|
|
|
42
47
|
enableLights: true,
|
|
43
48
|
enableShadows: true,
|
|
44
49
|
cameraAutoClip: true,
|
|
50
|
+
// Default terrain is 20x20 (size 10 * gridScale 2). The impostor
|
|
51
|
+
// lineup below stretches x=10..25, so widen the plane to fit
|
|
52
|
+
// everything plus a small margin on each side.
|
|
53
|
+
terrainSize: new Vector2(18, 12),
|
|
54
|
+
// Recentre the camera focus on the lineup's midpoint so the
|
|
55
|
+
// wider terrain is framed correctly rather than slid off-screen.
|
|
56
|
+
focus: new Vector3(17, 0, 10),
|
|
45
57
|
});
|
|
46
58
|
|
|
47
59
|
|
|
@@ -73,7 +85,7 @@ async function main(engine) {
|
|
|
73
85
|
const id = baker.bake({
|
|
74
86
|
objects,
|
|
75
87
|
frames: 32,
|
|
76
|
-
resolution: 1024,
|
|
88
|
+
resolution: 1024*8,
|
|
77
89
|
type: ImpostorCaptureType.FullSphere
|
|
78
90
|
});
|
|
79
91
|
// console.profileEnd('bake');
|
|
@@ -95,7 +107,12 @@ async function main(engine) {
|
|
|
95
107
|
.add(GUIElement.fromView(ctrl))
|
|
96
108
|
// .build(ecd);
|
|
97
109
|
|
|
98
|
-
// build out preview scene
|
|
110
|
+
// build out preview scene: lay out the five variants side-by-side
|
|
111
|
+
// (V0 / per-pixel V0 / lit / world-normal viz / true mesh) so they
|
|
112
|
+
// share the same scene lighting and the differences are easy to
|
|
113
|
+
// compare. The normals viz sits next to lit because it shows exactly
|
|
114
|
+
// what the lit shader is feeding to the lighting equations — handy
|
|
115
|
+
// for diagnosing wrong-looking shading.
|
|
99
116
|
const t0 = Transform.fromJSON({
|
|
100
117
|
position: { x: 10, y: 0.5, z: 10 },
|
|
101
118
|
scale: 3,
|
|
@@ -104,15 +121,45 @@ async function main(engine) {
|
|
|
104
121
|
|
|
105
122
|
t0.scale.setScalar(3 / (id.sphere_radius * 2));
|
|
106
123
|
|
|
124
|
+
// Slot 0: original V0 + its wireframe overlay.
|
|
107
125
|
const entity_impostor = make_impostor_entity(id, t0);
|
|
108
126
|
entity_impostor.build(ecd);
|
|
109
127
|
|
|
110
128
|
const entity_impostor_wireframe = make_impostor_wireframe(id, t0);
|
|
111
129
|
entity_impostor_wireframe.build(ecd);
|
|
112
130
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
131
|
+
// Slot 1: per-pixel projection variant (visually identical to V0).
|
|
132
|
+
const t_per_pixel = new Transform();
|
|
133
|
+
t_per_pixel.copy(t0);
|
|
134
|
+
t_per_pixel.position._add(3, 0, 0);
|
|
135
|
+
make_impostor_per_pixel_entity(id, t_per_pixel).build(ecd);
|
|
136
|
+
|
|
137
|
+
// Slot 2: lit variant — only one that responds to scene lights and
|
|
138
|
+
// receives shadows from neighbours.
|
|
139
|
+
const t_lit = new Transform();
|
|
140
|
+
t_lit.copy(t0);
|
|
141
|
+
t_lit.position._add(6, 0, 0);
|
|
142
|
+
make_impostor_lit_entity(id, t_lit).build(ecd);
|
|
143
|
+
|
|
144
|
+
// Slot 3: world-space normal viz (debug pair for the lit variant).
|
|
145
|
+
const t_normals = new Transform();
|
|
146
|
+
t_normals.copy(t0);
|
|
147
|
+
t_normals.position._add(9, 0, 0);
|
|
148
|
+
make_impostor_normals_entity(id, t_normals).build(ecd);
|
|
149
|
+
|
|
150
|
+
// Slot 4: viewport-depth viz — shows the parallax surface's
|
|
151
|
+
// gl_FragCoord.z-equivalent as grayscale. Same construction the
|
|
152
|
+
// caster's gl_FragDepth uses; useful for spotting where the lit
|
|
153
|
+
// shader's shadow coord diverges from what the shadow map holds.
|
|
154
|
+
const t_depth = new Transform();
|
|
155
|
+
t_depth.copy(t0);
|
|
156
|
+
t_depth.position._add(12, 0, 0);
|
|
157
|
+
make_impostor_viewport_depth_entity(id, t_depth).build(ecd);
|
|
158
|
+
|
|
159
|
+
// Slot 5: original mesh as ground-truth reference.
|
|
160
|
+
const t_truth = new Transform();
|
|
161
|
+
t_truth.copy(t0);
|
|
162
|
+
t_truth.position._add(15, 0, 0);
|
|
116
163
|
|
|
117
164
|
const sg_mesh = SGMesh.fromURL(path);
|
|
118
165
|
|
|
@@ -122,7 +169,7 @@ async function main(engine) {
|
|
|
122
169
|
const entity_true_mesh = new Entity();
|
|
123
170
|
entity_true_mesh
|
|
124
171
|
.add(sg_mesh)
|
|
125
|
-
.add(
|
|
172
|
+
.add(t_truth)
|
|
126
173
|
.build(ecd);
|
|
127
174
|
|
|
128
175
|
|
|
@@ -153,7 +200,7 @@ function make_spin(entity, speed = 1, axis = Vector3.up) {
|
|
|
153
200
|
|
|
154
201
|
/**
|
|
155
202
|
*
|
|
156
|
-
* @param {{tBase,tGeometry,uFrames,uOffset,uRadius,uIsFullSphere}} uniforms
|
|
203
|
+
* @param {{tBase,tGeometry,uFrames,uOffset,uRadius,uIsFullSphere,tMaterial?}} uniforms
|
|
157
204
|
* @param {ImpostorDescription} id
|
|
158
205
|
*/
|
|
159
206
|
function write_impostor_definition_to_material_uniforms(uniforms, id) {
|
|
@@ -164,6 +211,12 @@ function write_impostor_definition_to_material_uniforms(uniforms, id) {
|
|
|
164
211
|
uniforms.uOffset.value.set(id.offset[0], id.offset[1], id.offset[2]);
|
|
165
212
|
uniforms.uRadius.value = id.sphere_radius;
|
|
166
213
|
uniforms.uIsFullSphere.value = id.capture_type === ImpostorCaptureType.FullSphere;
|
|
214
|
+
|
|
215
|
+
// ORM texture is only consumed by the lit shader; older variants
|
|
216
|
+
// declare the uniform but never read it, so the assignment is harmless.
|
|
217
|
+
if (uniforms.tMaterial !== undefined) {
|
|
218
|
+
uniforms.tMaterial.value = id.rt.texture[2];
|
|
219
|
+
}
|
|
167
220
|
}
|
|
168
221
|
|
|
169
222
|
function make_impostor_wireframe(id, t = new Transform()) {
|
|
@@ -188,6 +241,19 @@ function make_impostor_wireframe(id, t = new Transform()) {
|
|
|
188
241
|
;
|
|
189
242
|
}
|
|
190
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Build a `depth_material` instance for shadow casting that mirrors the
|
|
246
|
+
* card geometry of whichever colour material we're using. Pulling this
|
|
247
|
+
* out keeps the per-variant factories below short.
|
|
248
|
+
*
|
|
249
|
+
* @param {ImpostorDescription} id
|
|
250
|
+
*/
|
|
251
|
+
function make_impostor_depth_material(id) {
|
|
252
|
+
const depth_mat = new ImpostorShaderDepthV0();
|
|
253
|
+
write_impostor_definition_to_material_uniforms(depth_mat.uniforms, id);
|
|
254
|
+
return depth_mat;
|
|
255
|
+
}
|
|
256
|
+
|
|
191
257
|
/**
|
|
192
258
|
*
|
|
193
259
|
* @param {ImpostorDescription} id
|
|
@@ -206,7 +272,104 @@ function make_impostor_entity(id, t = new Transform()) {
|
|
|
206
272
|
|
|
207
273
|
const sg = ShadedGeometry.from(build_geometry_from_cutout_shape(id.cutout), mat);
|
|
208
274
|
|
|
209
|
-
|
|
275
|
+
// V0 is unlit so it has no shadow to receive; it can still CAST a
|
|
276
|
+
// shadow using ImpostorShaderDepthV0, which traces the same parallax
|
|
277
|
+
// surface so the silhouette matches.
|
|
278
|
+
sg.depth_material = make_impostor_depth_material(id);
|
|
279
|
+
sg.clearFlag(ShadedGeometryFlags.ReceiveShadow);
|
|
280
|
+
|
|
281
|
+
return new Entity()
|
|
282
|
+
.add(transform)
|
|
283
|
+
.add(sg)
|
|
284
|
+
;
|
|
285
|
+
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Same as {@link make_impostor_entity} but with the per-pixel projection
|
|
290
|
+
* variant. Visually identical at the same camera distance — the difference
|
|
291
|
+
* is purely where the projection math runs on the GPU.
|
|
292
|
+
*
|
|
293
|
+
* @param {ImpostorDescription} id
|
|
294
|
+
* @param {Transform} t
|
|
295
|
+
* @returns {Entity}
|
|
296
|
+
*/
|
|
297
|
+
function make_impostor_per_pixel_entity(id, t = new Transform()) {
|
|
298
|
+
|
|
299
|
+
const mat = new ImpostorShaderPerPixelV0();
|
|
300
|
+
|
|
301
|
+
write_impostor_definition_to_material_uniforms(mat.uniforms, id);
|
|
302
|
+
|
|
303
|
+
const transform = new Transform();
|
|
304
|
+
transform.copy(t);
|
|
305
|
+
|
|
306
|
+
const sg = ShadedGeometry.from(build_geometry_from_cutout_shape(id.cutout), mat);
|
|
307
|
+
|
|
308
|
+
sg.depth_material = make_impostor_depth_material(id);
|
|
309
|
+
sg.clearFlag(ShadedGeometryFlags.ReceiveShadow);
|
|
310
|
+
|
|
311
|
+
return new Entity()
|
|
312
|
+
.add(transform)
|
|
313
|
+
.add(sg)
|
|
314
|
+
;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Debug viz: shows the world-space surface normal at each impostor pixel
|
|
319
|
+
* as RGB. Same parallax + atlas-decode math as the lit shader, so any
|
|
320
|
+
* weirdness here is also producing the lit shader's lighting.
|
|
321
|
+
*
|
|
322
|
+
* @param {ImpostorDescription} id
|
|
323
|
+
* @param {Transform} t
|
|
324
|
+
* @returns {Entity}
|
|
325
|
+
*/
|
|
326
|
+
function make_impostor_normals_entity(id, t = new Transform()) {
|
|
327
|
+
|
|
328
|
+
const mat = new ImpostorShaderNormalsV0();
|
|
329
|
+
|
|
330
|
+
write_impostor_definition_to_material_uniforms(mat.uniforms, id);
|
|
331
|
+
|
|
332
|
+
const transform = new Transform();
|
|
333
|
+
transform.copy(t);
|
|
334
|
+
|
|
335
|
+
const sg = ShadedGeometry.from(build_geometry_from_cutout_shape(id.cutout), mat);
|
|
336
|
+
|
|
337
|
+
// Viz pass: don't participate in shadow casting/receiving — otherwise
|
|
338
|
+
// it'd influence its lit neighbour's shadow and confuse the comparison.
|
|
339
|
+
sg.depth_material = make_impostor_depth_material(id);
|
|
340
|
+
sg.clearFlag(ShadedGeometryFlags.CastShadow);
|
|
341
|
+
sg.clearFlag(ShadedGeometryFlags.ReceiveShadow);
|
|
342
|
+
|
|
343
|
+
return new Entity()
|
|
344
|
+
.add(transform)
|
|
345
|
+
.add(sg)
|
|
346
|
+
;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Debug viz: shows the parallax-corrected surface's VIEWPORT depth as
|
|
351
|
+
* grayscale (closer = brighter). Same surface-position math as the lit
|
|
352
|
+
* shader's shadow coord and the depth material's gl_FragDepth write, so
|
|
353
|
+
* if shadows look wrong this slot is the most direct check.
|
|
354
|
+
*
|
|
355
|
+
* @param {ImpostorDescription} id
|
|
356
|
+
* @param {Transform} t
|
|
357
|
+
* @returns {Entity}
|
|
358
|
+
*/
|
|
359
|
+
function make_impostor_viewport_depth_entity(id, t = new Transform()) {
|
|
360
|
+
|
|
361
|
+
const mat = new ImpostorShaderViewportDepthV0();
|
|
362
|
+
|
|
363
|
+
write_impostor_definition_to_material_uniforms(mat.uniforms, id);
|
|
364
|
+
|
|
365
|
+
const transform = new Transform();
|
|
366
|
+
transform.copy(t);
|
|
367
|
+
|
|
368
|
+
const sg = ShadedGeometry.from(build_geometry_from_cutout_shape(id.cutout), mat);
|
|
369
|
+
|
|
370
|
+
// Same as normals viz — opt out of shadow casting/receiving so it
|
|
371
|
+
// doesn't influence what its neighbours see.
|
|
372
|
+
sg.depth_material = make_impostor_depth_material(id);
|
|
210
373
|
sg.clearFlag(ShadedGeometryFlags.CastShadow);
|
|
211
374
|
sg.clearFlag(ShadedGeometryFlags.ReceiveShadow);
|
|
212
375
|
|
|
@@ -214,7 +377,38 @@ function make_impostor_entity(id, t = new Transform()) {
|
|
|
214
377
|
.add(transform)
|
|
215
378
|
.add(sg)
|
|
216
379
|
;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Lit impostor — applies three.js's STANDARD lighting model and receives
|
|
384
|
+
* shadow maps in addition to casting them.
|
|
385
|
+
*
|
|
386
|
+
* @param {ImpostorDescription} id
|
|
387
|
+
* @param {Transform} t
|
|
388
|
+
* @returns {Entity}
|
|
389
|
+
*/
|
|
390
|
+
function make_impostor_lit_entity(id, t = new Transform()) {
|
|
391
|
+
|
|
392
|
+
const mat = new ImpostorShaderLitV0();
|
|
217
393
|
|
|
394
|
+
write_impostor_definition_to_material_uniforms(mat.uniforms, id);
|
|
395
|
+
|
|
396
|
+
const transform = new Transform();
|
|
397
|
+
transform.copy(t);
|
|
398
|
+
|
|
399
|
+
const sg = ShadedGeometry.from(build_geometry_from_cutout_shape(id.cutout), mat);
|
|
400
|
+
|
|
401
|
+
sg.depth_material = make_impostor_depth_material(id);
|
|
402
|
+
// CastShadow + ReceiveShadow on by default. Self-cast works because
|
|
403
|
+
// both sides (caster via gl_FragDepth, receiver via per-fragment
|
|
404
|
+
// customShadowCoord computed from the same atlas-derived parallax
|
|
405
|
+
// surface) agree on where the impostor's surface actually is in
|
|
406
|
+
// world space.
|
|
407
|
+
|
|
408
|
+
return new Entity()
|
|
409
|
+
.add(transform)
|
|
410
|
+
.add(sg)
|
|
411
|
+
;
|
|
218
412
|
}
|
|
219
413
|
|
|
220
414
|
new EngineHarness().initialize({
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Depth-pass companion to ImpostorShaderV0. Use this as
|
|
3
|
+
* ShadedGeometry.depth_material so the impostor casts a shadow whose
|
|
4
|
+
* silhouette matches the visible card.
|
|
5
|
+
*/
|
|
6
|
+
export class ImpostorShaderDepthV0 extends RawShaderMaterial {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
import { RawShaderMaterial } from "three";
|
|
10
|
+
//# sourceMappingURL=ImpostorShaderDepthV0.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImpostorShaderDepthV0.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.js"],"names":[],"mappings":"AA+VA;;;;GAIG;AACH;IACI,cAoCC;CACJ;kCArYM,OAAO"}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GLSL3,
|
|
3
|
+
NoBlending,
|
|
4
|
+
RawShaderMaterial,
|
|
5
|
+
Vector3
|
|
6
|
+
} from "three";
|
|
7
|
+
|
|
8
|
+
// Shadow-pass depth material that matches ImpostorShaderV0's surface so the
|
|
9
|
+
// shadow caster silhouette agrees with what the colour pass draws. We need
|
|
10
|
+
// the full TBN + per-frame xform machinery so that parallax in the fragment
|
|
11
|
+
// reproduces the same UV displacement as V0 — otherwise the alpha discard
|
|
12
|
+
// would cut a different silhouette than the visible card.
|
|
13
|
+
//
|
|
14
|
+
// We DO write gl_FragDepth here. Reason: the caster card orients toward
|
|
15
|
+
// the light (modelViewMatrix in the shadow pass is light_view * model),
|
|
16
|
+
// while the visible card orients toward the camera. The two cards
|
|
17
|
+
// intersect at the bounding-sphere centre plane but diverge — without
|
|
18
|
+
// per-pixel depth the caster's silhouette would be a thin disc at that
|
|
19
|
+
// plane and any visible fragment "below" the centre (in light space)
|
|
20
|
+
// would read as self-shadowed. Projecting the parallaxed surface point
|
|
21
|
+
// through MVP and writing it as gl_FragDepth fixes the silhouette: the
|
|
22
|
+
// shadow map records the actual depth of the impostor surface at each
|
|
23
|
+
// (light_x, light_y), matching what the visible card produces.
|
|
24
|
+
// Cost: this disables early-Z in the shadow pass, but shadow passes are
|
|
25
|
+
// usually fillrate-light and the geometry is just a card.
|
|
26
|
+
//
|
|
27
|
+
// Output is RGBA-packed depth — that's the format three.js's PCFShadowMap
|
|
28
|
+
// reads, and it's what the default MeshDepthMaterial would write here.
|
|
29
|
+
const shader_vx = `
|
|
30
|
+
|
|
31
|
+
in vec2 uv;
|
|
32
|
+
in vec3 position;
|
|
33
|
+
|
|
34
|
+
out vec2 vUv;
|
|
35
|
+
out vec3 vViewPos;
|
|
36
|
+
|
|
37
|
+
// Card position and normal in OBJECT space — the fragment shader uses
|
|
38
|
+
// these to project the parallaxed surface point through MVP for the
|
|
39
|
+
// gl_FragDepth write. Position interpolates between vertices; the
|
|
40
|
+
// normal is the same for every card vertex so we mark it flat.
|
|
41
|
+
out vec3 vCardPos_OS;
|
|
42
|
+
flat out vec3 vCardNormal_OS;
|
|
43
|
+
|
|
44
|
+
flat out vec2 vGridFloor;
|
|
45
|
+
flat out vec4 vWeights;
|
|
46
|
+
|
|
47
|
+
flat out vec3 vTangent;
|
|
48
|
+
flat out vec3 vBinormal;
|
|
49
|
+
flat out vec3 vNormal;
|
|
50
|
+
|
|
51
|
+
flat out vec4 vFrameXform00;
|
|
52
|
+
flat out vec4 vFrameXform10;
|
|
53
|
+
flat out vec4 vFrameXform01;
|
|
54
|
+
flat out vec4 vFrameXform11;
|
|
55
|
+
|
|
56
|
+
uniform mat4 modelViewMatrix;
|
|
57
|
+
uniform mat4 projectionMatrix;
|
|
58
|
+
|
|
59
|
+
uniform vec3 uOffset;
|
|
60
|
+
uniform float uRadius;
|
|
61
|
+
uniform float uFrames;
|
|
62
|
+
uniform bool uIsFullSphere;
|
|
63
|
+
|
|
64
|
+
vec2 VecToSphereOct(vec3 pivotToCamera)
|
|
65
|
+
{
|
|
66
|
+
vec3 octant = sign(pivotToCamera);
|
|
67
|
+
float sum = dot(pivotToCamera, octant);
|
|
68
|
+
vec3 octahedron = pivotToCamera / sum;
|
|
69
|
+
if (octahedron.y < 0.0) {
|
|
70
|
+
vec3 absolute = abs(octahedron);
|
|
71
|
+
octahedron.xz = octant.xz * vec2(1.0 - absolute.z, 1.0 - absolute.x);
|
|
72
|
+
}
|
|
73
|
+
return octahedron.xz;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
vec2 VecToHemiSphereOct(vec3 v)
|
|
77
|
+
{
|
|
78
|
+
v.y = max(v.y, 0.001);
|
|
79
|
+
v = normalize(v);
|
|
80
|
+
vec3 octant = sign(v);
|
|
81
|
+
float sum = dot(v, octant);
|
|
82
|
+
vec3 octahedron = v / sum;
|
|
83
|
+
return vec2(
|
|
84
|
+
octahedron.x + octahedron.z,
|
|
85
|
+
octahedron.z - octahedron.x
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
vec2 VectorToGrid(vec3 v)
|
|
90
|
+
{
|
|
91
|
+
return uIsFullSphere ? VecToSphereOct(v) : VecToHemiSphereOct(v);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
vec3 OctaSphereDec(vec2 coord)
|
|
95
|
+
{
|
|
96
|
+
coord = (coord - 0.5) * 2.0;
|
|
97
|
+
vec3 p = vec3(coord.x, 0.0, coord.y);
|
|
98
|
+
vec2 a = abs(p.xz);
|
|
99
|
+
p.y = 1.0 - a.x - a.y;
|
|
100
|
+
if (p.y < 0.0) {
|
|
101
|
+
p.xz = sign(p.xz) * vec2(1.0 - a.y, 1.0 - a.x);
|
|
102
|
+
}
|
|
103
|
+
return p;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
vec3 OctaHemiSphereDec(vec2 coord)
|
|
107
|
+
{
|
|
108
|
+
vec3 p = vec3(coord.x - coord.y, 0.0, -1.0 + coord.x + coord.y);
|
|
109
|
+
vec2 a = abs(p.xz);
|
|
110
|
+
p.y = 1.0 - a.x - a.y;
|
|
111
|
+
return p;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
vec3 GridToVector(vec2 coord)
|
|
115
|
+
{
|
|
116
|
+
return uIsFullSphere ? OctaSphereDec(coord) : OctaHemiSphereDec(coord);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
vec3 FrameToRay(vec2 frame, vec2 framesMinusOne)
|
|
120
|
+
{
|
|
121
|
+
vec2 f = clamp(frame / framesMinusOne, 0.0, 1.0);
|
|
122
|
+
return normalize(GridToVector(f));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
vec4 ComputeFrameXform(vec3 D_frame, vec3 tangent_OS, vec3 binormal_OS)
|
|
126
|
+
{
|
|
127
|
+
vec3 D = abs(D_frame.y) > 0.99999
|
|
128
|
+
? normalize(D_frame + vec3(0.0, 0.0, 0.0001))
|
|
129
|
+
: D_frame;
|
|
130
|
+
vec3 bake_right = normalize(cross(vec3(0.0, 1.0, 0.0), D));
|
|
131
|
+
vec3 bake_up = cross(D, bake_right);
|
|
132
|
+
return vec4(
|
|
133
|
+
dot(tangent_OS, bake_right),
|
|
134
|
+
dot(binormal_OS, bake_right),
|
|
135
|
+
dot(tangent_OS, bake_up),
|
|
136
|
+
dot(binormal_OS, bake_up)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
vec4 BilinearWeights(vec2 frac_uv)
|
|
141
|
+
{
|
|
142
|
+
vec2 omuv = vec2(1.0) - frac_uv;
|
|
143
|
+
return vec4(
|
|
144
|
+
omuv.x * omuv.y,
|
|
145
|
+
frac_uv.x * omuv.y,
|
|
146
|
+
omuv.x * frac_uv.y,
|
|
147
|
+
frac_uv.x * frac_uv.y
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
void main() {
|
|
152
|
+
vUv = uv;
|
|
153
|
+
|
|
154
|
+
// For shadow casting, modelViewMatrix = light_view * model. The
|
|
155
|
+
// camera in object space is therefore the LIGHT's position in
|
|
156
|
+
// object space, so the impostor card naturally orients to face the
|
|
157
|
+
// light — exactly what we want for a shadow caster.
|
|
158
|
+
vec3 cameraPos_OS = (inverse(modelViewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
|
159
|
+
// Atlas lookup must be relative to the bounding-sphere centre
|
|
160
|
+
// (uOffset), since the bake's D_frame is measured from the centre,
|
|
161
|
+
// not from the object origin. In the shadow pass "camera" is the
|
|
162
|
+
// light, so this is the direction from the centre to the light.
|
|
163
|
+
vec3 pivotToCameraRay = normalize(cameraPos_OS - uOffset);
|
|
164
|
+
|
|
165
|
+
vec2 framesMinusOne = vec2(uFrames - 1.0);
|
|
166
|
+
vec2 octahedral_uv = clamp(VectorToGrid(pivotToCameraRay) * 0.5 + 0.5, 0.0, 1.0);
|
|
167
|
+
vec2 grid = octahedral_uv * framesMinusOne;
|
|
168
|
+
vec2 gridFloor = min(floor(grid), framesMinusOne - 1.0);
|
|
169
|
+
vec4 weights = BilinearWeights(grid - gridFloor);
|
|
170
|
+
|
|
171
|
+
vGridFloor = gridFloor;
|
|
172
|
+
vWeights = weights;
|
|
173
|
+
|
|
174
|
+
vec3 ray00 = FrameToRay(gridFloor + vec2(0.0, 0.0), framesMinusOne);
|
|
175
|
+
vec3 ray10 = FrameToRay(gridFloor + vec2(1.0, 0.0), framesMinusOne);
|
|
176
|
+
vec3 ray01 = FrameToRay(gridFloor + vec2(0.0, 1.0), framesMinusOne);
|
|
177
|
+
vec3 ray11 = FrameToRay(gridFloor + vec2(1.0, 1.0), framesMinusOne);
|
|
178
|
+
vec3 projectedRay = normalize(
|
|
179
|
+
ray00 * weights.x +
|
|
180
|
+
ray10 * weights.y +
|
|
181
|
+
ray01 * weights.z +
|
|
182
|
+
ray11 * weights.w
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
vec3 normal_OS = projectedRay;
|
|
186
|
+
vec3 up_OS = abs(normal_OS.y) > 0.999
|
|
187
|
+
? vec3(0.0, 0.0, -1.0)
|
|
188
|
+
: vec3(0.0, 1.0, 0.0);
|
|
189
|
+
vec3 tangent_OS = normalize(cross(up_OS, normal_OS));
|
|
190
|
+
vec3 binormal_OS = cross(normal_OS, tangent_OS);
|
|
191
|
+
|
|
192
|
+
float card_diameter = uRadius * 2.0;
|
|
193
|
+
vec3 pos_OS = uOffset
|
|
194
|
+
+ position.x * card_diameter * tangent_OS
|
|
195
|
+
+ position.y * card_diameter * binormal_OS;
|
|
196
|
+
|
|
197
|
+
vCardPos_OS = pos_OS;
|
|
198
|
+
vCardNormal_OS = normal_OS;
|
|
199
|
+
|
|
200
|
+
vec4 mvPosition = modelViewMatrix * vec4(pos_OS, 1.0);
|
|
201
|
+
vViewPos = mvPosition.xyz;
|
|
202
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
203
|
+
|
|
204
|
+
mat3 m3 = mat3(modelViewMatrix);
|
|
205
|
+
vTangent = normalize(m3 * tangent_OS);
|
|
206
|
+
vBinormal = normalize(m3 * binormal_OS);
|
|
207
|
+
vNormal = normalize(m3 * normal_OS);
|
|
208
|
+
|
|
209
|
+
vFrameXform00 = ComputeFrameXform(ray00, tangent_OS, binormal_OS);
|
|
210
|
+
vFrameXform10 = ComputeFrameXform(ray10, tangent_OS, binormal_OS);
|
|
211
|
+
vFrameXform01 = ComputeFrameXform(ray01, tangent_OS, binormal_OS);
|
|
212
|
+
vFrameXform11 = ComputeFrameXform(ray11, tangent_OS, binormal_OS);
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
const shader_fg = `
|
|
216
|
+
precision highp float;
|
|
217
|
+
precision highp int;
|
|
218
|
+
|
|
219
|
+
in vec2 vUv;
|
|
220
|
+
in vec3 vViewPos;
|
|
221
|
+
|
|
222
|
+
in vec3 vCardPos_OS;
|
|
223
|
+
flat in vec3 vCardNormal_OS;
|
|
224
|
+
|
|
225
|
+
flat in vec2 vGridFloor;
|
|
226
|
+
flat in vec4 vWeights;
|
|
227
|
+
|
|
228
|
+
flat in vec3 vTangent;
|
|
229
|
+
flat in vec3 vBinormal;
|
|
230
|
+
flat in vec3 vNormal;
|
|
231
|
+
|
|
232
|
+
flat in vec4 vFrameXform00;
|
|
233
|
+
flat in vec4 vFrameXform10;
|
|
234
|
+
flat in vec4 vFrameXform01;
|
|
235
|
+
flat in vec4 vFrameXform11;
|
|
236
|
+
|
|
237
|
+
out vec4 color_out;
|
|
238
|
+
|
|
239
|
+
// Needed in the fragment to re-project the parallax-shifted surface
|
|
240
|
+
// point. three.js's WebGLRenderer sets both unconditionally for any
|
|
241
|
+
// ShaderMaterial-derived material (RawShaderMaterial included).
|
|
242
|
+
uniform mat4 modelViewMatrix;
|
|
243
|
+
uniform mat4 projectionMatrix;
|
|
244
|
+
|
|
245
|
+
uniform sampler2D tBase;
|
|
246
|
+
uniform sampler2D tGeometry;
|
|
247
|
+
uniform float uFrames;
|
|
248
|
+
uniform float uRadius;
|
|
249
|
+
uniform float uDepthScale;
|
|
250
|
+
|
|
251
|
+
// Mirrors three.js's packing.glsl chunk so the shadow map reader (PCF
|
|
252
|
+
// sampler in shadowmap_pars_fragment) can unpack our output.
|
|
253
|
+
const float PackUpscale = 256.0 / 255.0;
|
|
254
|
+
const vec3 PackFactors = vec3(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0);
|
|
255
|
+
const float ShiftRight8 = 1.0 / 256.0;
|
|
256
|
+
|
|
257
|
+
vec4 packDepthToRGBA(const in float v) {
|
|
258
|
+
vec4 r = vec4(fract(v * PackFactors), v);
|
|
259
|
+
r.yzw -= r.xyz * ShiftRight8;
|
|
260
|
+
return r * PackUpscale;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
vec2 apply_frame_xform(vec2 card_uv, vec4 xform)
|
|
264
|
+
{
|
|
265
|
+
vec2 c = card_uv - 0.5;
|
|
266
|
+
return vec2(
|
|
267
|
+
xform.x * c.x + xform.y * c.y,
|
|
268
|
+
xform.z * c.x + xform.w * c.y
|
|
269
|
+
) + 0.5;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
vec4 blend_4_frames(
|
|
273
|
+
sampler2D tex, vec2 card_uv,
|
|
274
|
+
vec2 gridFloor, vec4 w,
|
|
275
|
+
vec4 x00, vec4 x10, vec4 x01, vec4 x11
|
|
276
|
+
) {
|
|
277
|
+
vec2 frame_size = vec2(1.0 / uFrames);
|
|
278
|
+
vec2 uv00 = clamp(apply_frame_xform(card_uv, x00), 0.0, 1.0);
|
|
279
|
+
vec2 uv10 = clamp(apply_frame_xform(card_uv, x10), 0.0, 1.0);
|
|
280
|
+
vec2 uv01 = clamp(apply_frame_xform(card_uv, x01), 0.0, 1.0);
|
|
281
|
+
vec2 uv11 = clamp(apply_frame_xform(card_uv, x11), 0.0, 1.0);
|
|
282
|
+
vec4 s00 = texture(tex, (gridFloor + vec2(0.0, 0.0) + uv00) * frame_size);
|
|
283
|
+
vec4 s10 = texture(tex, (gridFloor + vec2(1.0, 0.0) + uv10) * frame_size);
|
|
284
|
+
vec4 s01 = texture(tex, (gridFloor + vec2(0.0, 1.0) + uv01) * frame_size);
|
|
285
|
+
vec4 s11 = texture(tex, (gridFloor + vec2(1.0, 1.0) + uv11) * frame_size);
|
|
286
|
+
return s00 * w.x + s10 * w.y + s01 * w.z + s11 * w.w;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
void main() {
|
|
290
|
+
// Same parallax shift as V0 so the silhouette we test matches the
|
|
291
|
+
// visible silhouette — otherwise the shadow would clip a slightly
|
|
292
|
+
// different cutout than the colour pass.
|
|
293
|
+
vec3 view_dir_view = normalize(-vViewPos);
|
|
294
|
+
vec3 view_dir = vec3(
|
|
295
|
+
dot(vTangent, view_dir_view),
|
|
296
|
+
dot(vBinormal, view_dir_view),
|
|
297
|
+
dot(vNormal, view_dir_view)
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
vec2 base_uv = vUv;
|
|
301
|
+
float depth = blend_4_frames(
|
|
302
|
+
tGeometry, base_uv,
|
|
303
|
+
vGridFloor, vWeights,
|
|
304
|
+
vFrameXform00, vFrameXform10, vFrameXform01, vFrameXform11
|
|
305
|
+
).a;
|
|
306
|
+
base_uv += (view_dir.xy / view_dir.z) * (depth - 0.5) * uDepthScale;
|
|
307
|
+
base_uv = clamp(base_uv, 0.0, 1.0);
|
|
308
|
+
|
|
309
|
+
vec4 texel_color = blend_4_frames(
|
|
310
|
+
tBase, base_uv,
|
|
311
|
+
vGridFloor, vWeights,
|
|
312
|
+
vFrameXform00, vFrameXform10, vFrameXform01, vFrameXform11
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (texel_color.a <= 0.5) {
|
|
316
|
+
discard;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Re-sample depth at the parallax-shifted UV. We want the surface
|
|
320
|
+
// depth at the post-parallax location, not the un-shifted one.
|
|
321
|
+
// For a single-step parallax these usually round-trip to a
|
|
322
|
+
// similar value, but the second sample keeps us self-consistent
|
|
323
|
+
// when the parallax shift is non-trivial.
|
|
324
|
+
float surface_depth = blend_4_frames(
|
|
325
|
+
tGeometry, base_uv,
|
|
326
|
+
vGridFloor, vWeights,
|
|
327
|
+
vFrameXform00, vFrameXform10, vFrameXform01, vFrameXform11
|
|
328
|
+
).a;
|
|
329
|
+
|
|
330
|
+
// Convention from BakeShaderStandard: depth=1 at the bounding
|
|
331
|
+
// sphere's near plane (front, +radius along bake camera), 0.5 at
|
|
332
|
+
// the centre (= the card plane), 0 at the far plane (-radius).
|
|
333
|
+
// So (depth - 0.5) is the signed height along the card normal in
|
|
334
|
+
// half-bounding-sphere units; multiply by 2*radius to get
|
|
335
|
+
// object-space height.
|
|
336
|
+
float height_OS = (surface_depth - 0.5) * 2.0 * uRadius;
|
|
337
|
+
|
|
338
|
+
vec3 surface_OS = vCardPos_OS + height_OS * vCardNormal_OS;
|
|
339
|
+
vec4 surface_clip = projectionMatrix * modelViewMatrix * vec4(surface_OS, 1.0);
|
|
340
|
+
float surface_ndc_z = surface_clip.z / surface_clip.w;
|
|
341
|
+
|
|
342
|
+
// gl_FragCoord.z / gl_FragDepth are in [0, 1] (assuming default
|
|
343
|
+
// depth range). Map NDC z (-1..+1) to that range, then clamp so
|
|
344
|
+
// out-of-frustum surface points don't write garbage into the
|
|
345
|
+
// shadow map.
|
|
346
|
+
gl_FragDepth = clamp(surface_ndc_z * 0.5 + 0.5, 0.0, 1.0);
|
|
347
|
+
|
|
348
|
+
color_out = packDepthToRGBA(gl_FragDepth);
|
|
349
|
+
}
|
|
350
|
+
`;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Depth-pass companion to ImpostorShaderV0. Use this as
|
|
354
|
+
* ShadedGeometry.depth_material so the impostor casts a shadow whose
|
|
355
|
+
* silhouette matches the visible card.
|
|
356
|
+
*/
|
|
357
|
+
export class ImpostorShaderDepthV0 extends RawShaderMaterial {
|
|
358
|
+
constructor() {
|
|
359
|
+
super({
|
|
360
|
+
fragmentShader: shader_fg,
|
|
361
|
+
vertexShader: shader_vx,
|
|
362
|
+
uniforms: {
|
|
363
|
+
tBase: {
|
|
364
|
+
value: null
|
|
365
|
+
},
|
|
366
|
+
tGeometry: {
|
|
367
|
+
value: null
|
|
368
|
+
},
|
|
369
|
+
tMaterial: {
|
|
370
|
+
value: null
|
|
371
|
+
},
|
|
372
|
+
uFrames: {
|
|
373
|
+
value: 0
|
|
374
|
+
},
|
|
375
|
+
uRadius: {
|
|
376
|
+
value: 0
|
|
377
|
+
},
|
|
378
|
+
uOffset: {
|
|
379
|
+
value: new Vector3(0, 0, 0)
|
|
380
|
+
},
|
|
381
|
+
uIsFullSphere: {
|
|
382
|
+
value: false
|
|
383
|
+
},
|
|
384
|
+
uDepthScale: {
|
|
385
|
+
value: 0.5
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
glslVersion: GLSL3
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Shadow pass writes a single RGBA depth value per fragment — no
|
|
392
|
+
// need for blending.
|
|
393
|
+
this.blending = NoBlending;
|
|
394
|
+
}
|
|
395
|
+
}
|