reze-engine 0.68.7 → 0.68.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine.d.ts +2 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +39 -41
- package/dist/graph/presets/pool_floor.d.ts +3 -0
- package/dist/graph/presets/pool_floor.d.ts.map +1 -0
- package/dist/graph/presets/pool_floor.js +42 -0
- package/dist/graph/presets/water.d.ts +3 -0
- package/dist/graph/presets/water.d.ts.map +1 -0
- package/dist/graph/presets/water.js +31 -0
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +5 -4
- package/dist/jelly-eyes.d.ts.map +1 -1
- package/dist/jelly-eyes.js +43 -17
- package/dist/physics/autofit.d.ts +147 -0
- package/dist/physics/autofit.d.ts.map +1 -0
- package/dist/physics/autofit.js +501 -0
- 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/dfg_lut.d.ts +3 -0
- package/dist/shaders/dfg_lut.d.ts.map +1 -0
- package/dist/shaders/dfg_lut.js +129 -0
- package/dist/shaders/ltc_mag_lut.d.ts +3 -0
- package/dist/shaders/ltc_mag_lut.d.ts.map +1 -0
- package/dist/shaders/ltc_mag_lut.js +1033 -0
- package/dist/shaders/materials/common.d.ts +6 -0
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +67 -60
- package/dist/shaders/passes/depth-prepass.d.ts.map +1 -1
- package/dist/shaders/passes/depth-prepass.js +11 -3
- package/dist/shaders/passes/field-blit.d.ts +26 -0
- package/dist/shaders/passes/field-blit.d.ts.map +1 -0
- package/dist/shaders/passes/field-blit.js +65 -0
- package/dist/shaders/passes/ghosts.d.ts +22 -0
- package/dist/shaders/passes/ghosts.d.ts.map +1 -0
- package/dist/shaders/passes/ghosts.js +75 -0
- package/dist/shaders/passes/ground-noise.d.ts +7 -0
- package/dist/shaders/passes/ground-noise.d.ts.map +1 -0
- package/dist/shaders/passes/ground-noise.js +88 -0
- package/dist/shaders/passes/outline.d.ts.map +1 -1
- package/dist/shaders/passes/outline.js +13 -2
- package/dist/shaders/passes/shadow.d.ts +1 -1
- package/dist/shaders/passes/shadow.d.ts.map +1 -1
- package/dist/shaders/passes/shadow.js +11 -3
- package/dist/shaders/passes/sim.d.ts +34 -0
- package/dist/shaders/passes/sim.d.ts.map +1 -0
- package/dist/shaders/passes/sim.js +169 -0
- package/dist/shaders/score-api.d.ts +10 -0
- package/dist/shaders/score-api.d.ts.map +1 -0
- package/dist/shaders/score-api.js +114 -0
- package/dist/shaders/shadow-api.d.ts +29 -0
- package/dist/shaders/shadow-api.d.ts.map +1 -0
- package/dist/shaders/shadow-api.js +112 -0
- package/package.json +1 -1
- package/src/engine.ts +39 -47
- package/src/graph/slots.ts +5 -4
- package/src/jelly-eyes.ts +41 -17
- package/src/shaders/materials/common.ts +68 -60
- package/src/shaders/passes/depth-prepass.ts +11 -3
- package/src/shaders/passes/outline.ts +13 -2
- package/src/shaders/passes/shadow.ts +11 -3
- package/dist/_dbg-loader.mjs +0 -1100
package/src/engine.ts
CHANGED
|
@@ -354,11 +354,6 @@ type GroupInstall = {
|
|
|
354
354
|
pipelineNoDepthWrite: GPURenderPipeline
|
|
355
355
|
/** hair render-class only: the stencil-matched IS_OVER_EYES=true variant. */
|
|
356
356
|
overEyesPipeline?: GPURenderPipeline
|
|
357
|
-
/** eye render-class only: the same pipeline with its cull flipped, for the
|
|
358
|
-
* mirror pass. The eye is the ONE material class that culls a face, and a
|
|
359
|
-
* reflection flips winding, so in the mirror the ordinary pipeline keeps
|
|
360
|
-
* exactly the faces it exists to discard. */
|
|
361
|
-
mirrorPipeline?: GPURenderPipeline
|
|
362
357
|
uniformBuffer: GPUBuffer
|
|
363
358
|
/** The group's own image maps, uploaded once per apply and owned here — the
|
|
364
359
|
* install destroys them when it is replaced, so a re-apply cannot leak. */
|
|
@@ -8512,6 +8507,8 @@ export class Engine {
|
|
|
8512
8507
|
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
|
|
8513
8508
|
{ binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
|
|
8514
8509
|
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
8510
|
+
// The model's ObjectLight, for its jelly eyes.
|
|
8511
|
+
{ binding: 3, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
|
|
8515
8512
|
],
|
|
8516
8513
|
})
|
|
8517
8514
|
const shadowShader = this.device.createShaderModule({
|
|
@@ -15180,6 +15177,20 @@ export class Engine {
|
|
|
15180
15177
|
physics.setFloor(this.physicsFloor)
|
|
15181
15178
|
}
|
|
15182
15179
|
|
|
15180
|
+
// Its ObjectLight: the world's ambient, and the layers its kind draws on —
|
|
15181
|
+
// the cast on the character layer as well as the default one. Words:
|
|
15182
|
+
// layers [0..3], ambient flag [4..7], SH [8..43], fill [44..47], jelly
|
|
15183
|
+
// eyes [48..71] — see materials/common.ts.
|
|
15184
|
+
const objectLight = new Float32Array(96)
|
|
15185
|
+
new Uint32Array(objectLight.buffer)[0] =
|
|
15186
|
+
isStage || isPlane || isProp ? RENDERING_LAYER_DEFAULT : (RENDERING_LAYER_DEFAULT | RENDERING_LAYER_CHARACTER) >>> 0
|
|
15187
|
+
const lightBuffer = this.device.createBuffer({
|
|
15188
|
+
label: `${name}: object light`,
|
|
15189
|
+
size: objectLight.byteLength,
|
|
15190
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
15191
|
+
})
|
|
15192
|
+
this.device.queue.writeBuffer(lightBuffer, 0, objectLight)
|
|
15193
|
+
|
|
15183
15194
|
// One per cascade: the shadow VERTEX shader reads a single matrix, and
|
|
15184
15195
|
// which one is the only thing that differs between the cascade passes.
|
|
15185
15196
|
const shadowBindGroups = SHADOW_CASCADES.map((_, ci) =>
|
|
@@ -15190,6 +15201,7 @@ export class Engine {
|
|
|
15190
15201
|
{ binding: 0, resource: { buffer: this.shadowCascadeVPBuffers[ci] } },
|
|
15191
15202
|
{ binding: 1, resource: { buffer: skinMatrixBuffer } },
|
|
15192
15203
|
{ binding: 2, resource: this.materialSampler },
|
|
15204
|
+
{ binding: 3, resource: { buffer: lightBuffer } },
|
|
15193
15205
|
],
|
|
15194
15206
|
}),
|
|
15195
15207
|
)
|
|
@@ -15202,23 +15214,10 @@ export class Engine {
|
|
|
15202
15214
|
{ binding: 0, resource: { buffer: this.castShadowVPBuffer } },
|
|
15203
15215
|
{ binding: 1, resource: { buffer: skinMatrixBuffer } },
|
|
15204
15216
|
{ binding: 2, resource: this.materialSampler },
|
|
15217
|
+
{ binding: 3, resource: { buffer: lightBuffer } },
|
|
15205
15218
|
],
|
|
15206
15219
|
}),
|
|
15207
15220
|
)
|
|
15208
|
-
|
|
15209
|
-
// Its ObjectLight: the world's ambient, and the layers its kind draws on —
|
|
15210
|
-
// the cast on the character layer as well as the default one. Words:
|
|
15211
|
-
// layers [0..3], ambient flag [4..7], SH [8..43], fill [44..47], jelly
|
|
15212
|
-
// eyes [48..71] — see materials/common.ts.
|
|
15213
|
-
const objectLight = new Float32Array(96)
|
|
15214
|
-
new Uint32Array(objectLight.buffer)[0] =
|
|
15215
|
-
isStage || isPlane || isProp ? RENDERING_LAYER_DEFAULT : (RENDERING_LAYER_DEFAULT | RENDERING_LAYER_CHARACTER) >>> 0
|
|
15216
|
-
const lightBuffer = this.device.createBuffer({
|
|
15217
|
-
label: `${name}: object light`,
|
|
15218
|
-
size: objectLight.byteLength,
|
|
15219
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
15220
|
-
})
|
|
15221
|
-
this.device.queue.writeBuffer(lightBuffer, 0, objectLight)
|
|
15222
15221
|
const mainPerInstanceBindGroup = this.perInstanceBindGroup(name, skinMatrixBuffer, lightBuffer)
|
|
15223
15222
|
|
|
15224
15223
|
const pickPerInstanceBindGroup = this.device.createBindGroup({
|
|
@@ -16189,6 +16188,10 @@ export class Engine {
|
|
|
16189
16188
|
// Alpha is the switch; clamp the rest so a stacked multiply cannot send a
|
|
16190
16189
|
// colour negative and light the material from the inside.
|
|
16191
16190
|
for (let k = 0; k < 11; k++) if (d[k] < 0) d[k] = 0
|
|
16191
|
+
// Alpha also stops at 1, as in MMD. Models ship additive +1 alpha morphs
|
|
16192
|
+
// (照れ on 拾星梦语) that MMD's clamp makes a no-op; let through, alpha 2
|
|
16193
|
+
// blends as src*2 - dst and burns every material white.
|
|
16194
|
+
if (d[3] > 1) d[3] = 1
|
|
16192
16195
|
if (d[3] < 0.0001) inst.morphHiddenMaterials.add(target.materialName)
|
|
16193
16196
|
let changed = false
|
|
16194
16197
|
for (let k = 0; k < 11; k++) {
|
|
@@ -18021,10 +18024,9 @@ export class Engine {
|
|
|
18021
18024
|
if (!result.ok) return
|
|
18022
18025
|
const module = this.cachedShaderModule(result.wgsl, `style group: ${g.id} (${renderClass})`)
|
|
18023
18026
|
await Promise.all([
|
|
18024
|
-
this.createRenderClassPipeline(renderClass, module, false, true,
|
|
18025
|
-
this.createRenderClassPipeline(renderClass, module, false, false,
|
|
18026
|
-
renderClass === "hair" ? this.createRenderClassPipeline(renderClass, module, true, true,
|
|
18027
|
-
renderClass === "eye" ? this.createRenderClassPipeline(renderClass, module, false, true, true, blend) : null,
|
|
18027
|
+
this.createRenderClassPipeline(renderClass, module, false, true, blend),
|
|
18028
|
+
this.createRenderClassPipeline(renderClass, module, false, false, blend),
|
|
18029
|
+
renderClass === "hair" ? this.createRenderClassPipeline(renderClass, module, true, true, blend) : null,
|
|
18028
18030
|
]).catch(() => {})
|
|
18029
18031
|
}),
|
|
18030
18032
|
)
|
|
@@ -18401,20 +18403,17 @@ export class Engine {
|
|
|
18401
18403
|
// The single-sided twins a draw of this group will ask for (sidedPipeline),
|
|
18402
18404
|
// built with their pipelines rather than synchronously at the first draw.
|
|
18403
18405
|
const singleSided = inst.drawCalls.some((dc) => !dc.doubleSided && group.materials.includes(dc.materialName))
|
|
18404
|
-
const variant = (overEyes: boolean, depthWrite: boolean
|
|
18405
|
-
this.createRenderClassPipeline(renderClass, module, overEyes, depthWrite,
|
|
18406
|
-
if (singleSided
|
|
18406
|
+
const variant = (overEyes: boolean, depthWrite: boolean) =>
|
|
18407
|
+
this.createRenderClassPipeline(renderClass, module, overEyes, depthWrite, blend).then(async (p) => {
|
|
18408
|
+
if (singleSided) await this.buildSidedTwin(p, false)
|
|
18407
18409
|
return p
|
|
18408
18410
|
})
|
|
18409
18411
|
const variants = Promise.all([
|
|
18410
|
-
variant(false, true
|
|
18412
|
+
variant(false, true),
|
|
18411
18413
|
// The depth-write-off twin: stage transparency draws with it (see
|
|
18412
18414
|
// pipelineForDrawCall), and a future OIT path would too.
|
|
18413
|
-
variant(false, false
|
|
18414
|
-
renderClass === "hair" ? variant(true, true
|
|
18415
|
-
// Only the eye needs one: every other class culls "none", which a flipped
|
|
18416
|
-
// winding leaves alone.
|
|
18417
|
-
renderClass === "eye" ? variant(false, true, true) : undefined,
|
|
18415
|
+
variant(false, false),
|
|
18416
|
+
renderClass === "hair" ? variant(true, true) : undefined,
|
|
18418
18417
|
])
|
|
18419
18418
|
// Handled below either way; this keeps a rejection off the console while
|
|
18420
18419
|
// the diagnostics are read.
|
|
@@ -18435,9 +18434,8 @@ export class Engine {
|
|
|
18435
18434
|
let pipeline: GPURenderPipeline
|
|
18436
18435
|
let pipelineNoDepthWrite: GPURenderPipeline
|
|
18437
18436
|
let overEyesPipeline: GPURenderPipeline | undefined
|
|
18438
|
-
let mirrorPipeline: GPURenderPipeline | undefined
|
|
18439
18437
|
try {
|
|
18440
|
-
;[pipeline, pipelineNoDepthWrite, overEyesPipeline
|
|
18438
|
+
;[pipeline, pipelineNoDepthWrite, overEyesPipeline] = await variants
|
|
18441
18439
|
} catch (e) {
|
|
18442
18440
|
diagnostics.push({ severity: "error", message: `pipeline creation failed: ${(e as Error).message}` })
|
|
18443
18441
|
return { ok: false, diagnostics, slotMap: result.slotMap }
|
|
@@ -18467,7 +18465,6 @@ export class Engine {
|
|
|
18467
18465
|
pipeline,
|
|
18468
18466
|
pipelineNoDepthWrite,
|
|
18469
18467
|
overEyesPipeline,
|
|
18470
|
-
mirrorPipeline,
|
|
18471
18468
|
uniformBuffer,
|
|
18472
18469
|
images: this.uploadGroupImages(group),
|
|
18473
18470
|
imagesByMaterial: group.imagesByMaterial
|
|
@@ -18643,29 +18640,25 @@ export class Engine {
|
|
|
18643
18640
|
/**
|
|
18644
18641
|
* Render-class pipeline state. A group's compiled graph swaps the fragment shading; the
|
|
18645
18642
|
* render-class owns pass integration (stencil interplay, depth bias, cull). auto = plain;
|
|
18646
|
-
* eye = stamp
|
|
18643
|
+
* eye = stencil stamp; hair = stencil-test (+ the over-eyes variant). Cull comes from
|
|
18644
|
+
* the material's double-sided flag (sidedPipeline).
|
|
18647
18645
|
*/
|
|
18648
18646
|
private createRenderClassPipeline(
|
|
18649
18647
|
renderClass: RenderClass,
|
|
18650
18648
|
module: GPUShaderModule,
|
|
18651
18649
|
overEyes: boolean,
|
|
18652
18650
|
depthWrite = true,
|
|
18653
|
-
mirrored = false,
|
|
18654
18651
|
blend: StyleBlend = "over",
|
|
18655
18652
|
): Promise<GPURenderPipeline> {
|
|
18656
18653
|
const base = {
|
|
18657
18654
|
label: `style ${renderClass}${overEyes ? " (over eyes)" : ""}`,
|
|
18658
18655
|
layout: this.mainPipelineLayout,
|
|
18659
18656
|
vertex: { module, buffers: this.materialVertexBufferLayouts },
|
|
18660
|
-
//
|
|
18661
|
-
//
|
|
18662
|
-
//
|
|
18663
|
-
//
|
|
18664
|
-
|
|
18665
|
-
// the camera sees.
|
|
18666
|
-
primitive: {
|
|
18667
|
-
cullMode: (renderClass === "eye" ? (mirrored ? "back" : "front") : "none") as GPUCullMode,
|
|
18668
|
-
},
|
|
18657
|
+
// Every class draws both sides here and takes the PMX double-sided flag
|
|
18658
|
+
// from sidedPipeline, whose single-sided twin culls front (back in the
|
|
18659
|
+
// mirror's flipped winding) — the eye's old fixed cull. A double-sided
|
|
18660
|
+
// iris sheet keeps the faces a turn of the eye swings away.
|
|
18661
|
+
primitive: { cullMode: "none" as GPUCullMode },
|
|
18669
18662
|
multisample: { count: Engine.MULTISAMPLE_COUNT },
|
|
18670
18663
|
}
|
|
18671
18664
|
const plainDepth: GPUDepthStencilState = {
|
|
@@ -18956,7 +18949,6 @@ export class Engine {
|
|
|
18956
18949
|
if (dc.groupId) {
|
|
18957
18950
|
const install = inst.styleGroups.get(dc.groupId)
|
|
18958
18951
|
if (install) {
|
|
18959
|
-
if (mirrored && install.mirrorPipeline) return install.mirrorPipeline
|
|
18960
18952
|
return stageGlass ? install.pipelineNoDepthWrite : install.pipeline
|
|
18961
18953
|
}
|
|
18962
18954
|
}
|
package/src/graph/slots.ts
CHANGED
|
@@ -102,13 +102,14 @@ function prelude(renderClass: RenderClass, alphaMode: AlphaMode, blend: StyleBle
|
|
|
102
102
|
// camera-averted fragments change (front surfaces have dot(n,v) > 0 and are
|
|
103
103
|
// untouched), unlike a front_facing test, which PMX's clockwise winding
|
|
104
104
|
// inverts. Fixes inner skirt linings (裙子白1-style flipped-normal copies)
|
|
105
|
-
// shading near-black through sheer outer layers. Eye is exempt:
|
|
106
|
-
//
|
|
105
|
+
// shading near-black through sheer outer layers. Eye is exempt: its normals
|
|
106
|
+
// shade as authored.
|
|
107
107
|
const flip =
|
|
108
108
|
renderClass === "eye" ? "" : "\n n = select(-n, n, dot(n, v) >= 0.0);"
|
|
109
109
|
return `@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
110
|
-
${PLAIN_SAMPLE} // MMD alpha semantics: material alpha × texture alpha
|
|
111
|
-
|
|
110
|
+
${PLAIN_SAMPLE} // MMD alpha semantics: material alpha × texture alpha, capped at 1 as MMD
|
|
111
|
+
// does (a PMX can author alpha above 1; blended, it burns the material white).
|
|
112
|
+
let alpha = saturate(material.alpha * tex_s.a);
|
|
112
113
|
${discard}
|
|
113
114
|
|
|
114
115
|
// ── Dissolve ──
|
package/src/jelly-eyes.ts
CHANGED
|
@@ -90,23 +90,23 @@ const HL_SCALE_MIN = 0.85
|
|
|
90
90
|
const HL_SCALE_MAX = 1.2
|
|
91
91
|
// The roll: while a closing expression holds, the iris turns about its eye
|
|
92
92
|
// bone's X axis (左目/右目, the eyeball's centre), rolling down with the lid —
|
|
93
|
-
// the angle at weight 1, and simultaneous expressions add.
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
// exactly, which the lid hides; here the roll follows on a spring
|
|
97
|
-
// pace
|
|
98
|
-
//
|
|
93
|
+
// the angle at weight 1, and simultaneous expressions add. It comes forward
|
|
94
|
+
// only with the spring (iris radii): pushed with the expression as well, a
|
|
95
|
+
// shut eye's iris came through a lid set close in front of it (白里黑子). The MME rolls with the lid
|
|
96
|
+
// exactly, which the lid hides; here the roll follows on a spring twice the
|
|
97
|
+
// blink's pace with its decay, so as the eye opens the iris swings up past rest
|
|
98
|
+
// and wobbles back, where it can be seen. At the blink's own pace it trailed
|
|
99
|
+
// the opening lid and swung a third of an iris past centre: a jump.
|
|
99
100
|
const ROLL: Record<string, number> = {
|
|
100
101
|
まばたき: -15, blink: -15, Blink: -15, eye_close: -15, Fcl_EYE_Close: -15, はぅ: -15, 笑い: -5,
|
|
101
102
|
ウィンク: -5, ウィンク: -5, wink: -5, Wink: -5, ウィンク2: -15, ウィンク2: -15, blink_L: -15, Fcl_EYE_Close_L: -15,
|
|
102
103
|
ウィンク右: -5, ウィンク右: -5, wink_R: -5, Wink_R: -5, ウィンク2右: -15, ウィンク2右: -15, blink_R: -15, Fcl_EYE_Close_R: -15,
|
|
103
104
|
}
|
|
104
|
-
const ROLL_HZ =
|
|
105
|
-
const ROLL_ZETA = Math.LN2 / HALF_LIFE / (2 * Math.PI *
|
|
106
|
-
// How much of the spring's swing about the expression angle shows:
|
|
107
|
-
// of
|
|
108
|
-
const ROLL_SWING = 0.
|
|
109
|
-
const PUSH_EXPRESSION = 0.15
|
|
105
|
+
const ROLL_HZ = 6
|
|
106
|
+
const ROLL_ZETA = Math.LN2 / HALF_LIFE / (2 * Math.PI * ROLL_HZ)
|
|
107
|
+
// How much of the spring's swing about the expression angle shows: a
|
|
108
|
+
// twenty-fifth of an iris radius past centre after a blink.
|
|
109
|
+
const ROLL_SWING = 0.25
|
|
110
110
|
const PUSH_ELASTIC = 0.04
|
|
111
111
|
// A highlight sheet by name, for rigs that keep it on its own material.
|
|
112
112
|
const HIGHLIGHT = /ハイライト|ハイライト|高光|highlight|hilight|(^|[^a-z])hl([^a-z]|$)/i
|
|
@@ -288,10 +288,23 @@ export class JellyEyes {
|
|
|
288
288
|
const l = index("左目")
|
|
289
289
|
const r = index("右目")
|
|
290
290
|
this.headBone = index("頭")
|
|
291
|
-
const
|
|
292
|
-
const
|
|
291
|
+
const all = model.getMorphing().morphs
|
|
292
|
+
const names = all.map((m) => m.name)
|
|
293
|
+
// The weights read are resolved, so a group whose every target is named
|
|
294
|
+
// (Reze's まばたき is ウィンク2 + ウィンク2右) arrives through its
|
|
295
|
+
// leaves; counted as well, a blink rolled the iris twice as far.
|
|
296
|
+
const known = new Set([...BOTH, ...LEFT, ...RIGHT])
|
|
297
|
+
const resolved = (i: number) => {
|
|
298
|
+
const refs = all[i].groupReferences
|
|
299
|
+
return !!refs?.length && refs.every((r) => known.has(names[r.morphIndex]))
|
|
300
|
+
}
|
|
301
|
+
const morphIndex = (n: string) => {
|
|
302
|
+
const i = names.indexOf(n)
|
|
303
|
+
return i >= 0 && !resolved(i) ? i : -1
|
|
304
|
+
}
|
|
305
|
+
const find = (list: string[]) => list.map(morphIndex).filter((i) => i >= 0)
|
|
293
306
|
this.morphs = [find(BOTH), find(LEFT), find(RIGHT)]
|
|
294
|
-
const rolls = (list: string[]) => list.map((n) => ({ morph:
|
|
307
|
+
const rolls = (list: string[]) => list.map((n) => ({ morph: morphIndex(n), deg: ROLL[n] ?? 0 })).filter((r) => r.morph >= 0 && r.deg !== 0)
|
|
295
308
|
this.rolls = [rolls([...BOTH, ...LEFT]), rolls([...BOTH, ...RIGHT])]
|
|
296
309
|
this.rest = findIrises(model, l, r, both)
|
|
297
310
|
// The bone that carries each iris, which may be under the eye bone.
|
|
@@ -423,7 +436,7 @@ export class JellyEyes {
|
|
|
423
436
|
const pivot = rest.pivot
|
|
424
437
|
o.set(pivot ? [pivot[0], pivot[1], pivot[2], (this.roll[e] + (s.roll - this.roll[e]) * ROLL_SWING) * DEG * g] : [0, 0, 0, 0], 80 + e * 4)
|
|
425
438
|
const hs = (k: 0 | 1) => Math.max(HL_SCALE_MIN, Math.min(HL_SCALE_MAX, 1 + x * HL_SCALE[k] * g))
|
|
426
|
-
o.set([hs(0), hs(1),
|
|
439
|
+
o.set([hs(0), hs(1), Math.min(Math.abs(x), SPRING_LIMIT) * PUSH_ELASTIC * r * g, 0], 88 + e * 4)
|
|
427
440
|
}
|
|
428
441
|
}
|
|
429
442
|
|
|
@@ -550,6 +563,11 @@ function blink(s: EyeState, dt: number): void {
|
|
|
550
563
|
* As draw ids (1-based over the materials that draw, as the engine numbers
|
|
551
564
|
* MaterialUniforms.materialId), at most two. A rig whose highlight is painted
|
|
552
565
|
* into the iris has none, and only the iris moves.
|
|
566
|
+
*
|
|
567
|
+
* A layer drawn on vertices an earlier layer already uses is a copy of that
|
|
568
|
+
* layer (白里黑子's ☆ and ❤ are star and heart irises on the iris's own
|
|
569
|
+
* vertices), so it moves with it. Slid as a highlight, a copy of the dome sits
|
|
570
|
+
* in front of the iris on one half, flipping sides with the tremble.
|
|
553
571
|
*/
|
|
554
572
|
function findHighlights(model: Model, rest: [EyeRest, EyeRest]): number[] {
|
|
555
573
|
const materials = model.getMaterials?.() ?? []
|
|
@@ -557,6 +575,7 @@ function findHighlights(model: Model, rest: [EyeRest, EyeRest]): number[] {
|
|
|
557
575
|
const iris = new Set([...rest[0].verts, ...rest[1].verts])
|
|
558
576
|
if (!materials.length || !indices.length || !iris.size) return []
|
|
559
577
|
const layers: { id: number; name: string; minX: number; maxX: number; minY: number; maxY: number }[] = []
|
|
578
|
+
const used = new Set<number>()
|
|
560
579
|
let start = 0
|
|
561
580
|
let id = 0
|
|
562
581
|
for (const m of materials) {
|
|
@@ -564,16 +583,21 @@ function findHighlights(model: Model, rest: [EyeRest, EyeRest]): number[] {
|
|
|
564
583
|
if (count === 0) continue
|
|
565
584
|
id++
|
|
566
585
|
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity
|
|
586
|
+
let copy = true
|
|
587
|
+
const mine: number[] = []
|
|
567
588
|
for (let k = start; k < start + count; k++) {
|
|
568
589
|
const v = indices[k]
|
|
569
590
|
if (!iris.has(v)) continue
|
|
591
|
+
if (!used.has(v)) copy = false
|
|
592
|
+
mine.push(v)
|
|
570
593
|
minX = Math.min(minX, positions[v * 3])
|
|
571
594
|
maxX = Math.max(maxX, positions[v * 3])
|
|
572
595
|
minY = Math.min(minY, positions[v * 3 + 1])
|
|
573
596
|
maxY = Math.max(maxY, positions[v * 3 + 1])
|
|
574
597
|
}
|
|
575
598
|
start += count
|
|
576
|
-
|
|
599
|
+
for (const v of mine) used.add(v)
|
|
600
|
+
if (minX <= maxX && !copy) layers.push({ id, name: m.name, minX, maxX, minY, maxY })
|
|
577
601
|
}
|
|
578
602
|
const out: number[] = []
|
|
579
603
|
layers.forEach((l, i) => {
|
|
@@ -26,6 +26,73 @@ import { WORLD_AMBIENT_WGSL } from "../lights"
|
|
|
26
26
|
// group(1): per-model skinning
|
|
27
27
|
// group(2): per-material (diffuse texture + material uniforms)
|
|
28
28
|
|
|
29
|
+
/** The jelly eyes' vertex move (jelly-eyes.ts). Every pass that places model
|
|
30
|
+
* vertices takes it, or a pass that skips it draws the iris where it was —
|
|
31
|
+
* the shadow map then shades half the moved iris with its own unmoved copy.
|
|
32
|
+
* Reads `objectLight.jelly` and `material.materialId`, which the including
|
|
33
|
+
* module declares. */
|
|
34
|
+
export const JELLY_VERTEX_WGSL = /* wgsl */ `
|
|
35
|
+
fn _rzJellyTurn(q: vec2f, a: f32) -> vec2f {
|
|
36
|
+
let c = cos(a);
|
|
37
|
+
let s = sin(a);
|
|
38
|
+
return vec2f(c * q.x - s * q.y, s * q.x + c * q.y);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Where the jelly puts an iris vertex. The iris is the part of the eye the eye
|
|
43
|
+
* bones carry, so it is moved as a whole, in bind space before skinning:
|
|
44
|
+
* scaled about its centre (the CPU composes the blink's spring, the lid's
|
|
45
|
+
* press and her motion's knock into one scale), turned and drifted a hair on
|
|
46
|
+
* the spring, brought forward a hair, and rolled about the eye bone with a
|
|
47
|
+
* closing expression — inside the white, which stays where it is. A highlight
|
|
48
|
+
* layer (this draw's material is one of jelly[1].w's) takes the iris's move,
|
|
49
|
+
* then its own scale, turn and tremble, before the roll. Vertices take it by their weight on the eye bones
|
|
50
|
+
* (jelly[2].w, jelly[3].w), so nothing else on the face moves.
|
|
51
|
+
*/
|
|
52
|
+
fn rzJellyVertex(p: vec3f, joints: vec4<u32>, nw: vec4f) -> vec3f {
|
|
53
|
+
let j = objectLight.jelly;
|
|
54
|
+
let ids = u32(j[1].w);
|
|
55
|
+
let mid = u32(material.materialId);
|
|
56
|
+
let hl = mid > 0u && (mid == (ids & 4095u) || mid == (ids >> 12u));
|
|
57
|
+
var out = p;
|
|
58
|
+
for (var e = 0u; e < 2u; e++) {
|
|
59
|
+
let bone = j[2u + e].w;
|
|
60
|
+
if (bone < 0.0) { continue; }
|
|
61
|
+
let b = u32(bone);
|
|
62
|
+
let w = dot(nw, select(vec4f(0.0), vec4f(1.0), joints == vec4<u32>(b)));
|
|
63
|
+
if (w <= 0.0) { continue; }
|
|
64
|
+
let c = j[e].xyz;
|
|
65
|
+
// A rig that carries both eyes on one bone: each vertex follows its own side.
|
|
66
|
+
if (j[2].w == j[3].w && e == 0u && distance(p.xy, j[1].xy) < distance(p.xy, j[0].xy)) { continue; }
|
|
67
|
+
if (j[2].w == j[3].w && e == 1u && distance(p.xy, j[0].xy) <= distance(p.xy, j[1].xy)) { continue; }
|
|
68
|
+
let t = j[4u + e];
|
|
69
|
+
let m = j[6u + e];
|
|
70
|
+
let h = j[10u + e];
|
|
71
|
+
var q = _rzJellyTurn((p.xy - c.xy) * t.xy, t.z) + m.xy;
|
|
72
|
+
// Forward is −Z (MMD faces −Z).
|
|
73
|
+
var push = h.z;
|
|
74
|
+
if (hl) {
|
|
75
|
+
// Sliding over the iris's dome on its own, a highlight sheet a hair in
|
|
76
|
+
// front of it would sink into it where the dome bulges more and the two
|
|
77
|
+
// fight for depth; so it comes forward by as far as it slid — a slope of
|
|
78
|
+
// one, steeper than the dome anywhere it draws.
|
|
79
|
+
let before = q;
|
|
80
|
+
q = _rzJellyTurn(q * h.xy, t.w) + m.zw;
|
|
81
|
+
push += length(q - before);
|
|
82
|
+
}
|
|
83
|
+
var moved = vec3f(c.xy + q, p.z - push);
|
|
84
|
+
let roll = j[8u + e];
|
|
85
|
+
if (roll.w != 0.0) {
|
|
86
|
+
let d = moved - roll.xyz;
|
|
87
|
+
let rd = _rzJellyTurn(d.yz, roll.w);
|
|
88
|
+
moved = roll.xyz + vec3f(d.x, rd.x, rd.y);
|
|
89
|
+
}
|
|
90
|
+
out = mix(p, moved, w);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
`
|
|
95
|
+
|
|
29
96
|
export const COMMON_BINDINGS_WGSL = /* wgsl */ `
|
|
30
97
|
|
|
31
98
|
struct CameraUniforms {
|
|
@@ -234,66 +301,7 @@ fn _rzCastShadow(wp: vec3f) -> f32 {
|
|
|
234
301
|
struct ObjectLight { layers: vec4u, ambient: vec4f, sh: array<vec4f, 9>, fill: vec4f, jelly: array<vec4f, 12> }
|
|
235
302
|
@group(1) @binding(1) var<uniform> objectLight: ObjectLight;
|
|
236
303
|
|
|
237
|
-
|
|
238
|
-
let c = cos(a);
|
|
239
|
-
let s = sin(a);
|
|
240
|
-
return vec2f(c * q.x - s * q.y, s * q.x + c * q.y);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Where the jelly puts an iris vertex. The iris is the part of the eye the eye
|
|
245
|
-
* bones carry, so it is moved as a whole, in bind space before skinning:
|
|
246
|
-
* scaled about its centre (the CPU composes the blink's spring, the lid's
|
|
247
|
-
* press and her motion's knock into one scale), turned and drifted a hair on
|
|
248
|
-
* the spring, brought forward a hair, and rolled about the eye bone with a
|
|
249
|
-
* closing expression — inside the white, which stays where it is. A highlight
|
|
250
|
-
* layer (this draw's material is one of jelly[1].w's) takes the iris's move,
|
|
251
|
-
* then its own scale, turn and tremble, before the roll. Vertices take it by their weight on the eye bones
|
|
252
|
-
* (jelly[2].w, jelly[3].w), so nothing else on the face moves.
|
|
253
|
-
*/
|
|
254
|
-
fn rzJellyVertex(p: vec3f, joints: vec4<u32>, nw: vec4f) -> vec3f {
|
|
255
|
-
let j = objectLight.jelly;
|
|
256
|
-
let ids = u32(j[1].w);
|
|
257
|
-
let mid = u32(material.materialId);
|
|
258
|
-
let hl = mid > 0u && (mid == (ids & 4095u) || mid == (ids >> 12u));
|
|
259
|
-
var out = p;
|
|
260
|
-
for (var e = 0u; e < 2u; e++) {
|
|
261
|
-
let bone = j[2u + e].w;
|
|
262
|
-
if (bone < 0.0) { continue; }
|
|
263
|
-
let b = u32(bone);
|
|
264
|
-
let w = dot(nw, select(vec4f(0.0), vec4f(1.0), joints == vec4<u32>(b)));
|
|
265
|
-
if (w <= 0.0) { continue; }
|
|
266
|
-
let c = j[e].xyz;
|
|
267
|
-
// A rig that carries both eyes on one bone: each vertex follows its own side.
|
|
268
|
-
if (j[2].w == j[3].w && e == 0u && distance(p.xy, j[1].xy) < distance(p.xy, j[0].xy)) { continue; }
|
|
269
|
-
if (j[2].w == j[3].w && e == 1u && distance(p.xy, j[0].xy) <= distance(p.xy, j[1].xy)) { continue; }
|
|
270
|
-
let t = j[4u + e];
|
|
271
|
-
let m = j[6u + e];
|
|
272
|
-
let h = j[10u + e];
|
|
273
|
-
var q = _rzJellyTurn((p.xy - c.xy) * t.xy, t.z) + m.xy;
|
|
274
|
-
// Forward is −Z (MMD faces −Z).
|
|
275
|
-
var push = h.z;
|
|
276
|
-
if (hl) {
|
|
277
|
-
// Sliding over the iris's dome on its own, a highlight sheet a hair in
|
|
278
|
-
// front of it would sink into it where the dome bulges more and the two
|
|
279
|
-
// fight for depth; so it comes forward by as far as it slid — a slope of
|
|
280
|
-
// one, steeper than the dome anywhere it draws.
|
|
281
|
-
let before = q;
|
|
282
|
-
q = _rzJellyTurn(q * h.xy, t.w) + m.zw;
|
|
283
|
-
push += length(q - before);
|
|
284
|
-
}
|
|
285
|
-
var moved = vec3f(c.xy + q, p.z - push);
|
|
286
|
-
let roll = j[8u + e];
|
|
287
|
-
if (roll.w != 0.0) {
|
|
288
|
-
let d = moved - roll.xyz;
|
|
289
|
-
let rd = _rzJellyTurn(d.yz, roll.w);
|
|
290
|
-
moved = roll.xyz + vec3f(d.x, rd.x, rd.y);
|
|
291
|
-
}
|
|
292
|
-
out = mix(p, moved, w);
|
|
293
|
-
}
|
|
294
|
-
return out;
|
|
295
|
-
}
|
|
296
|
-
|
|
304
|
+
${JELLY_VERTEX_WGSL}
|
|
297
305
|
/** The light arriving at a surface of this object facing n: its own ambient
|
|
298
306
|
* where loaded data gave it one (a stage's probes, a character's), else the
|
|
299
307
|
* world's. The same polynomial rzWorldAmbient evaluates. */
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// attachment, and that answer does not exist at import time. The constant could
|
|
28
28
|
// not have taken the outputs at all, which is most of why it did not have them.
|
|
29
29
|
|
|
30
|
-
import { DISSOLVE_WGSL } from "../materials/common"
|
|
30
|
+
import { DISSOLVE_WGSL, JELLY_VERTEX_WGSL } from "../materials/common"
|
|
31
31
|
import { sceneFsOutWgsl, sceneIdPadWgsl } from "./scene-contract"
|
|
32
32
|
|
|
33
33
|
export function transparentDepthPrepassWgsl(): string {
|
|
@@ -44,13 +44,20 @@ struct MaterialUniforms {
|
|
|
44
44
|
alpha: f32,
|
|
45
45
|
_skip0: vec4f,
|
|
46
46
|
_skip1: vec4f,
|
|
47
|
-
_skip2:
|
|
47
|
+
_skip2: f32,
|
|
48
|
+
materialId: f32,
|
|
49
|
+
_skip3: f32,
|
|
48
50
|
dissolve: f32,
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
52
54
|
@group(0) @binding(2) var diffuseSampler: sampler;
|
|
53
55
|
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
56
|
+
// The model's ObjectLight, for its jelly eyes: depth goes down where the colour
|
|
57
|
+
// pass will draw the iris.
|
|
58
|
+
struct ObjectLight { layers: vec4u, ambient: vec4f, sh: array<vec4f, 9>, fill: vec4f, jelly: array<vec4f, 12> }
|
|
59
|
+
@group(1) @binding(1) var<uniform> objectLight: ObjectLight;
|
|
60
|
+
${JELLY_VERTEX_WGSL}
|
|
54
61
|
@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
55
62
|
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
56
63
|
|
|
@@ -74,10 +81,11 @@ struct VSOut {
|
|
|
74
81
|
@location(3) joints0: vec4<u32>,
|
|
75
82
|
@location(4) weights0: vec4<f32>,
|
|
76
83
|
) -> VSOut {
|
|
77
|
-
let pos4 = vec4f(position, 1.0);
|
|
78
84
|
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
79
85
|
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
80
86
|
let w = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
87
|
+
var pos4 = vec4f(position, 1.0);
|
|
88
|
+
if (objectLight.jelly[0].w > 0.0) { pos4 = vec4f(rzJellyVertex(position, joints0, w), 1.0); }
|
|
81
89
|
var skinned = vec4f(0.0);
|
|
82
90
|
for (var i = 0u; i < 4u; i++) {
|
|
83
91
|
skinned += (skinMats[joints0[i]] * pos4) * w[i];
|
|
@@ -197,12 +197,22 @@ fn safe_normal2(v: vec2f) -> vec2f {
|
|
|
197
197
|
let w = max(clipPos.w, 1e-4);
|
|
198
198
|
var ndc: vec2f;
|
|
199
199
|
var coverage = 1.0;
|
|
200
|
+
// The scene's width dial splits around 1×. Above it, it widens the line
|
|
201
|
+
// before the minimum, as it always has. Below it, it thins the line AFTER
|
|
202
|
+
// the minimum: a line held at the minimum is drawn that wide and faded, and
|
|
203
|
+
// it still writes depth across all of it — so a dial at 0.1× or at 0.0001×
|
|
204
|
+
// left the same 1.5 px ring for whatever drew or read depth after it, and
|
|
205
|
+
// the dial did nothing visible. Thinned past the minimum instead, the hull
|
|
206
|
+
// is genuinely sub-pixel, the multisampling covers only what it covers, and
|
|
207
|
+
// the line fades with the dial and claims depth only where it is.
|
|
208
|
+
let widen = max(material.widthScale, 1.0);
|
|
209
|
+
let thin = min(material.widthScale, 1.0);
|
|
200
210
|
if (material.textured > 0.5) {
|
|
201
211
|
// The game's: a view-space offset of edgeSize world units along the view
|
|
202
212
|
// normal, projected, then each axis held between 1.2 px and 1.2 px ×
|
|
203
213
|
// (1 + _OutlineMaxOffsetMultiplier, 1.5) of a 1920-wide frame; a line the
|
|
204
214
|
// minimum had to push fades by how far it was pushed.
|
|
205
|
-
let off = viewNormal.xy * material.edgeSize *
|
|
215
|
+
let off = viewNormal.xy * material.edgeSize * widen * outlineNormal.w;
|
|
206
216
|
let d1 = vec2f(off.x * camera.projection[0][0], off.y * camera.projection[1][1]) / w;
|
|
207
217
|
// the minimum along the push direction (the game's dir · 2/screen · 1.2)
|
|
208
218
|
let pxNdc = 2.0 / refViewport * 1.2 * safe_normal2(viewNormal.xy);
|
|
@@ -214,7 +224,7 @@ fn safe_normal2(v: vec2f) -> vec2f {
|
|
|
214
224
|
ndc = o * select(vec2f(1.0), sign(d1), abs(d1) > vec2f(0.0));
|
|
215
225
|
} else {
|
|
216
226
|
let breakDepth = RZ_OUTLINE_FULL_FIGURE * camera.projection[1][1];
|
|
217
|
-
let nearNdc = viewNormal.xy * (material.edgeSize *
|
|
227
|
+
let nearNdc = viewNormal.xy * (material.edgeSize * widen * outlineNormal.w * 4.0 / refViewport);
|
|
218
228
|
ndc = nearNdc * (min(w, breakDepth) / w);
|
|
219
229
|
|
|
220
230
|
// Antialiased minimum (the game's _OutlineAntialias): held at the minimum
|
|
@@ -228,6 +238,7 @@ fn safe_normal2(v: vec2f) -> vec2f {
|
|
|
228
238
|
ndc = select(vec2f(0.0), ndc * (minPx / px), px > 1e-6);
|
|
229
239
|
}
|
|
230
240
|
}
|
|
241
|
+
ndc *= thin;
|
|
231
242
|
output.normal = worldNormal;
|
|
232
243
|
output.coverage = coverage;
|
|
233
244
|
output.position = vec4f(clipPos.xy + ndc * w, clipPos.z, clipPos.w);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DISSOLVE_WGSL } from "../materials/common"
|
|
1
|
+
import { DISSOLVE_WGSL, JELLY_VERTEX_WGSL } from "../materials/common"
|
|
2
2
|
|
|
3
3
|
// Shadow map depth pass. Skinned VS + alpha-test FS (depth-only attachment, no
|
|
4
4
|
// color targets): texels where diffuse-texture alpha × material alpha fall below
|
|
@@ -21,10 +21,17 @@ struct MaterialDiffuse {
|
|
|
21
21
|
diffuse: vec4f,
|
|
22
22
|
_skip0: vec4f,
|
|
23
23
|
_skip1: vec4f,
|
|
24
|
-
_skip2:
|
|
24
|
+
_skip2: f32,
|
|
25
|
+
materialId: f32,
|
|
26
|
+
_skip3: f32,
|
|
25
27
|
dissolve: f32,
|
|
26
28
|
};
|
|
27
29
|
@group(1) @binding(1) var<uniform> material: MaterialDiffuse;
|
|
30
|
+
// The model's ObjectLight, for its jelly eyes: the map must hold the iris where
|
|
31
|
+
// the colour pass draws it.
|
|
32
|
+
struct ObjectLight { layers: vec4u, ambient: vec4f, sh: array<vec4f, 9>, fill: vec4f, jelly: array<vec4f, 12> }
|
|
33
|
+
@group(0) @binding(3) var<uniform> objectLight: ObjectLight;
|
|
34
|
+
${JELLY_VERTEX_WGSL}
|
|
28
35
|
|
|
29
36
|
struct VSOut {
|
|
30
37
|
@builtin(position) position: vec4f,
|
|
@@ -38,10 +45,11 @@ struct VSOut {
|
|
|
38
45
|
|
|
39
46
|
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,
|
|
40
47
|
@location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) -> VSOut {
|
|
41
|
-
let pos4 = vec4f(position, 1.0);
|
|
42
48
|
let ws = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
43
49
|
let inv = select(1.0, 1.0 / ws, ws > 0.0001);
|
|
44
50
|
let nw = select(vec4f(1.0,0.0,0.0,0.0), weights0 * inv, ws > 0.0001);
|
|
51
|
+
var pos4 = vec4f(position, 1.0);
|
|
52
|
+
if (objectLight.jelly[0].w > 0.0) { pos4 = vec4f(rzJellyVertex(position, joints0, nw), 1.0); }
|
|
45
53
|
var sp = vec4f(0.0);
|
|
46
54
|
for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }
|
|
47
55
|
var out: VSOut;
|