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