ugly-game 0.5.12 → 0.5.14

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.
@@ -107,7 +107,7 @@ export declare class GpuShadowRenderer {
107
107
  depth: GPUTextureView;
108
108
  w: number;
109
109
  h: number;
110
- }, tint?: [number, number, number, number], extraShadow?: (sp: GPURenderPassEncoder, lightVP: Float32Array) => void): void;
110
+ }, tint?: [number, number, number, number], extraShadow?: (sp: GPURenderPassEncoder, lightVP: Float32Array) => void, up?: [number, number, number]): void;
111
111
  /** Read back the last frame's GPU cull result (camera-visible, light-visible). Does its OWN copy+submit so it's
112
112
  * safe to call from a live render loop (no per-frame readback cost, no race with the ongoing frames). IR/tests. */
113
113
  readCullStats(): Promise<{
package/dist/gpuShadow.js CHANGED
@@ -51,6 +51,7 @@ struct Cam {
51
51
  sunCol: vec4<f32>, // xyz = sun colour, w = shadow map size (texels)
52
52
  tint: vec4<f32>, // rgb = per-planet tint · w = tint strength (0 = none)
53
53
  atlas: vec4<f32>, // x = useAtlas (1/0)
54
+ up: vec4<f32>, // xyz = local gravity up (for hemisphere face-shading readability); w unused
54
55
  };
55
56
  struct Inst { pos: vec4<f32>, scl: vec4<f32>, rot: vec4<f32>, aux: vec4<f32> }; // pos.w = hue (<0 ⇒ ground) · scl.w = emissive/alpha · rot = quaternion · aux.x = atlas tile (<0 ⇒ hue) · aux.y = up-face code
56
57
  @group(0) @binding(0) var<uniform> cam: Cam;
@@ -239,6 +240,18 @@ fn blockShade(fx: f32, fz: f32) -> f32 {
239
240
  let t = clamp(edge / 0.17, 0.0, 1.0);
240
241
  return 0.6 + 0.4 * (t * t * (3.0 - 2.0 * t));
241
242
  }
243
+ // Minecraft-style FIXED per-face directional shade (independent of the sun) — the reliable readability cue for
244
+ // voxel terrain: top brightest, bottom darkest, and the TWO horizontal axes get DISTINCT brightness so any cube's
245
+ // three visible faces all differ - corners, steps and terrace risers read at a glance at ANY sun angle. up is
246
+ // the local gravity up; t is a stable horizontal tangent used only to break the side-face symmetry.
247
+ fn faceDirShade(N: vec3<f32>, up: vec3<f32>) -> f32 {
248
+ var t = cross(up, vec3(0.0, 0.0, 1.0));
249
+ if (dot(t, t) < 0.01) { t = cross(up, vec3(1.0, 0.0, 0.0)); }
250
+ t = normalize(t);
251
+ let nu = dot(N, up); // +1 up-facing, 0 side, -1 down-facing
252
+ let nt = dot(N, t); // splits the two horizontal side-axes apart
253
+ return clamp(0.68 + 0.32 * nu + 0.14 * nt, 0.36, 1.0);
254
+ }
242
255
  @fragment fn fs(i: VOut) -> @location(0) vec4<f32> {
243
256
  if (i.emis > 0.5) { // self-glowing effect cube: full-bright, unlit
244
257
  let g = mix(hsv(i.hue), vec3(1.0), 0.4);
@@ -269,7 +282,13 @@ fn blockShade(fx: f32, fz: f32) -> f32 {
269
282
  // per-block edge AO makes each voxel visible on FLAT-hue cubes, but reads as a hard GRID over textures — so the
270
283
  // atlas path drops it entirely (the texture already supplies surface detail).
271
284
  let ao = select(blockShade(fx, fz), 1.0, cam.atlas.x > 0.5 && i.tile >= 0.0);
272
- let lit = base * (cam.sun.w + cam.sunCol.xyz * ndl * sh) * ao;
285
+ // FACE-DIRECTIONAL SHADING (Minecraft-style, sun-independent) makes the 3D shape read at any time of day. It
286
+ // tints the ambient FILL heavily AND is applied at partial strength to the whole lit result — so even a face in
287
+ // full key light still shows its orientation, and terrace risers stay clearly darker than the tops above them.
288
+ let dir = faceDirShade(N, cam.up.xyz);
289
+ let fill = cam.sun.w * mix(0.5, 1.25, dir); // ambient sky term, strongly tinted by face orientation
290
+ let key = cam.sunCol.xyz * ndl * sh; // direct sun (shadowed)
291
+ let lit = base * (fill + key) * ao * (0.72 + 0.28 * dir); // 0.72..1.0 fixed face-light keeps corners legible under full sun
273
292
  return vec4(pow(clamp(lit, vec3(0.0), vec3(1.0)), vec3(0.4545)), 1.0); // gamma
274
293
  }
275
294
  `;
@@ -304,7 +323,7 @@ export class GpuShadowRenderer {
304
323
  mainBGL;
305
324
  shadowSampler;
306
325
  count = 0;
307
- cam = new Float32Array(48); // 2×mat4 + sun + sunCol + tint + atlas
326
+ cam = new Float32Array(52); // 2×mat4 + sun + sunCol + tint + atlas + up
308
327
  atlasView;
309
328
  atlasSampler;
310
329
  tileRectBuf;
@@ -585,7 +604,7 @@ export class GpuShadowRenderer {
585
604
  /** camVP/lightVP: column-major Float32Array(16). sunDir: travel direction. Pass `occlusion` (the depth target +
586
605
  * its size) to enable Hi-Z occlusion culling on the camera pass — the renderer rebuilds a coarse Hi-Z from that
587
606
  * depth each frame and the NEXT frame's cull uses it (hole-free: the first frame + any resize skip occlusion). */
588
- frame(enc, colorView, sceneDepth, camVP, lightVP, sunDir, sunCol, ambient, load = false, occlusion, tint = [1, 1, 1, 0], extraShadow) {
607
+ frame(enc, colorView, sceneDepth, camVP, lightVP, sunDir, sunCol, ambient, load = false, occlusion, tint = [1, 1, 1, 0], extraShadow, up = [0, 1, 0]) {
589
608
  this.cam.set(camVP, 0);
590
609
  this.cam.set(lightVP, 16);
591
610
  this.cam[32] = sunDir[0];
@@ -601,6 +620,9 @@ export class GpuShadowRenderer {
601
620
  this.cam[42] = tint[2];
602
621
  this.cam[43] = tint[3];
603
622
  this.cam[44] = this.useAtlas;
623
+ this.cam[48] = up[0];
624
+ this.cam[49] = up[1];
625
+ this.cam[50] = up[2];
604
626
  this.device.queue.writeBuffer(this.camBuf, 0, this.cam);
605
627
  if (occlusion)
606
628
  this.ensureHiZ(occlusion.w, occlusion.h);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-game",
3
- "version": "0.5.12",
3
+ "version": "0.5.14",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {