reze-engine 0.68.6 → 0.68.8

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.
@@ -219,36 +219,43 @@ fn _rzCastShadow(wp: vec3f) -> f32 {
219
219
  // jelly its jelly eyes (Engine.setModelJellyEyes, jelly-eyes.ts), not
220
220
  // lighting but per-model all the same, so it rides here:
221
221
  // [0] left iris centre (bind space), strength — 0 is off
222
- // [1] right iris centre, scale bounce (both eyes)
222
+ // [1] right iris centre, the highlight layers' materialIds
223
+ // (a + b·4096, 0 none)
223
224
  // [2] left iris half extents, closure, eye bone (−1 none)
224
225
  // [3] the right's
225
- // [4] left squash, 0, clock, shine [5] the right's
226
- struct ObjectLight { layers: vec4u, ambient: vec4f, sh: array<vec4f, 9>, fill: vec4f, jelly: array<vec4f, 6> }
226
+ // [4] left scale x, y, twist, highlight turn (radians)
227
+ // [5] the right's
228
+ // [6] left drift x, y, highlight's own offset x, y (bind units)
229
+ // [7] the right's
230
+ // [8] left eye bone's head (the roll's pivot), roll (radians)
231
+ // [9] the right's
232
+ // [10] left highlight scale x, y, forward push (bind units), 0
233
+ // [11] the right's
234
+ struct ObjectLight { layers: vec4u, ambient: vec4f, sh: array<vec4f, 9>, fill: vec4f, jelly: array<vec4f, 12> }
227
235
  @group(1) @binding(1) var<uniform> objectLight: ObjectLight;
228
236
 
229
- /** What the glints need to know about a fragment of the eye render class. */
230
- struct RzJelly {
231
- /** Where in the iris this fragment is, in iris radii. */
232
- src: vec2f,
233
- /** 1 inside the iris, easing to 0 at its rim (0 everywhere when off). */
234
- w: f32,
235
- closure: f32,
236
- shine: f32,
237
- /** Scene seconds (wrapped), for the twinkles. */
238
- clock: f32,
239
- };
237
+ fn _rzJellyTurn(q: vec2f, a: f32) -> vec2f {
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
+ }
240
242
 
241
243
  /**
242
244
  * Where the jelly puts an iris vertex. The iris is the part of the eye the eye
243
245
  * bones carry, so it is moved as a whole, in bind space before skinning:
244
- * shrunk about its centre while a lid moves, a little more down than across,
245
- * grown past rest as it springs back, and swelling as a turn or her motion
246
- * knocks it — scale only, in place, inside the white, which stays where it is. Vertices take it by
247
- * their weight on the eye bones (jelly[2].w, jelly[3].w), so nothing else on
248
- * the face moves.
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.
249
253
  */
250
254
  fn rzJellyVertex(p: vec3f, joints: vec4<u32>, nw: vec4f) -> vec3f {
251
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));
252
259
  var out = p;
253
260
  for (var e = 0u; e < 2u; e++) {
254
261
  let bone = j[2u + e].w;
@@ -260,134 +267,33 @@ fn rzJellyVertex(p: vec3f, joints: vec4<u32>, nw: vec4f) -> vec3f {
260
267
  // A rig that carries both eyes on one bone: each vertex follows its own side.
261
268
  if (j[2].w == j[3].w && e == 0u && distance(p.xy, j[1].xy) < distance(p.xy, j[0].xy)) { continue; }
262
269
  if (j[2].w == j[3].w && e == 1u && distance(p.xy, j[0].xy) <= distance(p.xy, j[1].xy)) { continue; }
263
- let st = j[4u + e];
264
- // The squash, nearly round: it shrinks both ways, a little more down than
265
- // across, so the iris keeps its circle and only leans oval; and the bounce
266
- // both eyes share (jelly[1].w). Held under 1.08 so a swell cannot rise
267
- // past the upper lid.
268
- let k = clamp(vec2f(1.0 - 0.8 * st.x, 1.0 - st.x) + j[1].w, vec2f(0.78, 0.75), vec2f(1.08, 1.08));
269
- let moved = c.xy + (p.xy - c.xy) * k;
270
- out = vec3f(mix(p.xy, moved, w), p.z);
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);
271
293
  }
272
294
  return out;
273
295
  }
274
296
 
275
- /**
276
- * The glints' frame for an eye fragment: its place in the iris in iris radii,
277
- * from the bind-space position, which the jelly moved the iris with — so the
278
- * glints ride the sloshing iris. Strength 0 gives w 0, which turns them off.
279
- */
280
- fn rzJellyEye(rp: vec3f) -> RzJelly {
281
- let j = objectLight.jelly;
282
- let qL = (rp.xy - j[0].xy) / max(j[2].xy, vec2f(1e-4));
283
- let qR = (rp.xy - j[1].xy) / max(j[3].xy, vec2f(1e-4));
284
- let right = dot(qR, qR) < dot(qL, qL);
285
- let a = select(qL, qR, right);
286
- let st = select(j[4], j[5], right);
287
- let w = (1.0 - smoothstep(0.85, 1.0, length(a))) * select(0.0, 1.0, j[0].w > 0.0);
288
- return RzJelly(a, w, select(j[2].z, j[3].z, right), st.w, j[4].z);
289
- }
290
-
291
- /**
292
- * The jelly shine, added after the graph: two hard glints where the light
293
- * would catch a bead — placed by the half vector in the head's frame, so they
294
- * hold still while the iris turns under them — and the eye brightening while
295
- * it wobbles. Flat, cel discs rather than a gradient lobe; HDR, so bloom
296
- * takes them. Shut eyes catch nothing.
297
- */
298
- fn rzJellyGlow(jj: RzJelly, tex: vec3f, alpha: f32, v: vec3f, l: vec3f, sun: vec3f, shadow: f32) -> vec3f {
299
- let strength = objectLight.jelly[0].w;
300
- var right = vec3f(1.0, 0.0, 0.0);
301
- var up = vec3f(0.0, 1.0, 0.0);
302
- var fwd = vec3f(0.0, 0.0, -1.0);
303
- if (material.headBoneIndex >= 0.0) {
304
- let hm = skinMats[u32(material.headBoneIndex)];
305
- right = normalize(hm[0].xyz);
306
- up = normalize(hm[1].xyz);
307
- fwd = -normalize(hm[2].xyz);
308
- }
309
- let h = normalize(l + v);
310
- // Held in the middle and lower iris: the top of an iris sits under the upper
311
- // lid, and a glint there — or its bloom halo — pokes out over it.
312
- var g = vec2f(dot(h, right), dot(h, up)) * 0.6;
313
- g = g * min(1.0, 0.25 / max(length(g), 1e-6)) - vec2f(0.0, 0.1);
314
- g.y = min(g.y, 0.0);
315
- let aa = max(length(fwidth(jj.src)), 1e-4);
316
- // A star, not only a disc: the disc with two thin rays through it, the
317
- // anime sparkle.
318
- let dg = jj.src - g;
319
- let rays = max(0.0, 1.0 - abs(dg.y) / 0.018) * (1.0 - smoothstep(0.0, 0.34, abs(dg.x)))
320
- + max(0.0, 1.0 - abs(dg.x) / 0.018) * (1.0 - smoothstep(0.0, 0.26, abs(dg.y)));
321
- let glint = max(1.0 - smoothstep(0.13 - aa, 0.13 + aa, length(dg)), rays * 0.8);
322
- let second = 1.0 - smoothstep(0.07 - aa, 0.07 + aa, distance(jj.src, -0.55 * g + vec2f(0.1, -0.12)));
323
- let facing = saturate(dot(fwd, v) * 2.0);
324
- let key = clamp(dot(sun, vec3f(0.2126, 0.7152, 0.0722)), 0.3, 2.0);
325
- let lit = facing * key * (0.35 + 0.65 * shadow);
326
- // HDR, well past 1, so bloom takes them as the hard sparkle of a gem.
327
- // Bright enough for bloom to catch, not so bright its halo spreads past the
328
- // iris onto the lid.
329
- let glints = (glint + 0.8 * second) * lit * (1.8 + 0.8 * jj.shine);
330
- // Gone as the lid starts down, not only once shut: a half-closed smile puts
331
- // the lid right over where the glint sits.
332
- let open = 1.0 - smoothstep(0.1, 0.45, jj.closure);
333
- // Only where the eye's own picture is (its alpha), never on the iris's top
334
- // rim, and gone as the lid comes down.
335
- let solid = smoothstep(0.5, 0.9, alpha);
336
- let onEye = solid * (1.0 - smoothstep(0.05, 0.35, jj.src.y));
337
- // Light through the jelly: the light enters where the glint is and comes out
338
- // the far side, so the iris glows in its own colour in a crescent opposite
339
- // the glint — the lit-from-within look of a gem, or a jelly.
340
- let r = length(jj.src);
341
- // Out the LOWER iris, always — the anime gem's bright underside — leaning
342
- // away from the glint; never up, where its bloom would spill on the lid.
343
- let away = normalize(vec2f(-g.x * 0.6, -1.0));
344
- let crescent = smoothstep(0.15, 0.7, dot(jj.src, away)) * (1.0 - smoothstep(0.8, 1.0, r));
345
- let through = tex * crescent * (2.2 + 1.0 * jj.shine) * lit;
346
- // And the colour runs deeper: the iris's own hue pushed, never its greys.
347
- let lum = dot(tex, vec3f(0.2126, 0.7152, 0.0722));
348
- let vivid = max(tex - vec3f(lum), vec3f(0.0)) * 1.2;
349
- // The glass over it: a broad soft sheen across the upper iris, under the
350
- // lid line, the dome of a bead catching the room.
351
- let dome = (1.0 - smoothstep(0.0, 0.5, distance(jj.src * vec2f(0.8, 1.4), vec2f(0.0, 0.2)))) * 0.45 * lit;
352
- // Everything that blooms keeps to the lower iris, so no halo reaches the lid.
353
- let lower = 1.0 - smoothstep(-0.05, 0.3, jj.src.y);
354
- // The cut: soft facets fanning across the lower iris, light split as a cut
355
- // stone splits it, turning slowly.
356
- let ang = atan2(jj.src.y, jj.src.x);
357
- let facets = pow(0.5 + 0.5 * cos(ang * 7.0 + jj.clock * 0.35), 6.0)
358
- * smoothstep(0.3, 0.75, r) * (1.0 - smoothstep(0.82, 0.97, r)) * lower;
359
- // The rim: a band of its own colour just inside the iris edge, HDR, which
360
- // bloom spreads into a glow around the stone.
361
- let rim = smoothstep(0.7, 0.84, r) * (1.0 - smoothstep(0.86, 0.97, r)) * lower;
362
- // Twinkles: three specks in the lower iris, each flashing now and then on
363
- // the scene clock.
364
- var twinkle = 0.0;
365
- for (var i = 0; i < 3; i++) {
366
- let fi = f32(i);
367
- let at = vec2f(0.42 * cos(3.6 + fi * 1.1), -0.25 - 0.18 * fi + 0.1 * sin(fi * 2.3));
368
- let flash = pow(max(0.0, sin(jj.clock * (1.3 + 0.37 * fi) + fi * 2.1)), 12.0);
369
- twinkle += (1.0 - smoothstep(0.0, 0.05 + aa, distance(jj.src, at))) * flash;
370
- }
371
- let sparkle = (vec3f(glints + twinkle * 2.0) * onEye
372
- + tex * facets * 2.5 * lit * solid * (1.0 + 0.6 * jj.shine));
373
- // The soft light (sheen, glow through, colour) needs only the eye's picture.
374
- let gem = (sparkle + (vec3f(dome) + through + vivid) * solid) * open * jj.w;
375
- // The edge, which jj.w (fading out from 0.85) would hide: the glowing rim
376
- // band, and a thin glossy line right at the border — light running round
377
- // the stone's edge — all the way round, bright below and dim above, where a
378
- // halo would reach the lid. Up to the mesh's edge and no further.
379
- let on = select(0.0, 1.0, strength > 0.0) * (1.0 - smoothstep(1.0 - aa, 1.0, r));
380
- let edgeLine = smoothstep(0.92, 0.955, r) * (1.0 - smoothstep(0.965, 0.995, r));
381
- let edgeLit = mix(0.3, 1.0, lower) * lit * (1.0 + 0.6 * jj.shine);
382
- let edge = (tex * rim * 2.0 + vec3f(rim * 0.25)) * lit * (1.0 + 0.6 * jj.shine)
383
- + (vec3f(0.55) + tex * 0.8) * edgeLine * edgeLit;
384
- let border = edge * solid * open * on;
385
- // The whole eye shines a little brighter, more while it wobbles — not just
386
- // the iris, so this one is not held to it (jj.w).
387
- let lift = tex * (0.25 + 0.35 * jj.shine) * select(0.0, 1.0, strength > 0.0);
388
- return (gem + border + lift) * min(strength, 1.0);
389
- }
390
-
391
297
  /** The light arriving at a surface of this object facing n: its own ambient
392
298
  * where loaded data gave it one (a stage's probes, a character's), else the
393
299
  * world's. The same polynomial rzWorldAmbient evaluates. */
@@ -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 * material.widthScale * outlineNormal.w;
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 * material.widthScale * outlineNormal.w * 4.0 / refViewport);
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);