reze-engine 0.64.0 → 0.66.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 (173) hide show
  1. package/dist/engine.d.ts +300 -94
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +1628 -787
  4. package/dist/graph/compile.d.ts.map +1 -1
  5. package/dist/graph/compile.js +5 -0
  6. package/dist/graph/presets/body.d.ts.map +1 -1
  7. package/dist/graph/presets/body.js +26 -24
  8. package/dist/graph/presets/cloth_rough.js +8 -8
  9. package/dist/graph/presets/cloth_smooth.js +8 -8
  10. package/dist/graph/presets/default.js +4 -4
  11. package/dist/graph/presets/eye.js +10 -10
  12. package/dist/graph/presets/face.d.ts.map +1 -1
  13. package/dist/graph/presets/face.js +28 -26
  14. package/dist/graph/presets/hair.js +6 -6
  15. package/dist/graph/presets/metal.js +8 -8
  16. package/dist/graph/presets/stockings.d.ts.map +1 -1
  17. package/dist/graph/presets/stockings.js +4 -11
  18. package/dist/graph/presets/unlit.d.ts.map +1 -1
  19. package/dist/graph/presets/unlit.js +9 -13
  20. package/dist/graph/registry.d.ts.map +1 -1
  21. package/dist/graph/registry.js +128 -157
  22. package/dist/graph/render-class.d.ts +5 -2
  23. package/dist/graph/render-class.d.ts.map +1 -1
  24. package/dist/graph/slots.d.ts.map +1 -1
  25. package/dist/graph/slots.js +12 -2
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/model.d.ts +23 -1
  29. package/dist/model.d.ts.map +1 -1
  30. package/dist/model.js +41 -1
  31. package/dist/outline-normals.d.ts +12 -0
  32. package/dist/outline-normals.d.ts.map +1 -0
  33. package/dist/outline-normals.js +100 -0
  34. package/dist/pmx-loader.d.ts +7 -0
  35. package/dist/pmx-loader.d.ts.map +1 -1
  36. package/dist/pmx-loader.js +17 -3
  37. package/dist/shaders/anchor-table.d.ts +11 -0
  38. package/dist/shaders/anchor-table.d.ts.map +1 -1
  39. package/dist/shaders/anchor-table.js +9 -3
  40. package/dist/shaders/directives.d.ts +42 -5
  41. package/dist/shaders/directives.d.ts.map +1 -1
  42. package/dist/shaders/directives.js +51 -4
  43. package/dist/shaders/lights.d.ts +11 -7
  44. package/dist/shaders/lights.d.ts.map +1 -1
  45. package/dist/shaders/lights.js +315 -271
  46. package/dist/shaders/materials/common.d.ts.map +1 -1
  47. package/dist/shaders/materials/common.js +30 -5
  48. package/dist/shaders/materials/nodes.d.ts +1 -1
  49. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  50. package/dist/shaders/materials/nodes.js +191 -271
  51. package/dist/shaders/passes/bloom.d.ts +4 -3
  52. package/dist/shaders/passes/bloom.d.ts.map +1 -1
  53. package/dist/shaders/passes/bloom.js +58 -87
  54. package/dist/shaders/passes/composite.d.ts +1 -1
  55. package/dist/shaders/passes/composite.d.ts.map +1 -1
  56. package/dist/shaders/passes/composite.js +121 -77
  57. package/dist/shaders/passes/ground.js +1 -1
  58. package/dist/shaders/passes/mirror.d.ts +18 -12
  59. package/dist/shaders/passes/mirror.d.ts.map +1 -1
  60. package/dist/shaders/passes/mirror.js +59 -13
  61. package/dist/shaders/passes/outline.d.ts.map +1 -1
  62. package/dist/shaders/passes/outline.js +71 -14
  63. package/dist/shaders/passes/particles.d.ts.map +1 -1
  64. package/dist/shaders/passes/particles.js +9 -3
  65. package/dist/shaders/passes/probe.d.ts +45 -0
  66. package/dist/shaders/passes/probe.d.ts.map +1 -0
  67. package/dist/shaders/passes/probe.js +230 -0
  68. package/dist/shaders/passes/scene-contract.d.ts +13 -0
  69. package/dist/shaders/passes/scene-contract.d.ts.map +1 -1
  70. package/dist/shaders/passes/scene-contract.js +4 -0
  71. package/dist/shaders/scene-light-api.d.ts.map +1 -1
  72. package/dist/shaders/scene-light-api.js +1 -0
  73. package/dist/unity/globals.d.ts +7 -0
  74. package/dist/unity/globals.d.ts.map +1 -1
  75. package/dist/unity/globals.js +4 -3
  76. package/dist/unity/host.d.ts +18 -0
  77. package/dist/unity/host.d.ts.map +1 -1
  78. package/dist/unity/host.js +144 -70
  79. package/dist/unity/looks.d.ts +47 -0
  80. package/dist/unity/looks.d.ts.map +1 -1
  81. package/dist/unity/looks.js +150 -6
  82. package/dist/unity/skin.d.ts.map +1 -1
  83. package/dist/unity/skin.js +5 -2
  84. package/dist/unity/stage.d.ts +24 -0
  85. package/dist/unity/stage.d.ts.map +1 -1
  86. package/dist/unity/stage.js +59 -7
  87. package/package.json +1 -1
  88. package/src/engine.ts +2010 -1033
  89. package/src/graph/compile.ts +4 -0
  90. package/src/graph/presets/body.ts +26 -24
  91. package/src/graph/presets/cloth_rough.ts +8 -8
  92. package/src/graph/presets/cloth_smooth.ts +8 -8
  93. package/src/graph/presets/default.ts +4 -4
  94. package/src/graph/presets/eye.ts +10 -10
  95. package/src/graph/presets/face.ts +28 -26
  96. package/src/graph/presets/hair.ts +6 -6
  97. package/src/graph/presets/metal.ts +8 -8
  98. package/src/graph/presets/stockings.ts +4 -11
  99. package/src/graph/presets/unlit.ts +9 -13
  100. package/src/graph/registry.ts +133 -158
  101. package/src/graph/render-class.ts +5 -2
  102. package/src/graph/slots.ts +12 -2
  103. package/src/index.ts +1 -0
  104. package/src/model.ts +50 -1
  105. package/src/outline-normals.ts +110 -0
  106. package/src/pmx-loader.ts +18 -3
  107. package/src/shaders/anchor-table.ts +15 -3
  108. package/src/shaders/directives.ts +88 -6
  109. package/src/shaders/lights.ts +464 -415
  110. package/src/shaders/materials/common.ts +30 -5
  111. package/src/shaders/materials/nodes.ts +191 -271
  112. package/src/shaders/passes/bloom.ts +59 -87
  113. package/src/shaders/passes/composite.ts +121 -77
  114. package/src/shaders/passes/ground.ts +1 -1
  115. package/src/shaders/passes/mirror.ts +60 -13
  116. package/src/shaders/passes/outline.ts +71 -14
  117. package/src/shaders/passes/particles.ts +9 -3
  118. package/src/shaders/passes/probe.ts +240 -0
  119. package/src/shaders/passes/scene-contract.ts +17 -0
  120. package/src/shaders/scene-light-api.ts +1 -0
  121. package/src/unity/globals.ts +7 -3
  122. package/src/unity/host.ts +93 -20
  123. package/src/unity/looks.ts +182 -9
  124. package/src/unity/skin.ts +5 -2
  125. package/src/unity/stage.ts +66 -7
  126. package/dist/physics-debug.d.ts +0 -30
  127. package/dist/physics-debug.d.ts.map +0 -1
  128. package/dist/physics-debug.js +0 -526
  129. package/dist/shaders/agx-lut.d.ts +0 -11
  130. package/dist/shaders/agx-lut.d.ts.map +0 -1
  131. package/dist/shaders/agx-lut.js +0 -29
  132. package/dist/shaders/dfg_lut.d.ts +0 -3
  133. package/dist/shaders/dfg_lut.d.ts.map +0 -1
  134. package/dist/shaders/dfg_lut.js +0 -129
  135. package/dist/shaders/ltc_mag_lut.d.ts +0 -3
  136. package/dist/shaders/ltc_mag_lut.d.ts.map +0 -1
  137. package/dist/shaders/ltc_mag_lut.js +0 -1033
  138. package/dist/shaders/materials/body.d.ts +0 -2
  139. package/dist/shaders/materials/body.d.ts.map +0 -1
  140. package/dist/shaders/materials/body.js +0 -95
  141. package/dist/shaders/materials/cloth_rough.d.ts +0 -2
  142. package/dist/shaders/materials/cloth_rough.d.ts.map +0 -1
  143. package/dist/shaders/materials/cloth_rough.js +0 -69
  144. package/dist/shaders/materials/cloth_smooth.d.ts +0 -2
  145. package/dist/shaders/materials/cloth_smooth.d.ts.map +0 -1
  146. package/dist/shaders/materials/cloth_smooth.js +0 -61
  147. package/dist/shaders/materials/default.d.ts +0 -2
  148. package/dist/shaders/materials/default.d.ts.map +0 -1
  149. package/dist/shaders/materials/default.js +0 -43
  150. package/dist/shaders/materials/eye.d.ts +0 -2
  151. package/dist/shaders/materials/eye.d.ts.map +0 -1
  152. package/dist/shaders/materials/eye.js +0 -60
  153. package/dist/shaders/materials/face.d.ts +0 -2
  154. package/dist/shaders/materials/face.d.ts.map +0 -1
  155. package/dist/shaders/materials/face.js +0 -95
  156. package/dist/shaders/materials/hair.d.ts +0 -2
  157. package/dist/shaders/materials/hair.d.ts.map +0 -1
  158. package/dist/shaders/materials/hair.js +0 -90
  159. package/dist/shaders/materials/metal.d.ts +0 -2
  160. package/dist/shaders/materials/metal.d.ts.map +0 -1
  161. package/dist/shaders/materials/metal.js +0 -77
  162. package/dist/shaders/materials/mmd_classic.d.ts +0 -2
  163. package/dist/shaders/materials/mmd_classic.d.ts.map +0 -1
  164. package/dist/shaders/materials/mmd_classic.js +0 -66
  165. package/dist/shaders/materials/stockings.d.ts +0 -2
  166. package/dist/shaders/materials/stockings.d.ts.map +0 -1
  167. package/dist/shaders/materials/stockings.js +0 -122
  168. package/dist/shaders/passes/physics-debug.d.ts +0 -2
  169. package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
  170. package/dist/shaders/passes/physics-debug.js +0 -69
  171. package/src/shaders/agx-lut.ts +0 -30
  172. package/src/shaders/dfg_lut.ts +0 -131
  173. package/src/shaders/ltc_mag_lut.ts +0 -1035
@@ -15,7 +15,7 @@
15
15
  // and the transparent queue in the engine's transparent phase. Shadows are the
16
16
  // engine's: a native material still casts through its pass.
17
17
 
18
- import { NativeHost, type NativeShader, type NativeStream, type NativeValue, type ValueSource } from "./host"
18
+ import { NativeHost, sameValue, type NativeShader, type NativeStream, type NativeValue, type ValueSource } from "./host"
19
19
  import type { PassState } from "./state"
20
20
  import { UNITY_SKIN_WGSL, tangentsFor } from "./skin"
21
21
  import { characterBlock, type CharacterRig } from "./character"
@@ -28,6 +28,19 @@ export type NativeTexture = {
28
28
  data: Uint8Array
29
29
  /** Colour (sRGB) or data (linear) — how the shader expects to read it. */
30
30
  srgb: boolean
31
+ /** Unity's wrap mode as the game imported it (Repeat, Clamp, Mirror,
32
+ * MirrorOnce); repeating when absent. A clamped mask sampled past its edge
33
+ * reads its border, not the far side. */
34
+ wrap?: string
35
+ /** Unity's filter mode (Point, Bilinear, Trilinear); bilinear when absent. */
36
+ filter?: string
37
+ }
38
+
39
+ const WRAP: Record<string, GPUAddressMode> = {
40
+ Repeat: "repeat",
41
+ Clamp: "clamp-to-edge",
42
+ Mirror: "mirror-repeat",
43
+ MirrorOnce: "mirror-repeat",
31
44
  }
32
45
 
33
46
  export type NativeMaterialSpec = {
@@ -48,6 +61,12 @@ export type NativeLook = {
48
61
  textures: Record<string, NativeTexture>
49
62
  materials: NativeMaterialSpec[]
50
63
  rig?: CharacterRig
64
+ /** The game vertex streams the model's PMX carries, by semantic: the
65
+ * additional UV channel (1–4) that holds each, four floats a vertex, keyed
66
+ * over time by that channel's UV morphs (PMX types 4–7) — a particle's
67
+ * colour over its life on a mesh's own vertex colours. A stream named here
68
+ * reads that channel; any other reads as Unity binds a missing one. */
69
+ streams?: Record<string, number>
51
70
  }
52
71
 
53
72
  /** What the engine lends for one model. */
@@ -72,12 +91,30 @@ export type NativeModel = {
72
91
  headRest: [number, number, number]
73
92
  /** Its rendering-layer bits, for a look without a rig. */
74
93
  layers: number
94
+ /** A PMX additional UV channel (1–4), four floats a vertex, or null. */
95
+ additionalUv: (channel: number) => Float32Array | null
96
+ /** Its morphs, in the order of morphWeights. */
97
+ morphs: readonly { type: number; uvOffsets?: { vertexIndex: number; offset: [number, number, number, number] }[] }[]
98
+ /** The current effective (group-resolved, clamped) morph weights. */
99
+ morphWeights: () => Float32Array
100
+ }
101
+
102
+ /** A vertex stream out of an additional UV channel: its rest values, the
103
+ * channel's morphs, and the weights it was last built at. */
104
+ type CarriedStream = {
105
+ buffer: GPUBuffer
106
+ base: Float32Array
107
+ data: Float32Array
108
+ morphs: { index: number; offsets: { vertexIndex: number; offset: [number, number, number, number] }[] }[]
109
+ weights: Float32Array
75
110
  }
76
111
 
77
112
  type Install = {
78
113
  model: NativeModel
79
114
  look: NativeLook
80
115
  views: Map<string, GPUTextureView>
116
+ /** Per look texture: its sampler, as the game imported the texture. */
117
+ samplers: Map<string, GPUSampler>
81
118
  textures: GPUTexture[]
82
119
  pos: GPUBuffer
83
120
  nrm: GPUBuffer
@@ -85,9 +122,12 @@ type Install = {
85
122
  restTan: GPUBuffer
86
123
  params: GPUBuffer
87
124
  skinBind: GPUBindGroup
88
- /** Per draw of the model: the spec that dresses it, if any. */
89
- dressed: { draw: NativeModel["draws"][number]; spec: NativeMaterialSpec }[]
125
+ /** Per draw of the model: the spec that dresses it, and the values set on
126
+ * that material since (setUniforms), over the spec's. */
127
+ dressed: { draw: NativeModel["draws"][number]; spec: NativeMaterialSpec; values: Record<string, NativeValue> }[]
90
128
  block: Record<string, NativeValue>
129
+ /** The look's streams (NativeLook.streams), by semantic. */
130
+ carried: Map<string, CarriedStream>
91
131
  }
92
132
 
93
133
  const OPAQUE_MODES = ["ALWAYS", "FORWARDBASE"]
@@ -97,6 +137,8 @@ const OVERRIDE_MODES = ["OVERRIDE1", "OVERRIDE2", "OVERRIDE3"]
97
137
  export class NativeLooks {
98
138
  readonly host: NativeHost
99
139
  private installs = new Map<string, Install>()
140
+ /** Per model: its passes' pipelines compiling (see install). */
141
+ private warming = new Map<string, Promise<void>>()
100
142
  private skinPipeline: GPUComputePipeline
101
143
  private zero: GPUBuffer
102
144
  private white: GPUBuffer
@@ -139,10 +181,38 @@ export class NativeLooks {
139
181
  return this.installs.size
140
182
  }
141
183
 
184
+ private samplerCache = new Map<string, GPUSampler>()
185
+
186
+ /** A texture's sampler: its wrap and filter, shared by every texture alike. */
187
+ private sampler(t: NativeTexture): GPUSampler {
188
+ const wrap = WRAP[t.wrap ?? "Repeat"] ?? "repeat"
189
+ const filter: GPUFilterMode = t.filter === "Point" ? "nearest" : "linear"
190
+ const key = `${wrap}|${filter}`
191
+ let s = this.samplerCache.get(key)
192
+ if (!s) {
193
+ s = this.device.createSampler({
194
+ label: `native look sampler ${key}`,
195
+ addressModeU: wrap,
196
+ addressModeV: wrap,
197
+ magFilter: filter,
198
+ minFilter: filter,
199
+ mipmapFilter: filter,
200
+ })
201
+ this.samplerCache.set(key, s)
202
+ }
203
+ return s
204
+ }
205
+
142
206
  has(model: string): boolean {
143
207
  return this.installs.has(model)
144
208
  }
145
209
 
210
+ /** Resolves once every pass of the model's look has its pipeline — until
211
+ * then those draws sit out (NativeHost never compiles at a draw). */
212
+ ready(model: string): Promise<void> {
213
+ return this.warming.get(model) ?? Promise.resolve()
214
+ }
215
+
146
216
  /** Is this model's material drawn by its native look? */
147
217
  dresses(model: string, material: string): boolean {
148
218
  const i = this.installs.get(model)
@@ -154,8 +224,10 @@ export class NativeLooks {
154
224
  const d = this.device
155
225
  for (const s of look.shaders) this.host.register(s)
156
226
  const views = new Map<string, GPUTextureView>()
227
+ const samplers = new Map<string, GPUSampler>()
157
228
  const textures: GPUTexture[] = []
158
229
  for (const [key, t] of Object.entries(look.textures)) {
230
+ if (t.wrap || t.filter) samplers.set(key, this.sampler(t))
159
231
  const tex = d.createTexture({
160
232
  label: `${model.name}: ${key}`,
161
233
  size: [t.width, t.height],
@@ -203,12 +275,27 @@ export class NativeLooks {
203
275
  const dressed: Install["dressed"] = []
204
276
  for (const draw of model.draws) {
205
277
  const spec = look.materials.find((m) => m.materials.includes(draw.materialName))
206
- if (spec) dressed.push({ draw, spec })
278
+ if (spec) dressed.push({ draw, spec, values: {} })
207
279
  }
208
- this.installs.set(model.name, {
280
+ const carried = new Map<string, CarriedStream>()
281
+ for (const [semantic, channel] of Object.entries(look.streams ?? {})) {
282
+ const base = model.additionalUv(channel)
283
+ if (!base || base.length < n * 4) continue
284
+ const morphs = model.morphs
285
+ .map((m, index) => ({ index, type: m.type, offsets: m.uvOffsets ?? [] }))
286
+ .filter((m) => m.type === 3 + channel && m.offsets.length)
287
+ .map(({ index, offsets }) => ({ index, offsets }))
288
+ const buffer = buf(`stream ${semantic}`, n * 4, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST)
289
+ const data = new Float32Array(base.subarray(0, n * 4))
290
+ d.queue.writeBuffer(buffer, 0, data)
291
+ // NaN: the first prepare builds the stream whatever the weights are
292
+ carried.set(semantic, { buffer, base: data.slice(), data, morphs, weights: new Float32Array(morphs.length).fill(NaN) })
293
+ }
294
+ const install: Install = {
209
295
  model,
210
296
  look,
211
297
  views,
298
+ samplers,
212
299
  textures,
213
300
  pos,
214
301
  nrm,
@@ -218,7 +305,19 @@ export class NativeLooks {
218
305
  skinBind,
219
306
  dressed,
220
307
  block: {},
221
- })
308
+ carried,
309
+ }
310
+ this.installs.set(model.name, install)
311
+ // Every pass this look will draw, compiled now and side by side — the
312
+ // shader, its state and the streams are all known here.
313
+ const drawn = [...OPAQUE_MODES, ...CHARACTER_MODES, ...OVERRIDE_MODES]
314
+ const jobs: Promise<void>[] = []
315
+ for (const d of dressed)
316
+ for (const p of d.spec.passes) if (drawn.includes(p.lightMode)) jobs.push(this.host.warm(p.shader, p.state, this.streams(install, p.shader)))
317
+ this.warming.set(
318
+ model.name,
319
+ Promise.all(jobs).then(() => {}),
320
+ )
222
321
  }
223
322
 
224
323
  remove(name: string): void {
@@ -226,7 +325,7 @@ export class NativeLooks {
226
325
  if (!i) return
227
326
  this.installs.delete(name)
228
327
  this.host.release(`${name}|`)
229
- const bufs = [i.pos, i.nrm, i.tan, i.restTan, i.params]
328
+ const bufs = [i.pos, i.nrm, i.tan, i.restTan, i.params, ...[...i.carried.values()].map((c) => c.buffer)]
230
329
  const texs = i.textures
231
330
  // Freed once the GPU is done with the frames that may still name them.
232
331
  void this.device.queue.onSubmittedWorkDone().then(() => {
@@ -259,12 +358,81 @@ export class NativeLooks {
259
358
  : {
260
359
  unity_RenderingLayer: new Uint32Array([i.model.layers >>> 0, 0, 0, 0]),
261
360
  }
361
+ if (i.carried.size) this.keyStreams(i)
262
362
  }
263
363
  pass.end()
264
364
  }
265
365
 
366
+ /** Each carried stream at the model's current morph weights: its rest values
367
+ * plus every one of its channel's morphs at its weight, rebuilt and uploaded
368
+ * only when one of those weights moved. */
369
+ private keyStreams(i: Install): void {
370
+ const weights = i.model.morphWeights()
371
+ for (const c of i.carried.values()) {
372
+ if (!c.morphs.length) continue
373
+ let moved = false
374
+ for (let k = 0; k < c.morphs.length; k++) {
375
+ const w = weights[c.morphs[k].index] ?? 0
376
+ if (w !== c.weights[k]) {
377
+ c.weights[k] = w
378
+ moved = true
379
+ }
380
+ }
381
+ if (!moved) continue
382
+ c.data.set(c.base)
383
+ for (let k = 0; k < c.morphs.length; k++) {
384
+ const w = c.weights[k]
385
+ if (!w) continue
386
+ for (const o of c.morphs[k].offsets) {
387
+ const at = o.vertexIndex * 4
388
+ if (at + 3 >= c.data.length) continue
389
+ c.data[at] += o.offset[0] * w
390
+ c.data[at + 1] += o.offset[1] * w
391
+ c.data[at + 2] += o.offset[2] * w
392
+ c.data[at + 3] += o.offset[3] * w
393
+ }
394
+ }
395
+ this.device.queue.writeBuffer(c.buffer, 0, c.data as Float32Array<ArrayBuffer>)
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Set some of one dressed material's values (its Unity material properties,
401
+ * as the look's `values` hold them) — what a game animates on a material
402
+ * over a take. Each value stands until set again; the look's own stand
403
+ * under it. Only names the material's spec carries are taken: they are the
404
+ * ones its draws' uniform blocks read per draw. A value equal to the one
405
+ * already set keeps that one's object, so the host, which refills a
406
+ * uniform member only when its value object changes, writes nothing for
407
+ * it. Returns whether the model wears a look that dresses this material.
408
+ */
409
+ setUniforms(model: string, material: string, values: Record<string, NativeValue>): boolean {
410
+ const i = this.installs.get(model)
411
+ if (!i) return false
412
+ let found = false
413
+ for (const d of i.dressed) {
414
+ if (d.draw.materialName !== material) continue
415
+ found = true
416
+ for (const name in values) {
417
+ const v = values[name]
418
+ if (v === undefined || v === null || !(name in d.spec.values)) continue
419
+ const was = d.values[name]
420
+ if (was === undefined || !sameValue(was, v)) d.values[name] = v
421
+ }
422
+ }
423
+ return found
424
+ }
425
+
266
426
  private streams(i: Install, shader: string): NativeStream[] {
267
427
  return this.host.inputs(shader).map(({ semantic }) => {
428
+ const carried = i.carried.get(semantic)
429
+ if (carried)
430
+ return {
431
+ buffer: carried.buffer,
432
+ offset: 0,
433
+ stride: 16,
434
+ format: "float32x4" as const,
435
+ }
268
436
  if (semantic === "POSITION")
269
437
  return {
270
438
  buffer: i.pos,
@@ -329,7 +497,11 @@ export class NativeLooks {
329
497
  const textures: Record<string, GPUTextureView | undefined> = {
330
498
  ...this.frameTextures,
331
499
  }
332
- for (const [slot, key] of Object.entries(d.spec.textures)) textures[slot] = key === "@diffuse" ? d.draw.diffuse : i.views.get(key)
500
+ const samplers: Record<string, GPUSampler | undefined> = {}
501
+ for (const [slot, key] of Object.entries(d.spec.textures)) {
502
+ textures[slot] = key === "@diffuse" ? d.draw.diffuse : i.views.get(key)
503
+ samplers[slot] = i.samplers.get(key)
504
+ }
333
505
  this.host.draw(pass, {
334
506
  shader: p.shader,
335
507
  state: p.state,
@@ -340,8 +512,9 @@ export class NativeLooks {
340
512
  offset: d.draw.firstIndex * 4,
341
513
  count: d.draw.count,
342
514
  },
343
- sources: [perDraw, i.block, d.spec.values, this.globals],
515
+ sources: [perDraw, i.block, d.values, d.spec.values, this.globals],
344
516
  textures,
517
+ samplers,
345
518
  key: `${i.model.name}|${d.draw.materialName}|${p.lightMode}|${p.shader}`,
346
519
  pass: "main",
347
520
  })
package/src/unity/skin.ts CHANGED
@@ -78,10 +78,13 @@ export function tangentsFor(verts: Float32Array, indices: Uint32Array | Uint16Ar
78
78
  const p = (v: number, k: number) => verts[v * 8 + k]
79
79
  const e1 = [p(b, 0) - p(a, 0), p(b, 1) - p(a, 1), p(b, 2) - p(a, 2)]
80
80
  const e2 = [p(c, 0) - p(a, 0), p(c, 1) - p(a, 1), p(c, 2) - p(a, 2)]
81
+ // v measured upward, as Unity builds tangent space: a PMX uv runs v down,
82
+ // and taken as it is the bitangent — the handedness in w — comes out
83
+ // reversed, which a game normal map reads as its green channel inverted.
81
84
  const du1 = p(b, 6) - p(a, 6),
82
- dv1 = p(b, 7) - p(a, 7)
85
+ dv1 = p(a, 7) - p(b, 7)
83
86
  const du2 = p(c, 6) - p(a, 6),
84
- dv2 = p(c, 7) - p(a, 7)
87
+ dv2 = p(a, 7) - p(c, 7)
85
88
  const det = du1 * dv2 - du2 * dv1
86
89
  if (Math.abs(det) < 1e-12) continue
87
90
  const r = 1 / det
@@ -21,6 +21,7 @@
21
21
  import { NativeHost, type NativeShader, type NativeStream, type NativeValue, type ValueSource } from "./host"
22
22
  import type { PassState } from "./state"
23
23
  import { gameToEngine, invert4, mul4, scale4, flipY } from "./globals"
24
+ import { SHADOW_CASCADES } from "../shadow-cascades"
24
25
 
25
26
  export type NativeStageMesh = {
26
27
  id: string
@@ -78,6 +79,12 @@ export type NativeStageLights = {
78
79
  direction: [number, number, number]
79
80
  color: [number, number, number]
80
81
  shadow: number
82
+ /** As the game's pipeline packed it, recorded: _MainLightColor and
83
+ * SimMainLightColor (linear, intensity in w), SimMainLightColorNoInt. */
84
+ unity?: { color: number[]; simColor: number[]; simColorNoInt: number[] }
85
+ /** The Light's shadowBias and shadowNormalBias, in shadow-map texels. */
86
+ shadowBias?: number
87
+ shadowNormalBias?: number
81
88
  }
82
89
  additional: {
83
90
  position: [number, number, number]
@@ -147,6 +154,15 @@ export class NativeStage {
147
154
  min: [number, number, number]
148
155
  max: [number, number, number]
149
156
  }
157
+ /**
158
+ * How far the stage reaches from the origin, in engine units: every
159
+ * renderer's box, casters or not. A game's sky is a dome in the stage
160
+ * (X317's reaches 1900 units out), and the camera's far plane has to reach it.
161
+ */
162
+ get extent(): number {
163
+ return this.reach
164
+ }
165
+ private reach = 0
150
166
  private buffers: GPUBuffer[] = []
151
167
  private textures: GPUTexture[] = []
152
168
  private opaque: Item[] = []
@@ -242,6 +258,10 @@ export class NativeStage {
242
258
  }),
243
259
  )
244
260
 
261
+ // Loaded means drawable: every pass's pipeline is compiled before this
262
+ // resolves, rather than one at a time in the frames after it.
263
+ const warming = st.warm(hosts, meshes)
264
+
245
265
  const textures = new Map<string, Tex>()
246
266
  await Promise.all(
247
267
  pkg.textures.map(async (t) => {
@@ -255,6 +275,7 @@ export class NativeStage {
255
275
  )
256
276
 
257
277
  st.build(meshes, textures, tools.fallback)
278
+ await warming
258
279
  return st
259
280
  }
260
281
 
@@ -384,6 +405,7 @@ export class NativeStage {
384
405
  ey = b.size[1] / 2,
385
406
  ez = b.size[2] / 2
386
407
  const half = [0, 1, 2].map((i) => (Math.abs(m[i]) * ex + Math.abs(m[4 + i]) * ey + Math.abs(m[8 + i]) * ez) * s)
408
+ this.reach = Math.max(this.reach, Math.hypot(center[0], center[1], center[2]) + Math.hypot(half[0], half[1], half[2]))
387
409
  const world = Float32Array.from(m)
388
410
  const det = m[0] * (m[5] * m[10] - m[9] * m[6]) - m[4] * (m[1] * m[10] - m[9] * m[2]) + m[8] * (m[1] * m[6] - m[5] * m[2])
389
411
  const perDraw: ValueSource = {
@@ -429,8 +451,10 @@ export class NativeStage {
429
451
  const samplers: Record<string, GPUSampler | undefined> = {
430
452
  ...globalSamplers,
431
453
  }
432
- // an empty slot reads what the shader declared it defaults to
433
- for (const [slot, def] of Object.entries(mat.defaults ?? {})) views[slot] = fallback[def] ?? fallback.white
454
+ // an empty slot reads what the shader declared it defaults to. A default
455
+ // with no name ("") is no picture at all — a cube's, usually — and is
456
+ // left to the host, which stands in by the binding's own kind.
457
+ for (const [slot, def] of Object.entries(mat.defaults ?? {})) if (def && fallback[def]) views[slot] = fallback[def]
434
458
  for (const [slot, id] of [...Object.entries(mat.textures), ...Object.entries(r.textures)]) {
435
459
  const t = textures.get(id)
436
460
  if (!t) continue
@@ -459,6 +483,35 @@ export class NativeStage {
459
483
  })
460
484
  }
461
485
 
486
+ /**
487
+ * Every pass's pipeline, compiled before the stage reports loaded (see
488
+ * NativeHost.warm) — side by side, and never at the first draw. Needs only
489
+ * the meshes' stream layouts, so it runs while the pictures decode. The
490
+ * passes are the ones build() keeps: casters that cast, and the main modes.
491
+ */
492
+ private warm(
493
+ hosts: { scene: NativeHost; shadow: NativeHost },
494
+ meshes: Map<string, { info: NativeStageMesh; buffer: GPUBuffer }>,
495
+ ): Promise<void> {
496
+ const jobs: Promise<void>[] = []
497
+ const mats = new Map(this.pkg.materials.map((m) => [m.id, m]))
498
+ for (const r of this.pkg.renderers) {
499
+ const mesh = meshes.get(r.mesh)
500
+ if (!mesh) continue
501
+ r.materials.forEach((mid, sub) => {
502
+ const mat = mid ? mats.get(mid) : undefined
503
+ if (!mat || sub >= mesh.info.submeshes.length) return
504
+ for (const pass of mat.passes) {
505
+ const caster = pass.lightMode === "SHADOWCASTER"
506
+ if (caster ? !r.castShadows : !MAIN_MODES.includes(pass.lightMode)) continue
507
+ const host = caster ? hosts.shadow : hosts.scene
508
+ jobs.push(host.warm(pass.shader, pass.state, this.streams(host, { mesh, pass } as Item)))
509
+ }
510
+ })
511
+ }
512
+ return Promise.all(jobs).then(() => {})
513
+ }
514
+
462
515
  private streams(host: NativeHost, it: Item): NativeStream[] {
463
516
  const info = it.mesh.info
464
517
  return host.inputs(it.pass.shader).map(({ semantic }) => {
@@ -514,18 +567,24 @@ export class NativeStage {
514
567
  engineScale: number,
515
568
  ): void {
516
569
  const vp = flipY(mul4(viewProj, gameToEngine(engineScale)))
517
- // ShadowUtils.GetShadowBias as the game set it: half a texel along the light
518
- // and half a texel in along the normal, a texel being this tile's width in
519
- // game units over its resolution (2048).
570
+ // ShadowUtils.GetShadowBias as the game sets it: (texel x the light's
571
+ // shadowBias, -texel x its shadowNormalBias), a texel being this tile's
572
+ // width in game units over its resolution. The stage's casters read it as
573
+ // _ShadowBias (Replica's CascadeShadowPass); the character's DrawShadowPass
574
+ // names it sim_ShadowBias. A package that predates the recorded biases
575
+ // takes half a texel of each.
520
576
  const rowLen = Math.hypot(vp[0], vp[4], vp[8])
521
- const texel = rowLen > 0 ? 2 / rowLen / 2048 : 0
577
+ const texel = rowLen > 0 ? 2 / rowLen / SHADOW_CASCADES[cascade].mapSize : 0
578
+ const main = this.pkg.lights.main
579
+ const bias = [texel * (main.shadowBias ?? 0.5), -texel * (main.shadowNormalBias ?? 0.5), 0, 0]
522
580
  const g: Record<string, NativeValue> = {
523
581
  ...globals,
524
582
  unity_MatrixVP: vp,
525
583
  unity_MatrixV: scale4(1),
526
584
  unity_MatrixP: vp,
527
585
  glstate_matrix_projection: vp,
528
- sim_ShadowBias: [0.5 * texel, -0.5 * texel, 0, 0],
586
+ _ShadowBias: bias,
587
+ sim_ShadowBias: bias,
529
588
  }
530
589
  for (const it of this.casters) this.draw(pass, host, it, g, {}, `|c${cascade}`)
531
590
  }
@@ -1,30 +0,0 @@
1
- import type { RezePhysics } from "./physics";
2
- export declare class PhysicsDebugRenderer {
3
- private device;
4
- private bindGroup;
5
- private wirePipelineSphere;
6
- private wirePipelineBox;
7
- private wirePipelineCapsule;
8
- private wireSphereBuffer;
9
- private wireBoxBuffer;
10
- private wireCapsuleBuffer;
11
- private wireSphereCount;
12
- private wireBoxCount;
13
- private wireCapsuleCount;
14
- private solidPipelineSphere;
15
- private solidPipelineBox;
16
- private solidPipelineCapsule;
17
- private solidSphereBuffer;
18
- private solidBoxBuffer;
19
- private solidCapsuleBuffer;
20
- private solidSphereCount;
21
- private solidBoxCount;
22
- private solidCapsuleCount;
23
- private instanceBuffer;
24
- private instanceData;
25
- private instanceCapacity;
26
- constructor(device: GPUDevice, cameraUniformBuffer: GPUBuffer, presentationFormat: GPUTextureFormat);
27
- render(pass: GPURenderPassEncoder, physics: RezePhysics): void;
28
- destroy(): void;
29
- }
30
- //# sourceMappingURL=physics-debug.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"physics-debug.d.ts","sourceRoot":"","sources":["../src/physics-debug.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAmB5C,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,SAAS,CAAc;IAG/B,OAAO,CAAC,kBAAkB,CAAmB;IAC7C,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,gBAAgB,CAAW;IACnC,OAAO,CAAC,aAAa,CAAW;IAChC,OAAO,CAAC,iBAAiB,CAAW;IACpC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,gBAAgB,CAAQ;IAIhC,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,oBAAoB,CAAmB;IAC/C,OAAO,CAAC,iBAAiB,CAAW;IACpC,OAAO,CAAC,cAAc,CAAW;IACjC,OAAO,CAAC,kBAAkB,CAAW;IACrC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,iBAAiB,CAAQ;IAEjC,OAAO,CAAC,cAAc,CAAW;IACjC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,gBAAgB,CAAQ;gBAG9B,MAAM,EAAE,SAAS,EACjB,mBAAmB,EAAE,SAAS,EAC9B,kBAAkB,EAAE,gBAAgB;IA2HtC,MAAM,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI;IAmI9D,OAAO,IAAI,IAAI;CAShB"}