reze-engine 0.41.0 → 0.41.2
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 +4 -2
- package/dist/engine.d.ts +31 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +190 -18
- package/dist/graph/presets/body.js +2 -2
- package/dist/graph/presets/cloth_rough.js +2 -2
- package/dist/graph/presets/cloth_smooth.js +2 -2
- package/dist/graph/presets/default.js +2 -2
- package/dist/graph/presets/eye.js +2 -2
- package/dist/graph/presets/face.js +2 -2
- package/dist/graph/presets/hair.js +2 -2
- package/dist/graph/presets/metal.js +2 -2
- package/dist/graph/presets/stockings.js +3 -3
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +426 -10
- package/dist/graph/style-group.d.ts +46 -0
- package/dist/graph/style-group.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/shaders/agx-lut.d.ts +11 -0
- package/dist/shaders/agx-lut.d.ts.map +1 -0
- package/dist/shaders/agx-lut.js +29 -0
- package/dist/shaders/materials/common.d.ts +1 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +10 -0
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +957 -576
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +76 -4
- package/package.json +1 -1
- package/src/engine.ts +211 -17
- package/src/graph/presets/body.ts +2 -2
- package/src/graph/presets/cloth_rough.ts +2 -2
- package/src/graph/presets/cloth_smooth.ts +2 -2
- package/src/graph/presets/default.ts +2 -2
- package/src/graph/presets/eye.ts +2 -2
- package/src/graph/presets/face.ts +2 -2
- package/src/graph/presets/hair.ts +2 -2
- package/src/graph/presets/metal.ts +2 -2
- package/src/graph/presets/stockings.ts +3 -3
- package/src/graph/registry.ts +439 -10
- package/src/graph/style-group.ts +44 -0
- package/src/index.ts +2 -0
- package/src/shaders/agx-lut.ts +30 -0
- package/src/shaders/materials/common.ts +10 -0
- package/src/shaders/materials/nodes.ts +962 -581
- package/src/shaders/passes/composite.ts +76 -4
package/dist/graph/registry.js
CHANGED
|
@@ -31,17 +31,21 @@ export function fmtValue(value, type) {
|
|
|
31
31
|
return `vec3f(${fmtFloat(x)})`;
|
|
32
32
|
return `vec3f(${fmtFloat(x)}, ${fmtFloat(y)}, ${fmtFloat(z)})`;
|
|
33
33
|
}
|
|
34
|
+
if (type === "float")
|
|
35
|
+
throw new Error(`color literal on float socket`);
|
|
36
|
+
// Blender's colour sockets are RGBA, so a ported literal arrives with four
|
|
37
|
+
// components whatever it feeds. Only a stop colour keeps the alpha; every other
|
|
38
|
+
// socket here is a vec3 and drops it, which is what Blender does downstream.
|
|
34
39
|
if (type !== "vec4")
|
|
35
|
-
|
|
40
|
+
return fmtValue([value[0], value[1], value[2]], type);
|
|
36
41
|
return `vec4f(${value.map(fmtFloat).join(", ")})`;
|
|
37
42
|
}
|
|
38
43
|
/** Does a literal's shape fit a socket type? (Scalar splats onto color/vector/vec4.) */
|
|
39
44
|
export function literalFits(value, type) {
|
|
40
45
|
if (typeof value === "number")
|
|
41
46
|
return true;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return type === "vec4";
|
|
47
|
+
// 3 and 4 components both fit anything but a float — see fmtValue on RGBA.
|
|
48
|
+
return type !== "float";
|
|
45
49
|
}
|
|
46
50
|
// ─── Implicit socket conversions (Blender-faithful) ──────────────────
|
|
47
51
|
// vec4 appears only on ramp stop-color literals — never linkable, so conversions
|
|
@@ -90,6 +94,18 @@ const RAMP_OUTPUTS = { color: "color", alpha: "float", fac_out: "float" };
|
|
|
90
94
|
// fac_out (.r) matches how a grayscale ramp feeds a scalar consumer in the hand ports —
|
|
91
95
|
// routing through the BT.601 color→float conversion instead would change the value.
|
|
92
96
|
const RAMP_SELECT = { color: ".rgb", alpha: ".a", fac_out: ".r" };
|
|
97
|
+
/** Principled v2 reflectance, evaluated at compile time when it can be. */
|
|
98
|
+
function foldSpecular(ior, level) {
|
|
99
|
+
const i = Number(ior);
|
|
100
|
+
const l = Number(level);
|
|
101
|
+
if (!Number.isFinite(i) || !Number.isFinite(l))
|
|
102
|
+
return `principled_specular(${ior}, ${level})`;
|
|
103
|
+
const r = (i - 1) / Math.max(i + 1, 1e-6);
|
|
104
|
+
// Snapped to 9 significant digits: the arithmetic leaves double noise
|
|
105
|
+
// (0.2² · 2 · 0.5 / 0.08 = 0.5000000000000001) that is meaningless in an f32
|
|
106
|
+
// and only makes the emitted shader harder to read.
|
|
107
|
+
return fmtFloat(Number(((r * r * 2 * Math.max(l, 0)) / 0.08).toPrecision(9)));
|
|
108
|
+
}
|
|
93
109
|
export const NODE_REGISTRY = {
|
|
94
110
|
// ── Context inputs (template locals; no emission) ──
|
|
95
111
|
texture: {
|
|
@@ -118,6 +134,42 @@ export const NODE_REGISTRY = {
|
|
|
118
134
|
reflection: "reflect(-v, n)",
|
|
119
135
|
},
|
|
120
136
|
},
|
|
137
|
+
// The scene's key light. Blender NPR presets rarely use a diffuse closure —
|
|
138
|
+
// they build their own term, typically dot(normal, direction) pushed through a
|
|
139
|
+
// ramp or a soft threshold band, because that is what gives an anime shader its
|
|
140
|
+
// hard terminator. Reaching that term needs the direction as a value, which no
|
|
141
|
+
// other node exposes: `shader_to_rgb*` and `bsdf_diffuse` bake the whole closure
|
|
142
|
+
// and hand back a result. `shadow` is the same cascade sample those closures
|
|
143
|
+
// take, so a graph can tint its own shadow instead of accepting theirs.
|
|
144
|
+
//
|
|
145
|
+
// A ported graph reads `direction` where the Blender original read an Attribute
|
|
146
|
+
// fed by a light empty; the empty and our sun mean the same thing.
|
|
147
|
+
light: {
|
|
148
|
+
inputs: {},
|
|
149
|
+
outputs: { direction: "vector", color: "color", ambient: "color", shadow: "float" },
|
|
150
|
+
contextOutputs: { direction: "l", color: "sun", ambient: "amb", shadow: "shadow" },
|
|
151
|
+
},
|
|
152
|
+
// The head bone's world basis — what an SDF face shadow is built on.
|
|
153
|
+
//
|
|
154
|
+
// That technique compares a face-shaped distance field against the light's
|
|
155
|
+
// angle IN THE HEAD'S OWN FRAME, so the shadow sweeps across the face as the
|
|
156
|
+
// light moves and stays put as the head turns. Without the frame there is
|
|
157
|
+
// nothing to measure the angle against, and the effect cannot be expressed at
|
|
158
|
+
// all. `right` also carries the sign that mirrors the field's U coordinate,
|
|
159
|
+
// which is how one half-face texture serves both sides.
|
|
160
|
+
//
|
|
161
|
+
// Read from the 頭 bone's skinning matrix, already bound to the fragment stage
|
|
162
|
+
// for the eye's rear-view gate. A model without that bone falls back to bone 0
|
|
163
|
+
// rather than sampling out of bounds; the face shadow is then wrong, not unsafe.
|
|
164
|
+
head_basis: {
|
|
165
|
+
inputs: {},
|
|
166
|
+
outputs: { forward: "vector", right: "vector", up: "vector" },
|
|
167
|
+
contextOutputs: {
|
|
168
|
+
forward: "(-normalize(skinMats[u32(max(material.headBoneIndex, 0.0))][2].xyz))",
|
|
169
|
+
right: "normalize(skinMats[u32(max(material.headBoneIndex, 0.0))][0].xyz)",
|
|
170
|
+
up: "normalize(skinMats[u32(max(material.headBoneIndex, 0.0))][1].xyz)",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
121
173
|
// PMX material's diffuse color (the authored base tint). Multiply the diffuse texture
|
|
122
174
|
// by this for the MMD-correct base — untextured materials carry their color here, so a
|
|
123
175
|
// texture-only base would render them white.
|
|
@@ -136,6 +188,258 @@ export const NODE_REGISTRY = {
|
|
|
136
188
|
outputs: { color: "color" },
|
|
137
189
|
emit: (a) => `pmx_sphere_map(${a.base}, ${a.strength}, n)`,
|
|
138
190
|
},
|
|
191
|
+
// ── Blender 5.2 Math node, full operation set ──
|
|
192
|
+
"math/absolute": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_absolute(${a.a})` },
|
|
193
|
+
"math/sqrt": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_sqrt(${a.a})` },
|
|
194
|
+
"math/inversesqrt": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_inversesqrt(${a.a})` },
|
|
195
|
+
"math/exponent": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_exponent(${a.a})` },
|
|
196
|
+
"math/sign": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_sign(${a.a})` },
|
|
197
|
+
"math/round": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_round(${a.a})` },
|
|
198
|
+
"math/floor": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_floor(${a.a})` },
|
|
199
|
+
"math/ceil": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_ceil(${a.a})` },
|
|
200
|
+
"math/truncate": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_truncate(${a.a})` },
|
|
201
|
+
"math/fraction": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_fraction(${a.a})` },
|
|
202
|
+
"math/sine": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_sine(${a.a})` },
|
|
203
|
+
"math/cosine": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_cosine(${a.a})` },
|
|
204
|
+
"math/tangent": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_tangent(${a.a})` },
|
|
205
|
+
"math/arcsine": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_arcsine(${a.a})` },
|
|
206
|
+
"math/arccosine": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_arccosine(${a.a})` },
|
|
207
|
+
"math/arctangent": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_arctangent(${a.a})` },
|
|
208
|
+
"math/radians": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_radians(${a.a})` },
|
|
209
|
+
"math/degrees": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `math_degrees(${a.a})` },
|
|
210
|
+
"math/subtract": { inputs: { a: F(0), b: F(0) }, outputs: { value: "float" }, emit: (a) => `math_subtract(${a.a}, ${a.b})` },
|
|
211
|
+
"math/divide": { inputs: { a: F(0), b: F(1) }, outputs: { value: "float" }, emit: (a) => `math_divide(${a.a}, ${a.b})` },
|
|
212
|
+
"math/logarithm": { inputs: { a: F(0), b: F(1) }, outputs: { value: "float" }, emit: (a) => `math_logarithm(${a.a}, ${a.b})` },
|
|
213
|
+
"math/minimum": { inputs: { a: F(0), b: F(0) }, outputs: { value: "float" }, emit: (a) => `math_minimum(${a.a}, ${a.b})` },
|
|
214
|
+
"math/maximum": { inputs: { a: F(0), b: F(0) }, outputs: { value: "float" }, emit: (a) => `math_maximum(${a.a}, ${a.b})` },
|
|
215
|
+
"math/less_than": { inputs: { a: F(0), b: F(0) }, outputs: { value: "float" }, emit: (a) => `math_less_than(${a.a}, ${a.b})` },
|
|
216
|
+
"math/modulo": { inputs: { a: F(0), b: F(1) }, outputs: { value: "float" }, emit: (a) => `math_modulo(${a.a}, ${a.b})` },
|
|
217
|
+
"math/floored_modulo": { inputs: { a: F(0), b: F(1) }, outputs: { value: "float" }, emit: (a) => `math_floored_modulo(${a.a}, ${a.b})` },
|
|
218
|
+
"math/snap": { inputs: { a: F(0), b: F(1) }, outputs: { value: "float" }, emit: (a) => `math_snap(${a.a}, ${a.b})` },
|
|
219
|
+
"math/pingpong": { inputs: { a: F(0), b: F(1) }, outputs: { value: "float" }, emit: (a) => `math_pingpong(${a.a}, ${a.b})` },
|
|
220
|
+
"math/arctan2": { inputs: { a: F(0), b: F(0) }, outputs: { value: "float" }, emit: (a) => `math_arctan2(${a.a}, ${a.b})` },
|
|
221
|
+
"math/multiply_add": { inputs: { a: F(0), b: F(0), c: F(0) }, outputs: { value: "float" }, emit: (a) => `math_multiply_add(${a.a}, ${a.b}, ${a.c})` },
|
|
222
|
+
"math/compare": { inputs: { a: F(0), b: F(0), c: F(0) }, outputs: { value: "float" }, emit: (a) => `math_compare(${a.a}, ${a.b}, ${a.c})` },
|
|
223
|
+
"math/smooth_min": { inputs: { a: F(0), b: F(0), c: F(0) }, outputs: { value: "float" }, emit: (a) => `math_smooth_min(${a.a}, ${a.b}, ${a.c})` },
|
|
224
|
+
"math/smooth_max": { inputs: { a: F(0), b: F(0), c: F(0) }, outputs: { value: "float" }, emit: (a) => `math_smooth_max(${a.a}, ${a.b}, ${a.c})` },
|
|
225
|
+
"math/wrap": { inputs: { a: F(0), b: F(0), c: F(0) }, outputs: { value: "float" }, emit: (a) => `math_wrap(${a.a}, ${a.b}, ${a.c})` },
|
|
226
|
+
// ── Blender 5.2 Vector Math node ──
|
|
227
|
+
"vector_math/normalize": { inputs: { a: V([0, 0, 0], true) }, outputs: { vector: "vector" }, emit: (a) => `vector_normalize(${a.a})` },
|
|
228
|
+
"vector_math/absolute": { inputs: { a: V([0, 0, 0], true) }, outputs: { vector: "vector" }, emit: (a) => `vector_absolute(${a.a})` },
|
|
229
|
+
"vector_math/floor": { inputs: { a: V([0, 0, 0], true) }, outputs: { vector: "vector" }, emit: (a) => `vector_floor(${a.a})` },
|
|
230
|
+
"vector_math/ceil": { inputs: { a: V([0, 0, 0], true) }, outputs: { vector: "vector" }, emit: (a) => `vector_ceil(${a.a})` },
|
|
231
|
+
"vector_math/fraction": { inputs: { a: V([0, 0, 0], true) }, outputs: { vector: "vector" }, emit: (a) => `vector_fraction(${a.a})` },
|
|
232
|
+
"vector_math/add": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_add(${a.a}, ${a.b})` },
|
|
233
|
+
"vector_math/subtract": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_subtract(${a.a}, ${a.b})` },
|
|
234
|
+
"vector_math/multiply": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_multiply(${a.a}, ${a.b})` },
|
|
235
|
+
"vector_math/divide": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_divide(${a.a}, ${a.b})` },
|
|
236
|
+
"vector_math/cross": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_cross(${a.a}, ${a.b})` },
|
|
237
|
+
"vector_math/project": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_project(${a.a}, ${a.b})` },
|
|
238
|
+
"vector_math/reflect": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_reflect(${a.a}, ${a.b})` },
|
|
239
|
+
"vector_math/minimum": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_minimum(${a.a}, ${a.b})` },
|
|
240
|
+
"vector_math/maximum": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_maximum(${a.a}, ${a.b})` },
|
|
241
|
+
"vector_math/modulo": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_modulo(${a.a}, ${a.b})` },
|
|
242
|
+
"vector_math/snap": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_snap(${a.a}, ${a.b})` },
|
|
243
|
+
"vector_math/dot": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { value: "float" }, emit: (a) => `vector_dot(${a.a}, ${a.b})` },
|
|
244
|
+
"vector_math/distance": { inputs: { a: V([0, 0, 0], true), b: V() }, outputs: { value: "float" }, emit: (a) => `vector_distance(${a.a}, ${a.b})` },
|
|
245
|
+
"vector_math/length": { inputs: { a: V([0, 0, 0], true) }, outputs: { value: "float" }, emit: (a) => `vector_length(${a.a})` },
|
|
246
|
+
"vector_math/scale": { inputs: { a: V([0, 0, 0], true), scale: F(1) }, outputs: { vector: "vector" }, emit: (a) => `vector_scale(${a.a}, ${a.scale})` },
|
|
247
|
+
"vector_math/multiply_add": { inputs: { a: V([0, 0, 0], true), b: V(), c: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_multiply_add(${a.a}, ${a.b}, ${a.c})` },
|
|
248
|
+
"vector_math/faceforward": { inputs: { a: V([0, 0, 0], true), b: V(), c: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_faceforward(${a.a}, ${a.b}, ${a.c})` },
|
|
249
|
+
"vector_math/refract": { inputs: { a: V([0, 0, 0], true), b: V(), ior: F(1.45) }, outputs: { vector: "vector" }, emit: (a) => `vector_refract(${a.a}, ${a.b}, ${a.ior})` },
|
|
250
|
+
"vector_math/wrap": { inputs: { a: V([0, 0, 0], true), b: V(), c: V() }, outputs: { vector: "vector" }, emit: (a) => `vector_wrap(${a.a}, ${a.b}, ${a.c})` },
|
|
251
|
+
// ── Blender 5.2 Mix (Color) blend set ──
|
|
252
|
+
"mix/add": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_add(${a.fac}, ${a.a}, ${a.b})` },
|
|
253
|
+
"mix/subtract": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_subtract(${a.fac}, ${a.a}, ${a.b})` },
|
|
254
|
+
"mix/darken": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_darken(${a.fac}, ${a.a}, ${a.b})` },
|
|
255
|
+
"mix/difference": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_difference(${a.fac}, ${a.a}, ${a.b})` },
|
|
256
|
+
"mix/exclusion": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_exclusion(${a.fac}, ${a.a}, ${a.b})` },
|
|
257
|
+
"mix/screen": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_screen(${a.fac}, ${a.a}, ${a.b})` },
|
|
258
|
+
"mix/soft_light": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_soft_light(${a.fac}, ${a.a}, ${a.b})` },
|
|
259
|
+
"mix/dodge": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_dodge(${a.fac}, ${a.a}, ${a.b})` },
|
|
260
|
+
"mix/burn": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_burn(${a.fac}, ${a.a}, ${a.b})` },
|
|
261
|
+
"mix/divide": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_divide(${a.fac}, ${a.a}, ${a.b})` },
|
|
262
|
+
"mix/hue": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_hue(${a.fac}, ${a.a}, ${a.b})` },
|
|
263
|
+
"mix/saturation": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_saturation(${a.fac}, ${a.a}, ${a.b})` },
|
|
264
|
+
"mix/value": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_value(${a.fac}, ${a.a}, ${a.b})` },
|
|
265
|
+
"mix/color": { inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() }, outputs: { color: "color" }, emit: (a) => `mix_color_blend(${a.fac}, ${a.a}, ${a.b})` },
|
|
266
|
+
// ── RGB Curves, sampled. See rgb_curve in nodes.ts for why five. ──
|
|
267
|
+
rgb_curve: {
|
|
268
|
+
inputs: {
|
|
269
|
+
color: C([1, 1, 1], true),
|
|
270
|
+
fac: F(1),
|
|
271
|
+
y0: F(0),
|
|
272
|
+
y1: F(0.25),
|
|
273
|
+
y2: F(0.5),
|
|
274
|
+
y3: F(0.75),
|
|
275
|
+
y4: F(1),
|
|
276
|
+
},
|
|
277
|
+
outputs: { color: "color" },
|
|
278
|
+
emit: (a) => `mix(${a.color}, rgb_curve(${a.color}, ${a.y0}, ${a.y1}, ${a.y2}, ${a.y3}, ${a.y4}), ${a.fac})`,
|
|
279
|
+
},
|
|
280
|
+
// UV Map — one layer on a PMX, which is the mesh UV the texture node uses.
|
|
281
|
+
uv_map: {
|
|
282
|
+
inputs: {},
|
|
283
|
+
outputs: { uv: "vector" },
|
|
284
|
+
contextOutputs: { uv: "vec3f(input.uv, 0.0)" },
|
|
285
|
+
},
|
|
286
|
+
// ── Normal Map, Vector Transform ──
|
|
287
|
+
normal_map: {
|
|
288
|
+
inputs: { color: C([0.5, 0.5, 1], true), strength: F(1) },
|
|
289
|
+
outputs: { normal: "vector" },
|
|
290
|
+
emit: (a) => `node_normal_map(${a.color}, ${a.strength}, n, input.worldPos, input.uv)`,
|
|
291
|
+
},
|
|
292
|
+
"vector_transform/world_to_camera": {
|
|
293
|
+
inputs: { vector: V([0, 0, 0], true) },
|
|
294
|
+
outputs: { vector: "vector" },
|
|
295
|
+
emit: (a) => `vector_world_to_camera(${a.vector})`,
|
|
296
|
+
},
|
|
297
|
+
"vector_transform/camera_to_world": {
|
|
298
|
+
inputs: { vector: V([0, 0, 0], true) },
|
|
299
|
+
outputs: { vector: "vector" },
|
|
300
|
+
emit: (a) => `vector_camera_to_world(${a.vector})`,
|
|
301
|
+
},
|
|
302
|
+
"vector_transform/point_world_to_camera": {
|
|
303
|
+
inputs: { vector: V([0, 0, 0], true) },
|
|
304
|
+
outputs: { vector: "vector" },
|
|
305
|
+
emit: (a) => `point_world_to_camera(${a.vector})`,
|
|
306
|
+
},
|
|
307
|
+
// ── Shader nodes. Shaders travel as RGB here (see shader_to_rgb_diffuse), so
|
|
308
|
+
// a "transparent" shader is the colour that contributes nothing; the actual
|
|
309
|
+
// cutout is the group's alphaMode, which is where transparency belongs in a
|
|
310
|
+
// rasteriser without order-independent blending.
|
|
311
|
+
bsdf_transparent: { inputs: {}, outputs: { color: "color" }, emit: () => `vec3f(0.0)` },
|
|
312
|
+
bsdf_diffuse: {
|
|
313
|
+
inputs: { color: C([0.8, 0.8, 0.8], true) },
|
|
314
|
+
outputs: { color: "color" },
|
|
315
|
+
emit: (a) => `${a.color} * shader_to_rgb_lit(n, l, sun, amb, shadow)`,
|
|
316
|
+
},
|
|
317
|
+
// ── Nodes with no meaning on a PMX, answered honestly with a constant ──
|
|
318
|
+
// Each returns what Blender returns for the absent case, so a graph that reads
|
|
319
|
+
// one degrades to a sensible look instead of failing to compile. Documented
|
|
320
|
+
// rather than silently wrong.
|
|
321
|
+
//
|
|
322
|
+
// attribute: MMD meshes carry no vertex colour layer, so Color reads white and
|
|
323
|
+
// Fac reads 1 — the identity for the multiply these usually feed.
|
|
324
|
+
attribute: {
|
|
325
|
+
inputs: {},
|
|
326
|
+
outputs: { color: "color", fac: "float" },
|
|
327
|
+
contextOutputs: { color: "vec3f(1.0)", fac: "1.0" },
|
|
328
|
+
},
|
|
329
|
+
// object_info: one model, one instance — Random is the only field with a real
|
|
330
|
+
// use (per-instance variation) and there are no instances to vary.
|
|
331
|
+
object_info: {
|
|
332
|
+
inputs: {},
|
|
333
|
+
outputs: { location: "vector", color: "color", random: "float" },
|
|
334
|
+
contextOutputs: { location: "vec3f(0.0)", color: "vec3f(1.0)", random: "0.0" },
|
|
335
|
+
},
|
|
336
|
+
// light_path: this is a raster pass, so every shaded fragment IS a camera ray.
|
|
337
|
+
light_path: {
|
|
338
|
+
inputs: {},
|
|
339
|
+
outputs: { is_camera_ray: "float", is_shadow_ray: "float", ray_depth: "float" },
|
|
340
|
+
contextOutputs: { is_camera_ray: "1.0", is_shadow_ray: "0.0", ray_depth: "0.0" },
|
|
341
|
+
},
|
|
342
|
+
// ── Image Texture on a style-group slot ──
|
|
343
|
+
// The PMX gives a material ONE image; this style needs several, so the extra
|
|
344
|
+
// maps ride on the group. Slot is part of the type because it selects a
|
|
345
|
+
// binding, which is topology rather than a value. The uv input defaults to the
|
|
346
|
+
// mesh UV, matching an unlinked Vector socket in Blender.
|
|
347
|
+
"tex_image/0": {
|
|
348
|
+
inputs: { uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" } },
|
|
349
|
+
outputs: { color: "color", alpha: "float" },
|
|
350
|
+
outputSelect: { color: ".rgb", alpha: ".a" },
|
|
351
|
+
emit: (a) => `group_tex0(${a.uv}.xy)`,
|
|
352
|
+
},
|
|
353
|
+
"tex_image/1": {
|
|
354
|
+
inputs: { uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" } },
|
|
355
|
+
outputs: { color: "color", alpha: "float" },
|
|
356
|
+
outputSelect: { color: ".rgb", alpha: ".a" },
|
|
357
|
+
emit: (a) => `group_tex1(${a.uv}.xy)`,
|
|
358
|
+
},
|
|
359
|
+
"tex_image/2": {
|
|
360
|
+
inputs: { uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" } },
|
|
361
|
+
outputs: { color: "color", alpha: "float" },
|
|
362
|
+
outputSelect: { color: ".rgb", alpha: ".a" },
|
|
363
|
+
emit: (a) => `group_tex2(${a.uv}.xy)`,
|
|
364
|
+
},
|
|
365
|
+
"tex_image/3": {
|
|
366
|
+
inputs: { uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" } },
|
|
367
|
+
outputs: { color: "color", alpha: "float" },
|
|
368
|
+
outputSelect: { color: ".rgb", alpha: ".a" },
|
|
369
|
+
emit: (a) => `group_tex3(${a.uv}.xy)`,
|
|
370
|
+
},
|
|
371
|
+
// ── Blender 5.2 colour utilities ──
|
|
372
|
+
separate_color: {
|
|
373
|
+
inputs: { color: C([1, 1, 1], true) },
|
|
374
|
+
outputs: { r: "float", g: "float", b: "float" },
|
|
375
|
+
outputSelect: { r: ".r", g: ".g", b: ".b" },
|
|
376
|
+
emit: (a) => a.color,
|
|
377
|
+
},
|
|
378
|
+
"separate_color/hsv": {
|
|
379
|
+
inputs: { color: C([1, 1, 1], true) },
|
|
380
|
+
outputs: { h: "float", s: "float", v: "float" },
|
|
381
|
+
outputSelect: { h: ".x", s: ".y", v: ".z" },
|
|
382
|
+
emit: (a) => `rgb_to_hsv(${a.color})`,
|
|
383
|
+
},
|
|
384
|
+
"separate_color/hsl": {
|
|
385
|
+
inputs: { color: C([1, 1, 1], true) },
|
|
386
|
+
outputs: { h: "float", s: "float", l: "float" },
|
|
387
|
+
outputSelect: { h: ".x", s: ".y", l: ".z" },
|
|
388
|
+
emit: (a) => `rgb_to_hsl(${a.color})`,
|
|
389
|
+
},
|
|
390
|
+
combine_color: {
|
|
391
|
+
inputs: { r: F(0), g: F(0), b: F(0) },
|
|
392
|
+
outputs: { color: "color" },
|
|
393
|
+
emit: (a) => `vec3f(${a.r}, ${a.g}, ${a.b})`,
|
|
394
|
+
},
|
|
395
|
+
"combine_color/hsv": {
|
|
396
|
+
inputs: { h: F(0), s: F(0), v: F(0) },
|
|
397
|
+
outputs: { color: "color" },
|
|
398
|
+
emit: (a) => `hsv_to_rgb(vec3f(${a.h}, ${a.s}, ${a.v}))`,
|
|
399
|
+
},
|
|
400
|
+
"combine_color/hsl": {
|
|
401
|
+
inputs: { h: F(0), s: F(0), l: F(0) },
|
|
402
|
+
outputs: { color: "color" },
|
|
403
|
+
emit: (a) => `hsl_to_rgb(vec3f(${a.h}, ${a.s}, ${a.l}))`,
|
|
404
|
+
},
|
|
405
|
+
combine_xyz: {
|
|
406
|
+
inputs: { x: F(0), y: F(0), z: F(0) },
|
|
407
|
+
outputs: { vector: "vector" },
|
|
408
|
+
emit: (a) => `vec3f(${a.x}, ${a.y}, ${a.z})`,
|
|
409
|
+
},
|
|
410
|
+
gamma: {
|
|
411
|
+
inputs: { color: C([1, 1, 1], true), gamma: F(1) },
|
|
412
|
+
outputs: { color: "color" },
|
|
413
|
+
emit: (a) => `node_gamma(${a.color}, ${a.gamma})`,
|
|
414
|
+
},
|
|
415
|
+
// Map Range: the interpolation is topology, as everywhere else here. The
|
|
416
|
+
// clamped form is Blender's default, which is why it carries the bare name.
|
|
417
|
+
map_range: {
|
|
418
|
+
inputs: { value: F(0, true), from_min: F(0), from_max: F(1), to_min: F(0), to_max: F(1) },
|
|
419
|
+
outputs: { value: "float" },
|
|
420
|
+
emit: (a) => `map_range_clamped(${a.value}, ${a.from_min}, ${a.from_max}, ${a.to_min}, ${a.to_max})`,
|
|
421
|
+
},
|
|
422
|
+
"map_range/linear": {
|
|
423
|
+
inputs: { value: F(0, true), from_min: F(0), from_max: F(1), to_min: F(0), to_max: F(1) },
|
|
424
|
+
outputs: { value: "float" },
|
|
425
|
+
emit: (a) => `map_range_linear(${a.value}, ${a.from_min}, ${a.from_max}, ${a.to_min}, ${a.to_max})`,
|
|
426
|
+
},
|
|
427
|
+
"map_range/smoothstep": {
|
|
428
|
+
inputs: { value: F(0, true), from_min: F(0), from_max: F(1), to_min: F(0), to_max: F(1) },
|
|
429
|
+
outputs: { value: "float" },
|
|
430
|
+
emit: (a) => `map_range_smooth(${a.value}, ${a.from_min}, ${a.from_max}, ${a.to_min}, ${a.to_max})`,
|
|
431
|
+
},
|
|
432
|
+
// Vector Rotate — the rotation TYPE is topology.
|
|
433
|
+
"vector_rotate/axis_angle": {
|
|
434
|
+
inputs: { vector: V([0, 0, 0], true), center: V(), axis: V([0, 0, 1]), angle: F(0) },
|
|
435
|
+
outputs: { vector: "vector" },
|
|
436
|
+
emit: (a) => `vector_rotate_axis(${a.vector}, ${a.center}, ${a.axis}, ${a.angle})`,
|
|
437
|
+
},
|
|
438
|
+
"vector_rotate/euler_xyz": {
|
|
439
|
+
inputs: { vector: V([0, 0, 0], true), center: V(), rotation: V() },
|
|
440
|
+
outputs: { vector: "vector" },
|
|
441
|
+
emit: (a) => `vector_rotate_euler(${a.vector}, ${a.center}, ${a.rotation})`,
|
|
442
|
+
},
|
|
139
443
|
// ── Literals as nodes (for editor ergonomics; inlined literals work too) ──
|
|
140
444
|
value: { inputs: { value: F(0) }, outputs: { value: "float" }, emit: (a) => a.value },
|
|
141
445
|
rgb: { inputs: { color: C() }, outputs: { color: "color" }, emit: (a) => a.color },
|
|
@@ -179,8 +483,27 @@ export const NODE_REGISTRY = {
|
|
|
179
483
|
outputSelect: RAMP_SELECT,
|
|
180
484
|
emit: (a) => `ramp_constant_edge_aa(${a.fac}, ${a.edge}, ${a.color0}, ${a.color1})`,
|
|
181
485
|
},
|
|
486
|
+
// Blender ColorRamp LINEAR with three arbitrary stops. A two-stop ramp cannot
|
|
487
|
+
// express a shadow that passes through a colour on its way — a warm terminator
|
|
488
|
+
// between cool shadow and lit skin is three stops, and that middle band is
|
|
489
|
+
// where a lot of NPR character lives. Decomposing it into two ramps and a
|
|
490
|
+
// select costs three extra nodes and stops reading as a ramp.
|
|
491
|
+
ramp_linear_3: {
|
|
492
|
+
inputs: {
|
|
493
|
+
fac: F(0.5, true),
|
|
494
|
+
pos0: F(0),
|
|
495
|
+
color0: V4([0, 0, 0, 1]),
|
|
496
|
+
pos1: F(0.5),
|
|
497
|
+
color1: V4([0.5, 0.5, 0.5, 1]),
|
|
498
|
+
pos2: F(1),
|
|
499
|
+
color2: V4([1, 1, 1, 1]),
|
|
500
|
+
},
|
|
501
|
+
outputs: RAMP_OUTPUTS,
|
|
502
|
+
outputSelect: RAMP_SELECT,
|
|
503
|
+
emit: (a) => `ramp_linear3(${a.fac}, ${a.pos0}, ${a.color0}, ${a.pos1}, ${a.color1}, ${a.pos2}, ${a.color2})`,
|
|
504
|
+
},
|
|
182
505
|
// Blender ColorRamp LINEAR with 3 stops black→white→black (triangular peak at 0.5).
|
|
183
|
-
//
|
|
506
|
+
// Folded to closed form like the hand port.
|
|
184
507
|
ramp_tri: {
|
|
185
508
|
inputs: { fac: F(0.5, true) },
|
|
186
509
|
outputs: { value: "float" },
|
|
@@ -270,11 +593,35 @@ export const NODE_REGISTRY = {
|
|
|
270
593
|
emit: (a) => `layer_weight_facing(${a.blend}, n, v)`,
|
|
271
594
|
},
|
|
272
595
|
// ── Lighting capture ──
|
|
596
|
+
/**
|
|
597
|
+
* Shader → RGB on a white diffuse closure, reduced to a scalar.
|
|
598
|
+
*
|
|
599
|
+
* Rec.709 luminance, kept exactly as it was: every shipped preset ramps this,
|
|
600
|
+
* and changing the weights would move every terminator in the library.
|
|
601
|
+
*/
|
|
273
602
|
shader_to_rgb_diffuse: {
|
|
274
603
|
inputs: {},
|
|
275
604
|
outputs: { value: "float" },
|
|
276
605
|
emit: () => `shader_to_rgb_diffuse(n, l, sun, amb, shadow)`,
|
|
277
606
|
},
|
|
607
|
+
/**
|
|
608
|
+
* The same closure as a COLOUR, which is what Blender's Shader to RGB
|
|
609
|
+
* actually returns.
|
|
610
|
+
*
|
|
611
|
+
* A warm sun against a cool ambient radiates warm light and cool shadow, and
|
|
612
|
+
* that colour IS the look in most of this style. Reducing it to luminance
|
|
613
|
+
* first — the only thing reachable before — leaves every ramp working on grey.
|
|
614
|
+
*
|
|
615
|
+
* Linking this into a scalar socket still yields a float, because Blender's
|
|
616
|
+
* own implicit Color → Value conversion (BT.601) happens at the link. So the
|
|
617
|
+
* two nodes differ only in whether the graph wants the colour or the scalar,
|
|
618
|
+
* and each gets Blender's answer for its own question.
|
|
619
|
+
*/
|
|
620
|
+
shader_to_rgb: {
|
|
621
|
+
inputs: {},
|
|
622
|
+
outputs: { color: "color" },
|
|
623
|
+
emit: () => `shader_to_rgb_lit(n, l, sun, amb, shadow)`,
|
|
624
|
+
},
|
|
278
625
|
// ── Vector ──
|
|
279
626
|
separate_xyz: {
|
|
280
627
|
inputs: { vector: V([0, 0, 0], true) },
|
|
@@ -323,18 +670,87 @@ export const NODE_REGISTRY = {
|
|
|
323
670
|
// ── Principled BSDF (frozen 3.6 legacy-EEVEE semantics: eval_principled port) ──
|
|
324
671
|
// normal defaults to the template's shading normal; link a bump/normal_map chain
|
|
325
672
|
// to perturb it (body/cloth_rough noise bump).
|
|
673
|
+
/**
|
|
674
|
+
* Principled BSDF, Blender 5.2 socket names.
|
|
675
|
+
*
|
|
676
|
+
* v2 (4.0+) renamed most of what a graph touches and re-derived one of them,
|
|
677
|
+
* which is exactly where a transcribed port goes quietly wrong: Specular
|
|
678
|
+
* became "Specular IOR Level" and no longer sets reflectance directly — IOR
|
|
679
|
+
* does, and the level scales it. So f0 is computed the v2 way here, and the
|
|
680
|
+
* defaults land on 0.04 for ior 1.5 at level 0.5, matching both versions at
|
|
681
|
+
* their defaults while tracking v2 when a preset moves either.
|
|
682
|
+
*
|
|
683
|
+
* Emission Strength defaults to 0 in v2 where 3.6's Emission was visible, so a
|
|
684
|
+
* naive port turns every emissive material black. Following v2 means honouring
|
|
685
|
+
* that default and letting the preset say otherwise.
|
|
686
|
+
*
|
|
687
|
+
* Not implemented, and not silently wrong — a graph setting these gets the
|
|
688
|
+
* base BSDF rather than a wrong approximation of them: Coat, Transmission,
|
|
689
|
+
* Subsurface, Anisotropic, Thin Film. They need path-traced or
|
|
690
|
+
* multi-scatter machinery this renderer does not have.
|
|
691
|
+
*/
|
|
326
692
|
principled: {
|
|
327
693
|
inputs: {
|
|
328
|
-
|
|
694
|
+
base_color: C([0.8, 0.8, 0.8], true),
|
|
329
695
|
metallic: F(0),
|
|
330
|
-
specular: F(0.5),
|
|
331
696
|
roughness: F(0.5),
|
|
332
|
-
|
|
333
|
-
|
|
697
|
+
ior: F(1.5),
|
|
698
|
+
specular_ior_level: F(0.5),
|
|
699
|
+
sheen_weight: F(0),
|
|
334
700
|
sheen_tint: F(0),
|
|
701
|
+
emission_color: C([1, 1, 1]),
|
|
702
|
+
emission_strength: F(0),
|
|
335
703
|
normal: { type: "vector", contextDefault: "n" },
|
|
704
|
+
/** Ours, not Blender's: caps firefly speculars from noise-bumped NDF
|
|
705
|
+
* aliasing, which EEVEE hides behind TAA and we have none. */
|
|
706
|
+
spec_clamp: F(1e30),
|
|
336
707
|
},
|
|
337
708
|
outputs: { color: "color" },
|
|
338
|
-
emit: (a) =>
|
|
709
|
+
emit: (a) => {
|
|
710
|
+
// Fold the reflectance when both sockets are literals, which is nearly
|
|
711
|
+
// always. It keeps the emitted WGSL as tight as the 3.6 form was — and at
|
|
712
|
+
// the defaults it folds to exactly 0.5, which is what 3.6's Specular held,
|
|
713
|
+
// so the two versions agree byte-for-byte where a preset changed nothing.
|
|
714
|
+
const spec = foldSpecular(a.ior, a.specular_ior_level);
|
|
715
|
+
const bsdf = `eval_principled(PrincipledIn(${a.base_color}, ${a.metallic}, ` +
|
|
716
|
+
`${spec}, ${a.roughness}, ` +
|
|
717
|
+
`${a.spec_clamp}, ${a.sheen_weight}, ${a.sheen_tint}), ${a.normal}, l, v, sun, amb, shadow)`;
|
|
718
|
+
// v2 defaults Emission Strength to 0, which is the overwhelming case. Emit
|
|
719
|
+
// the term only when it can do something, so the common shader carries no
|
|
720
|
+
// dead add and the output stays readable.
|
|
721
|
+
return a.emission_strength === "0.0" ? bsdf : `${bsdf} + ${a.emission_color} * ${a.emission_strength}`;
|
|
722
|
+
},
|
|
339
723
|
},
|
|
340
724
|
};
|
|
725
|
+
// ─── Blender socket parity ────────────────────────────────────────────
|
|
726
|
+
// Blender's Math node carries three Value inputs whatever operation is selected,
|
|
727
|
+
// its Vector Math node three Vectors plus a Scale, and Vector Rotate the union of
|
|
728
|
+
// every rotation type's inputs. Sockets are part of the node, not of the mode.
|
|
729
|
+
//
|
|
730
|
+
// Declaring the same set here is what makes a port mechanical: a transcriber maps
|
|
731
|
+
// socket-for-socket and never has to know which inputs a given operation happens
|
|
732
|
+
// to read. The emit functions read only what their operation uses, so the added
|
|
733
|
+
// sockets change no generated WGSL — an unread one is inert.
|
|
734
|
+
//
|
|
735
|
+
// Applied as a pass rather than written into each entry so that every operation
|
|
736
|
+
// KEEPS the default it already declared (divide's b is 1, not 0). Only genuinely
|
|
737
|
+
// missing sockets are added.
|
|
738
|
+
function widen(prefix, sockets) {
|
|
739
|
+
for (const [key, spec] of Object.entries(NODE_REGISTRY)) {
|
|
740
|
+
if (!key.startsWith(prefix))
|
|
741
|
+
continue;
|
|
742
|
+
for (const [name, def] of Object.entries(sockets)) {
|
|
743
|
+
if (!(name in spec.inputs))
|
|
744
|
+
spec.inputs[name] = def;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
widen("math/", { a: F(0), b: F(0), c: F(0) });
|
|
749
|
+
widen("vector_math/", { a: V(), b: V(), c: V(), scale: F(1) });
|
|
750
|
+
widen("vector_rotate/", {
|
|
751
|
+
vector: V([0, 0, 0], true),
|
|
752
|
+
center: V(),
|
|
753
|
+
axis: V([0, 0, 1]),
|
|
754
|
+
angle: F(0),
|
|
755
|
+
rotation: V(),
|
|
756
|
+
});
|
|
@@ -15,6 +15,52 @@ export type StyleGroup = {
|
|
|
15
15
|
renderClass?: RenderClass;
|
|
16
16
|
/** Alpha-handling axis, orthogonal to renderClass. Default "opaque". */
|
|
17
17
|
alphaMode?: AlphaMode;
|
|
18
|
+
/**
|
|
19
|
+
* Extra image maps for this group's shading, up to four.
|
|
20
|
+
*
|
|
21
|
+
* A PMX material carries exactly ONE image, and a Blender-authored look is
|
|
22
|
+
* usually built on more than that — a lightmap encoding shadow thresholds and
|
|
23
|
+
* specular masks in its channels, a ramp, a detail map. Those belong to the
|
|
24
|
+
* LOOK rather than to the model, so they ride on the group: the same graph
|
|
25
|
+
* applied to another character brings its own maps with it.
|
|
26
|
+
*
|
|
27
|
+
* Read by the `tex_image/0`…`tex_image/3` nodes, in order. A slot left empty
|
|
28
|
+
* (or a graph reading past the end) samples 1×1 white, so a missing map is a
|
|
29
|
+
* no-op rather than a failure.
|
|
30
|
+
*/
|
|
31
|
+
images?: (GroupImage | GroupImageSource | null)[];
|
|
32
|
+
};
|
|
33
|
+
export type GroupImageSource = ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
|
34
|
+
/**
|
|
35
|
+
* A group map, with the one thing the engine cannot infer from the pixels:
|
|
36
|
+
* whether they are colour.
|
|
37
|
+
*
|
|
38
|
+
* A PMX texture is always colour, so material textures are uploaded sRGB
|
|
39
|
+
* unconditionally. A group's maps are not — half of them are data whose channels
|
|
40
|
+
* encode thresholds, masks and distances, and running those through an sRGB
|
|
41
|
+
* decode bends every value. Blender asks the same question per image, and a
|
|
42
|
+
* ported look carries whatever answer it was authored with.
|
|
43
|
+
*
|
|
44
|
+
* A bare source reads as data (`srgb: false`), because a map that is silently
|
|
45
|
+
* mis-decoded as colour is the harder failure to see: it renders, just wrong.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* `premultiplied` — whether the upload should multiply RGB by alpha.
|
|
49
|
+
*
|
|
50
|
+
* Blender premultiplies a straight-alpha image for rendering, so a transparent
|
|
51
|
+
* pixel contributes nothing. A highlight texture is white everywhere with its
|
|
52
|
+
* shape only in alpha, and without this it screens to a white card instead of
|
|
53
|
+
* resolving to its shape. Left off for a channel-packed map, whose alpha is a
|
|
54
|
+
* separate signal and would scale colour by something unrelated.
|
|
55
|
+
*
|
|
56
|
+
* It has to happen HERE rather than in the host's decode: copyExternalImageToTexture
|
|
57
|
+
* converts to the destination's tagged alpha mode, so a premultiplied ImageBitmap
|
|
58
|
+
* copied into an untagged texture is un-premultiplied again on the way in.
|
|
59
|
+
*/
|
|
60
|
+
export type GroupImage = {
|
|
61
|
+
source: GroupImageSource;
|
|
62
|
+
srgb?: boolean;
|
|
63
|
+
premultiplied?: boolean;
|
|
18
64
|
};
|
|
19
65
|
export type GroupDiagnostic = {
|
|
20
66
|
groupId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style-group.d.ts","sourceRoot":"","sources":["../../src/graph/style-group.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,MAAM,MAAM,UAAU,GAAG;IACvB,wFAAwF;IACxF,EAAE,EAAE,MAAM,CAAA;IACV;2CACuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2EAA2E;IAC3E,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,sFAAsF;IACtF,KAAK,EAAE,WAAW,CAAA;IAClB,uEAAuE;IACvE,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,wEAAwE;IACxE,SAAS,CAAC,EAAE,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"style-group.d.ts","sourceRoot":"","sources":["../../src/graph/style-group.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,MAAM,MAAM,UAAU,GAAG;IACvB,wFAAwF;IACxF,EAAE,EAAE,MAAM,CAAA;IACV;2CACuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2EAA2E;IAC3E,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,sFAAsF;IACtF,KAAK,EAAE,WAAW,CAAA;IAClB,uEAAuE;IACvE,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,wEAAwE;IACxE,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,CAAC,UAAU,GAAG,gBAAgB,GAAG,IAAI,CAAC,EAAE,CAAA;CAClD,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,gBAAgB,GAAG,iBAAiB,CAAA;AAEjF;;;;;;;;;;;;GAYG;AACH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE9F,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,CAAA;AAEzF,MAAM,MAAM,sBAAsB,GAAG;IACnC,sCAAsC;IACtC,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,iEAAiE;IACjE,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,kFAAkF;IAClF,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAC;IAAC,OAAO,EAAE,SAAS,EAAE,CAAA;CAAE,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { compileGraph, validateGraph, assignStyleSlots, type CompileOptions, typ
|
|
|
4
4
|
export type { ShaderGraph, GraphNode, GraphLink, ExposedParam, SocketValue, Diagnostic, } from "./graph/schema";
|
|
5
5
|
export { NODE_REGISTRY, type NodeSpec, type SockT } from "./graph/registry";
|
|
6
6
|
export { RENDER_CLASSES, type RenderClass, type AlphaMode, type RenderClassInfo } from "./graph/render-class";
|
|
7
|
-
export type { StyleGroup, GroupDiagnostic, ApplyStyleGroupsResult, ApplyStyleGroupResult, } from "./graph/style-group";
|
|
7
|
+
export type { StyleGroup, GroupImage, GroupImageSource, GroupDiagnostic, ApplyStyleGroupsResult, ApplyStyleGroupResult, } from "./graph/style-group";
|
|
8
8
|
export { HAIR_GRAPH } from "./graph/presets/hair";
|
|
9
9
|
export { DEFAULT_GRAPH } from "./graph/presets/default";
|
|
10
10
|
export { CLOTH_SMOOTH_GRAPH } from "./graph/presets/cloth_smooth";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|