s3m-three 0.4.22 → 0.4.23
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/browser/s3m-three.iife.ext-three.js +35 -35
- package/dist/browser/s3m-three.iife.js +30 -30
- package/dist/render/DatasetManager.d.ts +13 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +20 -0
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +11 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +2 -1
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +53 -5
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +157 -12
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +10 -1
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +9 -1
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +23 -1
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +32 -8
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/render/viewer/LightRig.d.ts +21 -7
- package/dist/render/viewer/LightRig.d.ts.map +1 -1
- package/dist/render/viewer/LightRig.js +11 -13
- package/dist/render/viewer/LightRig.js.map +1 -1
- package/dist/render/viewer/types.d.ts +12 -0
- package/dist/render/viewer/types.d.ts.map +1 -1
- package/dist/render/viewer/types.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +19 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +16 -0
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/docs/API.md +24 -3
- package/package.json +1 -1
- package/src/render/DatasetManager.ts +21 -0
- package/src/render/GeometryBuilder.ts +13 -1
- package/src/render/S3MViewer.ts +168 -12
- package/src/render/StyleMaterial.ts +16 -2
- package/src/render/TextureFactory.ts +50 -8
- package/src/render/viewer/LightRig.ts +23 -13
- package/src/render/viewer/types.ts +12 -0
- package/src/tileset/S3MTileset.ts +24 -0
package/src/render/S3MViewer.ts
CHANGED
|
@@ -25,8 +25,9 @@ import { S3MTileset, usableGeoBounds } from '../tileset/S3MTileset.js';
|
|
|
25
25
|
import type { BuildTileFn, TileNode } from '../tileset/S3MTileset.js';
|
|
26
26
|
import type { AuthenticateFn } from '../core/AuthFetch.js';
|
|
27
27
|
import { setSplatViewport } from './GeometryBuilder.js';
|
|
28
|
-
import { setMaxAnisotropy, setCompressedTexturesSupported, setTextureOffThreadDecode, setMaxTextureSize as setTextureSizeCap } from './TextureFactory.js';
|
|
29
|
-
import {
|
|
28
|
+
import { setMaxAnisotropy, setCompressedTexturesSupported, setTextureOffThreadDecode, setMaxTextureSize as setTextureSizeCap, applyAnisotropyCap } from './TextureFactory.js';
|
|
29
|
+
import type { TextureCreateOptions } from './TextureFactory.js';
|
|
30
|
+
import { getMaterialQuality, normalizeMaterialQuality, getColorPipeline, normalizeColorPipeline } from './StyleMaterial.js';
|
|
30
31
|
import type { LightingPreset } from './lightingPreset.js';
|
|
31
32
|
import { LightRig } from './viewer/LightRig.js';
|
|
32
33
|
import { HeightIndex } from './viewer/HeightIndex.js';
|
|
@@ -62,6 +63,7 @@ import type {
|
|
|
62
63
|
EdgeModes,
|
|
63
64
|
EdgesOption,
|
|
64
65
|
MaterialQuality,
|
|
66
|
+
ColorPipeline,
|
|
65
67
|
} from './StyleMaterial.js';
|
|
66
68
|
import { applyPointCloudStyle, isOwnedPointCloudMaterial } from './PointCloudMaterial.js';
|
|
67
69
|
import type { DegradationKind, UnsupportedPolicy } from '../core/degradations.js';
|
|
@@ -204,6 +206,18 @@ const STYLE_SCALAR_KEYS = [
|
|
|
204
206
|
'outlineWidth',
|
|
205
207
|
] as const;
|
|
206
208
|
|
|
209
|
+
/**
|
|
210
|
+
* 标量样式键在「用户还没设过」时的动画起点。
|
|
211
|
+
*
|
|
212
|
+
* 只对 `metalness` / `roughness` 有意义:它们的 uniform 默认是 -1(不覆盖,保持数据
|
|
213
|
+
* 自身的值),从 -1 插值没意义 —— 取数据侧常见值当起点,过渡才不会先跳到 0。
|
|
214
|
+
* 其余键缺省仍是 0(与旧行为一致)。
|
|
215
|
+
*/
|
|
216
|
+
const SCALAR_ANIM_START: Partial<Record<(typeof STYLE_SCALAR_KEYS)[number], number>> = {
|
|
217
|
+
metalness: 0, // S3M 数据的 metalness 恒为 0
|
|
218
|
+
roughness: 0.95, // shininess 映射后的常见值,见 GeometryBuilder 的 roughness
|
|
219
|
+
};
|
|
220
|
+
|
|
207
221
|
/** 能逐帧插值的颜色键(在线性空间插值,与 shader 消费端一致) */
|
|
208
222
|
const STYLE_COLOR_KEYS = ['colorA', 'colorB', 'colorC', 'selectColor', 'edgeColor'] as const;
|
|
209
223
|
|
|
@@ -257,6 +271,17 @@ export class S3MViewer {
|
|
|
257
271
|
* 传了值就只认自己,多图层同页各用各的,互不覆盖。
|
|
258
272
|
*/
|
|
259
273
|
private _materialQuality: MaterialQuality | undefined;
|
|
274
|
+
/**
|
|
275
|
+
* 本图层的颜色管线。
|
|
276
|
+
*
|
|
277
|
+
* `undefined` = 没指定,跟随模块级默认(`getColorPipeline()`)。
|
|
278
|
+
* 与 materialQuality 同理:传了值就只认自己,同页多图层互不污染 —— 这条尤其重要,
|
|
279
|
+
* 因为 `lightingPreset: 'supermap'` 会同时改管线、纹理解码方式与各向异性,
|
|
280
|
+
* 以前是三个模块级全局一起被改写。
|
|
281
|
+
*/
|
|
282
|
+
private _colorPipeline: ColorPipeline | undefined;
|
|
283
|
+
/** fade-through 编排的代次:中途再切一次,旧编排作废(与 setMode 同一套) */
|
|
284
|
+
private _pipelineGen = 0;
|
|
260
285
|
/** 单体样式、专题着色与状态纹理管理器 */
|
|
261
286
|
private readonly _featureStyleManager: FeatureStyleManager;
|
|
262
287
|
/** GPU 路径动画管理器 */
|
|
@@ -395,7 +420,12 @@ export class S3MViewer {
|
|
|
395
420
|
);
|
|
396
421
|
|
|
397
422
|
this.uniforms = createSharedUniforms();
|
|
398
|
-
//
|
|
423
|
+
// 图层 profile 先落位(显式传了就只认自己),再把 LightRig 挂上来 ——
|
|
424
|
+
// 它构造时会按 lightingPreset 覆盖一次管线,这一步走的是图层级通道,
|
|
425
|
+
// 不再去改模块级全局(以前三个全局一起被改写,同页多图层互相污染)。
|
|
426
|
+
this._colorPipeline = normalizeColorPipeline(options.colorPipeline);
|
|
427
|
+
this._applyRenderProfile({}, true);
|
|
428
|
+
// 光源状态在这里就位:LightRig 构造时会按 lightingPreset 切图层颜色管线,
|
|
399
429
|
// 挂进场景要等 scene 建好(构造函数上面已经建了)。
|
|
400
430
|
this._lights = new LightRig({
|
|
401
431
|
scene: () => this.scene,
|
|
@@ -406,6 +436,8 @@ export class S3MViewer {
|
|
|
406
436
|
// 走公共 triggerRepaint 而不是 _requestRepaint:宿主 / 单测会覆盖前者来观察重绘,
|
|
407
437
|
// 绕过它会让「有没有触发重绘」这件事失去观测点
|
|
408
438
|
repaint: () => this.triggerRepaint(),
|
|
439
|
+
// 构造期(initial)只记状态:那时还没有材质可重建、没有数据集可同步
|
|
440
|
+
applyRenderProfile: (p, initial) => this._applyRenderProfile(p, initial),
|
|
409
441
|
});
|
|
410
442
|
this._lights.addToScene();
|
|
411
443
|
this._heights = new HeightIndex(this.uniforms);
|
|
@@ -1855,7 +1887,13 @@ export class S3MViewer {
|
|
|
1855
1887
|
this.animator.register(`style.${key}`, {
|
|
1856
1888
|
dim: 1,
|
|
1857
1889
|
read: (out) => {
|
|
1858
|
-
|
|
1890
|
+
// 金属度 / 粗糙度没设过时 uniform 是 -1(= 不覆盖,保持数据自身的值),
|
|
1891
|
+
// 动画起点不能取 0:那会让 roughness 从 0.95 先跳到 0 再爬回去。
|
|
1892
|
+
// 这里给的是数据侧的常见值(S3M 的 shininess 映射后 ≈0.95、metalness 恒 0),
|
|
1893
|
+
// 过渡起点因此是连续的。
|
|
1894
|
+
out[0] = Number(
|
|
1895
|
+
(this.style as unknown as Record<string, number>)[key] ?? SCALAR_ANIM_START[key] ?? 0
|
|
1896
|
+
);
|
|
1859
1897
|
},
|
|
1860
1898
|
write: (v) => {
|
|
1861
1899
|
(this.style as unknown as Record<string, number>)[key] = v[0];
|
|
@@ -2115,6 +2153,112 @@ export class S3MViewer {
|
|
|
2115
2153
|
return this._materialQuality ?? getMaterialQuality();
|
|
2116
2154
|
}
|
|
2117
2155
|
|
|
2156
|
+
/**
|
|
2157
|
+
* 运行时切换**本图层**的颜色管线(`'linear'` | `'direct'`)。
|
|
2158
|
+
*
|
|
2159
|
+
* `'direct'` = 官方 iClient3D 的 sRGB 直乘管线(配合纹理解码关 sRGB),
|
|
2160
|
+
* 见 `lightingPreset: 'supermap'`。切换牵扯三样东西:材质宏(换 program)、
|
|
2161
|
+
* 纹理 colorSpace(重传,**不重新下载瓦片**)、以及各向异性档位。
|
|
2162
|
+
*
|
|
2163
|
+
* 给 `duration > 0` 时走 fade-through:压暗 → 在同一帧回调里换管线 → 亮回来。
|
|
2164
|
+
* 逐像素混合做不到干净 —— 两条管线的差异有一部分在采样端(硬件 sRGB 解码 vs
|
|
2165
|
+
* 原始字节),要混合就得让贴图恒为原始字节并在片元里手动解码,那会改掉默认路径。
|
|
2166
|
+
*/
|
|
2167
|
+
setColorPipeline(q: ColorPipeline, opts: { duration?: number; dim?: number } = {}): Promise<void> {
|
|
2168
|
+
const next = normalizeColorPipeline(q) ?? 'linear';
|
|
2169
|
+
if (next === this.colorPipeline) return Promise.resolve();
|
|
2170
|
+
|
|
2171
|
+
const d = Math.max(0, opts.duration ?? 0);
|
|
2172
|
+
if (d <= 0) {
|
|
2173
|
+
this._applyRenderProfile({ colorPipeline: next });
|
|
2174
|
+
return Promise.resolve();
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
const dim = Math.min(1, Math.max(0.05, opts.dim ?? 0.35));
|
|
2178
|
+
const gen = ++this._pipelineGen;
|
|
2179
|
+
return new Promise<void>((resolve) => {
|
|
2180
|
+
this.animator.to('style.fade', dim, {
|
|
2181
|
+
duration: d,
|
|
2182
|
+
easing: 'easeInOutQuad',
|
|
2183
|
+
onComplete: () => {
|
|
2184
|
+
// 中途又切了一次:这次编排作废,收尾交给新的那次
|
|
2185
|
+
if (gen !== this._pipelineGen) {
|
|
2186
|
+
resolve();
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
this._applyRenderProfile({ colorPipeline: next });
|
|
2190
|
+
this.animator.to('style.fade', 1, {
|
|
2191
|
+
duration: d,
|
|
2192
|
+
easing: 'easeInOutQuad',
|
|
2193
|
+
onComplete: resolve,
|
|
2194
|
+
});
|
|
2195
|
+
},
|
|
2196
|
+
});
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
/** 当前颜色管线(本图层指定了就用本图层的,否则是模块级默认) */
|
|
2201
|
+
get colorPipeline(): ColorPipeline {
|
|
2202
|
+
return this._colorPipeline ?? getColorPipeline();
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
/** 本图层此刻该用的纹理创建参数(图层的渲染 profile 就落在这里) */
|
|
2206
|
+
private _textureOptions(): TextureCreateOptions {
|
|
2207
|
+
return {
|
|
2208
|
+
// direct 管线的前提是贴图字节值不解码,两者必须一起切
|
|
2209
|
+
srgbDecode: this.colorPipeline !== 'direct',
|
|
2210
|
+
maxSize: this.options.maxTextureSize ?? Infinity,
|
|
2211
|
+
anisotropyCap: this.options.anisotropyCap ?? Infinity,
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* 应用图层渲染 profile(颜色管线 / 各向异性上限)。
|
|
2217
|
+
*
|
|
2218
|
+
* `initial` = 构造期:只记状态并推给数据集管理器,不重建材质、不重传纹理
|
|
2219
|
+
* (那时还没有瓦片)。
|
|
2220
|
+
*/
|
|
2221
|
+
private _applyRenderProfile(
|
|
2222
|
+
patch: { colorPipeline?: ColorPipeline; anisotropyCap?: number },
|
|
2223
|
+
initial = false
|
|
2224
|
+
): void {
|
|
2225
|
+
const prev = this.colorPipeline;
|
|
2226
|
+
if (patch.colorPipeline !== undefined) {
|
|
2227
|
+
this._colorPipeline = normalizeColorPipeline(patch.colorPipeline) ?? 'linear';
|
|
2228
|
+
}
|
|
2229
|
+
if (patch.anisotropyCap !== undefined) this.options.anisotropyCap = patch.anisotropyCap;
|
|
2230
|
+
|
|
2231
|
+
const textureOptions = this._textureOptions();
|
|
2232
|
+
// 之后加载的瓦片按新参数走(已建好的数据集也要跟着换)
|
|
2233
|
+
this._datasetManager.setRenderProfile({ colorPipeline: this.colorPipeline, textureOptions });
|
|
2234
|
+
if (initial) return;
|
|
2235
|
+
|
|
2236
|
+
// 管线变了才需要换 program;只改各向异性时重建纯属浪费
|
|
2237
|
+
if (this.colorPipeline !== prev) this._rebuildStyledMaterials();
|
|
2238
|
+
this._syncCachedTextures(textureOptions);
|
|
2239
|
+
this._requestRepaint();
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/**
|
|
2243
|
+
* 把纹理解码方式 / 各向异性同步到**已上传**的纹理上。
|
|
2244
|
+
*
|
|
2245
|
+
* 两者都不需要重新解码:`colorSpace` 改了 + `needsUpdate` 让 three 重传一次像素,
|
|
2246
|
+
* `anisotropy` 同理。所以切管线不用重拉瓦片,代价只有一次纹理上传。
|
|
2247
|
+
*/
|
|
2248
|
+
private _syncCachedTextures(opts: TextureCreateOptions): void {
|
|
2249
|
+
const want = opts.srgbDecode ? THREE.SRGBColorSpace : THREE.NoColorSpace;
|
|
2250
|
+
for (const entry of this.datasets.values()) {
|
|
2251
|
+
const cache = entry.tileset?.textureCache;
|
|
2252
|
+
if (!cache) continue;
|
|
2253
|
+
for (const tex of cache.values()) {
|
|
2254
|
+
if (tex.colorSpace === want) continue;
|
|
2255
|
+
tex.colorSpace = want;
|
|
2256
|
+
tex.needsUpdate = true;
|
|
2257
|
+
}
|
|
2258
|
+
applyAnisotropyCap(cache.values(), opts.anisotropyCap ?? Infinity);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2118
2262
|
/** 按创建参数原位重建所有 styled 材质(materialQuality 切换用) */
|
|
2119
2263
|
private _rebuildStyledMaterials(): void {
|
|
2120
2264
|
// 逐材质 own uniform 的状态要在重建时搬过去,否则选中高亮 / 瓦片淡入会闪没
|
|
@@ -2134,9 +2278,14 @@ export class S3MViewer {
|
|
|
2134
2278
|
const old = raw as StyledMaterial | undefined;
|
|
2135
2279
|
if (!old?.userData?.isStyled || !old.userData?.s3mOpts) continue;
|
|
2136
2280
|
const opts = old.userData.s3mOpts as StyledMaterialOptions;
|
|
2137
|
-
//
|
|
2138
|
-
//
|
|
2139
|
-
const fresh = createStyledMaterial({
|
|
2281
|
+
// 显式传本层档位与管线:s3mOpts 里记的是创建时的参数,可能是 undefined(跟随全局),
|
|
2282
|
+
// 重建时必须钉成本层当前值,否则「别处改了全局」会把这一层带跑。
|
|
2283
|
+
const fresh = createStyledMaterial({
|
|
2284
|
+
...opts,
|
|
2285
|
+
uniforms: this.uniforms,
|
|
2286
|
+
materialQuality: this.materialQuality,
|
|
2287
|
+
colorPipeline: this.colorPipeline,
|
|
2288
|
+
});
|
|
2140
2289
|
for (const key of PRESERVED) {
|
|
2141
2290
|
const src = old.userData[key] as { value: unknown } | undefined;
|
|
2142
2291
|
const dst = fresh.userData[key] as { value: unknown } | undefined;
|
|
@@ -2508,16 +2657,19 @@ export class S3MViewer {
|
|
|
2508
2657
|
}
|
|
2509
2658
|
|
|
2510
2659
|
/**
|
|
2511
|
-
*
|
|
2512
|
-
*
|
|
2513
|
-
*
|
|
2660
|
+
* 调整本图层的各向异性过滤上限(默认 Infinity = 取 GPU 最大值)。
|
|
2661
|
+
*
|
|
2662
|
+
* 与 0.4.22 之前不同:已上传的纹理**会立刻跟着变**(改 sampling 参数 + 重传,
|
|
2663
|
+
* 不需要重新解码,也不需要重载瓦片),所以切 `lightingPreset: 'supermap'` 时
|
|
2664
|
+
* 立面清晰度是当场变的,不再等新瓦片。
|
|
2514
2665
|
*/
|
|
2515
2666
|
setAnisotropyCap(cap: number): void {
|
|
2516
2667
|
const next = Number.isFinite(cap) && cap > 0 ? Math.max(1, cap) : Infinity;
|
|
2517
2668
|
this.options.anisotropyCap = next;
|
|
2669
|
+
// 同步模块级默认:直接调 createTexture / buildTileObjects 的老用法仍读它
|
|
2518
2670
|
const gpuMax = this.renderer?.capabilities.getMaxAnisotropy?.() ?? 1;
|
|
2519
2671
|
setMaxAnisotropy(gpuMax, next);
|
|
2520
|
-
this.
|
|
2672
|
+
this._applyRenderProfile({ anisotropyCap: next });
|
|
2521
2673
|
}
|
|
2522
2674
|
|
|
2523
2675
|
/** 当前纹理长边像素上限。Infinity = 不限制 */
|
|
@@ -2528,12 +2680,16 @@ export class S3MViewer {
|
|
|
2528
2680
|
/**
|
|
2529
2681
|
* 调整纹理长边像素上限(默认 Infinity = 不限制)。
|
|
2530
2682
|
* 只影响之后解码的纹理;非图集的 RGBA / WebP 产物按 2× 减半逼近上限。
|
|
2531
|
-
*
|
|
2683
|
+
*
|
|
2684
|
+
* 图层级:只改本层,同页其它图层不受影响(同时同步模块级默认,照顾直接调
|
|
2685
|
+
* `createTexture` 的老用法)。已解码的纹理不会重解 —— 那是另一件事。
|
|
2532
2686
|
*/
|
|
2533
2687
|
setMaxTextureSize(n: number): void {
|
|
2534
2688
|
const next = Number.isFinite(n) && n >= 1 ? n : Infinity;
|
|
2535
2689
|
this.options.maxTextureSize = next;
|
|
2536
2690
|
setTextureSizeCap(next);
|
|
2691
|
+
// 只推给数据集,不动材质也不重传纹理(尺寸上限只影响「之后解码」的纹理)
|
|
2692
|
+
this._datasetManager.setRenderProfile({ textureOptions: this._textureOptions() });
|
|
2537
2693
|
this._requestRepaint();
|
|
2538
2694
|
}
|
|
2539
2695
|
|
|
@@ -317,7 +317,9 @@ export function getMaterialQuality(): MaterialQuality {
|
|
|
317
317
|
* (0.8·NdotL + 0.7)、输出前抵消 renderer 的 OETF。与 `setSrgbTextureDecode(false)`
|
|
318
318
|
* 配套使用,`lightingPreset: 'supermap'` 会同时打开两者。
|
|
319
319
|
*
|
|
320
|
-
* 与 materialQuality
|
|
320
|
+
* 与 materialQuality 同一套约定:这里只是**模块级默认值**,不是全局开关 ——
|
|
321
|
+
* 图层显式传了 `colorPipeline` 就只认自己,没传才读这里。只影响之后创建的材质,
|
|
322
|
+
* 已加载瓦片要重载或在图层上调 `setColorPipeline` 才会换。
|
|
321
323
|
*/
|
|
322
324
|
export type ColorPipeline = 'linear' | 'direct';
|
|
323
325
|
let colorPipeline: ColorPipeline = 'linear';
|
|
@@ -330,6 +332,13 @@ export function getColorPipeline(): ColorPipeline {
|
|
|
330
332
|
return colorPipeline;
|
|
331
333
|
}
|
|
332
334
|
|
|
335
|
+
/** 归一颜色管线:只认 'linear' / 'direct',其余返回 undefined(= 去读模块级默认) */
|
|
336
|
+
export function normalizeColorPipeline(p: unknown): ColorPipeline | undefined {
|
|
337
|
+
if (p === 'direct') return 'direct';
|
|
338
|
+
if (p === 'linear') return 'linear';
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
333
342
|
export type StyledMaterial = (THREE.MeshStandardMaterial | THREE.MeshLambertMaterial) & {
|
|
334
343
|
userData: {
|
|
335
344
|
uSelBatch: { value: number };
|
|
@@ -1128,6 +1137,11 @@ export interface StyledMaterialOptions {
|
|
|
1128
1137
|
* 一路透传到这里,不再让所有图层共享同一份模块级状态。
|
|
1129
1138
|
*/
|
|
1130
1139
|
materialQuality?: MaterialQuality;
|
|
1140
|
+
/**
|
|
1141
|
+
* 颜色管线。不传 = 读模块级默认(`getColorPipeline()`),行为与旧版一致。
|
|
1142
|
+
* 与 `materialQuality` 同一套:图层把自己的 profile 透传进来,同页互不污染。
|
|
1143
|
+
*/
|
|
1144
|
+
colorPipeline?: ColorPipeline;
|
|
1131
1145
|
}
|
|
1132
1146
|
|
|
1133
1147
|
/**
|
|
@@ -1230,7 +1244,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
1230
1244
|
if (texCoordZ) mat.defines = { ...(mat.defines as Record<string, string>), S3M_TEXCOORD_Z: '' };
|
|
1231
1245
|
if (secMap) mat.defines = { ...mat.defines, S3M_ATLAS_SEC: '' };
|
|
1232
1246
|
// supermap 完整版:官方 sRGB 直乘管线(配合 setSrgbTextureDecode(false))
|
|
1233
|
-
const direct = colorPipeline === 'direct';
|
|
1247
|
+
const direct = (opts.colorPipeline ?? colorPipeline) === 'direct';
|
|
1234
1248
|
if (direct) mat.defines = { ...mat.defines, S3M_DIRECT_COLOR: '' };
|
|
1235
1249
|
// 官方 uTextureDim = (base宽, base高, bake宽, bake高),取实际绑定纹理的像素尺寸
|
|
1236
1250
|
const uAtlasDim = { value: new THREE.Vector2(opts.map?.image?.width || 1, opts.map?.image?.height || 1) };
|
|
@@ -31,6 +31,43 @@ export function setMaxAnisotropy(n: number, cap = Infinity): void {
|
|
|
31
31
|
anisotropyCap = Number.isFinite(cap) && cap > 0 ? Math.max(1, cap) : Infinity;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 单次创建的纹理参数。三项都可缺省 —— 缺省时读模块级默认(`setSrgbTextureDecode`
|
|
36
|
+
* / `setMaxTextureSize` / `setAnisotropyCap`),所以老调用方行为逐字不变。
|
|
37
|
+
*
|
|
38
|
+
* 图层级隔离靠这条:`S3MLayer` 把自己的渲染 profile 透传进来,不再共享模块级状态。
|
|
39
|
+
*/
|
|
40
|
+
export interface TextureCreateOptions {
|
|
41
|
+
/** 是否做 sRGB 解码(默认读 `getSrgbTextureDecode()`)。`'direct'` 管线传 false */
|
|
42
|
+
srgbDecode?: boolean;
|
|
43
|
+
/** 长边像素上限(默认读 `setMaxTextureSize` 的值)。图集恒不缩放 */
|
|
44
|
+
maxSize?: number;
|
|
45
|
+
/** 各向异性上限(默认读 `setAnisotropyCap` 的值)。Infinity = 取 GPU 最大值 */
|
|
46
|
+
anisotropyCap?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 把「上限」解析成实际的 anisotropy 值(GPU 最大值与 cap 取小) */
|
|
50
|
+
function resolveAnisotropy(cap: number): number {
|
|
51
|
+
return Number.isFinite(cap) && cap > 0 ? Math.min(maxAnisotropy, Math.max(1, cap)) : maxAnisotropy;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 把各向异性上限**即时应用**到一批已上传的纹理。
|
|
56
|
+
*
|
|
57
|
+
* `anisotropy` 是采样参数,不需要重新解码 —— 改值 + `needsUpdate` 让 three 重传一次
|
|
58
|
+
* 即可,代价远小于重载瓦片。图集纹理(`configureAtlas` 打过 `s3mNoAniso` 标记)
|
|
59
|
+
* 保持 1:它关了 mip、靠子区域 rect 自己算层级,开各向异性会把相邻子区域混进来。
|
|
60
|
+
*/
|
|
61
|
+
export function applyAnisotropyCap(textures: Iterable<THREE.Texture>, cap: number): void {
|
|
62
|
+
const n = resolveAnisotropy(cap);
|
|
63
|
+
for (const tex of textures) {
|
|
64
|
+
if (!tex || (tex.userData as { s3mNoAniso?: boolean })?.s3mNoAniso) continue;
|
|
65
|
+
if (tex.anisotropy === n) continue;
|
|
66
|
+
tex.anisotropy = n;
|
|
67
|
+
tex.needsUpdate = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
34
71
|
/**
|
|
35
72
|
* 单独调各向异性上限(只影响之后创建的纹理)。
|
|
36
73
|
* supermap 预设会设成 1 —— 官方 iClient3D 不开各向异性,斜视角直接采高 mip
|
|
@@ -114,15 +151,15 @@ export function getSrgbTextureDecode(): boolean {
|
|
|
114
151
|
return srgbTextureDecode;
|
|
115
152
|
}
|
|
116
153
|
|
|
117
|
-
function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
154
|
+
function configure<T extends THREE.Texture>(tex: T, srgb = true, opts: TextureCreateOptions = {}): T {
|
|
118
155
|
tex.wrapS = THREE.RepeatWrapping;
|
|
119
156
|
tex.wrapT = THREE.RepeatWrapping;
|
|
120
157
|
tex.magFilter = THREE.LinearFilter;
|
|
121
158
|
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
122
159
|
tex.generateMipmaps = true;
|
|
123
160
|
// 各向异性过滤对斜视角立面清晰度的提升比提高分辨率更直接
|
|
124
|
-
tex.anisotropy = anisotropyCap
|
|
125
|
-
if (srgb && srgbTextureDecode) tex.colorSpace = THREE.SRGBColorSpace;
|
|
161
|
+
tex.anisotropy = resolveAnisotropy(opts.anisotropyCap ?? anisotropyCap);
|
|
162
|
+
if (srgb && (opts.srgbDecode ?? srgbTextureDecode)) tex.colorSpace = THREE.SRGBColorSpace;
|
|
126
163
|
tex.flipY = false;
|
|
127
164
|
tex.needsUpdate = true;
|
|
128
165
|
return tex;
|
|
@@ -136,18 +173,23 @@ function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
|
136
173
|
* 自动选的层级会把相邻子区域混进来,远处就是一糊串色 —— 实测照抄公式但留着 GPU mip
|
|
137
174
|
* 时,画面反而比不改更糟,就是因为这两个变量叠在一起了。
|
|
138
175
|
*/
|
|
139
|
-
function configureAtlas<T extends THREE.Texture>(tex: T): T {
|
|
140
|
-
configure(tex);
|
|
176
|
+
function configureAtlas<T extends THREE.Texture>(tex: T, opts: TextureCreateOptions = {}): T {
|
|
177
|
+
configure(tex, true, opts);
|
|
141
178
|
tex.wrapS = THREE.ClampToEdgeWrapping;
|
|
142
179
|
tex.wrapT = THREE.ClampToEdgeWrapping;
|
|
143
180
|
tex.minFilter = THREE.LinearFilter;
|
|
144
181
|
tex.generateMipmaps = false;
|
|
145
182
|
tex.anisotropy = 1;
|
|
183
|
+
// 标记给 applyAnisotropyCap:图集不吃各向异性上限(见该函数注释)
|
|
184
|
+
(tex.userData as { s3mNoAniso?: boolean }).s3mNoAniso = true;
|
|
146
185
|
tex.needsUpdate = true;
|
|
147
186
|
return tex;
|
|
148
187
|
}
|
|
149
188
|
|
|
150
|
-
export async function createTexture(
|
|
189
|
+
export async function createTexture(
|
|
190
|
+
tex: S3MTexture | null | undefined,
|
|
191
|
+
opts: TextureCreateOptions = {}
|
|
192
|
+
): Promise<DecodedTexture> {
|
|
151
193
|
if (!tex || !tex.width || !tex.height) return { texture: null, decoded: 'empty' };
|
|
152
194
|
|
|
153
195
|
// 图集纹理(flipY 的原始 DXT 图集 / AtlasPack 拼出来的 RGBA 大图 / leaf 组合画布)都必须
|
|
@@ -157,7 +199,7 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
157
199
|
// 压缩直传只在「宿主 GL 支持 + 非图集」时开:图集要 flipY / 无 mip,压缩上传做不了翻行
|
|
158
200
|
const wantCompressed = compressedTexturesSupported && !isAtlas;
|
|
159
201
|
// 尺寸上限同样跳过图集:UV 已重映射到子区域,缩放会让整体错位
|
|
160
|
-
const sizeCap = isAtlas ? Infinity : maxTextureSize;
|
|
202
|
+
const sizeCap = isAtlas ? Infinity : (opts.maxSize ?? maxTextureSize);
|
|
161
203
|
const data = await decodeTexture(tex, wantCompressed, sizeCap);
|
|
162
204
|
|
|
163
205
|
if (!data.rgba && !data.blocks && !data.bitmap) {
|
|
@@ -194,7 +236,7 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
194
236
|
texture = dt;
|
|
195
237
|
}
|
|
196
238
|
texture.needsUpdate = true;
|
|
197
|
-
const configured = isAtlas ? configureAtlas(texture) : configure(texture);
|
|
239
|
+
const configured = isAtlas ? configureAtlas(texture, opts) : configure(texture, true, opts);
|
|
198
240
|
if (data.blocks) {
|
|
199
241
|
configured.generateMipmaps = false;
|
|
200
242
|
// 带链 → 三线性取链上高层级(官方同款平均效果);纯 level0 才退回 Linear 兜底
|
|
@@ -4,17 +4,16 @@
|
|
|
4
4
|
* 从 `S3MViewer` 里整体搬出来的第一块 —— 它只依赖 `scene` / `renderer` / 共享
|
|
5
5
|
* uniform / 宿主方位角,不碰几何、瓦片、拾取,是耦合最低的一块。
|
|
6
6
|
*
|
|
7
|
-
* ##
|
|
7
|
+
* ## 颜色管线是图层级的
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* 颜色管线 / 纹理解码 / 各向异性三者一起决定「官方 supermap 观感」,都由
|
|
10
|
+
* `ctx.applyRenderProfile` 落到本图层(见 `S3MViewer._applyRenderProfile`),
|
|
11
|
+
* 不再写模块级全局 —— 同页两个 viewer 用不同预设互不影响,已加载瓦片也会
|
|
12
|
+
* 当场重建材质、重传纹理,不用重载。
|
|
13
13
|
*/
|
|
14
14
|
import * as THREE from 'three';
|
|
15
15
|
import { resolveLightingPreset, type LightingPreset } from '../lightingPreset.js';
|
|
16
|
-
import {
|
|
17
|
-
import { setAnisotropyCap, setSrgbTextureDecode } from '../TextureFactory.js';
|
|
16
|
+
import type { ColorPipeline } from '../StyleMaterial.js';
|
|
18
17
|
import type { LightOptions, S3MViewerOptions } from '../S3MViewer.js';
|
|
19
18
|
import type { StyleUniforms } from '../../core/types.js';
|
|
20
19
|
|
|
@@ -49,6 +48,14 @@ export interface LightRigContext {
|
|
|
49
48
|
getBearing?: () => number | undefined;
|
|
50
49
|
/** 宿主需要重画的信号 */
|
|
51
50
|
repaint: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* 应用图层级渲染 profile(颜色管线 + 各向异性)。
|
|
53
|
+
*
|
|
54
|
+
* `initial = true` 是构造期:那时还没有瓦片与材质,落地方只记状态即可。
|
|
55
|
+
* 换掉以前的 `setColorPipeline` / `setSrgbTextureDecode` / `setAnisotropyCap`
|
|
56
|
+
* 三个模块级全局调用 —— 它们会让同页多图层互相污染。
|
|
57
|
+
*/
|
|
58
|
+
applyRenderProfile: (p: { colorPipeline: ColorPipeline; anisotropyCap: number }, initial?: boolean) => void;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
export class LightRig {
|
|
@@ -74,7 +81,7 @@ export class LightRig {
|
|
|
74
81
|
const options = ctx.options;
|
|
75
82
|
if (options.lightingPreset && options.lightingPreset !== 'default') {
|
|
76
83
|
Object.assign(this._state, resolveLightingPreset(options.lightingPreset));
|
|
77
|
-
this.applyColorPipeline(options.lightingPreset);
|
|
84
|
+
this.applyColorPipeline(options.lightingPreset, true);
|
|
78
85
|
}
|
|
79
86
|
if (options.light) Object.assign(this._state, options.light);
|
|
80
87
|
}
|
|
@@ -155,15 +162,18 @@ export class LightRig {
|
|
|
155
162
|
/**
|
|
156
163
|
* supermap 完整版 = 光照强度 + 颜色管线一起切:
|
|
157
164
|
* direct 管线下纹理不做 sRGB 解码、片元按官方常数 Lambert 直乘(见 lightingPreset.ts)。
|
|
158
|
-
*
|
|
165
|
+
*
|
|
166
|
+
* 这三样(管线 / 解码 / 各向异性)以前都是模块级全局,同页两个图层用不同预设
|
|
167
|
+
* 会互相污染;现在交给 `ctx.applyRenderProfile`,由图层自己持有并透传给材质与纹理。
|
|
159
168
|
*/
|
|
160
|
-
applyColorPipeline(preset: LightingPreset): void {
|
|
169
|
+
applyColorPipeline(preset: LightingPreset, initial = false): void {
|
|
161
170
|
const direct = preset === 'supermap';
|
|
162
|
-
setColorPipeline(direct ? 'direct' : 'linear');
|
|
163
|
-
setSrgbTextureDecode(!direct);
|
|
164
171
|
// 官方不开各向异性:远处直接采高 mip 平均成纯色("灰色滤镜"的一部分)。
|
|
165
172
|
// 我们默认 Infinity(倾斜摄影立面清晰度优先),supermap 模式必须收回。
|
|
166
|
-
|
|
173
|
+
this.ctx.applyRenderProfile(
|
|
174
|
+
{ colorPipeline: direct ? 'direct' : 'linear', anisotropyCap: direct ? 1 : Infinity },
|
|
175
|
+
initial
|
|
176
|
+
);
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
setLightingPreset(preset: LightingPreset): void {
|
|
@@ -174,6 +174,18 @@ export interface S3MViewerOptions {
|
|
|
174
174
|
* `setMaterialQuality()` 原位重建。
|
|
175
175
|
*/
|
|
176
176
|
materialQuality?: 'pbr' | 'lambert';
|
|
177
|
+
/**
|
|
178
|
+
* 颜色管线(默认 `'linear'`)。
|
|
179
|
+
*
|
|
180
|
+
* - `'linear'`:纹理 sRGB 解码 → 线性 PBR 光照 → linear→sRGB 输出(three 标准姿势)
|
|
181
|
+
* - `'direct'`:官方 iClient3D 的 sRGB 直乘管线,同时把纹理解码关掉(字节值原样进
|
|
182
|
+
* shader)。`lightingPreset: 'supermap'` 会选它
|
|
183
|
+
*
|
|
184
|
+
* **图层级**:只定这一层,同页多图层各用各的;不传 = 跟随模块级默认。
|
|
185
|
+
* 已加载瓦片可以在运行时用 `setColorPipeline(q, { duration })` 平滑切过去
|
|
186
|
+
* (重建材质 + 重传纹理,不重新下载瓦片)。
|
|
187
|
+
*/
|
|
188
|
+
colorPipeline?: 'linear' | 'direct';
|
|
177
189
|
/** 自定义瓦片构建器(测试 / 服务端渲染时注入) */
|
|
178
190
|
buildTile?: BuildTileFn | null;
|
|
179
191
|
/**
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
} from '../core/S3MBlockParser.js';
|
|
33
33
|
import type { Anchor, ParsedTile, Stats, StyleUniforms } from '../core/types.js';
|
|
34
34
|
import type { TileGeometryData } from '../core/TileGeometry.js';
|
|
35
|
+
import type { TextureCreateOptions } from '../render/TextureFactory.js';
|
|
35
36
|
import type { Group, Texture } from 'three';
|
|
36
37
|
|
|
37
38
|
function dirOf(url: string): string {
|
|
@@ -248,6 +249,10 @@ export type BuildTileFn = (
|
|
|
248
249
|
polygonOffset?: { factor: number; units: number };
|
|
249
250
|
/** 光照模型档位(透传给 buildTileObjects),见 BuildTileOptions.materialQuality */
|
|
250
251
|
materialQuality?: 'pbr' | 'lambert';
|
|
252
|
+
/** 颜色管线(透传给 buildTileObjects),见 BuildTileOptions.colorPipeline */
|
|
253
|
+
colorPipeline?: 'linear' | 'direct';
|
|
254
|
+
/** 纹理创建参数(透传给 buildTileObjects → createTexture) */
|
|
255
|
+
textureOptions?: TextureCreateOptions;
|
|
251
256
|
}
|
|
252
257
|
) => Promise<{ group: Group; stats: Partial<Stats> }>;
|
|
253
258
|
|
|
@@ -265,6 +270,10 @@ export interface S3MTilesetOptions {
|
|
|
265
270
|
* 由 DatasetManager 从图层 / 数据集设置带下来。
|
|
266
271
|
*/
|
|
267
272
|
materialQuality?: 'pbr' | 'lambert';
|
|
273
|
+
/** 颜色管线,透传给 buildTileObjects。不传 = 读模块级默认。由图层的渲染 profile 带下来 */
|
|
274
|
+
colorPipeline?: 'linear' | 'direct';
|
|
275
|
+
/** 纹理创建参数(sRGB 解码 / 尺寸上限 / 各向异性上限),透传给 createTexture */
|
|
276
|
+
textureOptions?: TextureCreateOptions;
|
|
268
277
|
/**
|
|
269
278
|
* 是否用 Web Worker 解析经典 `.s3mb` 瓦片(默认开)。
|
|
270
279
|
*
|
|
@@ -479,6 +488,8 @@ export class S3MTileset {
|
|
|
479
488
|
private _buildTile: BuildTileFn | null;
|
|
480
489
|
private readonly _polygonOffset: { factor: number; units: number } | undefined;
|
|
481
490
|
private _materialQuality: 'pbr' | 'lambert' | undefined;
|
|
491
|
+
private _colorPipeline: 'linear' | 'direct' | undefined;
|
|
492
|
+
private _textureOptions: TextureCreateOptions | undefined;
|
|
482
493
|
private readonly _uniforms: StyleUniforms | null;
|
|
483
494
|
/** 经典 .s3mb 的 Worker 解析客户端;undefined = 尚未尝试创建,null = 不可用/已降级 */
|
|
484
495
|
private _workerClient: S3MWorkerClient | null | undefined; private readonly _useWorker: boolean;
|
|
@@ -500,6 +511,8 @@ export class S3MTileset {
|
|
|
500
511
|
this._buildTile = options.buildTile ?? null;
|
|
501
512
|
this._polygonOffset = options.polygonOffset;
|
|
502
513
|
this._materialQuality = options.materialQuality;
|
|
514
|
+
this._colorPipeline = options.colorPipeline;
|
|
515
|
+
this._textureOptions = options.textureOptions;
|
|
503
516
|
// 着色器共享 uniform,由 S3MLayer 持有并传进来
|
|
504
517
|
this._uniforms = options.uniforms ?? null;
|
|
505
518
|
this._useWorker = options.useWorker !== false;
|
|
@@ -740,6 +753,15 @@ export class S3MTileset {
|
|
|
740
753
|
this._materialQuality = q;
|
|
741
754
|
}
|
|
742
755
|
|
|
756
|
+
/**
|
|
757
|
+
* 运行时改本数据集的渲染 profile(颜色管线 / 纹理参数)。
|
|
758
|
+
* 只影响**之后**构建的瓦片材质与纹理;场上已有的由 `S3MViewer` 统一重建 / 重传。
|
|
759
|
+
*/
|
|
760
|
+
setRenderProfile(p: { colorPipeline?: 'linear' | 'direct'; textureOptions?: TextureCreateOptions }): void {
|
|
761
|
+
if (p.colorPipeline !== undefined) this._colorPipeline = p.colorPipeline;
|
|
762
|
+
if (p.textureOptions !== undefined) this._textureOptions = p.textureOptions;
|
|
763
|
+
}
|
|
764
|
+
|
|
743
765
|
/**
|
|
744
766
|
* 当前数据集驻留的显存与内存总字节数(几何缓冲精确大小 + 纹理显存估计)
|
|
745
767
|
*/
|
|
@@ -1264,6 +1286,8 @@ export class S3MTileset {
|
|
|
1264
1286
|
assembled,
|
|
1265
1287
|
polygonOffset: this._polygonOffset,
|
|
1266
1288
|
materialQuality: this._materialQuality,
|
|
1289
|
+
colorPipeline: this._colorPipeline,
|
|
1290
|
+
textureOptions: this._textureOptions,
|
|
1267
1291
|
});
|
|
1268
1292
|
const { group, stats } = buildRes;
|
|
1269
1293
|
const texKeys = (buildRes as any).textureKeys ?? (group.userData?.textureKeys as string[] | undefined) ?? Object.keys(tile.textures ?? {});
|