@d5techs/3dgs-lib 1.4.53 → 1.4.55

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.
package/dist/3dgs-lib.cjs CHANGED
@@ -2183,10 +2183,22 @@ function decodeScanline(bytes, pos, width) {
2183
2183
  }
2184
2184
  return { pixels, newPos: pos };
2185
2185
  }
2186
+ const f32Buf = new Float32Array(1);
2187
+ const u32Buf = new Uint32Array(f32Buf.buffer);
2188
+ function float32ToFloat16(val) {
2189
+ f32Buf[0] = val;
2190
+ const f = u32Buf[0];
2191
+ const sign = f >> 16 & 32768;
2192
+ const exp = (f >> 23 & 255) - 127 + 15;
2193
+ const frac = f >> 13 & 1023;
2194
+ if (exp <= 0) return sign;
2195
+ if (exp >= 31) return sign | 31744;
2196
+ return sign | exp << 10 | frac;
2197
+ }
2186
2198
  const CUBE_WGSL = (
2187
2199
  /* wgsl */
2188
2200
  `
2189
- struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2201
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32>, extra: vec4<f32> };
2190
2202
  @group(0) @binding(0) var<uniform> u: Uniforms;
2191
2203
  @group(0) @binding(1) var cubeSampler: sampler;
2192
2204
  @group(0) @binding(2) var cubeTexture: texture_cube<f32>;
@@ -2205,12 +2217,17 @@ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2205
2217
  const EQUIRECT_WGSL = (
2206
2218
  /* wgsl */
2207
2219
  `
2208
- struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2220
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32>, extra: vec4<f32> };
2209
2221
  @group(0) @binding(0) var<uniform> u: Uniforms;
2210
2222
  @group(0) @binding(1) var envSampler: sampler;
2211
2223
  @group(0) @binding(2) var envTexture: texture_2d<f32>;
2212
2224
  struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2213
2225
  const PI: f32 = 3.14159265358979;
2226
+
2227
+ fn dirToUv(d: vec3<f32>) -> vec2<f32> {
2228
+ return vec2(atan2(d.z, d.x) / (2.*PI) + .5, 1. - (asin(clamp(d.y,-1.,1.)) / PI + .5));
2229
+ }
2230
+
2214
2231
  @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2215
2232
  let ps = array<vec2<f32>,3>(vec2(-1.,-1.),vec2(3.,-1.),vec2(-1.,3.));
2216
2233
  var o: V; let p = ps[vi]; o.pos = vec4(p, 0., 1.);
@@ -2218,10 +2235,44 @@ const PI: f32 = 3.14159265358979;
2218
2235
  o.dir = vec3(dot(u.col0.xyz,e), dot(u.col1.xyz,e), dot(u.col2.xyz,e));
2219
2236
  return o;
2220
2237
  }
2238
+
2221
2239
  @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2222
2240
  let d = normalize(i.dir);
2223
- let uv = vec2(atan2(d.z, d.x) / (2.*PI) + .5, asin(clamp(d.y,-1.,1.)) / PI + .5);
2224
- let c = textureSample(envTexture, envSampler, vec2(uv.x, 1. - uv.y)).rgb;
2241
+ let camY = u.extra.x;
2242
+ let sphereR = u.extra.y;
2243
+
2244
+ // --- sky UV (always computed) ---
2245
+ let skyUv = dirToUv(d);
2246
+
2247
+ // --- ground projection UV (always computed to satisfy uniform control flow) ---
2248
+ // ray-plane intersection: camera at height camY, plane y=0
2249
+ let dySafe = select(d.y, -0.0001, d.y > -0.0001);
2250
+ let t = -camY / dySafe;
2251
+ let hitX = t * d.x;
2252
+ let hitZ = t * d.z;
2253
+ let hitDist2 = hitX * hitX + hitZ * hitZ;
2254
+ let R2 = sphereR * sphereR;
2255
+ // clamp hit to sphere radius to avoid extreme stretching at edges
2256
+ let needClamp = hitDist2 > R2;
2257
+ let clampScale = select(1.0, sphereR / sqrt(max(hitDist2, 0.0001)), needClamp);
2258
+ let pX = hitX * clampScale;
2259
+ let pZ = hitZ * clampScale;
2260
+ // project back onto sphere: y = -sqrt(R^2 - x^2 - z^2)
2261
+ let projY = -sqrt(max(R2 - pX * pX - pZ * pZ, 0.0));
2262
+ let groundDir = normalize(vec3(pX, projY, pZ));
2263
+ let groundUv = dirToUv(groundDir);
2264
+
2265
+ // --- sample both (unconditional = uniform control flow) ---
2266
+ let skyColor = textureSample(envTexture, envSampler, skyUv).rgb;
2267
+ let groundColor = textureSample(envTexture, envSampler, groundUv).rgb;
2268
+
2269
+ // blend near horizon for smooth transition
2270
+ let blend = smoothstep(0.0, -0.08, d.y);
2271
+ let mixed = mix(skyColor, groundColor, blend);
2272
+ let useGround = d.y < 0.0 && camY > 0.01;
2273
+ let c = select(skyColor, mixed, useGround);
2274
+
2275
+ // Reinhard tone mapping + gamma
2225
2276
  let mapped = c / (c + vec3(1.));
2226
2277
  let gamma = pow(mapped, vec3(1./2.2));
2227
2278
  return vec4(gamma, 1.);
@@ -2232,7 +2283,7 @@ class SkyboxRenderer {
2232
2283
  __publicField(this, "device");
2233
2284
  __publicField(this, "uniformBuffer");
2234
2285
  __publicField(this, "sampler");
2235
- __publicField(this, "uniformData", new Float32Array(12));
2286
+ __publicField(this, "uniformData", new Float32Array(16));
2236
2287
  __publicField(this, "cubePipeline");
2237
2288
  __publicField(this, "cubeBindGroupLayout");
2238
2289
  __publicField(this, "equirectPipeline");
@@ -2241,6 +2292,8 @@ class SkyboxRenderer {
2241
2292
  __publicField(this, "bindGroup", null);
2242
2293
  __publicField(this, "frameReady", false);
2243
2294
  __publicField(this, "mode", "none");
2295
+ /** Ground projection sphere radius (world units). Larger = ground extends further. */
2296
+ __publicField(this, "groundRadius", 32);
2244
2297
  this.device = device;
2245
2298
  const depthStencil = {
2246
2299
  format: depthFormat,
@@ -2249,7 +2302,7 @@ class SkyboxRenderer {
2249
2302
  };
2250
2303
  this.cubeBindGroupLayout = device.createBindGroupLayout({
2251
2304
  entries: [
2252
- { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2305
+ { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
2253
2306
  { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2254
2307
  { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "cube" } }
2255
2308
  ]
@@ -2263,7 +2316,7 @@ class SkyboxRenderer {
2263
2316
  });
2264
2317
  this.equirectBindGroupLayout = device.createBindGroupLayout({
2265
2318
  entries: [
2266
- { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2319
+ { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
2267
2320
  { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2268
2321
  { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "2d" } }
2269
2322
  ]
@@ -2276,7 +2329,7 @@ class SkyboxRenderer {
2276
2329
  depthStencil
2277
2330
  });
2278
2331
  this.uniformBuffer = device.createBuffer({
2279
- size: 48,
2332
+ size: 64,
2280
2333
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
2281
2334
  });
2282
2335
  this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
@@ -2331,15 +2384,20 @@ class SkyboxRenderer {
2331
2384
  async loadEquirectangular(url) {
2332
2385
  this.clear();
2333
2386
  const hdr = await loadHdr(url);
2387
+ const pixelCount = hdr.width * hdr.height * 4;
2388
+ const f16 = new Uint16Array(pixelCount);
2389
+ for (let i = 0; i < pixelCount; i++) {
2390
+ f16[i] = float32ToFloat16(hdr.data[i]);
2391
+ }
2334
2392
  const tex = this.device.createTexture({
2335
2393
  size: [hdr.width, hdr.height],
2336
- format: "rgba32float",
2394
+ format: "rgba16float",
2337
2395
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
2338
2396
  });
2339
2397
  this.device.queue.writeTexture(
2340
2398
  { texture: tex },
2341
- hdr.data.buffer,
2342
- { bytesPerRow: hdr.width * 16, rowsPerImage: hdr.height },
2399
+ f16.buffer,
2400
+ { bytesPerRow: hdr.width * 8, rowsPerImage: hdr.height },
2343
2401
  { width: hdr.width, height: hdr.height }
2344
2402
  );
2345
2403
  this.bindGroup = this.device.createBindGroup({
@@ -2354,7 +2412,7 @@ class SkyboxRenderer {
2354
2412
  this.mode = "equirect";
2355
2413
  }
2356
2414
  // ---- common ----
2357
- prepareFrame(viewMatrix, projectionMatrix) {
2415
+ prepareFrame(viewMatrix, projectionMatrix, cameraPosition) {
2358
2416
  if (!this.isActive) {
2359
2417
  this.frameReady = false;
2360
2418
  return;
@@ -2372,6 +2430,10 @@ class SkyboxRenderer {
2372
2430
  ud[9] = viewMatrix[9];
2373
2431
  ud[10] = viewMatrix[10];
2374
2432
  ud[11] = 0;
2433
+ ud[12] = cameraPosition ? cameraPosition[1] : 0;
2434
+ ud[13] = this.groundRadius;
2435
+ ud[14] = 0;
2436
+ ud[15] = 0;
2375
2437
  this.device.queue.writeBuffer(this.uniformBuffer, 0, ud);
2376
2438
  this.frameReady = true;
2377
2439
  }
@@ -19008,9 +19070,11 @@ class App {
19008
19070
  this.updateAdaptivePerformance();
19009
19071
  this.hotspotManager.updateBillboards();
19010
19072
  if ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) {
19073
+ const cam = this.camera.position;
19011
19074
  this.skyboxRenderer.prepareFrame(
19012
19075
  this.camera.viewMatrix,
19013
- this.camera.projectionMatrix
19076
+ this.camera.projectionMatrix,
19077
+ [cam[0], cam[1], cam[2]]
19014
19078
  );
19015
19079
  }
19016
19080
  const pass = this.renderer.beginFrame();
@@ -19205,6 +19269,11 @@ class App {
19205
19269
  }
19206
19270
  await this.skyboxRenderer.loadEquirectangular(url);
19207
19271
  }
19272
+ setHdrGroundRadius(radius) {
19273
+ if (this.skyboxRenderer) {
19274
+ this.skyboxRenderer.groundRadius = radius;
19275
+ }
19276
+ }
19208
19277
  clearSkybox() {
19209
19278
  var _a2;
19210
19279
  (_a2 = this.skyboxRenderer) == null ? void 0 : _a2.clear();