reze-engine 0.50.7 → 0.50.8
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/dist/engine.d.ts +71 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +114 -1
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +35 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/shaders/cast-api.d.ts +1 -1
- package/dist/shaders/cast-api.d.ts.map +1 -1
- package/dist/shaders/cast-api.js +11 -0
- package/dist/shaders/materials/common.d.ts +20 -0
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +85 -1
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/depth-prepass.d.ts.map +1 -1
- package/dist/shaders/passes/depth-prepass.js +21 -0
- package/dist/shaders/passes/shadow.d.ts +1 -1
- package/dist/shaders/passes/shadow.d.ts.map +1 -1
- package/dist/shaders/passes/shadow.js +19 -1
- package/package.json +1 -1
- package/src/engine.ts +139 -1
- package/src/graph/slots.ts +37 -4
- package/src/index.ts +1 -0
- package/src/shaders/cast-api.ts +11 -0
- package/src/shaders/materials/common.ts +86 -1
- package/src/shaders/passes/depth-prepass.ts +21 -0
- package/src/shaders/passes/shadow.ts +20 -1
|
@@ -69,7 +69,16 @@ struct MaterialUniforms {
|
|
|
69
69
|
// just empty. f32 because the buffer is written as floats; the shader casts.
|
|
70
70
|
materialId: f32,
|
|
71
71
|
objectId: f32,
|
|
72
|
-
|
|
72
|
+
// How much of this material is still THERE: 1 whole, 0 gone. Rides the last
|
|
73
|
+
// of the padding this struct already carried, for the same reason the ids do
|
|
74
|
+
// — the buffer's size and layout are untouched, so the indirect-draw path and
|
|
75
|
+
// every existing pipeline keep working, and a material that never dissolves
|
|
76
|
+
// pays one float it was already paying.
|
|
77
|
+
//
|
|
78
|
+
// A material morph rebuilds this block from its base copy, which is why the
|
|
79
|
+
// engine writes the value into that copy as well as into the live buffer: a
|
|
80
|
+
// face morphing while she dissolves must not come back solid for those frames.
|
|
81
|
+
dissolve: f32,
|
|
73
82
|
};
|
|
74
83
|
|
|
75
84
|
struct VertexOutput {
|
|
@@ -267,6 +276,82 @@ export function commonFsOutWgsl(): string {
|
|
|
267
276
|
return `\n\n${sceneFsOutWgsl()}\n`;
|
|
268
277
|
}
|
|
269
278
|
|
|
279
|
+
// ─── Dissolve ───────────────────────────────────────────────────────
|
|
280
|
+
/**
|
|
281
|
+
* Whether a fragment survives the dissolve, and how close it is to the front.
|
|
282
|
+
*
|
|
283
|
+
* ONE implementation, shared by the colour pass and the depth prepass, and that
|
|
284
|
+
* is the whole reason it lives here rather than in either of them. The prepass
|
|
285
|
+
* claims depth for fragments the colour pass will shade; if the two disagreed
|
|
286
|
+
* about which flakes are gone, the model would keep writing depth where it no
|
|
287
|
+
* longer draws — holes that occlude the floor behind her and read as sky.
|
|
288
|
+
*
|
|
289
|
+
* OBJECT SPACE, off the bind-pose position, for the same reason the procedural
|
|
290
|
+
* texture nodes use restPos: a world-space field would let the flakes swim
|
|
291
|
+
* through her as she moves, and a screen-space one would leave them hanging in
|
|
292
|
+
* the air while she turns. In object space the pattern is painted ON the
|
|
293
|
+
* surface — the flakes stay where they were on her arm as the arm moves.
|
|
294
|
+
*
|
|
295
|
+
* The hash is value noise rather than a bare hash so the flakes come apart in
|
|
296
|
+
* clumps of a few millimetres rather than as single-pixel snow, which at any
|
|
297
|
+
* distance is just a fade.
|
|
298
|
+
*/
|
|
299
|
+
export const DISSOLVE_WGSL = /* wgsl */ `
|
|
300
|
+
/** Flake size, in object-space units — MMD's are roughly centimetres. */
|
|
301
|
+
const RZ_DISSOLVE_GRAIN: f32 = 0.55;
|
|
302
|
+
/** How wide the glowing front is, in threshold units. */
|
|
303
|
+
const RZ_DISSOLVE_EDGE: f32 = 0.16;
|
|
304
|
+
/** What the front burns with. Emission, added after lighting and after the
|
|
305
|
+
* graph — a colour that ramps or takes a shadow reads as paint, not as heat.
|
|
306
|
+
* Above 1 on purpose: it is meant to reach the bloom pyramid. */
|
|
307
|
+
const RZ_BURN_COLOR: vec3f = vec3f(0.55, 1.85, 2.60);
|
|
308
|
+
|
|
309
|
+
fn rz_dissolve_hash(p: vec3f) -> f32 {
|
|
310
|
+
let q = fract(p * 0.3183099 + vec3f(0.1, 0.2, 0.3));
|
|
311
|
+
let r = q * 17.0 * (q + 34.0);
|
|
312
|
+
return fract(r.x * r.y * r.z);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Value noise over that hash — smooth within a flake, uncorrelated between. */
|
|
316
|
+
fn rz_dissolve_field(p: vec3f) -> f32 {
|
|
317
|
+
let i = floor(p);
|
|
318
|
+
let f = fract(p);
|
|
319
|
+
let u = f * f * (3.0 - 2.0 * f);
|
|
320
|
+
let c000 = rz_dissolve_hash(i);
|
|
321
|
+
let c100 = rz_dissolve_hash(i + vec3f(1.0, 0.0, 0.0));
|
|
322
|
+
let c010 = rz_dissolve_hash(i + vec3f(0.0, 1.0, 0.0));
|
|
323
|
+
let c110 = rz_dissolve_hash(i + vec3f(1.0, 1.0, 0.0));
|
|
324
|
+
let c001 = rz_dissolve_hash(i + vec3f(0.0, 0.0, 1.0));
|
|
325
|
+
let c101 = rz_dissolve_hash(i + vec3f(1.0, 0.0, 1.0));
|
|
326
|
+
let c011 = rz_dissolve_hash(i + vec3f(0.0, 1.0, 1.0));
|
|
327
|
+
let c111 = rz_dissolve_hash(i + vec3f(1.0, 1.0, 1.0));
|
|
328
|
+
let x00 = mix(c000, c100, u.x);
|
|
329
|
+
let x10 = mix(c010, c110, u.x);
|
|
330
|
+
let x01 = mix(c001, c101, u.x);
|
|
331
|
+
let x11 = mix(c011, c111, u.x);
|
|
332
|
+
return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The threshold this fragment is measured against.
|
|
337
|
+
*
|
|
338
|
+
* Tilted by HEIGHT so the dissolve sweeps up the body instead of arriving
|
|
339
|
+
* everywhere at once: a body that comes apart from the feet reads as something
|
|
340
|
+
* happening TO her, and one that fades uniformly reads as an opacity slider.
|
|
341
|
+
* The tilt is gentle — a third of the range — so the noise still decides which
|
|
342
|
+
* flake goes when, and the sweep only decides roughly where it is.
|
|
343
|
+
*
|
|
344
|
+
* FEET FIRST, head last: the face is the last thing to go and the first thing
|
|
345
|
+
* back, which is the order every disappearance in film is cut in. Object space
|
|
346
|
+
* puts the floor at y = 0 on any PMX, so the tilt needs no rig measurement.
|
|
347
|
+
*/
|
|
348
|
+
fn rz_dissolve_threshold(restPos: vec3f) -> f32 {
|
|
349
|
+
let n = rz_dissolve_field(restPos / RZ_DISSOLVE_GRAIN);
|
|
350
|
+
let low = 1.0 - clamp(restPos.y * 0.045, 0.0, 1.0);
|
|
351
|
+
return clamp(n * 0.68 + low * 0.32, 0.0, 1.0);
|
|
352
|
+
}
|
|
353
|
+
`
|
|
354
|
+
|
|
270
355
|
// ─── Convenience: full shared prelude ───────────────────────────────
|
|
271
356
|
// Material files compose this as `${NODES_WGSL}${COMMON_MATERIAL_PRELUDE_WGSL}` to
|
|
272
357
|
// pull in everything structural. Each material then adds its own constants + fs().
|
|
@@ -27,14 +27,25 @@
|
|
|
27
27
|
// attachment, and that answer does not exist at import time. The constant could
|
|
28
28
|
// not have taken the outputs at all, which is most of why it did not have them.
|
|
29
29
|
|
|
30
|
+
import { DISSOLVE_WGSL } from "../materials/common"
|
|
30
31
|
import { sceneFsOutWgsl, sceneIdPadWgsl } from "./scene-contract"
|
|
31
32
|
|
|
32
33
|
export function transparentDepthPrepassWgsl(): string {
|
|
33
34
|
return /* wgsl */ `
|
|
35
|
+
${DISSOLVE_WGSL}
|
|
34
36
|
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
37
|
+
// The head of the material block, plus the one field at its tail. The middle is
|
|
38
|
+
// skipped rather than named: this pass shades nothing, and every field it
|
|
39
|
+
// declares is a field that must stay in step with the real struct for no gain.
|
|
40
|
+
// The padding is explicit so the offsets are checkable by eye — vec4 at 16 and
|
|
41
|
+
// 32, vec3 at 48, and dissolve last at 60.
|
|
35
42
|
struct MaterialUniforms {
|
|
36
43
|
diffuseColor: vec3f,
|
|
37
44
|
alpha: f32,
|
|
45
|
+
_skip0: vec4f,
|
|
46
|
+
_skip1: vec4f,
|
|
47
|
+
_skip2: vec3f,
|
|
48
|
+
dissolve: f32,
|
|
38
49
|
};
|
|
39
50
|
|
|
40
51
|
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
@@ -48,6 +59,9 @@ struct VSOut {
|
|
|
48
59
|
// must land on exactly the depths this wrote.
|
|
49
60
|
@builtin(position) @invariant position: vec4f,
|
|
50
61
|
@location(0) uv: vec2f,
|
|
62
|
+
// The bind-pose position, carried for one reason: the dissolve test below has
|
|
63
|
+
// to be the SAME test the colour pass runs, and that one is in object space.
|
|
64
|
+
@location(1) restPos: vec3f,
|
|
51
65
|
};
|
|
52
66
|
|
|
53
67
|
@vertex fn vs(
|
|
@@ -68,6 +82,7 @@ struct VSOut {
|
|
|
68
82
|
var o: VSOut;
|
|
69
83
|
o.position = camera.projection * camera.view * vec4f(skinned.xyz, 1.0);
|
|
70
84
|
o.uv = uv;
|
|
85
|
+
o.restPos = position;
|
|
71
86
|
return o;
|
|
72
87
|
}
|
|
73
88
|
|
|
@@ -89,6 +104,12 @@ override CUTOFF: f32 = 0.5;
|
|
|
89
104
|
@fragment fn fs(in: VSOut) -> PrepassOut {
|
|
90
105
|
let a = material.alpha * textureSample(diffuseTexture, diffuseSampler, in.uv).a;
|
|
91
106
|
if (a < CUTOFF) { discard; }
|
|
107
|
+
// The dissolve, run identically to the colour pass — shared code, not a
|
|
108
|
+
// second copy of the same idea. A prepass that kept claiming depth for flakes
|
|
109
|
+
// the colour pass throws away would punch holes that occlude the floor behind
|
|
110
|
+
// her: you would see sky through her, which is the failure this line exists
|
|
111
|
+
// to prevent.
|
|
112
|
+
if (material.dissolve < 0.9995 && rz_dissolve_threshold(in.restPos) > material.dissolve) { discard; }
|
|
92
113
|
var out: PrepassOut;
|
|
93
114
|
out.color = vec4f(0.0);
|
|
94
115
|
out.mask = vec4f(0.0);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DISSOLVE_WGSL } from "../materials/common"
|
|
2
|
+
|
|
1
3
|
// Shadow map depth pass. Skinned VS + alpha-test FS (depth-only attachment, no
|
|
2
4
|
// color targets): texels where diffuse-texture alpha × material alpha fall below
|
|
3
5
|
// the cutoff are discarded, so lace casts lace-shaped shadows and a true veil
|
|
@@ -6,17 +8,29 @@
|
|
|
6
8
|
// bindings 0/1 are declared here (a layout may carry bindings a shader ignores).
|
|
7
9
|
|
|
8
10
|
export const SHADOW_DEPTH_SHADER_WGSL = /* wgsl */ `
|
|
11
|
+
${DISSOLVE_WGSL}
|
|
9
12
|
struct LightVP { viewProj: mat4x4f, };
|
|
10
13
|
@group(0) @binding(0) var<uniform> lp: LightVP;
|
|
11
14
|
@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;
|
|
12
15
|
@group(0) @binding(2) var texSampler: sampler;
|
|
13
16
|
@group(1) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
14
|
-
|
|
17
|
+
// The head of the material block and the one field at its tail — the same
|
|
18
|
+
// reach the depth prepass takes, and for the same reason: a shadow cast by a
|
|
19
|
+
// body that is no longer drawn is the tell that the vanishing is a trick.
|
|
20
|
+
struct MaterialDiffuse {
|
|
21
|
+
diffuse: vec4f,
|
|
22
|
+
_skip0: vec4f,
|
|
23
|
+
_skip1: vec4f,
|
|
24
|
+
_skip2: vec3f,
|
|
25
|
+
dissolve: f32,
|
|
26
|
+
};
|
|
15
27
|
@group(1) @binding(1) var<uniform> material: MaterialDiffuse;
|
|
16
28
|
|
|
17
29
|
struct VSOut {
|
|
18
30
|
@builtin(position) position: vec4f,
|
|
19
31
|
@location(0) uv: vec2f,
|
|
32
|
+
/** Bind-pose position, for the dissolve test — object space, as everywhere. */
|
|
33
|
+
@location(1) restPos: vec3f,
|
|
20
34
|
};
|
|
21
35
|
|
|
22
36
|
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,
|
|
@@ -30,11 +44,16 @@ struct VSOut {
|
|
|
30
44
|
var out: VSOut;
|
|
31
45
|
out.position = lp.viewProj * vec4f(sp.xyz, 1.0);
|
|
32
46
|
out.uv = uv;
|
|
47
|
+
out.restPos = position;
|
|
33
48
|
return out;
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
@fragment fn fs(in: VSOut) {
|
|
37
52
|
let alpha = textureSample(diffuseTexture, texSampler, in.uv).a * material.diffuse.a;
|
|
38
53
|
if (alpha < 0.5) { discard; }
|
|
54
|
+
// The dissolve, run as the colour pass runs it. A flake that is gone stops
|
|
55
|
+
// casting: without this she leaves a whole shadow standing on the floor while
|
|
56
|
+
// her body is in the air.
|
|
57
|
+
if (material.dissolve < 0.9995 && rz_dissolve_threshold(in.restPos) > material.dissolve) { discard; }
|
|
39
58
|
}
|
|
40
59
|
`
|