reze-engine 0.18.1 → 0.20.0
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/README.md +291 -252
- package/dist/engine.d.ts +45 -41
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +401 -421
- package/dist/graph/compile.d.ts +6 -0
- package/dist/graph/compile.d.ts.map +1 -1
- package/dist/graph/compile.js +1 -1
- package/dist/graph/presets/body.js +1 -1
- package/dist/graph/presets/cloth_rough.js +1 -1
- package/dist/graph/presets/cloth_smooth.js +1 -1
- package/dist/graph/presets/default.d.ts.map +1 -1
- package/dist/graph/presets/default.js +14 -7
- package/dist/graph/presets/eye.js +1 -1
- package/dist/graph/presets/face.js +1 -1
- package/dist/graph/presets/hair.js +1 -1
- package/dist/graph/presets/metal.js +1 -1
- package/dist/graph/presets/stockings.js +1 -1
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +8 -0
- package/dist/graph/render-class.d.ts +16 -0
- package/dist/graph/render-class.d.ts.map +1 -0
- package/dist/graph/render-class.js +15 -0
- package/dist/graph/schema.d.ts +4 -5
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/slots.d.ts +7 -12
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +68 -100
- package/dist/graph/style-group.d.ts +38 -0
- package/dist/graph/style-group.d.ts.map +1 -0
- package/dist/graph/style-group.js +6 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/tga-loader.d.ts +7 -0
- package/dist/tga-loader.d.ts.map +1 -0
- package/dist/tga-loader.js +154 -0
- package/package.json +2 -2
- package/src/engine.ts +4488 -4479
- package/src/graph/compile.ts +7 -1
- package/src/graph/presets/body.ts +1 -1
- package/src/graph/presets/cloth_rough.ts +1 -1
- package/src/graph/presets/cloth_smooth.ts +1 -1
- package/src/graph/presets/default.ts +14 -7
- package/src/graph/presets/eye.ts +1 -1
- package/src/graph/presets/face.ts +1 -1
- package/src/graph/presets/hair.ts +1 -1
- package/src/graph/presets/metal.ts +1 -1
- package/src/graph/presets/stockings.ts +1 -1
- package/src/graph/registry.ts +8 -0
- package/src/graph/render-class.ts +33 -0
- package/src/graph/schema.ts +4 -6
- package/src/graph/slots.ts +76 -113
- package/src/graph/style-group.ts +39 -0
- package/src/index.ts +7 -1
- package/src/tga-loader.ts +154 -0
package/src/graph/compile.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { Diagnostic, ExposedParam, GraphNode, SocketValue, StyleGraph } fro
|
|
|
8
8
|
import { NODE_REGISTRY, canConvert, convert, fmtValue, literalFits } from "./registry"
|
|
9
9
|
import type { NodeSpec, SockT } from "./registry"
|
|
10
10
|
import { assembleModule } from "./slots"
|
|
11
|
+
import type { AlphaMode, RenderClass } from "./render-class"
|
|
11
12
|
|
|
12
13
|
export type CompileOptions = {
|
|
13
14
|
/** Fold exposed params to their defaults as consts (no StyleUniforms binding).
|
|
@@ -16,6 +17,11 @@ export type CompileOptions = {
|
|
|
16
17
|
/** Override the graph output with any node's socket — Blender's node-preview
|
|
17
18
|
* (Ctrl+Shift+Click viewer) workflow for the editor. */
|
|
18
19
|
previewNode?: { node: string; socket: string }
|
|
20
|
+
/** Pass-integration class the fs() shell is assembled for (stencil/cull/gate). The
|
|
21
|
+
* graph is pure shading; the group supplies this. Default "auto". */
|
|
22
|
+
renderClass?: RenderClass
|
|
23
|
+
/** Alpha-handling axis, orthogonal to renderClass. Default "opaque". */
|
|
24
|
+
alphaMode?: AlphaMode
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
/** UBO slot for one exposed param: write `value` at style.p[vec4Index] (+ component). */
|
|
@@ -337,6 +343,6 @@ export function compileGraph(graph: StyleGraph, opts: CompileOptions = {}): Comp
|
|
|
337
343
|
lines.push(` let final_color = ${outputExpr(out, "color")}; // @node:${out.node}`)
|
|
338
344
|
|
|
339
345
|
const fsBody = lines.join("\n")
|
|
340
|
-
const wgsl = assembleModule(
|
|
346
|
+
const wgsl = assembleModule(opts.renderClass ?? "auto", opts.alphaMode ?? "opaque", fsBody, usesStyle.current)
|
|
341
347
|
return { ok: true, wgsl, fsBody, slotMap, diagnostics, prunedNodes }
|
|
342
348
|
}
|
|
@@ -8,7 +8,7 @@ import type { StyleGraph } from "../schema"
|
|
|
8
8
|
export const CLOTH_SMOOTH_GRAPH: StyleGraph = {
|
|
9
9
|
version: 1,
|
|
10
10
|
name: "Smooth Cloth",
|
|
11
|
-
|
|
11
|
+
tags: ["cloth_smooth"],
|
|
12
12
|
nodes: [
|
|
13
13
|
{ id: "tex", type: "texture" },
|
|
14
14
|
{ id: "geo", type: "geometry" },
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
// Default material
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Default material — the neutral base used two ways: the ungrouped fallback (a material
|
|
2
|
+
// in no style group renders this) and the blank-canvas starter the editor's "New graph"
|
|
3
|
+
// begins from, so the two always agree. MMD-correct PBSDF base: diffuse texture × the
|
|
4
|
+
// PMX material diffuse color → Principled BSDF (Metallic 0, Specular 0.5, Roughness 0.5).
|
|
5
|
+
// The material-color multiply is what keeps untextured/solid-color materials from
|
|
6
|
+
// rendering white (they carry their color in material.diffuse, not a texture).
|
|
6
7
|
|
|
7
8
|
import type { StyleGraph } from "../schema"
|
|
8
9
|
|
|
9
10
|
export const DEFAULT_GRAPH: StyleGraph = {
|
|
10
11
|
version: 1,
|
|
11
12
|
name: "Principled BSDF",
|
|
12
|
-
|
|
13
|
+
tags: ["default"],
|
|
13
14
|
nodes: [
|
|
14
15
|
{ id: "tex", type: "texture" },
|
|
16
|
+
{ id: "mat", type: "material_diffuse" },
|
|
17
|
+
{ id: "base", type: "mix/multiply", inputs: { fac: 1.0 } }, // texture × material diffuse
|
|
15
18
|
{
|
|
16
19
|
id: "principled",
|
|
17
20
|
type: "principled",
|
|
18
21
|
inputs: { metallic: 0.0, specular: 0.5, roughness: 0.5, spec_clamp: 10.0, sheen: 0.0, sheen_tint: 0.0 },
|
|
19
22
|
},
|
|
20
23
|
],
|
|
21
|
-
links: [
|
|
24
|
+
links: [
|
|
25
|
+
{ from: { node: "tex", socket: "color" }, to: { node: "base", socket: "a" } },
|
|
26
|
+
{ from: { node: "mat", socket: "color" }, to: { node: "base", socket: "b" } },
|
|
27
|
+
{ from: { node: "base", socket: "color" }, to: { node: "principled", socket: "base" } },
|
|
28
|
+
],
|
|
22
29
|
output: { node: "principled", socket: "color" },
|
|
23
30
|
}
|
package/src/graph/presets/eye.ts
CHANGED
package/src/graph/registry.ts
CHANGED
|
@@ -137,6 +137,14 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
137
137
|
reflection: "reflect(-v, n)",
|
|
138
138
|
},
|
|
139
139
|
},
|
|
140
|
+
// PMX material's diffuse color (the authored base tint). Multiply the diffuse texture
|
|
141
|
+
// by this for the MMD-correct base — untextured materials carry their color here, so a
|
|
142
|
+
// texture-only base would render them white.
|
|
143
|
+
material_diffuse: {
|
|
144
|
+
inputs: {},
|
|
145
|
+
outputs: { color: "color" },
|
|
146
|
+
contextOutputs: { color: "material.diffuseColor" },
|
|
147
|
+
},
|
|
140
148
|
|
|
141
149
|
// ── Literals as nodes (for editor ergonomics; inlined literals work too) ──
|
|
142
150
|
value: { inputs: { value: F(0) }, outputs: { value: "float" }, emit: (a) => a.value },
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Render-class + alpha-mode: the small, closed, engine-owned vocabularies that carry
|
|
2
|
+
// pass integration for a style group. A group's node graph is pure shading; these select
|
|
3
|
+
// how the engine wires that shading into the passes. Not user-extensible — this is where
|
|
4
|
+
// rendering correctness lives (stencil, cull, draw order, alpha test). See
|
|
5
|
+
// docs/style-groups-spec.md §5.
|
|
6
|
+
|
|
7
|
+
/** Pass-integration class. Selects stencil/cull/draw-order behavior. */
|
|
8
|
+
export type RenderClass = "auto" | "eye" | "hair"
|
|
9
|
+
|
|
10
|
+
/** Alpha-handling axis, orthogonal to RenderClass. "hashed" = Wyman & McGuire object-space
|
|
11
|
+
* hashed alpha test (stockings); "opaque" = the standard near-zero threshold discard. */
|
|
12
|
+
export type AlphaMode = "opaque" | "hashed"
|
|
13
|
+
|
|
14
|
+
/** Descriptive manifest for hosts (reze-design) so the render-class picker is data-driven
|
|
15
|
+
* instead of hardcoding strings. The effect implementations stay engine-side; this only
|
|
16
|
+
* describes them. `pairsWith` = the effect needs a counterpart class present to show. */
|
|
17
|
+
export type RenderClassInfo = {
|
|
18
|
+
id: RenderClass
|
|
19
|
+
label: string
|
|
20
|
+
description: string
|
|
21
|
+
pairsWith?: RenderClass
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const RENDER_CLASSES: readonly RenderClassInfo[] = [
|
|
25
|
+
{ id: "auto", label: "Standard", description: "Opaque or transparent, derived from material alpha." },
|
|
26
|
+
{ id: "eye", label: "Eye", description: "Stamps the see-through stencil; visible through hair.", pairsWith: "hair" },
|
|
27
|
+
{
|
|
28
|
+
id: "hair",
|
|
29
|
+
label: "Hair",
|
|
30
|
+
description: "Reads the eye stencil so eyes show through the silhouette.",
|
|
31
|
+
pairsWith: "eye",
|
|
32
|
+
},
|
|
33
|
+
]
|
package/src/graph/schema.ts
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
// time (Blender Normal Z → engine Y), the compiler never sees Blender conventions.
|
|
4
4
|
// See docs/graph-compiler-spec.md.
|
|
5
5
|
|
|
6
|
-
import type { MaterialPreset } from "../engine"
|
|
7
|
-
|
|
8
6
|
export type SocketValue = number | [number, number, number] | [number, number, number, number]
|
|
9
7
|
|
|
10
8
|
export type GraphNode = {
|
|
@@ -39,15 +37,15 @@ export type ExposedParam = {
|
|
|
39
37
|
export type StyleGraph = {
|
|
40
38
|
version: 1
|
|
41
39
|
name: string
|
|
42
|
-
/** Which preset slot this style targets. The slot owns pass integration (stencil
|
|
43
|
-
* variants like hair-over-eyes, alpha semantics, draw order) — always on,
|
|
44
|
-
* graph-invisible. The graph only restyles the slot's shading. */
|
|
45
|
-
slot: MaterialPreset
|
|
46
40
|
nodes: GraphNode[]
|
|
47
41
|
links: GraphLink[]
|
|
48
42
|
/** Must resolve to a color (vec3f) or float (auto-splatted) socket. */
|
|
49
43
|
output: { node: string; socket: string }
|
|
50
44
|
params?: ExposedParam[]
|
|
45
|
+
/** Soft, host-only hints for library filtering and smart default-group / render-class
|
|
46
|
+
* matching (e.g. ["hair"]). Ignored by the compiler; round-tripped. A graph is pure
|
|
47
|
+
* shading — pass integration lives on the style group's renderClass, not here. */
|
|
48
|
+
tags?: string[]
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
export type Diagnostic = {
|
package/src/graph/slots.ts
CHANGED
|
@@ -1,52 +1,24 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// fs() shell assembly. The graph computes only `final_color`; everything around it —
|
|
2
|
+
// texture sample, alpha discard, lighting context, built-in gates, and the FSOut/mask
|
|
3
|
+
// write — is composed here from two orthogonal, engine-owned axes:
|
|
4
|
+
// RenderClass ("auto" | "eye" | "hair") — stencil/cull/draw-order + eye rear-gate +
|
|
5
|
+
// hair over-eyes alpha. See render-class.ts.
|
|
6
|
+
// AlphaMode ("opaque" | "hashed") — standard threshold discard vs Wyman hashed.
|
|
7
|
+
// A style group supplies both; this file turns them into the WGSL shell around fsBody.
|
|
8
|
+
// See docs/style-groups-spec.md §5, §7.
|
|
5
9
|
|
|
6
10
|
import { NODES_WGSL } from "../shaders/materials/nodes"
|
|
7
11
|
import { COMMON_MATERIAL_PRELUDE_WGSL } from "../shaders/materials/common"
|
|
8
|
-
import type {
|
|
12
|
+
import type { AlphaMode, RenderClass } from "./render-class"
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
prelude?: string
|
|
15
|
-
/** Tail of fs(): consumes `final_color` + template locals, writes FSOut. */
|
|
16
|
-
epilogue: string
|
|
17
|
-
}
|
|
14
|
+
// ── Module-scope declarations ──
|
|
15
|
+
// hair: the over-eyes pipeline-override constant (a second pipeline is compiled with
|
|
16
|
+
// IS_OVER_EYES=1). hashed: the Wyman & McGuire object-space hashed-alpha helpers.
|
|
17
|
+
const HAIR_OVER_EYES_DECL = `override IS_OVER_EYES: bool = false;
|
|
18
18
|
|
|
19
|
-
const DEFAULT_EPILOGUE = ` var out: FSOut;
|
|
20
|
-
out.color = vec4f(final_color, alpha);
|
|
21
|
-
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
22
|
-
return out;
|
|
23
19
|
`
|
|
24
20
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
// Hair's built-in over-eyes effect (see hair.ts): the engine compiles two pipeline
|
|
28
|
-
// variants from the same module — normal opaque hair, and a stencil-matched re-draw
|
|
29
|
-
// at 25% alpha so eyes read through the silhouette. Always on for the hair slot.
|
|
30
|
-
const HAIR_TEMPLATE: SlotTemplate = {
|
|
31
|
-
decls: `override IS_OVER_EYES: bool = false;
|
|
32
|
-
|
|
33
|
-
`,
|
|
34
|
-
epilogue: ` var outAlpha = alpha;
|
|
35
|
-
if (IS_OVER_EYES) { outAlpha = alpha * 0.25; }
|
|
36
|
-
|
|
37
|
-
var out: FSOut;
|
|
38
|
-
out.color = vec4f(final_color, outAlpha);
|
|
39
|
-
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
40
|
-
return out;
|
|
41
|
-
`,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Stockings' built-in alpha behavior (see stockings.ts): Wyman & McGuire hashed
|
|
45
|
-
// alpha testing on bind-pose restPos replaces the standard threshold discard —
|
|
46
|
-
// sort-independent through self-overlap, dither pinned to the fabric. The graph
|
|
47
|
-
// only computes final_color; the hash gate and the alpha=1 output are slot-owned.
|
|
48
|
-
const STOCKINGS_TEMPLATE: SlotTemplate = {
|
|
49
|
-
decls: `fn _hash3d_wm(a: vec3f) -> f32 {
|
|
21
|
+
const HASHED_ALPHA_DECLS = `fn _hash3d_wm(a: vec3f) -> f32 {
|
|
50
22
|
return _hash33(a).x * 0.5 + 0.5;
|
|
51
23
|
}
|
|
52
24
|
fn hashed_alpha_threshold(co: vec3f) -> f32 {
|
|
@@ -71,89 +43,48 @@ fn hashed_alpha_threshold(co: vec3f) -> f32 {
|
|
|
71
43
|
return clamp(threshold, 1e-6, 1.0);
|
|
72
44
|
}
|
|
73
45
|
|
|
74
|
-
|
|
75
|
-
prelude: `@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
76
|
-
let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
|
|
77
|
-
// Hashed alpha test (EEVEE "Hashed" blend) instead of the standard threshold.
|
|
78
|
-
let alpha = material.alpha * tex_s.a;
|
|
79
|
-
if (alpha < hashed_alpha_threshold(input.restPos)) { discard; }
|
|
80
|
-
|
|
81
|
-
let n = safe_normal(input.normal);
|
|
82
|
-
let v = normalize(camera.viewPos - input.worldPos);
|
|
83
|
-
let l = -light.lights[0].direction.xyz;
|
|
84
|
-
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
85
|
-
let amb = light.ambientColor.xyz;
|
|
86
|
-
let shadow = sampleShadow(input.worldPos, n);
|
|
87
|
-
let tex_color = tex_s.rgb;
|
|
88
|
-
|
|
89
|
-
`,
|
|
90
|
-
epilogue: ` var out: FSOut;
|
|
91
|
-
out.color = vec4f(final_color, 1.0);
|
|
92
|
-
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
93
|
-
return out;
|
|
94
|
-
`,
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Eye's built-in rear-view gate (see eye.ts): open-shell PMX heads don't occlude
|
|
98
|
-
// the eye from behind, so it would draw (and stamp the see-through stencil) through
|
|
99
|
-
// the back of the head. Gate by camera-vs-face hemisphere via the 頭 bone's skinning
|
|
100
|
-
// matrix. Discarding here drops color, depth, and the stencil stamp together. The
|
|
101
|
-
// stencil-stamp pipeline state + front-face cull are slot-owned in createSlotPipeline;
|
|
102
|
-
// the graph only computes the eye's shading (Principled + emission).
|
|
103
|
-
const EYE_TEMPLATE: SlotTemplate = {
|
|
104
|
-
decls: "",
|
|
105
|
-
prelude: `@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
106
|
-
let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
|
|
107
|
-
let alpha = material.alpha * tex_s.a;
|
|
108
|
-
if (alpha < 0.001) { discard; }
|
|
109
|
-
|
|
110
|
-
let n = safe_normal(input.normal);
|
|
111
|
-
let v = normalize(camera.viewPos - input.worldPos);
|
|
46
|
+
`
|
|
112
47
|
|
|
48
|
+
// Eye's built-in rear-view gate (open-shell PMX heads don't occlude the eye from behind,
|
|
49
|
+
// so gate by camera-vs-face hemisphere via the 頭 bone; discard drops color + depth +
|
|
50
|
+
// the stencil stamp together). Injected between the view vectors and the lighting setup.
|
|
51
|
+
const EYE_REAR_GATE = `
|
|
113
52
|
if (material.headBoneIndex >= 0.0) {
|
|
114
53
|
let hm = skinMats[u32(material.headBoneIndex)];
|
|
115
54
|
let faceDir = -normalize(hm[2].xyz);
|
|
116
55
|
if (dot(faceDir, v) < -0.15) { discard; }
|
|
117
56
|
}
|
|
57
|
+
`
|
|
118
58
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
let amb = light.ambientColor.xyz;
|
|
122
|
-
let shadow = sampleShadow(input.worldPos, n);
|
|
123
|
-
let tex_color = tex_s.rgb;
|
|
124
|
-
|
|
125
|
-
`,
|
|
126
|
-
epilogue: DEFAULT_EPILOGUE,
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export const SLOT_TEMPLATES: Partial<Record<MaterialPreset, SlotTemplate>> = {
|
|
130
|
-
hair: HAIR_TEMPLATE,
|
|
131
|
-
stockings: STOCKINGS_TEMPLATE,
|
|
132
|
-
eye: EYE_TEMPLATE,
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function slotTemplate(slot: MaterialPreset): SlotTemplate {
|
|
136
|
-
return SLOT_TEMPLATES[slot] ?? DEFAULT_TEMPLATE
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Adjust-tier uniforms — fixed 16-vec4f block (256 B) so the single shared material
|
|
140
|
-
// bind group layout serves every graph; non-graph presets bind a zero buffer.
|
|
59
|
+
// ── Adjust-tier uniforms — fixed 16-vec4f block (256 B) shared by the single material
|
|
60
|
+
// bind group layout; non-graph draws bind a zero buffer. ──
|
|
141
61
|
export const STYLE_UNIFORMS_WGSL = `struct StyleUniforms { p: array<vec4f, 16> };
|
|
142
62
|
@group(2) @binding(4) var<uniform> style: StyleUniforms;
|
|
143
63
|
|
|
144
64
|
`
|
|
145
65
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
66
|
+
function decls(renderClass: RenderClass, alphaMode: AlphaMode): string {
|
|
67
|
+
return (alphaMode === "hashed" ? HASHED_ALPHA_DECLS : "") + (renderClass === "hair" ? HAIR_OVER_EYES_DECL : "")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// fs() header up to and including the graph body's context locals. Composed so the
|
|
71
|
+
// hand-written material shaders' local names (tex_color, n, v, l, sun, amb, shadow) are
|
|
72
|
+
// preserved exactly — the registry's context nodes and emit functions reference them.
|
|
73
|
+
function prelude(renderClass: RenderClass, alphaMode: AlphaMode): string {
|
|
74
|
+
const discard =
|
|
75
|
+
alphaMode === "hashed"
|
|
76
|
+
? " if (alpha < hashed_alpha_threshold(input.restPos)) { discard; }"
|
|
77
|
+
: " if (alpha < 0.001) { discard; }"
|
|
78
|
+
const gate = renderClass === "eye" ? EYE_REAR_GATE : ""
|
|
79
|
+
return `@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
149
80
|
let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
|
|
150
|
-
// MMD alpha semantics: material alpha × texture alpha
|
|
151
|
-
// their shapes in the alpha channel).
|
|
81
|
+
// MMD alpha semantics: material alpha × texture alpha.
|
|
152
82
|
let alpha = material.alpha * tex_s.a;
|
|
153
|
-
|
|
83
|
+
${discard}
|
|
154
84
|
|
|
155
85
|
let n = safe_normal(input.normal);
|
|
156
86
|
let v = normalize(camera.viewPos - input.worldPos);
|
|
87
|
+
${gate}
|
|
157
88
|
let l = -light.lights[0].direction.xyz;
|
|
158
89
|
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
159
90
|
let amb = light.ambientColor.xyz;
|
|
@@ -161,18 +92,50 @@ const FS_PRELUDE = `@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
|
161
92
|
let tex_color = tex_s.rgb;
|
|
162
93
|
|
|
163
94
|
`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Tail of fs(): consumes `final_color` + locals, writes FSOut. hashed forces output
|
|
98
|
+
// alpha to 1 (the discard already did the cutout); hair scales alpha for the over-eyes
|
|
99
|
+
// pass when IS_OVER_EYES is compiled true.
|
|
100
|
+
function epilogue(renderClass: RenderClass, alphaMode: AlphaMode): string {
|
|
101
|
+
const alphaBase = alphaMode === "hashed" ? "1.0" : "alpha"
|
|
102
|
+
if (renderClass === "hair") {
|
|
103
|
+
return ` var outAlpha = ${alphaBase};
|
|
104
|
+
if (IS_OVER_EYES) { outAlpha = ${alphaBase} * 0.25; }
|
|
105
|
+
|
|
106
|
+
var out: FSOut;
|
|
107
|
+
out.color = vec4f(final_color, outAlpha);
|
|
108
|
+
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
109
|
+
return out;
|
|
110
|
+
`
|
|
111
|
+
}
|
|
112
|
+
return ` var out: FSOut;
|
|
113
|
+
out.color = vec4f(final_color, ${alphaBase});
|
|
114
|
+
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
115
|
+
return out;
|
|
116
|
+
`
|
|
117
|
+
}
|
|
164
118
|
|
|
165
|
-
|
|
166
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Assemble a full WGSL module: shared node library + bindings/skinning prelude, optional
|
|
121
|
+
* StyleUniforms, render-class/alpha-mode declarations, the composed fs() shell, and the
|
|
122
|
+
* graph's node body.
|
|
123
|
+
*/
|
|
124
|
+
export function assembleModule(
|
|
125
|
+
renderClass: RenderClass,
|
|
126
|
+
alphaMode: AlphaMode,
|
|
127
|
+
fsBody: string,
|
|
128
|
+
includeStyleUniforms: boolean,
|
|
129
|
+
): string {
|
|
167
130
|
return (
|
|
168
131
|
NODES_WGSL +
|
|
169
132
|
COMMON_MATERIAL_PRELUDE_WGSL +
|
|
170
133
|
(includeStyleUniforms ? STYLE_UNIFORMS_WGSL : "") +
|
|
171
|
-
|
|
172
|
-
(
|
|
134
|
+
decls(renderClass, alphaMode) +
|
|
135
|
+
prelude(renderClass, alphaMode) +
|
|
173
136
|
fsBody +
|
|
174
137
|
"\n" +
|
|
175
|
-
|
|
138
|
+
epilogue(renderClass, alphaMode) +
|
|
176
139
|
"}\n"
|
|
177
140
|
)
|
|
178
141
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Style groups — the user-facing unit of material styling. A group binds a set of
|
|
2
|
+
// materials to a node graph (pure shading) plus the engine-owned pass-integration axes
|
|
3
|
+
// (renderClass + alphaMode). Each material belongs to at most one group; grouped
|
|
4
|
+
// materials render via the group's compiled-graph pipeline, ungrouped ones fall back to
|
|
5
|
+
// the hand-written preset path. See docs/style-groups-spec.md.
|
|
6
|
+
|
|
7
|
+
import type { Diagnostic, StyleGraph } from "./schema"
|
|
8
|
+
import type { AlphaMode, RenderClass } from "./render-class"
|
|
9
|
+
import type { StyleSlot } from "./compile"
|
|
10
|
+
|
|
11
|
+
export type StyleGroup = {
|
|
12
|
+
/** Stable, unique within a model; /^[a-z0-9_-]+$/. The engine keys installs by this. */
|
|
13
|
+
id: string
|
|
14
|
+
/** Human display name ("Skirt", "Visor"); host-owned, round-tripped, never interpreted
|
|
15
|
+
* by the engine. Defaults to `id`. */
|
|
16
|
+
label?: string
|
|
17
|
+
/** Material names in this group; each material is in AT MOST one group. */
|
|
18
|
+
materials: string[]
|
|
19
|
+
/** Pure shading (StyleGraph has no `slot` — integration lives here on the group). */
|
|
20
|
+
graph: StyleGraph
|
|
21
|
+
/** Pass-integration class: stencil/cull/draw-order. Default "auto". */
|
|
22
|
+
renderClass?: RenderClass
|
|
23
|
+
/** Alpha-handling axis, orthogonal to renderClass. Default "opaque". */
|
|
24
|
+
alphaMode?: AlphaMode
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type GroupDiagnostic = { groupId: string; diagnostics: Diagnostic[]; ok: boolean }
|
|
28
|
+
|
|
29
|
+
export type ApplyStyleGroupsResult = {
|
|
30
|
+
/** Every group compiled + swapped. */
|
|
31
|
+
ok: boolean
|
|
32
|
+
groups: GroupDiagnostic[]
|
|
33
|
+
/** Names in a group that no model material matches (warning). */
|
|
34
|
+
unknownMaterials: string[]
|
|
35
|
+
/** Materials claimed by >1 group; the last group in array order wins (logged). */
|
|
36
|
+
conflicts: string[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ApplyStyleGroupResult = { ok: boolean; diagnostics: Diagnostic[]; slotMap: StyleSlot[] }
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,6 @@ export {
|
|
|
10
10
|
type MaterialPreset,
|
|
11
11
|
type ResolvedMaterialPreset,
|
|
12
12
|
type MaterialPresetMap,
|
|
13
|
-
type ApplyStyleResult,
|
|
14
13
|
type GizmoDragEvent,
|
|
15
14
|
type GizmoDragCallback,
|
|
16
15
|
type GizmoDragKind,
|
|
@@ -33,6 +32,13 @@ export type {
|
|
|
33
32
|
Diagnostic,
|
|
34
33
|
} from "./graph/schema"
|
|
35
34
|
export { NODE_REGISTRY, type NodeSpec, type SockT } from "./graph/registry"
|
|
35
|
+
export { RENDER_CLASSES, type RenderClass, type AlphaMode, type RenderClassInfo } from "./graph/render-class"
|
|
36
|
+
export type {
|
|
37
|
+
StyleGroup,
|
|
38
|
+
GroupDiagnostic,
|
|
39
|
+
ApplyStyleGroupsResult,
|
|
40
|
+
ApplyStyleGroupResult,
|
|
41
|
+
} from "./graph/style-group"
|
|
36
42
|
export { HAIR_GRAPH } from "./graph/presets/hair"
|
|
37
43
|
export { DEFAULT_GRAPH } from "./graph/presets/default"
|
|
38
44
|
export { CLOTH_SMOOTH_GRAPH } from "./graph/presets/cloth_smooth"
|