reze-engine 0.58.4 → 0.59.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.
@@ -1 +1 @@
1
- {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AAoDA;uDACuD;AACvD,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,MAAM,CAAA;AAC7B,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,QAAkB,CAAA;AAC/C;0EAC0E;AAC1E,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AACrC,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAA2C,CAAA;AACvE,wDAAwD;AACxD,eAAO,MAAM,aAAa,QAAwD,CAAA;AAElF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,QAA4B,CAAA;AAE3D,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,SAAK,GACd,MAAM,CAoGR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAgIhE"}
1
+ {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AAoDA;uDACuD;AACvD,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,MAAM,CAAA;AAC7B,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,QAAkB,CAAA;AAC/C;0EAC0E;AAC1E,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AACrC,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAA2C,CAAA;AACvE,wDAAwD;AACxD,eAAO,MAAM,aAAa,QAAwD,CAAA;AAElF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,QAA4B,CAAA;AAE3D,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,SAAK,GACd,MAAM,CAoGR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA+HhE"}
@@ -241,6 +241,11 @@ export function lightsApi(group, binding) {
241
241
  @group(${group}) @binding(${binding}) var<storage, read> _rzLights: array<vec4u>;
242
242
 
243
243
  const RZ_MAX_LIGHTS: u32 = ${MAX_LIGHTS}u;
244
+ // A lamp's bulb, in world units: the inverse square is held flat inside it,
245
+ // so the spike beside the lamp is finite. Aether Gazer's lamps are
246
+ // 0.1 m across (their shapeRadius, capping 1/d² at 1/0.1), which at MMD scale
247
+ // is 2.5 units.
248
+ const RZ_LAMP_NEAR: f32 = 2.5;
244
249
 
245
250
  /** How many positional lights the scene has. Zero is the ordinary case. */
246
251
  fn rzLightCount() -> u32 { return min(u32(bitcast<f32>(_rzLights[0].x)), RZ_MAX_LIGHTS); }
@@ -282,19 +287,11 @@ fn _rzLightCellMask(p: vec3f) -> vec4u {
282
287
  /**
283
288
  * One light's contribution at a surface point.
284
289
  *
285
- * FALLOFF IS RELATIVE TO THE RADIUS, and deliberately not physical.
286
- *
287
- * The first version windowed a real inverse-square, and it was unusable: 1/d²
288
- * is measured in world units, an MMD character is about 18 of them tall, so a
289
- * lamp two metres off her shoulder divided by 37 and an intensity of 4 landed
290
- * as 0.06 — invisible. Radius and intensity were fighting, and intensity had no
291
- * scale a person could learn.
292
- *
293
- * So: intensity is the brightness AT the light, radius is where it reaches
294
- * zero, and the curve between them is the same shape whatever the scene's
295
- * scale. Both dials now mean what they say, which for a composer beats being
296
- * right about photons. (1 - t²)² — smooth at both ends, exactly 0 at the
297
- * radius, so the bound the grid is built from is real.
290
+ * A LIGHT FALLS OFF AS THE INVERSE SQUARE, the curve Unity, Unreal, Blender and
291
+ * glTF all light with: intensity / max(d², RZ_LAMP_NEAR²), so its intensity is
292
+ * the brightness one unit away, windowed by (1 − (d/R)⁴)² so it is exactly zero
293
+ * at its radius and the bound the grid is built from is real. A lamp imported
294
+ * from a game carries the game's own numbers.
298
295
  */
299
296
  fn _rzLightOne(i: u32, p: vec3f, n: vec3f) -> vec3f {
300
297
  let pr = _rzLightVec(i, 0u);
@@ -310,14 +307,16 @@ fn _rzLightOne(i: u32, p: vec3f, n: vec3f) -> vec3f {
310
307
  let ndl = max(dot(n, toLight), 0.0);
311
308
  if (ndl <= 0.0) { return vec3f(0.0); }
312
309
  let t = clamp(dist / max(pr.w, 1e-4), 0.0, 1.0);
313
- let falloff = 1.0 - t * t;
310
+ let t2 = t * t;
311
+ let window = 1.0 - t2 * t2;
312
+ let falloff = window * window / max(dist * dist, RZ_LAMP_NEAR * RZ_LAMP_NEAR);
314
313
  // How far inside the cone this point sits: 1 within the inner angle, 0 past
315
314
  // the outer one, squared for the same soft edge the falloff has. A point
316
315
  // light's (-1, -1) divides by the floor and clamps to 1, so it pays one
317
316
  // dot product and no branch.
318
317
  let cone = rzLightCone(i);
319
318
  let aim = clamp((dot(-toLight, rzLightAim(i)) - cone.x) / max(cone.y - cone.x, 1e-4), 0.0, 1.0);
320
- return rzLightColor(i) * (ndl * falloff * falloff * aim * aim);
319
+ return rzLightColor(i) * (ndl * falloff * aim * aim);
321
320
  }
322
321
 
323
322
  /** The lamps named by one word of a cell's bits, lowest first. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.58.4",
3
+ "version": "0.59.0",
4
4
  "description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -253,6 +253,11 @@ export function lightsApi(group: number, binding: number): string {
253
253
  @group(${group}) @binding(${binding}) var<storage, read> _rzLights: array<vec4u>;
254
254
 
255
255
  const RZ_MAX_LIGHTS: u32 = ${MAX_LIGHTS}u;
256
+ // A lamp's bulb, in world units: the inverse square is held flat inside it,
257
+ // so the spike beside the lamp is finite. Aether Gazer's lamps are
258
+ // 0.1 m across (their shapeRadius, capping 1/d² at 1/0.1), which at MMD scale
259
+ // is 2.5 units.
260
+ const RZ_LAMP_NEAR: f32 = 2.5;
256
261
 
257
262
  /** How many positional lights the scene has. Zero is the ordinary case. */
258
263
  fn rzLightCount() -> u32 { return min(u32(bitcast<f32>(_rzLights[0].x)), RZ_MAX_LIGHTS); }
@@ -294,19 +299,11 @@ fn _rzLightCellMask(p: vec3f) -> vec4u {
294
299
  /**
295
300
  * One light's contribution at a surface point.
296
301
  *
297
- * FALLOFF IS RELATIVE TO THE RADIUS, and deliberately not physical.
298
- *
299
- * The first version windowed a real inverse-square, and it was unusable: 1/d²
300
- * is measured in world units, an MMD character is about 18 of them tall, so a
301
- * lamp two metres off her shoulder divided by 37 and an intensity of 4 landed
302
- * as 0.06 — invisible. Radius and intensity were fighting, and intensity had no
303
- * scale a person could learn.
304
- *
305
- * So: intensity is the brightness AT the light, radius is where it reaches
306
- * zero, and the curve between them is the same shape whatever the scene's
307
- * scale. Both dials now mean what they say, which for a composer beats being
308
- * right about photons. (1 - t²)² — smooth at both ends, exactly 0 at the
309
- * radius, so the bound the grid is built from is real.
302
+ * A LIGHT FALLS OFF AS THE INVERSE SQUARE, the curve Unity, Unreal, Blender and
303
+ * glTF all light with: intensity / max(d², RZ_LAMP_NEAR²), so its intensity is
304
+ * the brightness one unit away, windowed by (1 − (d/R)⁴)² so it is exactly zero
305
+ * at its radius and the bound the grid is built from is real. A lamp imported
306
+ * from a game carries the game's own numbers.
310
307
  */
311
308
  fn _rzLightOne(i: u32, p: vec3f, n: vec3f) -> vec3f {
312
309
  let pr = _rzLightVec(i, 0u);
@@ -322,14 +319,16 @@ fn _rzLightOne(i: u32, p: vec3f, n: vec3f) -> vec3f {
322
319
  let ndl = max(dot(n, toLight), 0.0);
323
320
  if (ndl <= 0.0) { return vec3f(0.0); }
324
321
  let t = clamp(dist / max(pr.w, 1e-4), 0.0, 1.0);
325
- let falloff = 1.0 - t * t;
322
+ let t2 = t * t;
323
+ let window = 1.0 - t2 * t2;
324
+ let falloff = window * window / max(dist * dist, RZ_LAMP_NEAR * RZ_LAMP_NEAR);
326
325
  // How far inside the cone this point sits: 1 within the inner angle, 0 past
327
326
  // the outer one, squared for the same soft edge the falloff has. A point
328
327
  // light's (-1, -1) divides by the floor and clamps to 1, so it pays one
329
328
  // dot product and no branch.
330
329
  let cone = rzLightCone(i);
331
330
  let aim = clamp((dot(-toLight, rzLightAim(i)) - cone.x) / max(cone.y - cone.x, 1e-4), 0.0, 1.0);
332
- return rzLightColor(i) * (ndl * falloff * falloff * aim * aim);
331
+ return rzLightColor(i) * (ndl * falloff * aim * aim);
333
332
  }
334
333
 
335
334
  /** The lamps named by one word of a cell's bits, lowest first. */