ugly-game 0.5.13 → 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,6 +240,17 @@ 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
+ // 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
+ 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 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)
253
+ }
243
254
  @fragment fn fs(i: VOut) -> @location(0) vec4<f32> {
244
255
  if (i.emis > 0.5) { // self-glowing effect cube: full-bright, unlit
245
256
  let g = mix(hsv(i.hue), vec3(1.0), 0.4);
@@ -270,12 +281,12 @@ fn blockShade(fx: f32, fz: f32) -> f32 {
270
281
  // per-block edge AO makes each voxel visible on FLAT-hue cubes, but reads as a hard GRID over textures — so the
271
282
  // atlas path drops it entirely (the texture already supplies surface detail).
272
283
  let ao = select(blockShade(fx, fz), 1.0, cam.atlas.x > 0.5 && i.tile >= 0.0);
273
- // HEMISPHERE FACE-SHADING: brighten faces pointing toward the local up (sky), darken side/down faces so every
274
- // block's orientation reads at a glance (edges/corners legible) WITHOUT a per-block grid. Modulates the ambient
275
- // sky term (not the direct sun), so sunlit faces keep full key light while shaded areas still show 3D form.
276
- let hemi = dot(N, cam.up.xyz) * 0.5 + 0.5; // 0 = down-facing, 0.5 = side, 1 = up-facing
277
- let amb = cam.sun.w * mix(0.55, 1.15, hemi);
278
- let lit = base * (amb + cam.sunCol.xyz * ndl * sh) * ao;
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;
279
290
  return vec4(pow(clamp(lit, vec3(0.0), vec3(1.0)), vec3(0.4545)), 1.0); // gamma
280
291
  }
281
292
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-game",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {