reze-engine 0.62.1 → 0.64.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.
Files changed (83) hide show
  1. package/dist/engine.d.ts +95 -57
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +466 -162
  4. package/dist/graph/compile.d.ts.map +1 -1
  5. package/dist/graph/compile.js +4 -1
  6. package/dist/graph/registry.d.ts +3 -0
  7. package/dist/graph/registry.d.ts.map +1 -1
  8. package/dist/graph/registry.js +133 -0
  9. package/dist/graph/schema.d.ts +12 -0
  10. package/dist/graph/schema.d.ts.map +1 -1
  11. package/dist/graph/slots.d.ts.map +1 -1
  12. package/dist/graph/slots.js +11 -20
  13. package/dist/index.d.ts +9 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/shaders/lights.d.ts +3 -2
  17. package/dist/shaders/lights.d.ts.map +1 -1
  18. package/dist/shaders/lights.js +16 -3
  19. package/dist/shaders/materials/common.d.ts.map +1 -1
  20. package/dist/shaders/materials/common.js +63 -56
  21. package/dist/shaders/materials/nodes.d.ts +1 -1
  22. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  23. package/dist/shaders/materials/nodes.js +210 -0
  24. package/dist/shaders/passes/composite.d.ts.map +1 -1
  25. package/dist/shaders/passes/composite.js +13 -1
  26. package/dist/shaders/passes/cull.d.ts +1 -1
  27. package/dist/shaders/passes/cull.d.ts.map +1 -1
  28. package/dist/shaders/passes/cull.js +7 -3
  29. package/dist/shaders/passes/ground.d.ts.map +1 -1
  30. package/dist/shaders/passes/ground.js +32 -55
  31. package/dist/shaders/passes/particles.d.ts +11 -1
  32. package/dist/shaders/passes/particles.d.ts.map +1 -1
  33. package/dist/shaders/passes/particles.js +9 -4
  34. package/dist/shaders/scene-light-api.d.ts +18 -10
  35. package/dist/shaders/scene-light-api.d.ts.map +1 -1
  36. package/dist/shaders/scene-light-api.js +58 -46
  37. package/dist/shadow-cascades.d.ts +47 -20
  38. package/dist/shadow-cascades.d.ts.map +1 -1
  39. package/dist/shadow-cascades.js +36 -13
  40. package/dist/unity/character.d.ts +59 -0
  41. package/dist/unity/character.d.ts.map +1 -0
  42. package/dist/unity/character.js +120 -0
  43. package/dist/unity/globals.d.ts +93 -0
  44. package/dist/unity/globals.d.ts.map +1 -0
  45. package/dist/unity/globals.js +340 -0
  46. package/dist/unity/host.d.ts +137 -0
  47. package/dist/unity/host.d.ts.map +1 -0
  48. package/dist/unity/host.js +489 -0
  49. package/dist/unity/looks.d.ts +88 -0
  50. package/dist/unity/looks.d.ts.map +1 -0
  51. package/dist/unity/looks.js +295 -0
  52. package/dist/unity/skin.d.ts +9 -0
  53. package/dist/unity/skin.d.ts.map +1 -0
  54. package/dist/unity/skin.js +117 -0
  55. package/dist/unity/stage.d.ts +148 -0
  56. package/dist/unity/stage.d.ts.map +1 -0
  57. package/dist/unity/stage.js +375 -0
  58. package/dist/unity/state.d.ts +22 -0
  59. package/dist/unity/state.d.ts.map +1 -0
  60. package/dist/unity/state.js +170 -0
  61. package/package.json +1 -1
  62. package/src/engine.ts +504 -176
  63. package/src/graph/compile.ts +3 -1
  64. package/src/graph/registry.ts +142 -0
  65. package/src/graph/schema.ts +9 -0
  66. package/src/graph/slots.ts +11 -20
  67. package/src/index.ts +11 -0
  68. package/src/shaders/lights.ts +16 -3
  69. package/src/shaders/materials/common.ts +63 -57
  70. package/src/shaders/materials/nodes.ts +210 -0
  71. package/src/shaders/passes/composite.ts +13 -1
  72. package/src/shaders/passes/cull.ts +7 -3
  73. package/src/shaders/passes/ground.ts +32 -55
  74. package/src/shaders/passes/particles.ts +19 -5
  75. package/src/shaders/scene-light-api.ts +59 -46
  76. package/src/shadow-cascades.ts +61 -32
  77. package/src/unity/character.ts +169 -0
  78. package/src/unity/globals.ts +443 -0
  79. package/src/unity/host.ts +587 -0
  80. package/src/unity/looks.ts +372 -0
  81. package/src/unity/skin.ts +128 -0
  82. package/src/unity/stage.ts +568 -0
  83. package/src/unity/state.ts +172 -0
@@ -0,0 +1,295 @@
1
+ // Native looks: a model dressed in the game's own materials.
2
+ //
3
+ // A look names translated shader variants (host.ts), the shared images they
4
+ // sample (ramps, matcaps), and per material the passes it draws and the values
5
+ // it carries — what a game character's material held, with the model's own
6
+ // texture standing in for its _MainTex. Optionally a CharacterRig, which makes
7
+ // the model a character in the game's sense (character.ts).
8
+ //
9
+ // The frame, as the game's CharacterFeature lays it out after the opaques:
10
+ // 1. every native opaque material's passes (Outline, ForwardBase), in queue
11
+ // order — after the engine's own opaque bundle, so both share one depth;
12
+ // 2. CharHairShadow, CharFaceShadow, CharHairTransE, each over every
13
+ // material that has one (the hair's shadow on the face, by stencil);
14
+ // 3. the eyes' see-through-hair passes, Override1 for all, then 2, then 3;
15
+ // and the transparent queue in the engine's transparent phase. Shadows are the
16
+ // engine's: a native material still casts through its pass.
17
+ import { UNITY_SKIN_WGSL, tangentsFor } from "./skin";
18
+ import { characterBlock } from "./character";
19
+ import { engineToGame, gameToEngine } from "./globals";
20
+ const OPAQUE_MODES = ["ALWAYS", "FORWARDBASE"];
21
+ const CHARACTER_MODES = ["CHARHAIRSHADOW", "CHARFACESHADOW", "CHARHAIRTRANSE"];
22
+ const OVERRIDE_MODES = ["OVERRIDE1", "OVERRIDE2", "OVERRIDE3"];
23
+ export class NativeLooks {
24
+ constructor(device, host) {
25
+ this.device = device;
26
+ this.installs = new Map();
27
+ this.globals = {};
28
+ this.frameTextures = {};
29
+ this.scale = 8;
30
+ this.host = host;
31
+ this.skinPipeline = device.createComputePipeline({
32
+ label: "native skinning",
33
+ layout: "auto",
34
+ compute: {
35
+ module: device.createShaderModule({
36
+ label: "native skinning",
37
+ code: UNITY_SKIN_WGSL,
38
+ }),
39
+ entryPoint: "main",
40
+ },
41
+ });
42
+ this.zero = device.createBuffer({
43
+ label: "native zero stream",
44
+ size: 64,
45
+ usage: GPUBufferUsage.VERTEX,
46
+ });
47
+ this.white = device.createBuffer({
48
+ label: "native white stream",
49
+ size: 64,
50
+ usage: GPUBufferUsage.VERTEX,
51
+ mappedAtCreation: true,
52
+ });
53
+ new Float32Array(this.white.getMappedRange()).fill(1);
54
+ this.white.unmap();
55
+ }
56
+ get size() {
57
+ return this.installs.size;
58
+ }
59
+ has(model) {
60
+ return this.installs.has(model);
61
+ }
62
+ /** Is this model's material drawn by its native look? */
63
+ dresses(model, material) {
64
+ const i = this.installs.get(model);
65
+ return !!i && i.dressed.some((d) => d.draw.materialName === material);
66
+ }
67
+ install(model, look) {
68
+ this.remove(model.name);
69
+ const d = this.device;
70
+ for (const s of look.shaders)
71
+ this.host.register(s);
72
+ const views = new Map();
73
+ const textures = [];
74
+ for (const [key, t] of Object.entries(look.textures)) {
75
+ const tex = d.createTexture({
76
+ label: `${model.name}: ${key}`,
77
+ size: [t.width, t.height],
78
+ format: t.srgb ? "rgba8unorm-srgb" : "rgba8unorm",
79
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
80
+ });
81
+ d.queue.writeTexture({ texture: tex }, t.data, { bytesPerRow: t.width * 4 }, [t.width, t.height]);
82
+ textures.push(tex);
83
+ views.set(key, tex.createView());
84
+ }
85
+ const n = model.vertices.length / 8;
86
+ const buf = (label, floats, usage) => d.createBuffer({
87
+ label: `${model.name}: ${label}`,
88
+ size: Math.max(16, floats * 4),
89
+ usage,
90
+ });
91
+ const out = GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX;
92
+ const pos = buf("native position", n * 3, out);
93
+ const nrm = buf("native normal", n * 3, out);
94
+ const tan = buf("native tangent", n * 4, out);
95
+ const restTan = buf("rest tangent", n * 4, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
96
+ d.queue.writeBuffer(restTan, 0, tangentsFor(model.vertices, model.indices));
97
+ const params = d.createBuffer({
98
+ label: `${model.name}: skin params`,
99
+ size: 16,
100
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
101
+ });
102
+ d.queue.writeBuffer(params, 0, new Uint32Array([n, 0, 0, 0]));
103
+ const skinBind = d.createBindGroup({
104
+ label: `${model.name}: native skinning`,
105
+ layout: this.skinPipeline.getBindGroupLayout(0),
106
+ entries: [
107
+ { binding: 0, resource: { buffer: params } },
108
+ { binding: 1, resource: { buffer: model.vertexBuffer } },
109
+ { binding: 2, resource: { buffer: model.jointsBuffer } },
110
+ { binding: 3, resource: { buffer: model.weightsBuffer } },
111
+ { binding: 4, resource: { buffer: model.skinMatrixBuffer } },
112
+ { binding: 5, resource: { buffer: restTan } },
113
+ { binding: 6, resource: { buffer: pos } },
114
+ { binding: 7, resource: { buffer: nrm } },
115
+ { binding: 8, resource: { buffer: tan } },
116
+ ],
117
+ });
118
+ const dressed = [];
119
+ for (const draw of model.draws) {
120
+ const spec = look.materials.find((m) => m.materials.includes(draw.materialName));
121
+ if (spec)
122
+ dressed.push({ draw, spec });
123
+ }
124
+ this.installs.set(model.name, {
125
+ model,
126
+ look,
127
+ views,
128
+ textures,
129
+ pos,
130
+ nrm,
131
+ tan,
132
+ restTan,
133
+ params,
134
+ skinBind,
135
+ dressed,
136
+ block: {},
137
+ });
138
+ }
139
+ remove(name) {
140
+ const i = this.installs.get(name);
141
+ if (!i)
142
+ return;
143
+ this.installs.delete(name);
144
+ this.host.release(`${name}|`);
145
+ const bufs = [i.pos, i.nrm, i.tan, i.restTan, i.params];
146
+ const texs = i.textures;
147
+ // Freed once the GPU is done with the frames that may still name them.
148
+ void this.device.queue.onSubmittedWorkDone().then(() => {
149
+ for (const b of bufs)
150
+ b.destroy();
151
+ for (const t of texs)
152
+ t.destroy();
153
+ });
154
+ }
155
+ /**
156
+ * Before the scene pass: skin every dressed model and compute each
157
+ * character's block; the frame's Unity globals come computed (engine.ts).
158
+ */
159
+ prepare(encoder, globals, scale, textures) {
160
+ if (!this.installs.size)
161
+ return;
162
+ this.scale = scale;
163
+ this.globals = globals;
164
+ this.frameTextures = textures;
165
+ const pass = encoder.beginComputePass({ label: "native skinning" });
166
+ pass.setPipeline(this.skinPipeline);
167
+ for (const i of this.installs.values()) {
168
+ pass.setBindGroup(0, i.skinBind);
169
+ pass.dispatchWorkgroups(Math.ceil(i.model.vertices.length / 8 / 64));
170
+ i.block = i.look.rig
171
+ ? characterBlock(i.look.rig, i.model.headSkin(), i.model.headRest, scale)
172
+ : {
173
+ unity_RenderingLayer: new Uint32Array([i.model.layers >>> 0, 0, 0, 0]),
174
+ };
175
+ }
176
+ pass.end();
177
+ }
178
+ streams(i, shader) {
179
+ return this.host.inputs(shader).map(({ semantic }) => {
180
+ if (semantic === "POSITION")
181
+ return {
182
+ buffer: i.pos,
183
+ offset: 0,
184
+ stride: 12,
185
+ format: "float32x3",
186
+ };
187
+ if (semantic === "NORMAL")
188
+ return {
189
+ buffer: i.nrm,
190
+ offset: 0,
191
+ stride: 12,
192
+ format: "float32x3",
193
+ };
194
+ if (semantic === "TANGENT")
195
+ return {
196
+ buffer: i.tan,
197
+ offset: 0,
198
+ stride: 16,
199
+ format: "float32x4",
200
+ };
201
+ if (semantic === "TEXCOORD0")
202
+ return {
203
+ buffer: i.model.vertexBuffer,
204
+ offset: 24,
205
+ stride: 32,
206
+ format: "float32x2",
207
+ };
208
+ // A missing vertex colour reads white, as Unity binds it; any other
209
+ // missing stream reads zero.
210
+ if (semantic === "COLOR")
211
+ return {
212
+ buffer: this.white,
213
+ offset: 0,
214
+ stride: 0,
215
+ format: "float32x4",
216
+ };
217
+ return {
218
+ buffer: this.zero,
219
+ offset: 0,
220
+ stride: 0,
221
+ format: "float32x4",
222
+ };
223
+ });
224
+ }
225
+ drawPass(pass, i, d, p) {
226
+ if (i.model.hidden(d.draw.materialName))
227
+ return;
228
+ const s = this.scale;
229
+ const perDraw = {
230
+ // The skinned streams are in engine units; the game's world is 1/scale of them.
231
+ unity_ObjectToWorld: engineToGame(s),
232
+ unity_WorldToObject: gameToEngine(s),
233
+ unity_WorldTransformParams: [0, 0, 0, 1],
234
+ unity_LightData: [0, 0, 0, 0],
235
+ unity_LightIndices: [
236
+ [0, 0, 0, 0],
237
+ [0, 0, 0, 0],
238
+ ],
239
+ unity_LODFade: [1, 1, 0, 0],
240
+ };
241
+ const textures = {
242
+ ...this.frameTextures,
243
+ };
244
+ for (const [slot, key] of Object.entries(d.spec.textures))
245
+ textures[slot] = key === "@diffuse" ? d.draw.diffuse : i.views.get(key);
246
+ this.host.draw(pass, {
247
+ shader: p.shader,
248
+ state: p.state,
249
+ streams: this.streams(i, p.shader),
250
+ index: {
251
+ buffer: i.model.indexBuffer,
252
+ format: "uint32",
253
+ offset: d.draw.firstIndex * 4,
254
+ count: d.draw.count,
255
+ },
256
+ sources: [perDraw, i.block, d.spec.values, this.globals],
257
+ textures,
258
+ key: `${i.model.name}|${d.draw.materialName}|${p.lightMode}|${p.shader}`,
259
+ pass: "main",
260
+ });
261
+ }
262
+ /** Every dressed material whose queue is in range, in queue order. */
263
+ each(opaque) {
264
+ const out = [];
265
+ for (const i of this.installs.values())
266
+ for (const d of i.dressed)
267
+ if (d.spec.queue <= 2500 === opaque)
268
+ out.push({ i, d });
269
+ return out.sort((a, b) => a.d.spec.queue - b.d.spec.queue);
270
+ }
271
+ /** After the engine's opaque phase: the opaques, then the character passes. */
272
+ drawOpaque(pass) {
273
+ if (!this.installs.size)
274
+ return;
275
+ const list = this.each(true);
276
+ for (const { i, d } of list)
277
+ for (const p of d.spec.passes)
278
+ if (OPAQUE_MODES.includes(p.lightMode))
279
+ this.drawPass(pass, i, d, p);
280
+ for (const mode of [...CHARACTER_MODES, ...OVERRIDE_MODES])
281
+ for (const { i, d } of list)
282
+ for (const p of d.spec.passes)
283
+ if (p.lightMode === mode)
284
+ this.drawPass(pass, i, d, p);
285
+ }
286
+ /** In the engine's transparent phase. */
287
+ drawTransparent(pass) {
288
+ if (!this.installs.size)
289
+ return;
290
+ for (const { i, d } of this.each(false))
291
+ for (const p of d.spec.passes)
292
+ if (OPAQUE_MODES.includes(p.lightMode))
293
+ this.drawPass(pass, i, d, p);
294
+ }
295
+ }
@@ -0,0 +1,9 @@
1
+ export declare const UNITY_SKIN_WGSL = "\nstruct Params { count: u32, _a: u32, _b: u32, _c: u32 }\n@group(0) @binding(0) var<uniform> P: Params;\n@group(0) @binding(1) var<storage, read> verts: array<f32>; // pos(3) nrm(3) uv(2)\n@group(0) @binding(2) var<storage, read> joints: array<u32>; // uint16x4: two words\n@group(0) @binding(3) var<storage, read> weights: array<u32>; // unorm8x4: one word\n@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;\n@group(0) @binding(5) var<storage, read> tangents: array<f32>; // rest tangent(4)\n@group(0) @binding(6) var<storage, read_write> outPos: array<f32>;\n@group(0) @binding(7) var<storage, read_write> outNrm: array<f32>;\n@group(0) @binding(8) var<storage, read_write> outTan: array<f32>;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let v = id.x;\n if (v >= P.count) { return; }\n let p = vec4f(verts[v * 8u], verts[v * 8u + 1u], verts[v * 8u + 2u], 1.0);\n let n = vec3f(verts[v * 8u + 3u], verts[v * 8u + 4u], verts[v * 8u + 5u]);\n let t = vec4f(tangents[v * 4u], tangents[v * 4u + 1u], tangents[v * 4u + 2u], tangents[v * 4u + 3u]);\n let j01 = joints[v * 2u];\n let j23 = joints[v * 2u + 1u];\n let j = vec4u(j01 & 0xffffu, j01 >> 16u, j23 & 0xffffu, j23 >> 16u);\n var w = unpack4x8unorm(weights[v]);\n // Renormalised as the material vertex stage does: PMX ships unnormalised\n // weights on extras, and a sum of zero is the bind pose.\n let s = w.x + w.y + w.z + w.w;\n w = select(vec4f(1.0, 0.0, 0.0, 0.0), w / s, s > 0.0001);\n var sp = vec3f(0.0);\n var sn = vec3f(0.0);\n var st = vec3f(0.0);\n for (var k = 0u; k < 4u; k++) {\n let m = skinMats[j[k]];\n let r = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);\n sp += (m * p).xyz * w[k];\n sn += (r * n) * w[k];\n st += (r * t.xyz) * w[k];\n }\n // Degenerate blends (opposing bones at 50/50) cancel to zero; up, not NaN.\n let ln = dot(sn, sn);\n let lt = dot(st, st);\n sn = select(vec3f(0.0, 1.0, 0.0), sn * inverseSqrt(ln), ln > 1e-12);\n st = select(vec3f(1.0, 0.0, 0.0), st * inverseSqrt(lt), lt > 1e-12);\n outPos[v * 3u] = sp.x; outPos[v * 3u + 1u] = sp.y; outPos[v * 3u + 2u] = sp.z;\n outNrm[v * 3u] = sn.x; outNrm[v * 3u + 1u] = sn.y; outNrm[v * 3u + 2u] = sn.z;\n outTan[v * 4u] = st.x; outTan[v * 4u + 1u] = st.y; outTan[v * 4u + 2u] = st.z; outTan[v * 4u + 3u] = t.w;\n}\n";
2
+ /**
3
+ * Per-vertex tangents (xyz, w = handedness) from positions, normals and uvs,
4
+ * accumulated per triangle and orthogonalised against the normal — the
5
+ * standard construction, which is what a mesh imported without tangents gets
6
+ * in Unity too. `verts` is the engine's interleaved layout (8 floats).
7
+ */
8
+ export declare function tangentsFor(verts: Float32Array, indices: Uint32Array | Uint16Array): Float32Array;
9
+ //# sourceMappingURL=skin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skin.d.ts","sourceRoot":"","sources":["../../src/unity/skin.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,eAAe,g0EA8C3B,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CA0DjG"}
@@ -0,0 +1,117 @@
1
+ // Skinned vertex streams for native (game-shader) materials.
2
+ //
3
+ // The engine skins in the vertex stage of its own material shaders, from the
4
+ // bone palette (skinMats) and the post-morph vertex buffer. A game shader skins
5
+ // nothing: Unity hands it world-space-ready POSITION / NORMAL / TANGENT streams
6
+ // from its SkinnedMeshRenderer. So a model that wears native materials is
7
+ // skinned once per frame here, by compute, into three plain vertex buffers the
8
+ // game shaders read as Unity's — the same streams the compare page feeds them.
9
+ //
10
+ // Inputs are the engine's own buffers, untouched: the interleaved vertex
11
+ // buffer (position, normal, uv — 8 floats, morphs already applied), the joints
12
+ // (uint16x4, two words a vertex), the weights (unorm8x4, one word) and the
13
+ // skin matrices. Tangents are not in the engine's vertex format; they are
14
+ // derived once at install (tangentsFor) and skinned here with the normal.
15
+ export const UNITY_SKIN_WGSL = /* wgsl */ `
16
+ struct Params { count: u32, _a: u32, _b: u32, _c: u32 }
17
+ @group(0) @binding(0) var<uniform> P: Params;
18
+ @group(0) @binding(1) var<storage, read> verts: array<f32>; // pos(3) nrm(3) uv(2)
19
+ @group(0) @binding(2) var<storage, read> joints: array<u32>; // uint16x4: two words
20
+ @group(0) @binding(3) var<storage, read> weights: array<u32>; // unorm8x4: one word
21
+ @group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
22
+ @group(0) @binding(5) var<storage, read> tangents: array<f32>; // rest tangent(4)
23
+ @group(0) @binding(6) var<storage, read_write> outPos: array<f32>;
24
+ @group(0) @binding(7) var<storage, read_write> outNrm: array<f32>;
25
+ @group(0) @binding(8) var<storage, read_write> outTan: array<f32>;
26
+
27
+ @compute @workgroup_size(64)
28
+ fn main(@builtin(global_invocation_id) id: vec3u) {
29
+ let v = id.x;
30
+ if (v >= P.count) { return; }
31
+ let p = vec4f(verts[v * 8u], verts[v * 8u + 1u], verts[v * 8u + 2u], 1.0);
32
+ let n = vec3f(verts[v * 8u + 3u], verts[v * 8u + 4u], verts[v * 8u + 5u]);
33
+ let t = vec4f(tangents[v * 4u], tangents[v * 4u + 1u], tangents[v * 4u + 2u], tangents[v * 4u + 3u]);
34
+ let j01 = joints[v * 2u];
35
+ let j23 = joints[v * 2u + 1u];
36
+ let j = vec4u(j01 & 0xffffu, j01 >> 16u, j23 & 0xffffu, j23 >> 16u);
37
+ var w = unpack4x8unorm(weights[v]);
38
+ // Renormalised as the material vertex stage does: PMX ships unnormalised
39
+ // weights on extras, and a sum of zero is the bind pose.
40
+ let s = w.x + w.y + w.z + w.w;
41
+ w = select(vec4f(1.0, 0.0, 0.0, 0.0), w / s, s > 0.0001);
42
+ var sp = vec3f(0.0);
43
+ var sn = vec3f(0.0);
44
+ var st = vec3f(0.0);
45
+ for (var k = 0u; k < 4u; k++) {
46
+ let m = skinMats[j[k]];
47
+ let r = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
48
+ sp += (m * p).xyz * w[k];
49
+ sn += (r * n) * w[k];
50
+ st += (r * t.xyz) * w[k];
51
+ }
52
+ // Degenerate blends (opposing bones at 50/50) cancel to zero; up, not NaN.
53
+ let ln = dot(sn, sn);
54
+ let lt = dot(st, st);
55
+ sn = select(vec3f(0.0, 1.0, 0.0), sn * inverseSqrt(ln), ln > 1e-12);
56
+ st = select(vec3f(1.0, 0.0, 0.0), st * inverseSqrt(lt), lt > 1e-12);
57
+ outPos[v * 3u] = sp.x; outPos[v * 3u + 1u] = sp.y; outPos[v * 3u + 2u] = sp.z;
58
+ outNrm[v * 3u] = sn.x; outNrm[v * 3u + 1u] = sn.y; outNrm[v * 3u + 2u] = sn.z;
59
+ outTan[v * 4u] = st.x; outTan[v * 4u + 1u] = st.y; outTan[v * 4u + 2u] = st.z; outTan[v * 4u + 3u] = t.w;
60
+ }
61
+ `;
62
+ /**
63
+ * Per-vertex tangents (xyz, w = handedness) from positions, normals and uvs,
64
+ * accumulated per triangle and orthogonalised against the normal — the
65
+ * standard construction, which is what a mesh imported without tangents gets
66
+ * in Unity too. `verts` is the engine's interleaved layout (8 floats).
67
+ */
68
+ export function tangentsFor(verts, indices) {
69
+ const n = verts.length / 8;
70
+ const tan = new Float64Array(n * 3);
71
+ const bit = new Float64Array(n * 3);
72
+ for (let i = 0; i + 2 < indices.length; i += 3) {
73
+ const a = indices[i], b = indices[i + 1], c = indices[i + 2];
74
+ const p = (v, k) => verts[v * 8 + k];
75
+ const e1 = [p(b, 0) - p(a, 0), p(b, 1) - p(a, 1), p(b, 2) - p(a, 2)];
76
+ const e2 = [p(c, 0) - p(a, 0), p(c, 1) - p(a, 1), p(c, 2) - p(a, 2)];
77
+ const du1 = p(b, 6) - p(a, 6), dv1 = p(b, 7) - p(a, 7);
78
+ const du2 = p(c, 6) - p(a, 6), dv2 = p(c, 7) - p(a, 7);
79
+ const det = du1 * dv2 - du2 * dv1;
80
+ if (Math.abs(det) < 1e-12)
81
+ continue;
82
+ const r = 1 / det;
83
+ for (const v of [a, b, c]) {
84
+ for (let k = 0; k < 3; k++) {
85
+ tan[v * 3 + k] += (e1[k] * dv2 - e2[k] * dv1) * r;
86
+ bit[v * 3 + k] += (e2[k] * du1 - e1[k] * du2) * r;
87
+ }
88
+ }
89
+ }
90
+ const out = new Float32Array(n * 4);
91
+ for (let v = 0; v < n; v++) {
92
+ const nx = verts[v * 8 + 3], ny = verts[v * 8 + 4], nz = verts[v * 8 + 5];
93
+ let tx = tan[v * 3], ty = tan[v * 3 + 1], tz = tan[v * 3 + 2];
94
+ const d = tx * nx + ty * ny + tz * nz;
95
+ tx -= nx * d;
96
+ ty -= ny * d;
97
+ tz -= nz * d;
98
+ let len = Math.hypot(tx, ty, tz);
99
+ if (len < 1e-12) {
100
+ // Any direction perpendicular to the normal: a surface with no uv
101
+ // gradient has no tangent to speak of, and a zero one is NaN downstream.
102
+ const ax = Math.abs(nx) < 0.9 ? 1 : 0;
103
+ tx = ax ? 0 : -nz;
104
+ ty = ax ? nz : 0;
105
+ tz = ax ? -ny : nx;
106
+ len = Math.hypot(tx, ty, tz) || 1;
107
+ }
108
+ tx /= len;
109
+ ty /= len;
110
+ tz /= len;
111
+ // Handedness: whether the bitangent agrees with n × t.
112
+ const cx = ny * tz - nz * ty, cy = nz * tx - nx * tz, cz = nx * ty - ny * tx;
113
+ const w = cx * bit[v * 3] + cy * bit[v * 3 + 1] + cz * bit[v * 3 + 2] < 0 ? -1 : 1;
114
+ out.set([tx, ty, tz, w], v * 4);
115
+ }
116
+ return out;
117
+ }
@@ -0,0 +1,148 @@
1
+ import { NativeHost, type NativeValue } from "./host";
2
+ import type { PassState } from "./state";
3
+ export type NativeStageMesh = {
4
+ id: string;
5
+ vertexCount: number;
6
+ /** Semantic -> [byte offset, components] (float32). */
7
+ streams: Record<string, [number, number]>;
8
+ submeshes: {
9
+ offset: number;
10
+ count: number;
11
+ topology?: string;
12
+ }[];
13
+ bounds: {
14
+ center: [number, number, number];
15
+ size: [number, number, number];
16
+ };
17
+ };
18
+ export type NativeStageTexture = {
19
+ id: string;
20
+ name: string;
21
+ width: number;
22
+ height: number;
23
+ hdr: number;
24
+ srgb: number;
25
+ cube: number;
26
+ wrap?: string;
27
+ filter?: string;
28
+ mips?: number;
29
+ mipsExported?: number;
30
+ aniso?: number;
31
+ files: string[];
32
+ };
33
+ export type NativeStageMaterial = {
34
+ id: string;
35
+ name: string;
36
+ shader: string;
37
+ queue: number;
38
+ passes: {
39
+ lightMode: string;
40
+ shader: string;
41
+ state: PassState;
42
+ }[];
43
+ values: Record<string, NativeValue>;
44
+ textures: Record<string, string>;
45
+ /** An empty slot's stand-in, per the shader: white, black, grey, bump. */
46
+ defaults?: Record<string, string>;
47
+ };
48
+ export type NativeStageRenderer = {
49
+ id: string;
50
+ path: string;
51
+ mesh: string;
52
+ materials: (string | null)[];
53
+ /** Object to world, game units, column-major. */
54
+ matrix: number[];
55
+ layers: number;
56
+ castShadows: number;
57
+ receiveShadows: number;
58
+ values: Record<string, NativeValue>;
59
+ textures: Record<string, string>;
60
+ };
61
+ export type NativeStageLights = {
62
+ main: {
63
+ direction: [number, number, number];
64
+ color: [number, number, number];
65
+ shadow: number;
66
+ };
67
+ additional: {
68
+ position: [number, number, number];
69
+ range: number;
70
+ color: [number, number, number];
71
+ type: "point" | "spot";
72
+ aim?: [number, number, number];
73
+ cosOuter?: number;
74
+ cosInner?: number;
75
+ unity?: {
76
+ spotDir: number[];
77
+ colorW: number;
78
+ extra: number[];
79
+ worldToLight: number[] | null;
80
+ lightType: number;
81
+ simSpotDir?: number[] | null;
82
+ simColorW?: number;
83
+ };
84
+ }[];
85
+ };
86
+ /** stage.json, as stage_native.py writes it. */
87
+ export type NativeStagePackage = {
88
+ version: number;
89
+ kind: "unity-native-stage";
90
+ name: string;
91
+ source?: string;
92
+ /** Engine units per game unit. */
93
+ scale: number;
94
+ keywords: string[];
95
+ shaders: string[];
96
+ meshes: NativeStageMesh[];
97
+ textures: NativeStageTexture[];
98
+ materials: NativeStageMaterial[];
99
+ renderers: NativeStageRenderer[];
100
+ lights: NativeStageLights;
101
+ settings: Record<string, NativeValue>;
102
+ globalTextures: Record<string, string>;
103
+ };
104
+ /** Reads one file of the package by its path inside it (stage.json's folder). */
105
+ export type NativeStageReader = (path: string) => Promise<ArrayBuffer>;
106
+ export declare class NativeStage {
107
+ private device;
108
+ readonly name: string;
109
+ readonly pkg: NativeStagePackage;
110
+ /** Engine-unit box around everything that casts, for the cascades. */
111
+ readonly bounds: {
112
+ min: [number, number, number];
113
+ max: [number, number, number];
114
+ };
115
+ private buffers;
116
+ private textures;
117
+ private opaque;
118
+ private transparent;
119
+ private casters;
120
+ private zero;
121
+ private white;
122
+ private constructor();
123
+ /**
124
+ * Load a package: every mesh and texture it names through `read`, every
125
+ * shader registered with the hosts (the scene pass's and the shadow atlas's).
126
+ */
127
+ static load(device: GPUDevice, pkg: NativeStagePackage, read: NativeStageReader, hosts: {
128
+ scene: NativeHost;
129
+ shadow: NativeHost;
130
+ }, tools: {
131
+ mipmaps: (t: GPUTexture, levels: number) => void;
132
+ fallback: Record<string, GPUTextureView>;
133
+ }, onProgress?: (done: number, total: number) => void): Promise<NativeStage>;
134
+ private loadTexture;
135
+ /** Every renderer's draws, resolved once: the stage does not move. */
136
+ private build;
137
+ private addItems;
138
+ private streams;
139
+ private draw;
140
+ /** One cascade tile of the engine's atlas: every caster's SHADOWCASTER pass. */
141
+ drawShadow(pass: GPURenderPassEncoder, host: NativeHost, globals: Record<string, NativeValue>, cascade: number, viewProj: ArrayLike<number>, engineScale: number): void;
142
+ /** The opaque queue, front to back within each queue. */
143
+ drawOpaque(pass: GPURenderPassEncoder, host: NativeHost, globals: Record<string, NativeValue>, frame: Record<string, GPUTextureView | undefined>, eye: [number, number, number]): void;
144
+ /** The transparent queue, back to front within each queue. */
145
+ drawTransparent(pass: GPURenderPassEncoder, host: NativeHost, globals: Record<string, NativeValue>, frame: Record<string, GPUTextureView | undefined>, eye: [number, number, number]): void;
146
+ destroy(hosts: NativeHost[]): void;
147
+ }
148
+ //# sourceMappingURL=stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../../src/unity/stage.ts"],"names":[],"mappings":"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"}