@d5techs/3dgs-lib 1.4.51 → 1.4.53

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
@@ -2098,144 +2098,199 @@ class SceneAidsRenderer {
2098
2098
  this.axesBindGroup = null;
2099
2099
  }
2100
2100
  }
2101
- const WGSL = (
2101
+ async function loadHdr(url) {
2102
+ const res = await fetch(url);
2103
+ if (!res.ok) throw new Error(`HdrLoader: fetch failed ${url} (${res.status})`);
2104
+ const buf = await res.arrayBuffer();
2105
+ return parseHdr(new Uint8Array(buf));
2106
+ }
2107
+ function parseHdr(bytes) {
2108
+ let pos = 0;
2109
+ const readLine = () => {
2110
+ let line = "";
2111
+ while (pos < bytes.length) {
2112
+ const c = bytes[pos++];
2113
+ if (c === 10) return line;
2114
+ line += String.fromCharCode(c);
2115
+ }
2116
+ return line;
2117
+ };
2118
+ const magic = readLine();
2119
+ if (!magic.startsWith("#?")) throw new Error("HdrLoader: not a Radiance HDR file");
2120
+ while (pos < bytes.length) {
2121
+ const line = readLine();
2122
+ if (line.length === 0) break;
2123
+ }
2124
+ const resLine = readLine();
2125
+ const m = resLine.match(/-Y\s+(\d+)\s+\+X\s+(\d+)/);
2126
+ if (!m) throw new Error("HdrLoader: unsupported resolution format: " + resLine);
2127
+ const height = parseInt(m[1], 10);
2128
+ const width = parseInt(m[2], 10);
2129
+ const rgbe = new Uint8Array(width * height * 4);
2130
+ for (let y = 0; y < height; y++) {
2131
+ const scanline = decodeScanline(bytes, pos, width);
2132
+ pos = scanline.newPos;
2133
+ rgbe.set(scanline.pixels, y * width * 4);
2134
+ }
2135
+ const data = new Float32Array(width * height * 4);
2136
+ for (let i = 0; i < width * height; i++) {
2137
+ const r = rgbe[i * 4];
2138
+ const g = rgbe[i * 4 + 1];
2139
+ const b = rgbe[i * 4 + 2];
2140
+ const e = rgbe[i * 4 + 3];
2141
+ if (e === 0) {
2142
+ data[i * 4] = 0;
2143
+ data[i * 4 + 1] = 0;
2144
+ data[i * 4 + 2] = 0;
2145
+ } else {
2146
+ const scale = Math.pow(2, e - 128 - 8);
2147
+ data[i * 4] = r * scale;
2148
+ data[i * 4 + 1] = g * scale;
2149
+ data[i * 4 + 2] = b * scale;
2150
+ }
2151
+ data[i * 4 + 3] = 1;
2152
+ }
2153
+ return { width, height, data };
2154
+ }
2155
+ function decodeScanline(bytes, pos, width) {
2156
+ const pixels = new Uint8Array(width * 4);
2157
+ if (width < 8 || width > 32767) {
2158
+ for (let i = 0; i < width * 4; i++) pixels[i] = bytes[pos++];
2159
+ return { pixels, newPos: pos };
2160
+ }
2161
+ const b0 = bytes[pos], b1 = bytes[pos + 1], b22 = bytes[pos + 2], b3 = bytes[pos + 3];
2162
+ if (b0 !== 2 || b1 !== 2 || (b22 & 128) !== 0) {
2163
+ for (let i = 0; i < width * 4; i++) pixels[i] = bytes[pos++];
2164
+ return { pixels, newPos: pos };
2165
+ }
2166
+ const lineWidth = b22 << 8 | b3;
2167
+ if (lineWidth !== width) throw new Error("HdrLoader: scanline width mismatch");
2168
+ pos += 4;
2169
+ for (let ch = 0; ch < 4; ch++) {
2170
+ let x = 0;
2171
+ while (x < width) {
2172
+ const code = bytes[pos++];
2173
+ if (code > 128) {
2174
+ const count = code - 128;
2175
+ const val = bytes[pos++];
2176
+ for (let i = 0; i < count; i++) pixels[(x + i) * 4 + ch] = val;
2177
+ x += count;
2178
+ } else {
2179
+ for (let i = 0; i < code; i++) pixels[(x + i) * 4 + ch] = bytes[pos++];
2180
+ x += code;
2181
+ }
2182
+ }
2183
+ }
2184
+ return { pixels, newPos: pos };
2185
+ }
2186
+ const CUBE_WGSL = (
2102
2187
  /* wgsl */
2103
2188
  `
2104
- struct Uniforms {
2105
- col0: vec4<f32>,
2106
- col1: vec4<f32>,
2107
- col2: vec4<f32>,
2108
- extra: vec4<f32>,
2109
- };
2110
-
2189
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2111
2190
  @group(0) @binding(0) var<uniform> u: Uniforms;
2112
2191
  @group(0) @binding(1) var cubeSampler: sampler;
2113
2192
  @group(0) @binding(2) var cubeTexture: texture_cube<f32>;
2114
-
2115
- struct VSOut {
2116
- @builtin(position) position: vec4<f32>,
2117
- @location(0) dir: vec3<f32>,
2118
- };
2119
-
2120
- @vertex
2121
- fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
2122
- let positions = array<vec2<f32>, 3>(
2123
- vec2<f32>(-1.0, -1.0),
2124
- vec2<f32>(3.0, -1.0),
2125
- vec2<f32>(-1.0, 3.0),
2126
- );
2127
- var out: VSOut;
2128
- let p = positions[vi];
2129
- out.position = vec4<f32>(p, 0.0, 1.0);
2130
-
2131
- let eyeDir = vec3<f32>(p.x * u.col0.w, p.y * u.col1.w, -1.0);
2132
-
2133
- out.dir = vec3<f32>(
2134
- dot(u.col0.xyz, eyeDir),
2135
- dot(u.col1.xyz, eyeDir),
2136
- dot(u.col2.xyz, eyeDir),
2137
- );
2138
- return out;
2193
+ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2194
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2195
+ let ps = array<vec2<f32>,3>(vec2(-1.,-1.),vec2(3.,-1.),vec2(-1.,3.));
2196
+ var o: V; let p = ps[vi]; o.pos = vec4(p, 0., 1.);
2197
+ let e = vec3(p.x*u.col0.w, p.y*u.col1.w, -1.);
2198
+ o.dir = vec3(dot(u.col0.xyz,e), dot(u.col1.xyz,e), dot(u.col2.xyz,e));
2199
+ return o;
2139
2200
  }
2140
-
2141
- @fragment
2142
- fn fs(input: VSOut) -> @location(0) vec4<f32> {
2143
- let dir = normalize(input.dir);
2144
-
2145
- let camX = u.extra.y;
2146
- let camY = u.extra.z;
2147
- let camZ = u.extra.w;
2148
-
2149
- let doGround = u.extra.x > 0.5 && dir.y < -0.001 && camY > 0.01;
2150
- let safeDirY = select(dir.y, -0.01, dir.y > -0.001);
2151
- let t = camY / (-safeDirY);
2152
- let gx = camX + dir.x * t;
2153
- let gz = camZ + dir.z * t;
2154
- let scale = max(camY, 1.0) * 5.0;
2155
- let groundDir = normalize(vec3<f32>(gx / max(scale, 0.01), -1.0, gz / max(scale, 0.01)));
2156
-
2157
- let skyColor = textureSample(cubeTexture, cubeSampler, dir);
2158
- let groundColor = textureSample(cubeTexture, cubeSampler, groundDir);
2159
-
2160
- let blend = smoothstep(0.0, -0.12, dir.y);
2161
- let mixed = mix(skyColor.rgb, groundColor.rgb, blend);
2162
-
2163
- let final_color = select(skyColor.rgb, mixed, doGround);
2164
- return vec4<f32>(final_color, 1.0);
2201
+ @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2202
+ return vec4(textureSample(cubeTexture, cubeSampler, normalize(i.dir)).rgb, 1.);
2203
+ }`
2204
+ );
2205
+ const EQUIRECT_WGSL = (
2206
+ /* wgsl */
2207
+ `
2208
+ struct Uniforms { col0: vec4<f32>, col1: vec4<f32>, col2: vec4<f32> };
2209
+ @group(0) @binding(0) var<uniform> u: Uniforms;
2210
+ @group(0) @binding(1) var envSampler: sampler;
2211
+ @group(0) @binding(2) var envTexture: texture_2d<f32>;
2212
+ struct V { @builtin(position) pos: vec4<f32>, @location(0) dir: vec3<f32> };
2213
+ const PI: f32 = 3.14159265358979;
2214
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> V {
2215
+ let ps = array<vec2<f32>,3>(vec2(-1.,-1.),vec2(3.,-1.),vec2(-1.,3.));
2216
+ var o: V; let p = ps[vi]; o.pos = vec4(p, 0., 1.);
2217
+ let e = vec3(p.x*u.col0.w, p.y*u.col1.w, -1.);
2218
+ o.dir = vec3(dot(u.col0.xyz,e), dot(u.col1.xyz,e), dot(u.col2.xyz,e));
2219
+ return o;
2165
2220
  }
2166
- `
2221
+ @fragment fn fs(i: V) -> @location(0) vec4<f32> {
2222
+ 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;
2225
+ let mapped = c / (c + vec3(1.));
2226
+ let gamma = pow(mapped, vec3(1./2.2));
2227
+ return vec4(gamma, 1.);
2228
+ }`
2167
2229
  );
2168
2230
  class SkyboxRenderer {
2169
2231
  constructor(device, format, depthFormat) {
2170
2232
  __publicField(this, "device");
2171
- __publicField(this, "pipeline");
2172
2233
  __publicField(this, "uniformBuffer");
2173
2234
  __publicField(this, "sampler");
2174
- __publicField(this, "bindGroupLayout");
2175
- __publicField(this, "uniformData", new Float32Array(16));
2176
- __publicField(this, "cubeTexture", null);
2177
- __publicField(this, "cubeBindGroup", null);
2235
+ __publicField(this, "uniformData", new Float32Array(12));
2236
+ __publicField(this, "cubePipeline");
2237
+ __publicField(this, "cubeBindGroupLayout");
2238
+ __publicField(this, "equirectPipeline");
2239
+ __publicField(this, "equirectBindGroupLayout");
2240
+ __publicField(this, "texture", null);
2241
+ __publicField(this, "bindGroup", null);
2178
2242
  __publicField(this, "frameReady", false);
2179
- __publicField(this, "alignToGround", true);
2243
+ __publicField(this, "mode", "none");
2180
2244
  this.device = device;
2181
- const shaderModule = device.createShaderModule({ code: WGSL });
2182
- this.bindGroupLayout = device.createBindGroupLayout({
2245
+ const depthStencil = {
2246
+ format: depthFormat,
2247
+ depthWriteEnabled: false,
2248
+ depthCompare: "always"
2249
+ };
2250
+ this.cubeBindGroupLayout = device.createBindGroupLayout({
2183
2251
  entries: [
2184
- {
2185
- binding: 0,
2186
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
2187
- buffer: { type: "uniform" }
2188
- },
2189
- {
2190
- binding: 1,
2191
- visibility: GPUShaderStage.FRAGMENT,
2192
- sampler: { type: "filtering" }
2193
- },
2194
- {
2195
- binding: 2,
2196
- visibility: GPUShaderStage.FRAGMENT,
2197
- texture: { sampleType: "float", viewDimension: "cube" }
2198
- }
2252
+ { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2253
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2254
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "cube" } }
2199
2255
  ]
2200
2256
  });
2201
- this.pipeline = device.createRenderPipeline({
2202
- layout: device.createPipelineLayout({
2203
- bindGroupLayouts: [this.bindGroupLayout]
2204
- }),
2205
- vertex: { module: shaderModule, entryPoint: "vs" },
2206
- fragment: {
2207
- module: shaderModule,
2208
- entryPoint: "fs",
2209
- targets: [{ format }]
2210
- },
2257
+ this.cubePipeline = device.createRenderPipeline({
2258
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.cubeBindGroupLayout] }),
2259
+ vertex: { module: device.createShaderModule({ code: CUBE_WGSL }), entryPoint: "vs" },
2260
+ fragment: { module: device.createShaderModule({ code: CUBE_WGSL }), entryPoint: "fs", targets: [{ format }] },
2211
2261
  primitive: { topology: "triangle-list", cullMode: "none" },
2212
- depthStencil: {
2213
- format: depthFormat,
2214
- depthWriteEnabled: false,
2215
- depthCompare: "always"
2216
- }
2262
+ depthStencil
2263
+ });
2264
+ this.equirectBindGroupLayout = device.createBindGroupLayout({
2265
+ entries: [
2266
+ { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
2267
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
2268
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "2d" } }
2269
+ ]
2270
+ });
2271
+ this.equirectPipeline = device.createRenderPipeline({
2272
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.equirectBindGroupLayout] }),
2273
+ vertex: { module: device.createShaderModule({ code: EQUIRECT_WGSL }), entryPoint: "vs" },
2274
+ fragment: { module: device.createShaderModule({ code: EQUIRECT_WGSL }), entryPoint: "fs", targets: [{ format }] },
2275
+ primitive: { topology: "triangle-list", cullMode: "none" },
2276
+ depthStencil
2217
2277
  });
2218
2278
  this.uniformBuffer = device.createBuffer({
2219
- size: 64,
2279
+ size: 48,
2220
2280
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
2221
2281
  });
2222
- this.sampler = device.createSampler({
2223
- magFilter: "linear",
2224
- minFilter: "linear"
2225
- });
2282
+ this.sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
2226
2283
  }
2227
2284
  get isActive() {
2228
- return this.cubeTexture !== null && this.cubeBindGroup !== null;
2285
+ return this.mode !== "none" && this.texture !== null && this.bindGroup !== null;
2229
2286
  }
2287
+ // ---- cubemap ----
2230
2288
  async loadCubemap(faceUrls) {
2231
2289
  this.clear();
2232
2290
  const bitmaps = await Promise.all(
2233
2291
  faceUrls.map(async (url) => {
2234
2292
  const res = await fetch(url);
2235
- if (!res.ok)
2236
- throw new Error(
2237
- `SkyboxRenderer: failed to fetch ${url} (${res.status})`
2238
- );
2293
+ if (!res.ok) throw new Error(`SkyboxRenderer: failed to fetch ${url} (${res.status})`);
2239
2294
  return createImageBitmap(await res.blob());
2240
2295
  })
2241
2296
  );
@@ -2244,9 +2299,7 @@ class SkyboxRenderer {
2244
2299
  for (let i = 1; i < 6; i++) {
2245
2300
  if (bitmaps[i].width !== width || bitmaps[i].height !== height) {
2246
2301
  bitmaps.forEach((b) => b.close());
2247
- throw new Error(
2248
- "SkyboxRenderer: all cubemap faces must share the same dimensions"
2249
- );
2302
+ throw new Error("SkyboxRenderer: all cubemap faces must share the same dimensions");
2250
2303
  }
2251
2304
  }
2252
2305
  const tex = this.device.createTexture({
@@ -2263,17 +2316,45 @@ class SkyboxRenderer {
2263
2316
  );
2264
2317
  }
2265
2318
  bitmaps.forEach((b) => b.close());
2266
- this.cubeBindGroup = this.device.createBindGroup({
2267
- layout: this.bindGroupLayout,
2319
+ this.bindGroup = this.device.createBindGroup({
2320
+ layout: this.cubeBindGroupLayout,
2268
2321
  entries: [
2269
2322
  { binding: 0, resource: { buffer: this.uniformBuffer } },
2270
2323
  { binding: 1, resource: this.sampler },
2271
2324
  { binding: 2, resource: tex.createView({ dimension: "cube" }) }
2272
2325
  ]
2273
2326
  });
2274
- this.cubeTexture = tex;
2327
+ this.texture = tex;
2328
+ this.mode = "cubemap";
2275
2329
  }
2276
- prepareFrame(viewMatrix, projectionMatrix, cameraPosition) {
2330
+ // ---- equirectangular HDR ----
2331
+ async loadEquirectangular(url) {
2332
+ this.clear();
2333
+ const hdr = await loadHdr(url);
2334
+ const tex = this.device.createTexture({
2335
+ size: [hdr.width, hdr.height],
2336
+ format: "rgba32float",
2337
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
2338
+ });
2339
+ this.device.queue.writeTexture(
2340
+ { texture: tex },
2341
+ hdr.data.buffer,
2342
+ { bytesPerRow: hdr.width * 16, rowsPerImage: hdr.height },
2343
+ { width: hdr.width, height: hdr.height }
2344
+ );
2345
+ this.bindGroup = this.device.createBindGroup({
2346
+ layout: this.equirectBindGroupLayout,
2347
+ entries: [
2348
+ { binding: 0, resource: { buffer: this.uniformBuffer } },
2349
+ { binding: 1, resource: this.sampler },
2350
+ { binding: 2, resource: tex.createView() }
2351
+ ]
2352
+ });
2353
+ this.texture = tex;
2354
+ this.mode = "equirect";
2355
+ }
2356
+ // ---- common ----
2357
+ prepareFrame(viewMatrix, projectionMatrix) {
2277
2358
  if (!this.isActive) {
2278
2359
  this.frameReady = false;
2279
2360
  return;
@@ -2291,33 +2372,24 @@ class SkyboxRenderer {
2291
2372
  ud[9] = viewMatrix[9];
2292
2373
  ud[10] = viewMatrix[10];
2293
2374
  ud[11] = 0;
2294
- if (this.alignToGround && cameraPosition) {
2295
- ud[12] = 1;
2296
- ud[13] = cameraPosition[0];
2297
- ud[14] = cameraPosition[1];
2298
- ud[15] = cameraPosition[2];
2299
- } else {
2300
- ud[12] = 0;
2301
- ud[13] = 0;
2302
- ud[14] = 0;
2303
- ud[15] = 0;
2304
- }
2305
2375
  this.device.queue.writeBuffer(this.uniformBuffer, 0, ud);
2306
2376
  this.frameReady = true;
2307
2377
  }
2308
2378
  draw(pass) {
2309
- if (!this.frameReady || !this.cubeBindGroup) return;
2310
- pass.setPipeline(this.pipeline);
2311
- pass.setBindGroup(0, this.cubeBindGroup);
2379
+ if (!this.frameReady || !this.bindGroup) return;
2380
+ const pipeline = this.mode === "cubemap" ? this.cubePipeline : this.equirectPipeline;
2381
+ pass.setPipeline(pipeline);
2382
+ pass.setBindGroup(0, this.bindGroup);
2312
2383
  pass.draw(3);
2313
2384
  }
2314
2385
  clear() {
2315
- if (this.cubeTexture) {
2316
- this.cubeTexture.destroy();
2317
- this.cubeTexture = null;
2386
+ if (this.texture) {
2387
+ this.texture.destroy();
2388
+ this.texture = null;
2318
2389
  }
2319
- this.cubeBindGroup = null;
2390
+ this.bindGroup = null;
2320
2391
  this.frameReady = false;
2392
+ this.mode = "none";
2321
2393
  }
2322
2394
  destroy() {
2323
2395
  this.clear();
@@ -18936,11 +19008,9 @@ class App {
18936
19008
  this.updateAdaptivePerformance();
18937
19009
  this.hotspotManager.updateBillboards();
18938
19010
  if ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) {
18939
- const cam = this.camera.position;
18940
19011
  this.skyboxRenderer.prepareFrame(
18941
19012
  this.camera.viewMatrix,
18942
- this.camera.projectionMatrix,
18943
- [cam[0], cam[1], cam[2]]
19013
+ this.camera.projectionMatrix
18944
19014
  );
18945
19015
  }
18946
19016
  const pass = this.renderer.beginFrame();
@@ -19125,6 +19195,16 @@ class App {
19125
19195
  faceUrls.negZ
19126
19196
  ]);
19127
19197
  }
19198
+ async setHdrBackground(url) {
19199
+ if (!this.skyboxRenderer) {
19200
+ this.skyboxRenderer = new SkyboxRenderer(
19201
+ this.renderer.device,
19202
+ this.renderer.format,
19203
+ "depth24plus"
19204
+ );
19205
+ }
19206
+ await this.skyboxRenderer.loadEquirectangular(url);
19207
+ }
19128
19208
  clearSkybox() {
19129
19209
  var _a2;
19130
19210
  (_a2 = this.skyboxRenderer) == null ? void 0 : _a2.clear();
@@ -19133,11 +19213,6 @@ class App {
19133
19213
  var _a2;
19134
19214
  return ((_a2 = this.skyboxRenderer) == null ? void 0 : _a2.isActive) ?? false;
19135
19215
  }
19136
- setSkyboxAlignToGround(value) {
19137
- if (this.skyboxRenderer) {
19138
- this.skyboxRenderer.alignToGround = value;
19139
- }
19140
- }
19141
19216
  // ============================================
19142
19217
  // Gizmo(委托给 GizmoManager)
19143
19218
  // ============================================
@@ -19621,6 +19696,7 @@ exports.exportEditedPLY = exportEditedPLY;
19621
19696
  exports.getRecommendedDPR = getRecommendedDPR;
19622
19697
  exports.isMobileDevice = isMobileDevice;
19623
19698
  exports.isWebGPUSupported = isWebGPUSupported;
19699
+ exports.loadHdr = loadHdr;
19624
19700
  exports.loadPLY = loadPLY;
19625
19701
  exports.loadPLYMobile = loadPLYMobile;
19626
19702
  exports.loadSOG = loadSOG;