ugly-game 0.5.14 → 0.5.15

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.
@@ -25,5 +25,5 @@ export declare class GpuPropsRenderer {
25
25
  setInstances(instances: PropInstance[]): void;
26
26
  /** Occluder pass: draw all instanced props' depth into the shared shadow map from the sun's ortho view. */
27
27
  shadowPass(pass: GPURenderPassEncoder, lightVP: Float32Array): void;
28
- frame(enc: GPUCommandEncoder, colorView: GPUTextureView, depthView: GPUTextureView, camVP: Float32Array, sunDir: [number, number, number], sunCol: [number, number, number], amb: [number, number, number], camPos: [number, number, number], fog: [number, number, number], fogDensity: number): void;
28
+ frame(enc: GPUCommandEncoder, colorView: GPUTextureView, depthView: GPUTextureView, camVP: Float32Array, sunDir: [number, number, number], sunCol: [number, number, number], amb: [number, number, number], camPos: [number, number, number], fog: [number, number, number], fogDensity: number, up?: [number, number, number]): void;
29
29
  }
@@ -4,7 +4,7 @@
4
4
  // terrain. Runs in a load pass after the terrain so props depth-test against the surface.
5
5
  // ─────────────────────────────────────────────────────────────────────────────
6
6
  const SHADER = /* wgsl */ `
7
- struct U { mvp: mat4x4<f32>, sun: vec4<f32>, sunCol: vec4<f32>, amb: vec4<f32>, cam: vec4<f32>, fog: vec4<f32> };
7
+ struct U { mvp: mat4x4<f32>, sun: vec4<f32>, sunCol: vec4<f32>, amb: vec4<f32>, cam: vec4<f32>, fog: vec4<f32>, up: vec4<f32> };
8
8
  @group(0) @binding(0) var<uniform> u: U;
9
9
  @group(0) @binding(1) var<storage,read> models: array<mat4x4<f32>>;
10
10
  struct VO { @builtin(position) pos: vec4<f32>, @location(0) world: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c: vec3<f32> };
@@ -13,9 +13,20 @@ struct VO { @builtin(position) pos: vec4<f32>, @location(0) world: vec3<f32>, @l
13
13
  let world = (m * vec4(p, 1.0)).xyz;
14
14
  var o: VO; o.pos = u.mvp * vec4(world, 1.0); o.world = world; o.n = (m * vec4(nrm, 0.0)).xyz; o.c = col; return o;
15
15
  }
16
+ // same fixed directional FILL as the terrain cubes (gpuShadow.fixedFill) so plants/rocks read the same 3D shape,
17
+ // day or night, under one shared light. up.w > 0.5 signals a valid local-up was supplied (else fall back to flat).
18
+ fn fixedFill(N: vec3<f32>, up: vec3<f32>) -> f32 {
19
+ var t = cross(up, vec3(0.0, 0.0, 1.0));
20
+ if (dot(t, t) < 0.01) { t = cross(up, vec3(1.0, 0.0, 0.0)); }
21
+ t = normalize(t);
22
+ let L = normalize(up * 0.82 + t * 0.57);
23
+ return max(dot(N, L), 0.0);
24
+ }
16
25
  @fragment fn fs(i: VO) -> @location(0) vec4<f32> {
17
- let d = clamp(dot(normalize(i.n), -normalize(u.sun.xyz)), 0.0, 1.0);
18
- let lit = i.c * (u.amb.xyz + d * u.sunCol.xyz);
26
+ let N = normalize(i.n);
27
+ let ff = select(1.0, 0.5 + 0.85 * fixedFill(N, u.up.xyz), u.up.w > 0.5); // fixed fill (fall back to flat amb if no up)
28
+ let d = clamp(dot(N, -normalize(u.sun.xyz)), 0.0, 1.0);
29
+ let lit = i.c * (u.amb.xyz * ff + d * u.sunCol.xyz);
19
30
  let dist = length(i.world - u.cam.xyz);
20
31
  let f = clamp(1.0 - exp(-dist * u.fog.w), 0.0, 1.0);
21
32
  return vec4(mix(lit, u.fog.xyz, f), 1.0);
@@ -59,7 +70,7 @@ export class GpuPropsRenderer {
59
70
  primitive: { topology: 'triangle-list', cullMode: 'none' },
60
71
  depthStencil: { format: 'depth32float', depthWriteEnabled: true, depthCompare: 'less' },
61
72
  });
62
- this.uBuf = device.createBuffer({ size: 96 + 16 * 5, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
73
+ this.uBuf = device.createBuffer({ size: 96 + 16 * 6, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
63
74
  this.lightBuf = device.createBuffer({ size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
64
75
  }
65
76
  setModels(meshes) {
@@ -120,14 +131,16 @@ export class GpuPropsRenderer {
120
131
  pass.drawIndexed(mdl.idxCount, this.counts[m]);
121
132
  }
122
133
  }
123
- frame(enc, colorView, depthView, camVP, sunDir, sunCol, amb, camPos, fog, fogDensity) {
124
- const u = new Float32Array(24 + 20);
134
+ frame(enc, colorView, depthView, camVP, sunDir, sunCol, amb, camPos, fog, fogDensity, up) {
135
+ const u = new Float32Array(24 + 24);
125
136
  u.set(camVP, 0);
126
137
  u.set([sunDir[0], sunDir[1], sunDir[2], 0], 16);
127
138
  u.set([sunCol[0], sunCol[1], sunCol[2], 0], 20);
128
139
  u.set([amb[0], amb[1], amb[2], 0], 24);
129
140
  u.set([camPos[0], camPos[1], camPos[2], 0], 28);
130
141
  u.set([fog[0], fog[1], fog[2], fogDensity], 32);
142
+ if (up)
143
+ u.set([up[0], up[1], up[2], 1], 36); // up.w=1 → enable the fixed directional fill (matches the terrain)
131
144
  this.device.queue.writeBuffer(this.uBuf, 0, u);
132
145
  const rp = enc.beginRenderPass({ colorAttachments: [{ view: colorView, loadOp: 'load', storeOp: 'store' }], depthStencilAttachment: { view: depthView, depthLoadOp: 'load', depthStoreOp: 'store' } });
133
146
  rp.setPipeline(this.pipe);
package/dist/gpuShadow.js CHANGED
@@ -240,17 +240,16 @@ fn blockShade(fx: f32, fz: f32) -> f32 {
240
240
  let t = clamp(edge / 0.17, 0.0, 1.0);
241
241
  return 0.6 + 0.4 * (t * t * (3.0 - 2.0 * t));
242
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 {
243
+ // ONE fixed, subtle directional FILL light in the local gravity frame always on, independent of the sun, so
244
+ // terrain + props keep their 3D shape even in darkness (the sun stays the primary, shadow-casting key light). The
245
+ // fill comes from up + a fixed horizontal tangent (~35 deg off vertical), so a cube's three visible faces all get
246
+ // distinct brightness and corners/terrace risers read at a glance. Cubes and props (gpuProps) share this formula.
247
+ fn fixedFill(N: vec3<f32>, up: vec3<f32>) -> f32 {
248
248
  var t = cross(up, vec3(0.0, 0.0, 1.0));
249
249
  if (dot(t, t) < 0.01) { t = cross(up, vec3(1.0, 0.0, 0.0)); }
250
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);
251
+ let L = normalize(up * 0.82 + t * 0.57); // fixed key-fill direction
252
+ return max(dot(N, L), 0.0); // 0 (facing away) .. 1 (facing the fill)
254
253
  }
255
254
  @fragment fn fs(i: VOut) -> @location(0) vec4<f32> {
256
255
  if (i.emis > 0.5) { // self-glowing effect cube: full-bright, unlit
@@ -282,13 +281,12 @@ fn faceDirShade(N: vec3<f32>, up: vec3<f32>) -> f32 {
282
281
  // per-block edge AO makes each voxel visible on FLAT-hue cubes, but reads as a hard GRID over textures — so the
283
282
  // atlas path drops it entirely (the texture already supplies surface detail).
284
283
  let ao = select(blockShade(fx, fz), 1.0, cam.atlas.x > 0.5 && i.tile >= 0.0);
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
284
+ // LIGHTING = fixed directional FILL (always on, reads 3D shape in the dark) + the sun KEY (primary, shadowed).
285
+ // cam.sun.w is the fill/ambient brightness knob; the fill's directional term (0.5..1.35) keeps every face's
286
+ // orientation legible day or night, while the sun adds bright, shadow-casting key light on top during the day.
287
+ let fill = cam.sun.w * (0.5 + 0.85 * fixedFill(N, cam.up.xyz));
288
+ let key = cam.sunCol.xyz * ndl * sh; // direct sun (shadowed) primary light
289
+ let lit = base * (fill + key) * ao;
292
290
  return vec4(pow(clamp(lit, vec3(0.0), vec3(1.0)), vec3(0.4545)), 1.0); // gamma
293
291
  }
294
292
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-game",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {