reze-engine 0.50.6 → 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/_dbg-loader.mjs +1100 -0
- 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/physics-debug.d.ts +30 -0
- package/dist/physics-debug.d.ts.map +1 -0
- package/dist/physics-debug.js +526 -0
- 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/body.d.ts +2 -0
- package/dist/shaders/materials/body.d.ts.map +1 -0
- package/dist/shaders/materials/body.js +95 -0
- package/dist/shaders/materials/cloth_rough.d.ts +2 -0
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_rough.js +69 -0
- package/dist/shaders/materials/cloth_smooth.d.ts +2 -0
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_smooth.js +61 -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/materials/default.d.ts +2 -0
- package/dist/shaders/materials/default.d.ts.map +1 -0
- package/dist/shaders/materials/default.js +43 -0
- package/dist/shaders/materials/eye.d.ts +2 -0
- package/dist/shaders/materials/eye.d.ts.map +1 -0
- package/dist/shaders/materials/eye.js +60 -0
- package/dist/shaders/materials/face.d.ts +2 -0
- package/dist/shaders/materials/face.d.ts.map +1 -0
- package/dist/shaders/materials/face.js +95 -0
- package/dist/shaders/materials/hair.d.ts +2 -0
- package/dist/shaders/materials/hair.d.ts.map +1 -0
- package/dist/shaders/materials/hair.js +90 -0
- package/dist/shaders/materials/metal.d.ts +2 -0
- package/dist/shaders/materials/metal.d.ts.map +1 -0
- package/dist/shaders/materials/metal.js +77 -0
- package/dist/shaders/materials/mmd_classic.d.ts +2 -0
- package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
- package/dist/shaders/materials/mmd_classic.js +66 -0
- package/dist/shaders/materials/stockings.d.ts +2 -0
- package/dist/shaders/materials/stockings.d.ts.map +1 -0
- package/dist/shaders/materials/stockings.js +122 -0
- 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/physics-debug.d.ts +2 -0
- package/dist/shaders/passes/physics-debug.d.ts.map +1 -0
- package/dist/shaders/passes/physics-debug.js +69 -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/dist/vmd-writer.d.ts +11 -6
- package/dist/vmd-writer.d.ts.map +1 -1
- package/dist/vmd-writer.js +35 -17
- 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
- package/src/vmd-writer.ts +36 -16
- package/dist/physics/autofit.d.ts +0 -147
- package/dist/physics/autofit.d.ts.map +0 -1
- package/dist/physics/autofit.js +0 -501
- package/dist/shaders/passes/field-blit.d.ts +0 -26
- package/dist/shaders/passes/field-blit.d.ts.map +0 -1
- package/dist/shaders/passes/field-blit.js +0 -65
- package/dist/shaders/passes/ground-noise.d.ts +0 -7
- package/dist/shaders/passes/ground-noise.d.ts.map +0 -1
- package/dist/shaders/passes/ground-noise.js +0 -88
- package/dist/shaders/passes/sim.d.ts +0 -34
- package/dist/shaders/passes/sim.d.ts.map +0 -1
- package/dist/shaders/passes/sim.js +0 -169
- package/dist/shaders/score-api.d.ts +0 -10
- package/dist/shaders/score-api.d.ts.map +0 -1
- package/dist/shaders/score-api.js +0 -114
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const PHYSICS_DEBUG_SHADER_WGSL = "\nstruct CameraUniforms {\n view: mat4x4f,\n projection: mat4x4f,\n viewPos: vec3f,\n _pad: f32,\n};\n\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n\noverride SHAPE_KIND: u32 = 0u;\noverride SOLID_ALPHA: f32 = 1.0;\n\nstruct VsIn {\n @location(0) unit: vec4f, // xyz = unit pos; w = capsule axial anchor\n @location(1) modelCol0: vec4f,\n @location(2) modelCol1: vec4f,\n @location(3) modelCol2: vec4f,\n @location(4) modelCol3: vec4f,\n @location(5) size: vec4f, // xyz = size; w unused\n @location(6) color: vec4f,\n};\n\nstruct VsOut {\n @builtin(position) pos: vec4f,\n @location(0) color: vec4f,\n};\n\n@vertex\nfn vsMain(in: VsIn) -> VsOut {\n let model = mat4x4f(in.modelCol0, in.modelCol1, in.modelCol2, in.modelCol3);\n var local: vec3f;\n if (SHAPE_KIND == 1u) {\n local = in.unit.xyz * in.size.xyz;\n } else if (SHAPE_KIND == 2u) {\n // PMX capsule: size.x = radius, size.y = full cylinder height. Bullet 2.x's\n // btCapsuleShape(radius, height) stores height/2 internally, so the actual\n // collision cylinder spans \u00B1size.y/2 \u2014 halve here to match.\n let r = in.size.x;\n let halfH = in.size.y * 0.5;\n local = vec3f(in.unit.x * r, in.unit.y * r + halfH * in.unit.w, in.unit.z * r);\n } else {\n local = in.unit.xyz * in.size.x;\n }\n let world = model * vec4f(local, 1.0);\n var out: VsOut;\n out.pos = camera.projection * camera.view * world;\n out.color = in.color;\n return out;\n}\n\n@fragment\nfn fsMain(in: VsOut) -> @location(0) vec4f {\n return vec4f(in.color.rgb, in.color.a * SOLID_ALPHA);\n}\n";
|
|
2
|
+
//# sourceMappingURL=physics-debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics-debug.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/physics-debug.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,yBAAyB,2kDAuDrC,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Wireframe + solid overlay for physics rigid bodies (sphere / box / capsule).
|
|
2
|
+
// Rendered in its own pass after composite, straight to the swapchain — no
|
|
3
|
+
// MSAA, no depth, no MRT — so it sits cleanly on top of the tonemapped scene
|
|
4
|
+
// regardless of camera angle, model occlusion, or the composite alpha gate.
|
|
5
|
+
//
|
|
6
|
+
// SHAPE_KIND 0 = sphere → unit-sphere line/tri primitive scaled by size.x (radius)
|
|
7
|
+
// 1 = box → unit cube ±1 scaled by size.xyz (half-extents)
|
|
8
|
+
// 2 = capsule → unit cap+ring with per-vertex `axial` (-1/0/+1)
|
|
9
|
+
// that lifts cap rings/arcs ±halfHeight along Y;
|
|
10
|
+
// XZ scales by size.x (radius), Y by size.y/2.
|
|
11
|
+
// SOLID_ALPHA fragment alpha multiplier. Wireframe pipelines leave this at
|
|
12
|
+
// 1.0 for crisp edges; solid pipelines set it to ~0.2 so the fill
|
|
13
|
+
// stays gentle under stacked bodies.
|
|
14
|
+
export const PHYSICS_DEBUG_SHADER_WGSL = /* wgsl */ `
|
|
15
|
+
struct CameraUniforms {
|
|
16
|
+
view: mat4x4f,
|
|
17
|
+
projection: mat4x4f,
|
|
18
|
+
viewPos: vec3f,
|
|
19
|
+
_pad: f32,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
23
|
+
|
|
24
|
+
override SHAPE_KIND: u32 = 0u;
|
|
25
|
+
override SOLID_ALPHA: f32 = 1.0;
|
|
26
|
+
|
|
27
|
+
struct VsIn {
|
|
28
|
+
@location(0) unit: vec4f, // xyz = unit pos; w = capsule axial anchor
|
|
29
|
+
@location(1) modelCol0: vec4f,
|
|
30
|
+
@location(2) modelCol1: vec4f,
|
|
31
|
+
@location(3) modelCol2: vec4f,
|
|
32
|
+
@location(4) modelCol3: vec4f,
|
|
33
|
+
@location(5) size: vec4f, // xyz = size; w unused
|
|
34
|
+
@location(6) color: vec4f,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
struct VsOut {
|
|
38
|
+
@builtin(position) pos: vec4f,
|
|
39
|
+
@location(0) color: vec4f,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
@vertex
|
|
43
|
+
fn vsMain(in: VsIn) -> VsOut {
|
|
44
|
+
let model = mat4x4f(in.modelCol0, in.modelCol1, in.modelCol2, in.modelCol3);
|
|
45
|
+
var local: vec3f;
|
|
46
|
+
if (SHAPE_KIND == 1u) {
|
|
47
|
+
local = in.unit.xyz * in.size.xyz;
|
|
48
|
+
} else if (SHAPE_KIND == 2u) {
|
|
49
|
+
// PMX capsule: size.x = radius, size.y = full cylinder height. Bullet 2.x's
|
|
50
|
+
// btCapsuleShape(radius, height) stores height/2 internally, so the actual
|
|
51
|
+
// collision cylinder spans ±size.y/2 — halve here to match.
|
|
52
|
+
let r = in.size.x;
|
|
53
|
+
let halfH = in.size.y * 0.5;
|
|
54
|
+
local = vec3f(in.unit.x * r, in.unit.y * r + halfH * in.unit.w, in.unit.z * r);
|
|
55
|
+
} else {
|
|
56
|
+
local = in.unit.xyz * in.size.x;
|
|
57
|
+
}
|
|
58
|
+
let world = model * vec4f(local, 1.0);
|
|
59
|
+
var out: VsOut;
|
|
60
|
+
out.pos = camera.projection * camera.view * world;
|
|
61
|
+
out.color = in.color;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@fragment
|
|
66
|
+
fn fsMain(in: VsOut) -> @location(0) vec4f {
|
|
67
|
+
return vec4f(in.color.rgb, in.color.a * SOLID_ALPHA);
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SHADOW_DEPTH_SHADER_WGSL = "\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> lp: LightVP;\n@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;\n@group(0) @binding(2) var texSampler: sampler;\n@group(1) @binding(0) var diffuseTexture: texture_2d<f32>;\nstruct MaterialDiffuse {
|
|
1
|
+
export declare const SHADOW_DEPTH_SHADER_WGSL = "\n\n/** Flake size, in object-space units \u2014 MMD's are roughly centimetres. */\nconst RZ_DISSOLVE_GRAIN: f32 = 0.55;\n/** How wide the glowing front is, in threshold units. */\nconst RZ_DISSOLVE_EDGE: f32 = 0.16;\n/** What the front burns with. Emission, added after lighting and after the\n * graph \u2014 a colour that ramps or takes a shadow reads as paint, not as heat.\n * Above 1 on purpose: it is meant to reach the bloom pyramid. */\nconst RZ_BURN_COLOR: vec3f = vec3f(0.55, 1.85, 2.60);\n\nfn rz_dissolve_hash(p: vec3f) -> f32 {\n let q = fract(p * 0.3183099 + vec3f(0.1, 0.2, 0.3));\n let r = q * 17.0 * (q + 34.0);\n return fract(r.x * r.y * r.z);\n}\n\n/** Value noise over that hash \u2014 smooth within a flake, uncorrelated between. */\nfn rz_dissolve_field(p: vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let c000 = rz_dissolve_hash(i);\n let c100 = rz_dissolve_hash(i + vec3f(1.0, 0.0, 0.0));\n let c010 = rz_dissolve_hash(i + vec3f(0.0, 1.0, 0.0));\n let c110 = rz_dissolve_hash(i + vec3f(1.0, 1.0, 0.0));\n let c001 = rz_dissolve_hash(i + vec3f(0.0, 0.0, 1.0));\n let c101 = rz_dissolve_hash(i + vec3f(1.0, 0.0, 1.0));\n let c011 = rz_dissolve_hash(i + vec3f(0.0, 1.0, 1.0));\n let c111 = rz_dissolve_hash(i + vec3f(1.0, 1.0, 1.0));\n let x00 = mix(c000, c100, u.x);\n let x10 = mix(c010, c110, u.x);\n let x01 = mix(c001, c101, u.x);\n let x11 = mix(c011, c111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\n/**\n * The threshold this fragment is measured against.\n *\n * Tilted by HEIGHT so the dissolve sweeps up the body instead of arriving\n * everywhere at once: a body that comes apart from the feet reads as something\n * happening TO her, and one that fades uniformly reads as an opacity slider.\n * The tilt is gentle \u2014 a third of the range \u2014 so the noise still decides which\n * flake goes when, and the sweep only decides roughly where it is.\n *\n * FEET FIRST, head last: the face is the last thing to go and the first thing\n * back, which is the order every disappearance in film is cut in. Object space\n * puts the floor at y = 0 on any PMX, so the tilt needs no rig measurement.\n */\nfn rz_dissolve_threshold(restPos: vec3f) -> f32 {\n let n = rz_dissolve_field(restPos / RZ_DISSOLVE_GRAIN);\n let low = 1.0 - clamp(restPos.y * 0.045, 0.0, 1.0);\n return clamp(n * 0.68 + low * 0.32, 0.0, 1.0);\n}\n\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> lp: LightVP;\n@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;\n@group(0) @binding(2) var texSampler: sampler;\n@group(1) @binding(0) var diffuseTexture: texture_2d<f32>;\n// The head of the material block and the one field at its tail \u2014 the same\n// reach the depth prepass takes, and for the same reason: a shadow cast by a\n// body that is no longer drawn is the tell that the vanishing is a trick.\nstruct MaterialDiffuse {\n diffuse: vec4f,\n _skip0: vec4f,\n _skip1: vec4f,\n _skip2: vec3f,\n dissolve: f32,\n};\n@group(1) @binding(1) var<uniform> material: MaterialDiffuse;\n\nstruct VSOut {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n /** Bind-pose position, for the dissolve test \u2014 object space, as everywhere. */\n @location(1) restPos: vec3f,\n};\n\n@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,\n @location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) -> VSOut {\n let pos4 = vec4f(position, 1.0);\n let ws = weights0.x + weights0.y + weights0.z + weights0.w;\n let inv = select(1.0, 1.0 / ws, ws > 0.0001);\n let nw = select(vec4f(1.0,0.0,0.0,0.0), weights0 * inv, ws > 0.0001);\n var sp = vec4f(0.0);\n for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }\n var out: VSOut;\n out.position = lp.viewProj * vec4f(sp.xyz, 1.0);\n out.uv = uv;\n out.restPos = position;\n return out;\n}\n\n@fragment fn fs(in: VSOut) {\n let alpha = textureSample(diffuseTexture, texSampler, in.uv).a * material.diffuse.a;\n if (alpha < 0.5) { discard; }\n // The dissolve, run as the colour pass runs it. A flake that is gone stops\n // casting: without this she leaves a whole shadow standing on the floor while\n // her body is in the air.\n if (material.dissolve < 0.9995 && rz_dissolve_threshold(in.restPos) > material.dissolve) { discard; }\n}\n";
|
|
2
2
|
//# sourceMappingURL=shadow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/shadow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/shadow.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,wBAAwB,y1IAiDpC,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DISSOLVE_WGSL } from "../materials/common";
|
|
1
2
|
// Shadow map depth pass. Skinned VS + alpha-test FS (depth-only attachment, no
|
|
2
3
|
// color targets): texels where diffuse-texture alpha × material alpha fall below
|
|
3
4
|
// the cutoff are discarded, so lace casts lace-shaped shadows and a true veil
|
|
@@ -5,17 +6,29 @@
|
|
|
5
6
|
// Group 1 is the main pass's per-material bind group reused as-is; only
|
|
6
7
|
// bindings 0/1 are declared here (a layout may carry bindings a shader ignores).
|
|
7
8
|
export const SHADOW_DEPTH_SHADER_WGSL = /* wgsl */ `
|
|
9
|
+
${DISSOLVE_WGSL}
|
|
8
10
|
struct LightVP { viewProj: mat4x4f, };
|
|
9
11
|
@group(0) @binding(0) var<uniform> lp: LightVP;
|
|
10
12
|
@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;
|
|
11
13
|
@group(0) @binding(2) var texSampler: sampler;
|
|
12
14
|
@group(1) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
13
|
-
|
|
15
|
+
// The head of the material block and the one field at its tail — the same
|
|
16
|
+
// reach the depth prepass takes, and for the same reason: a shadow cast by a
|
|
17
|
+
// body that is no longer drawn is the tell that the vanishing is a trick.
|
|
18
|
+
struct MaterialDiffuse {
|
|
19
|
+
diffuse: vec4f,
|
|
20
|
+
_skip0: vec4f,
|
|
21
|
+
_skip1: vec4f,
|
|
22
|
+
_skip2: vec3f,
|
|
23
|
+
dissolve: f32,
|
|
24
|
+
};
|
|
14
25
|
@group(1) @binding(1) var<uniform> material: MaterialDiffuse;
|
|
15
26
|
|
|
16
27
|
struct VSOut {
|
|
17
28
|
@builtin(position) position: vec4f,
|
|
18
29
|
@location(0) uv: vec2f,
|
|
30
|
+
/** Bind-pose position, for the dissolve test — object space, as everywhere. */
|
|
31
|
+
@location(1) restPos: vec3f,
|
|
19
32
|
};
|
|
20
33
|
|
|
21
34
|
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,
|
|
@@ -29,11 +42,16 @@ struct VSOut {
|
|
|
29
42
|
var out: VSOut;
|
|
30
43
|
out.position = lp.viewProj * vec4f(sp.xyz, 1.0);
|
|
31
44
|
out.uv = uv;
|
|
45
|
+
out.restPos = position;
|
|
32
46
|
return out;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
@fragment fn fs(in: VSOut) {
|
|
36
50
|
let alpha = textureSample(diffuseTexture, texSampler, in.uv).a * material.diffuse.a;
|
|
37
51
|
if (alpha < 0.5) { discard; }
|
|
52
|
+
// The dissolve, run as the colour pass runs it. A flake that is gone stops
|
|
53
|
+
// casting: without this she leaves a whole shadow standing on the floor while
|
|
54
|
+
// her body is in the air.
|
|
55
|
+
if (material.dissolve < 0.9995 && rz_dissolve_threshold(in.restPos) > material.dissolve) { discard; }
|
|
38
56
|
}
|
|
39
57
|
`;
|
package/dist/vmd-writer.d.ts
CHANGED
|
@@ -17,12 +17,17 @@ export declare class VMDWriter {
|
|
|
17
17
|
/**
|
|
18
18
|
* A camera VMD: the shot's own file, with no model motion in it.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* that
|
|
20
|
+
* Every section count is written, including the empty ones. The format is
|
|
21
|
+
* positional, so a reader reaches each block by walking the counts before it —
|
|
22
|
+
* bone and morph to arrive at the camera block, and light, self-shadow and IK
|
|
23
|
+
* to leave it cleanly.
|
|
24
|
+
*
|
|
25
|
+
* The trailing three used to be omitted, on the theory that every reader
|
|
26
|
+
* bounds-checks past the camera block. They do not. This file type is
|
|
27
|
+
* カメラ・照明 — camera AND lighting — so MMD looks for the light block exactly
|
|
28
|
+
* where the file used to stop, read past the end, and lit the scene from
|
|
29
|
+
* whatever it found there. It reached users as a stray coloured light on a
|
|
30
|
+
* camera-only export.
|
|
26
31
|
*
|
|
27
32
|
* `frames` is sorted by frame on the way out: CameraAnimation binary-searches
|
|
28
33
|
* the track it loads, and an out-of-order file would sample wrong rather than
|
package/dist/vmd-writer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vmd-writer.d.ts","sourceRoot":"","sources":["../src/vmd-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAmC,MAAM,aAAa,CAAA;AAC5E,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAsDhF;;;;;;;;8CAQ8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE3D,qBAAa,SAAS;IACpB,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;KAAE,GAAG,WAAW;
|
|
1
|
+
{"version":3,"file":"vmd-writer.d.ts","sourceRoot":"","sources":["../src/vmd-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAmC,MAAM,aAAa,CAAA;AAC5E,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAsDhF;;;;;;;;8CAQ8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE3D,qBAAa,SAAS;IACpB,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;KAAE,GAAG,WAAW;IA0IjF;;;;;;;;;;;;;;;;;;OAkBG;IACH,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,WAAW;CAiDnD"}
|
package/dist/vmd-writer.js
CHANGED
|
@@ -90,10 +90,17 @@ export class VMDWriter {
|
|
|
90
90
|
MODEL_NAME_SIZE +
|
|
91
91
|
4 + totalBoneFrames * BONE_FRAME_SIZE +
|
|
92
92
|
4 + totalMorphFrames * MORPH_FRAME_SIZE +
|
|
93
|
-
// Camera, light
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
93
|
+
// Camera, light, self-shadow and IK counts, ALWAYS — 16 bytes even when
|
|
94
|
+
// every one of them is zero.
|
|
95
|
+
//
|
|
96
|
+
// A VMD's sections are positional: a reader reaches each block by walking
|
|
97
|
+
// every count before it. Writing these only when there was IK state to
|
|
98
|
+
// carry left an ordinary motion ending right after the morph block, so
|
|
99
|
+
// anything looking for the sections that follow read past the end of the
|
|
100
|
+
// file and took whatever it found. MMD's own motion files carry all five
|
|
101
|
+
// counts unconditionally; so do ours now.
|
|
102
|
+
4 * 4 +
|
|
103
|
+
ikSize;
|
|
97
104
|
const buffer = new ArrayBuffer(size);
|
|
98
105
|
const view = new DataView(buffer);
|
|
99
106
|
let offset = 0;
|
|
@@ -150,13 +157,14 @@ export class VMDWriter {
|
|
|
150
157
|
offset += 4;
|
|
151
158
|
}
|
|
152
159
|
}
|
|
160
|
+
// Empty camera, light and self-shadow blocks, then the IK count — written
|
|
161
|
+
// whether or not there is IK state, so the file always ends on a complete
|
|
162
|
+
// section table.
|
|
163
|
+
for (let i = 0; i < 3; i++, offset += 4)
|
|
164
|
+
view.setUint32(offset, 0, true);
|
|
165
|
+
view.setUint32(offset, ikFrames.length, true);
|
|
166
|
+
offset += 4;
|
|
153
167
|
if (ikFrames.length > 0) {
|
|
154
|
-
// Empty camera, light and self-shadow blocks — a reader walking the file
|
|
155
|
-
// in order has to pass through them to reach the IK block.
|
|
156
|
-
for (let i = 0; i < 3; i++, offset += 4)
|
|
157
|
-
view.setUint32(offset, 0, true);
|
|
158
|
-
view.setUint32(offset, ikFrames.length, true);
|
|
159
|
-
offset += 4;
|
|
160
168
|
for (const [frame, states] of ikFrames) {
|
|
161
169
|
view.setUint32(offset, frame, true);
|
|
162
170
|
offset += 4;
|
|
@@ -176,12 +184,17 @@ export class VMDWriter {
|
|
|
176
184
|
/**
|
|
177
185
|
* A camera VMD: the shot's own file, with no model motion in it.
|
|
178
186
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* that
|
|
187
|
+
* Every section count is written, including the empty ones. The format is
|
|
188
|
+
* positional, so a reader reaches each block by walking the counts before it —
|
|
189
|
+
* bone and morph to arrive at the camera block, and light, self-shadow and IK
|
|
190
|
+
* to leave it cleanly.
|
|
191
|
+
*
|
|
192
|
+
* The trailing three used to be omitted, on the theory that every reader
|
|
193
|
+
* bounds-checks past the camera block. They do not. This file type is
|
|
194
|
+
* カメラ・照明 — camera AND lighting — so MMD looks for the light block exactly
|
|
195
|
+
* where the file used to stop, read past the end, and lit the scene from
|
|
196
|
+
* whatever it found there. It reached users as a stray coloured light on a
|
|
197
|
+
* camera-only export.
|
|
185
198
|
*
|
|
186
199
|
* `frames` is sorted by frame on the way out: CameraAnimation binary-searches
|
|
187
200
|
* the track it loads, and an out-of-order file would sample wrong rather than
|
|
@@ -189,7 +202,7 @@ export class VMDWriter {
|
|
|
189
202
|
*/
|
|
190
203
|
writeCamera(frames) {
|
|
191
204
|
const sorted = [...frames].sort((a, b) => a.frame - b.frame);
|
|
192
|
-
const size = HEADER_SIZE + MODEL_NAME_SIZE + 4 + 4 + 4 + sorted.length * CAMERA_FRAME_SIZE;
|
|
205
|
+
const size = HEADER_SIZE + MODEL_NAME_SIZE + 4 + 4 + 4 + sorted.length * CAMERA_FRAME_SIZE + 4 * 3;
|
|
193
206
|
const buffer = new ArrayBuffer(size);
|
|
194
207
|
const view = new DataView(buffer);
|
|
195
208
|
let offset = 0;
|
|
@@ -234,6 +247,11 @@ export class VMDWriter {
|
|
|
234
247
|
view.setUint8(offset, 0); // 0 = perspective
|
|
235
248
|
offset += 1;
|
|
236
249
|
}
|
|
250
|
+
// Light, self-shadow and IK/display counts. See the note above: the light
|
|
251
|
+
// block begins here, and leaving it off is what lit MMD from past the end
|
|
252
|
+
// of the file.
|
|
253
|
+
for (let i = 0; i < 3; i++, offset += 4)
|
|
254
|
+
view.setUint32(offset, 0, true);
|
|
237
255
|
return buffer;
|
|
238
256
|
}
|
|
239
257
|
}
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -666,12 +666,40 @@ interface PickDrawCall {
|
|
|
666
666
|
bindGroup: GPUBindGroup
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
+
/**
|
|
670
|
+
* A repeating dissolve, in seconds within one cycle.
|
|
671
|
+
*
|
|
672
|
+
* Four moments rather than a duration and a delay: every one of them is a thing
|
|
673
|
+
* you can see happen, and an author tuning this is watching for exactly those
|
|
674
|
+
* four frames.
|
|
675
|
+
*/
|
|
676
|
+
export interface DissolveCycle {
|
|
677
|
+
period: number
|
|
678
|
+
/** She starts to come apart. */
|
|
679
|
+
breakAt: number
|
|
680
|
+
/** Fully gone. */
|
|
681
|
+
hiddenAt: number
|
|
682
|
+
/** She starts to come back. */
|
|
683
|
+
backAt: number
|
|
684
|
+
/** Whole again. */
|
|
685
|
+
doneAt: number
|
|
686
|
+
}
|
|
687
|
+
|
|
669
688
|
interface ModelInstance {
|
|
670
689
|
name: string
|
|
671
690
|
/** This model's id in the id attachment — 1-based, so 0 stays "nothing".
|
|
672
691
|
* The pick pass has always minted it; the cast carries it now too, so an
|
|
673
692
|
* effect can compare what it reads out of the id buffer against a subject. */
|
|
674
693
|
objectId: number
|
|
694
|
+
/** How much of this model is still THERE: 1 whole, 0 gone. Written into every
|
|
695
|
+
* material's uniform (see setModelDissolve) and mirrored into the cast, so
|
|
696
|
+
* the material shell can take her apart and an effect can draw what is
|
|
697
|
+
* leaving — both from one number rather than two clocks that must agree. */
|
|
698
|
+
dissolve: number
|
|
699
|
+
/** Every material's uniform buffer, in draw order. Kept because a dissolve
|
|
700
|
+
* writes ONE float into each of them and needs no other reason to hold a
|
|
701
|
+
* block: the whole 16-float copy exists only for materials that morph. */
|
|
702
|
+
materialUniformBuffers: GPUBuffer[]
|
|
675
703
|
model: Model
|
|
676
704
|
basePath: string
|
|
677
705
|
assetReader: AssetReader
|
|
@@ -1595,6 +1623,8 @@ export class Engine {
|
|
|
1595
1623
|
* every frame. */
|
|
1596
1624
|
private fieldClockScratch = new Float32Array(4)
|
|
1597
1625
|
/** Material parameters driven by the scene clock — see setStyleParamTrack. */
|
|
1626
|
+
/** Repeating dissolves, by model name — see setModelDissolveCycle. */
|
|
1627
|
+
private dissolveCycles = new Map<string, DissolveCycle>()
|
|
1598
1628
|
private paramTracks = new Map<
|
|
1599
1629
|
string,
|
|
1600
1630
|
{ modelName: string; groupId: string; paramId: string; keys: ParamKey[]; last: ParamValue | null }
|
|
@@ -8662,6 +8692,8 @@ export class Engine {
|
|
|
8662
8692
|
materialToGroup: new Map(),
|
|
8663
8693
|
styleGroupGen: new Map(),
|
|
8664
8694
|
objectId: this.modelInstances.size + 1,
|
|
8695
|
+
dissolve: 1,
|
|
8696
|
+
materialUniformBuffers: [],
|
|
8665
8697
|
cullModelIndex: 0,
|
|
8666
8698
|
// Seeded false: the first skin-matrix upload decides it, and until then the
|
|
8667
8699
|
// sphere path is the safe answer (it never culls something it should not).
|
|
@@ -9069,6 +9101,7 @@ export class Engine {
|
|
|
9069
9101
|
modelId,
|
|
9070
9102
|
)
|
|
9071
9103
|
inst.gpuBuffers.push(materialUniformBuffer)
|
|
9104
|
+
inst.materialUniformBuffers.push(materialUniformBuffer)
|
|
9072
9105
|
if (morphedMaterials.has(pmxMaterialIndex)) {
|
|
9073
9106
|
const base = this.materialUniformData(mat, sphereMode, headBoneIndex, materialId, modelId)
|
|
9074
9107
|
morphTargets.push({
|
|
@@ -9225,6 +9258,10 @@ export class Engine {
|
|
|
9225
9258
|
// bound and the indirect-draw path is untouched.
|
|
9226
9259
|
data[13] = materialId
|
|
9227
9260
|
data[14] = objectId
|
|
9261
|
+
// 15 is the last of that padding: how much of this material is there. ONE,
|
|
9262
|
+
// not zero — the default has to be "whole", or every model would load
|
|
9263
|
+
// already gone.
|
|
9264
|
+
data[15] = 1
|
|
9228
9265
|
return data
|
|
9229
9266
|
}
|
|
9230
9267
|
|
|
@@ -10329,6 +10366,7 @@ export class Engine {
|
|
|
10329
10366
|
// and a grid stepped after them is one frame stale in everything that used it.
|
|
10330
10367
|
// Material parameters on the scene clock, before anything reads their
|
|
10331
10368
|
// uniforms this frame.
|
|
10369
|
+
this.evaluateDissolveCycles()
|
|
10332
10370
|
this.evaluateParamTracks()
|
|
10333
10371
|
this.stepSim(encoder, deltaTime)
|
|
10334
10372
|
this.stepParticles(encoder, deltaTime)
|
|
@@ -10730,6 +10768,102 @@ export class Engine {
|
|
|
10730
10768
|
return true
|
|
10731
10769
|
}
|
|
10732
10770
|
|
|
10771
|
+
/**
|
|
10772
|
+
* How much of a model is still THERE: 1 whole, 0 gone.
|
|
10773
|
+
*
|
|
10774
|
+
* The instant tier, like setStyleParam — one float per material, no recompile,
|
|
10775
|
+
* no pipeline touched. What it drives is a THRESHOLD, not an opacity: the
|
|
10776
|
+
* material shell throws away every flake whose object-space threshold has
|
|
10777
|
+
* passed, and lights the ones about to go. So a model at 0.5 is not
|
|
10778
|
+
* half-transparent, it is half GONE, which is the difference between a fade
|
|
10779
|
+
* and a disintegration.
|
|
10780
|
+
*
|
|
10781
|
+
* Written into the depth prepass's copy of the same test as well, so the
|
|
10782
|
+
* flakes stop claiming depth the moment they stop being drawn — see
|
|
10783
|
+
* DISSOLVE_WGSL for why that has to be one implementation.
|
|
10784
|
+
*
|
|
10785
|
+
* Mirrored into the cast, so an effect can read rzSubject(i).dissolve and draw
|
|
10786
|
+
* the sparks that leave her in step with the body they came off. That is the
|
|
10787
|
+
* whole reason this lives on the model rather than in an effect's uniform: the
|
|
10788
|
+
* material pass runs long before any effect, and only the engine sees both.
|
|
10789
|
+
*/
|
|
10790
|
+
setModelDissolve(modelName: string, value: number): boolean {
|
|
10791
|
+
const inst = this.modelInstances.get(modelName)
|
|
10792
|
+
if (!inst) return false
|
|
10793
|
+
const v = Math.min(1, Math.max(0, value))
|
|
10794
|
+
if (inst.dissolve === v) return true
|
|
10795
|
+
inst.dissolve = v
|
|
10796
|
+
// Offset 60: the sixteenth float of MaterialUniforms. One four-byte write
|
|
10797
|
+
// per material rather than the whole block — the block only exists as a
|
|
10798
|
+
// copy for materials that morph.
|
|
10799
|
+
const one = new Float32Array([v])
|
|
10800
|
+
for (const buffer of inst.materialUniformBuffers) {
|
|
10801
|
+
this.device.queue.writeBuffer(buffer, 60, one)
|
|
10802
|
+
}
|
|
10803
|
+
// The morph path rebuilds a material's block from its `base` copy and
|
|
10804
|
+
// uploads it whole, which would put the old value straight back. Patching
|
|
10805
|
+
// `base` is what keeps a face that is morphing while she dissolves from
|
|
10806
|
+
// coming back solid for those frames; `last` is cleared so the next
|
|
10807
|
+
// comparison genuinely re-uploads rather than deciding nothing moved.
|
|
10808
|
+
if (inst.materialMorphTargets) {
|
|
10809
|
+
for (const t of inst.materialMorphTargets) {
|
|
10810
|
+
t.base[15] = v
|
|
10811
|
+
t.last[15] = Number.NaN
|
|
10812
|
+
}
|
|
10813
|
+
}
|
|
10814
|
+
return true
|
|
10815
|
+
}
|
|
10816
|
+
|
|
10817
|
+
/**
|
|
10818
|
+
* A repeating dissolve, on the scene clock.
|
|
10819
|
+
*
|
|
10820
|
+
* The alternative was a host calling setModelDissolve every frame, and it is
|
|
10821
|
+
* the wrong shape twice: an exported take stepped at another rate would land
|
|
10822
|
+
* on different values than the preview did, and the effect drawing the sparks
|
|
10823
|
+
* would be reading a number some other clock wrote. Here the engine samples it
|
|
10824
|
+
* where it samples everything else time-driven, so a take reproduces exactly
|
|
10825
|
+
* and rzSubject().dissolve is the same value the material shell used on that
|
|
10826
|
+
* very frame.
|
|
10827
|
+
*
|
|
10828
|
+
* The five numbers are seconds within one cycle: when she starts to go, when
|
|
10829
|
+
* she is fully gone, when she starts to come back, and when she is whole. The
|
|
10830
|
+
* gaps between them are the timing, and the hold between the middle two is how
|
|
10831
|
+
* long she is away.
|
|
10832
|
+
*/
|
|
10833
|
+
setModelDissolveCycle(modelName: string, cycle: DissolveCycle | null): boolean {
|
|
10834
|
+
if (!this.modelInstances.has(modelName)) return false
|
|
10835
|
+
if (!cycle) {
|
|
10836
|
+
if (this.dissolveCycles.delete(modelName)) this.setModelDissolve(modelName, 1)
|
|
10837
|
+
return true
|
|
10838
|
+
}
|
|
10839
|
+
this.dissolveCycles.set(modelName, cycle)
|
|
10840
|
+
return true
|
|
10841
|
+
}
|
|
10842
|
+
|
|
10843
|
+
/** Every dissolve cycle, at the current scene clock. Once per frame, before
|
|
10844
|
+
* the cast is written and long before any effect reads it. */
|
|
10845
|
+
private evaluateDissolveCycles(): void {
|
|
10846
|
+
if (this.dissolveCycles.size === 0) return
|
|
10847
|
+
for (const [name, c] of this.dissolveCycles) {
|
|
10848
|
+
const period = Math.max(c.period, 1e-3)
|
|
10849
|
+
const t = this.sceneClock - Math.floor(this.sceneClock / period) * period
|
|
10850
|
+
let v = 1
|
|
10851
|
+
if (t >= c.breakAt && t < c.hiddenAt) {
|
|
10852
|
+
v = 1 - (t - c.breakAt) / Math.max(c.hiddenAt - c.breakAt, 1e-4)
|
|
10853
|
+
} else if (t >= c.hiddenAt && t < c.backAt) {
|
|
10854
|
+
v = 0
|
|
10855
|
+
} else if (t >= c.backAt && t < c.doneAt) {
|
|
10856
|
+
v = (t - c.backAt) / Math.max(c.doneAt - c.backAt, 1e-4)
|
|
10857
|
+
}
|
|
10858
|
+
this.setModelDissolve(name, v)
|
|
10859
|
+
}
|
|
10860
|
+
}
|
|
10861
|
+
|
|
10862
|
+
/** What setModelDissolve last set, or 1 for a model that has never dissolved. */
|
|
10863
|
+
getModelDissolve(modelName: string): number {
|
|
10864
|
+
return this.modelInstances.get(modelName)?.dissolve ?? 1
|
|
10865
|
+
}
|
|
10866
|
+
|
|
10733
10867
|
// Materials claimed by the model's currently-installed groups (for upsert/remove paths;
|
|
10734
10868
|
// applyStyleGroups derives claims from its input array instead).
|
|
10735
10869
|
private currentClaims(inst: ModelInstance): Map<string, string> {
|
|
@@ -11494,7 +11628,11 @@ export class Engine {
|
|
|
11494
11628
|
cd[b] = px
|
|
11495
11629
|
cd[b + 1] = floorY
|
|
11496
11630
|
cd[b + 2] = pz
|
|
11497
|
-
|
|
11631
|
+
// The root vec4's w, a constant 1 until now: how much of this subject is
|
|
11632
|
+
// still there. An effect that draws what is LEAVING her needs to know how
|
|
11633
|
+
// far along she is, and reading it here is what keeps the sparks in step
|
|
11634
|
+
// with the body without a second clock to agree with.
|
|
11635
|
+
cd[b + 3] = inst.dissolve
|
|
11498
11636
|
cd[b + 4] = px
|
|
11499
11637
|
cd[b + 5] = py
|
|
11500
11638
|
cd[b + 6] = pz
|
package/src/graph/slots.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// See docs/style-groups-spec.md §5, §7.
|
|
9
9
|
|
|
10
10
|
import { NODES_WGSL } from "../shaders/materials/nodes"
|
|
11
|
-
import { COMMON_MATERIAL_PRELUDE_WGSL, commonFsOutWgsl } from "../shaders/materials/common"
|
|
11
|
+
import { COMMON_MATERIAL_PRELUDE_WGSL, DISSOLVE_WGSL, commonFsOutWgsl } from "../shaders/materials/common"
|
|
12
12
|
import { sceneIdWriteWgsl } from "../shaders/passes/scene-contract"
|
|
13
13
|
import type { AlphaMode, RenderClass } from "./render-class"
|
|
14
14
|
|
|
@@ -65,7 +65,16 @@ const STYLE_UNIFORMS_WGSL = `struct StyleUniforms { p: array<vec4f, 16> };
|
|
|
65
65
|
`
|
|
66
66
|
|
|
67
67
|
function decls(renderClass: RenderClass, alphaMode: AlphaMode): string {
|
|
68
|
-
|
|
68
|
+
// The dissolve helpers are ALWAYS declared, unlike the hashed-alpha ones.
|
|
69
|
+
// They cost nothing on a material that never dissolves — the branch that
|
|
70
|
+
// calls them is uniform across a draw and folds away — and making them
|
|
71
|
+
// conditional would mean deciding at COMPILE time which materials may ever
|
|
72
|
+
// come apart, which is the one thing a runtime value must not need.
|
|
73
|
+
return (
|
|
74
|
+
DISSOLVE_WGSL +
|
|
75
|
+
(alphaMode === "hashed" ? HASHED_ALPHA_DECLS : "") +
|
|
76
|
+
(renderClass === "hair" ? HAIR_OVER_EYES_DECL : "")
|
|
77
|
+
)
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
// fs() header up to and including the graph body's context locals. Composed so the
|
|
@@ -92,6 +101,25 @@ function prelude(renderClass: RenderClass, alphaMode: AlphaMode): string {
|
|
|
92
101
|
let alpha = material.alpha * tex_s.a;
|
|
93
102
|
${discard}
|
|
94
103
|
|
|
104
|
+
// ── Dissolve ──
|
|
105
|
+
//
|
|
106
|
+
// A fragment is either THERE or it is not: the flake is thrown away rather
|
|
107
|
+
// than faded. A fade would be an opacity slider — she would go see-through
|
|
108
|
+
// and you would read the wall through her hair — while a threshold takes her
|
|
109
|
+
// apart in pieces, which is the thing being asked for. The depth prepass runs
|
|
110
|
+
// this identical test (rz_dissolve_threshold, shared), so depth and colour
|
|
111
|
+
// agree about which pieces are gone.
|
|
112
|
+
//
|
|
113
|
+
// The surviving edge glows: fragments within RZ_DISSOLVE_EDGE of the front
|
|
114
|
+
// are the ones about to go, and lighting them is what makes the boundary read
|
|
115
|
+
// as burning away rather than as a stencil moving over her.
|
|
116
|
+
var rz_burn = 0.0;
|
|
117
|
+
if (material.dissolve < 0.9995) {
|
|
118
|
+
let rz_t = rz_dissolve_threshold(input.restPos);
|
|
119
|
+
if (rz_t > material.dissolve) { discard; }
|
|
120
|
+
rz_burn = smoothstep(material.dissolve - RZ_DISSOLVE_EDGE, material.dissolve, rz_t);
|
|
121
|
+
}
|
|
122
|
+
|
|
95
123
|
var n = safe_normal(input.normal);
|
|
96
124
|
let v = normalize(camera.viewPos - input.worldPos);${flip}
|
|
97
125
|
${gate}
|
|
@@ -132,18 +160,23 @@ function epilogue(renderClass: RenderClass, alphaMode: AlphaMode): string {
|
|
|
132
160
|
const LIT = ` + rzLightsDiffuse(input.worldPos, n) * albedo`
|
|
133
161
|
const ALBEDO = ` let albedo = tex_color;
|
|
134
162
|
`
|
|
163
|
+
// The dissolve's burning edge, ADDED after the graph and after the lights —
|
|
164
|
+
// it is emission, not a surface colour, so nothing may ramp or shadow it. Zero
|
|
165
|
+
// on every fragment of every material that is not dissolving, which is what
|
|
166
|
+
// keeps this out of the way of a scene that never uses it.
|
|
167
|
+
const BURN = ` + RZ_BURN_COLOR * rz_burn`
|
|
135
168
|
if (renderClass === "hair") {
|
|
136
169
|
return `${ALBEDO} var outAlpha = ${alphaBase};
|
|
137
170
|
if (IS_OVER_EYES) { outAlpha = ${alphaBase} * 0.25; }
|
|
138
171
|
|
|
139
172
|
var out: FSOut;
|
|
140
|
-
out.color = vec4f(final_color${LIT}, outAlpha);
|
|
173
|
+
out.color = vec4f(final_color${LIT}${BURN}, outAlpha);
|
|
141
174
|
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
142
175
|
${ID_WRITE} return out;
|
|
143
176
|
`
|
|
144
177
|
}
|
|
145
178
|
return `${ALBEDO} var out: FSOut;
|
|
146
|
-
out.color = vec4f(final_color${LIT}, ${alphaBase});
|
|
179
|
+
out.color = vec4f(final_color${LIT}${BURN}, ${alphaBase});
|
|
147
180
|
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
148
181
|
${ID_WRITE} return out;
|
|
149
182
|
`
|
package/src/index.ts
CHANGED
package/src/shaders/cast-api.ts
CHANGED
|
@@ -45,6 +45,16 @@ struct RzSubject {
|
|
|
45
45
|
center: vec3f,
|
|
46
46
|
/** Bounding sphere: xyz centre, w radius. Deliberately generous — cull with it. */
|
|
47
47
|
bounds: vec4f,
|
|
48
|
+
/**
|
|
49
|
+
* How much of this character is still THERE: 1 whole, 0 gone.
|
|
50
|
+
*
|
|
51
|
+
* What setModelDissolve last set on them, which the material pass has already
|
|
52
|
+
* acted on by the time an effect runs — so an effect drawing what LEAVES a
|
|
53
|
+
* dissolving body (sparks, ash, a ghost) reads the same number the body was
|
|
54
|
+
* taken apart with, rather than keeping a clock of its own and hoping the two
|
|
55
|
+
* agree. 1 on a scene that never dissolves anybody.
|
|
56
|
+
*/
|
|
57
|
+
dissolve: f32,
|
|
48
58
|
/** False past the end of the cast, and every field is then zero. */
|
|
49
59
|
valid: bool,
|
|
50
60
|
}
|
|
@@ -73,6 +83,7 @@ fn rzSubject(i: i32) -> RzSubject {
|
|
|
73
83
|
if (!s.valid) { return s; }
|
|
74
84
|
let b = i * 3;
|
|
75
85
|
s.root = _rzCast[b].xyz;
|
|
86
|
+
s.dissolve = _rzCast[b].w;
|
|
76
87
|
s.center = _rzCast[b + 1].xyz;
|
|
77
88
|
s.bounds = _rzCast[b + 2];
|
|
78
89
|
return s;
|