s3m-three 0.4.20 → 0.4.22
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 +30 -30
- package/dist/browser/s3m-three.iife.js +30 -30
- package/dist/render/DatasetManager.d.ts +20 -1
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +23 -1
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +8 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +25 -0
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +23 -6
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +53 -12
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +13 -1
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +26 -4
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/viewer/types.d.ts +10 -2
- 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 +44 -2
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +110 -4
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/docs/API.md +11 -30
- package/package.json +1 -1
- package/src/render/DatasetManager.ts +31 -1
- package/src/render/GeometryBuilder.ts +30 -0
- package/src/render/S3MViewer.ts +53 -12
- package/src/render/StyleMaterial.ts +32 -4
- package/src/render/viewer/types.ts +10 -2
- package/src/tileset/S3MTileset.ts +125 -5
package/docs/API.md
CHANGED
|
@@ -162,6 +162,7 @@ new S3MViewer(options?: S3MViewerOptions)
|
|
|
162
162
|
| `pickable` | `boolean` | `true` | 是否响应拾取 |
|
|
163
163
|
| `maxTiles` | `number` | `64` | 每个数据集驻留瓦片上限,超了淘汰最久没用的(不是停止加载) |
|
|
164
164
|
| `maxTriangles` | `number` | `Infinity` | 每个数据集**驻留三角形总数**的软上限(不限制)。`maxTiles` 数不了「一块精模几十万面」,这条按实际面数做刹车,走同一套 make-before-break 淘汰,不开洞 |
|
|
165
|
+
| `maxMemory` | `number` | `Infinity` | 每个数据集**驻留显存/内存字节数**的软上限(不限制)。包含几何缓冲精确字节与纹理显存估计,超限走同一套 make-before-break 淘汰,不开洞 |
|
|
165
166
|
| `totalMaxTiles` | `number` | — | 多数据集全局驻留瓦片总预算;开启后在可见数据集间动态均衡分配,防止叠加爆显存 |
|
|
166
167
|
| `light` | `LightOptions` | — | 场景光照配置,可配环境光与主平行光的颜色、强度、模式与方位角/高度角 |
|
|
167
168
|
| `maxConcurrent` | `number` | `4` | 每个数据集并发请求数 |
|
|
@@ -174,7 +175,7 @@ new S3MViewer(options?: S3MViewerOptions)
|
|
|
174
175
|
| `anisotropyCap` | `number` | `Infinity` | 各向异性过滤上限(不封顶 = 取 GPU 最大值,通常 16)。移动端封到 4 / 8 肉眼通常无差,采样成本明显下降。只影响之后创建的纹理 |
|
|
175
176
|
| `maxTextureSize` | `number` | `Infinity` | 纹理长边像素上限(不限制)。非图集的 RGBA / WebP 产物按 2× 盒式滤波减半到上限内;DXT 压缩直传与图集不受影响 |
|
|
176
177
|
| `maxImageCache` | `number` | `128` | 纹理缓存条数上限。一条 = 一张已解码的瓦片纹理,1024² RGBA 约 4MB,128 条即约 512MB 显存;低端设备 32~48。LRU,降下来下次淘汰时释放 |
|
|
177
|
-
| `materialQuality` | `'pbr' \| 'lambert'` | `'pbr'` | 光照模型。`lambert` 用 `MeshLambertMaterial` 替换 PBR,去掉 GGX 与环境球谐,片元约省一半;白模 / 纯色 /
|
|
178
|
+
| `materialQuality` | `'pbr' \| 'lambert'` | `'pbr'` | 光照模型。`lambert` 用 `MeshLambertMaterial` 替换 PBR,去掉 GGX 与环境球谐,片元约省一半;白模 / 纯色 / 按高度着色几乎无差。**图层级**:同页多个图层各用各的,互不覆盖;不传则跟随模块级默认(`setMaterialQuality`)。只影响之后创建的材质 |
|
|
178
179
|
| `buildTile` | `BuildTileFn \| null` | — | 自定义瓦片构建器(测试 / 服务端渲染时注入) |
|
|
179
180
|
| `attributes` | `AttributeQueryOptions` | — | 可选的 iServer 数据服务配置,配了才能用 `colorByField()` 等便利方法;不配就自己取数 + `setFeaturesStyle()`,见[业务字段](#业务字段锚点在库内对应关系在外部) |
|
|
180
181
|
| `onRequestRepaint` | `() => void` | — | 宿主请求重画的信号:纯 three 宿主接自己的渲染循环 |
|
|
@@ -397,6 +398,8 @@ setLodPixelRatioCap(cap: number): void // Infinity = 不截断(默认
|
|
|
397
398
|
readonly lodPixelRatioCap: number
|
|
398
399
|
setMaxTriangles(n: number, datasetId?: string): void // Infinity = 不限制(默认)
|
|
399
400
|
readonly maxTriangles: number
|
|
401
|
+
setMaxMemory(bytes: number, datasetId?: string): void // 显存字节上限,Infinity = 不限制(默认)
|
|
402
|
+
readonly maxMemory: number
|
|
400
403
|
setAnisotropyCap(cap: number): void // Infinity = 取 GPU 最大值(默认)
|
|
401
404
|
readonly anisotropyCap: number
|
|
402
405
|
setMaxTextureSize(n: number): void // Infinity = 不限制(默认)
|
|
@@ -1311,11 +1314,12 @@ buildAttributes({ smid, box, triangles, floorHeight }) // → S3MFeatureAttribu
|
|
|
1311
1314
|
| `totalMaxTiles` | 多数据集全局瓦片总预算(如 `96` 或 `128`),自动在可见数据集间均衡分配配额,隐藏数据集时自动回收预算,防止多图层叠加爆显存 |
|
|
1312
1315
|
| `maxTiles` | 单数据集瓦片驻留上限,默认 64。叠加多个数据集时建议配合 `totalMaxTiles` 或下调至 32~48 |
|
|
1313
1316
|
| `maxTriangles` | 单数据集驻留面数软上限,默认不限制。`maxTiles` 只数瓦片个数,精模场景(一块几十万面)建议配 60 万~120 万 |
|
|
1317
|
+
| `maxMemory` | 单数据集驻留显存/内存软上限(字节),默认不限制。精确统计几何缓冲与纹理显存,超限按 make-before-break 自动主动回收 |
|
|
1314
1318
|
| `lodPixelRatioCap` | LOD 判定用屏幕高度的 DPR 上限,默认不截断。见下方「移动端 / 差设备」 |
|
|
1315
1319
|
| `anisotropyCap` | 各向异性过滤上限,默认取 GPU 最大值。移动端 4 / 8 通常肉眼无差 |
|
|
1316
1320
|
| `maxTextureSize` | 纹理长边像素上限,默认不限制。手机上 1024 即可,显存降到 1/4 |
|
|
1317
1321
|
| `maxImageCache` | 纹理缓存条数,默认 128。低端机 32~48,和 `maxTextureSize` 一起管显存 |
|
|
1318
|
-
| `materialQuality: 'lambert'` | 换成 Lambert 光照,片元约省一半。白模 /
|
|
1322
|
+
| `materialQuality: 'lambert'` | 换成 Lambert 光照,片元约省一半。白模 / 按高度配色看不出差别,贴图模式会略平。图层级设置,同页其它图层不受影响 |
|
|
1319
1323
|
| `maxImageCache` | 原始图集与 leaf 段图像记录的 LRU 缓存容量上限(默认 128),超限自动淘汰旧缓存,防止漫游时内存不断累积 |
|
|
1320
1324
|
| 纹理引用计数 | 瓦片淘汰时按引用计数释放未被其他瓦片复用的 `Texture` 并调用 `dispose()`,真正归还 GPU 显存 |
|
|
1321
1325
|
| 几何/材质深度释放 | 瓦片注销时深度遍历对象树,彻底释放 `Mesh`、`Points`(点云/高斯泼溅)以及 `LineSegments`(棱线包)的 `BufferGeometry` 与材质 |
|
|
@@ -1395,34 +1399,11 @@ viewer.flushOverrides();
|
|
|
1395
1399
|
|
|
1396
1400
|
## 已知限制
|
|
1397
1401
|
|
|
1398
|
-
-
|
|
1399
|
-
-
|
|
1400
|
-
-
|
|
1401
|
-
-
|
|
1402
|
-
|
|
1403
|
-
- **解析与 UV 重映射**:解析端把顶点 UV 用 `fract` 平铺语义重映射进图集空间,多图集自动走货架式装箱合并(`AtlasPack`),下游材质直接绑定整张图集;
|
|
1404
|
-
- **细级 LOD(leaf)外部图集加载**:细级瓦片的图集未内嵌原始像素(`data = null`),由 `_fillExternalAtlases` 解析子请求名(`_subRequestNames`):
|
|
1405
|
-
- 支持展开 `Tex_x.texblock#<三位数字串>` 多精灵容器索引;
|
|
1406
|
-
- 对齐官方 `Uci` 规范的 pack 请求:URL 拼装 `extratiles=<names>` 批量合并拉取,避免高并发零碎请求;
|
|
1407
|
-
- 响应体通过 `parseTexblockSegments` 解码 `kMi` 分段记录,并通过 `leafRequestCache` 与 `_leafPending` 实现跨瓦片并发请求自动去重缓存;
|
|
1408
|
-
- **组合画布与装配**:对 leaf 模式,解出子精灵 RGBA 缓冲,按 `subTexInfos[i]` 的偏移量(`offsetX`、`offsetY`)以标准自上而下行序拷贝进高分辨率组合画布;
|
|
1409
|
-
- **安全判定与重拼**:外部图集接入前通过 `evaluateAtlasUpgrade` 进行长宽比与下采样色差校验,判定通过后由 `rebuildBlockTextureState` 重新生成拼图与重算归一化 `texRect`;
|
|
1410
|
-
- 实现位于 `core/S3MBlockParser.ts`、`core/AtlasPack.ts` 与 `tileset/S3MTileset.ts`,并通过 `tests/unit/s3mblock.test.ts`、`tests/unit/leaf-atlas.test.ts` 和 `tests/unit/leaf-uv-render.test.ts` 交叉验证钉住。
|
|
1411
|
-
- **多块 MeshOpt 骨架**(`blockCount > 1`)已支持。这类骨架里**每块后面紧跟自己的索引包**(不是所有块之后再统一放一份),索引是块内局部编号;解析时按块顺序合并顶点流,再把每块索引加上顶点基数偏移,对外仍是一份顶点缓冲 + 多组索引包。实测 `3D-WorkSpace_pbr1/pbrmodel` 的 `blockCount=2` / `3` 瓦片可正常加载。
|
|
1412
|
-
- **切线顶点流**只跳过不解码(渲染没有法线贴图通道),带 TANGENT 的数据不会被判失败。
|
|
1413
|
-
- **S3M 1.x(`.s3m`,SuperMapCache)已支持**。这类服务的 `config` 返回 `SuperMapCache` XML(`sml:FileType = OSGBFile`),位置与范围按米制盒反算(见「锚点」);瓦片是 `73 33 6D 00` 魔数 + zlib 的老容器,`parseS3MB` 认出魔数后交给 `core/S3M1Parser.ts`,输出同形状的 `ParsedTile`,下游渲染 / LOD / 拾取零改动。
|
|
1414
|
-
**注意:它的下一级 LOD 并不是 `.osgb`** —— XML 里 `RangeDataList` 写的 `..._L16_0.osgb` 只是切片时的原始文件名,服务实际发布的是同名换 `.s3m` 后缀,走 `datas/<数据源>/data/path/<瓦片目录>/<名>.s3m`(实测 L16/L17/L18 全部 200,`?sceneName=` 不参与寻址)。所以整棵 LOD 树都是同一种容器,**不需要 OSG Binary 解码器**;早先「因为子瓦是 .osgb 所以做不了」的结论是没验证就下的,已作废。
|
|
1415
|
-
已标定:`73 33 6D 00` 魔数 + zlib;解压后 `uint32 xmlSize + XML + 补齐 4 字节对齐(实测 2~3 个 0x30,不是固定标记)+ (uint32 size + data) × 3`,三段长度链在四块样本上全部精确闭合;几何流 = `uint32 meshCount` + 每块网格 `{ geoName, 7, 772, V, 0 }` + `V × 16B` 顶点 `(x, y, z, z)` + 数组区 + `uint32 1` + `uint32 materialId`(与 XML 材质号一致);纹理流 = `uint32 count` + 每条 `{ id, width, height, format, size, flag }` + `size` 字节(实测 `format=14`、0.5 B/px = DXT1,`28 + size` 精确等于段长);索引确认为 u16(值域最大恰为 `V-1`);几何里**没有法线数组**,接解码时要 `computeVertexNormals()`。
|
|
1416
|
-
接这种格式时有三个坑,都是实测出来的,别凭直觉:
|
|
1417
|
-
1. **几何里没有法线数组**(全段扫 `|v|≈1` 的最长游程只有 4~10 个),建完要 `computeVertexNormals()`;
|
|
1418
|
-
2. **头里的 `format` 不是 `S3MPixelFormat` 枚举** —— 实测四块瓦片都是 `14`,而这枚举里 14 = BGRA(4 B/px),照搬就会把 0.5 B/px 的 DXT1 当 4 字节读,满屏彩噪。必须按 `size / (width*height)` 反推(`pixelFormatOfBpp`);
|
|
1419
|
-
3. **UV 是「v = 0 对应文件首行」**,与 DXT 解码器内部那次 GL 方向的 Y 翻转相反。不抵消的话几何的 v(实测 0.483~0.983)会正好采到图集空的那半张 —— 区域均值亮度 5.6 对比 76.1,模型上就是一片黑块。所以 v1 纹理要置 `S3MTexture.flipY = true`。
|
|
1420
|
-
|
|
1421
|
-
子瓦地址规则见 `s3m1ChildUrl`:XML 的 `RangeDataList` 给的是切片时残留的 `.osgb` 名,实际发布是同名换 `.s3m` 并插在 `datas/<数据源>/data/path/<瓦片目录>/` 下;父瓦自己已经在 `data/path` 下时只能换文件名,再插一层就成了 `data/path/data/path/...` 全部 404(有回归测试钉住)。
|
|
1422
|
-
- **高斯泼溅(`3DGaussianSplatting`)只按点渲染**。加载、细化、出图整条链路是通的(这类瓦片没有索引包,顶点即点,`GeometryBuilder` 会补一个「顶点即点」的伪包走 `THREE.Points`;不补的话整块瓦片落到「无可用几何」被丢掉,这是之前"加载不出来"的第二个原因)。但画出来的是带顶色的点集,不是真正的泼溅:`gsScale` / `gsRotation` 两条自定义流的量化格式还没标定,而且颜色是 RGBA(A 通道是泼溅不透明度),现在的顶点色路径会丢掉 alpha。要上 EWA splatting 得先按深度层级取样把这两条流对准,再补协方差 + 深度排序的 alpha 混合。
|
|
1423
|
-
- **KTX2** 不在 S3M 的 `pixelFormat` 枚举里,iServer 也不会发;要支持需自行引入 `basis_transcoder.wasm` 并解析容器,属于「换数据源才需要考虑」的项。
|
|
1424
|
-
- **骨骼动画**解析了但未驱动。
|
|
1425
|
-
- 跨城市量级的数据集叠加会有 float32 精度问题。
|
|
1402
|
+
- **实例化单体(`instanceMode` 16 / 17)**:白模中较常见(`pickInfo` 里给的是 `instanceIds` 而不是 `batchId`)。目前支持正常网格渲染,单体级操作(`setFeatureStyle` / `filterByField` / `elevateByField`)与 GPU 单体拾取走的都是 `aBatchId`,对实例化几何暂不生效。
|
|
1403
|
+
- **切线(TANGENT)顶点流不参与法线贴图**:着色器目前未接入切线空间法线贴图通道,TANGENT 顶点流直接跳过不解码;带 TANGENT 的模型可正常着色渲染,不受影响。
|
|
1404
|
+
- **骨骼动画未挂接驱动**:S3M 数据中的骨骼动画元信息与 JSON 已解析,但目前未接入逐帧骨骼驱动。
|
|
1405
|
+
- **KTX2 纹理未内置**:S3M 官方 `pixelFormat` 枚举与主流 iServer 服务不包含 KTX2 / Basis 压缩纹理,当前未内置 `basis_transcoder.wasm` 转码。
|
|
1406
|
+
- **多数据集跨城市叠加精度**:跨城市广域(数十公里以上)多个不同中心的数据集叠加受 WebGL 单精度 float32 深度和位置精度限制,建议同场景数据集位于同一城市量级内(几公里内)。
|
|
1426
1407
|
|
|
1427
1408
|
---
|
|
1428
1409
|
|
package/package.json
CHANGED
|
@@ -24,6 +24,8 @@ export interface S3MDatasetOptions {
|
|
|
24
24
|
maxTiles?: number;
|
|
25
25
|
/** 该数据集驻留三角形总数的软上限(默认跟随图层级设置;都不传则不限制) */
|
|
26
26
|
maxTriangles?: number;
|
|
27
|
+
/** 该数据集驻留显存/内存字节数的软上限(默认跟随图层级设置;都不传则不限制) */
|
|
28
|
+
maxMemory?: number;
|
|
27
29
|
/** 该数据集的纹理缓存条数上限(默认跟随图层级设置,图层不传则 128) */
|
|
28
30
|
maxImageCache?: number;
|
|
29
31
|
lodRangeScale?: number;
|
|
@@ -41,6 +43,11 @@ export interface S3MDatasetOptions {
|
|
|
41
43
|
useWorker?: boolean;
|
|
42
44
|
/** 三角面材质深度偏移(共面摩尔纹 / 重合面归属),见 BuildTileOptions.polygonOffset */
|
|
43
45
|
polygonOffset?: { factor: number; units: number };
|
|
46
|
+
/**
|
|
47
|
+
* 本数据集的光照模型档位(不填则用图层级设置)。
|
|
48
|
+
* 只影响该数据集**之后创建**的材质。
|
|
49
|
+
*/
|
|
50
|
+
materialQuality?: 'pbr' | 'lambert';
|
|
44
51
|
/**
|
|
45
52
|
* 是否对该数据集生成几何棱线(CPU 端 EdgesGeometry)。
|
|
46
53
|
* - 不填:跟随全局 `style.edges`
|
|
@@ -91,6 +98,25 @@ export class DatasetManager {
|
|
|
91
98
|
readonly datasets = new Map<string, S3MDatasetEntry>();
|
|
92
99
|
sceneAnchor: { x: number; y: number; z: number; meterInMercator: number; altitude?: number } | null = null;
|
|
93
100
|
totalMaxTiles: number = 0;
|
|
101
|
+
/**
|
|
102
|
+
* 图层级默认光照模型档位(`undefined` = 跟随模块级默认)。
|
|
103
|
+
* 数据集自己指定了 `materialQuality` 就用自己的。
|
|
104
|
+
*/
|
|
105
|
+
defaultMaterialQuality: 'pbr' | 'lambert' | undefined = undefined;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 图层档位变更:同步给已存在的数据集(它们之后加载的瓦片才用新档位;
|
|
109
|
+
* 场上已有材质由 `S3MViewer._rebuildStyledMaterials` 原位重建)。
|
|
110
|
+
*
|
|
111
|
+
* 数据集自己指定过档位的不动 —— 那是它显式要的,不该被图层级设置覆盖。
|
|
112
|
+
*/
|
|
113
|
+
setDefaultMaterialQuality(q: 'pbr' | 'lambert' | undefined): void {
|
|
114
|
+
this.defaultMaterialQuality = q;
|
|
115
|
+
for (const entry of this.datasets.values()) {
|
|
116
|
+
if (entry.options.materialQuality) continue;
|
|
117
|
+
entry.tileset?.setMaterialQuality(q);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
94
120
|
|
|
95
121
|
get(id: string): S3MDatasetEntry | undefined {
|
|
96
122
|
return this.datasets.get(id);
|
|
@@ -185,7 +211,8 @@ export class DatasetManager {
|
|
|
185
211
|
callbacks: DatasetManagerCallbacks,
|
|
186
212
|
maxTriangles: number = Infinity,
|
|
187
213
|
maxImageCache: number = 128,
|
|
188
|
-
authenticate?: AuthenticateFn
|
|
214
|
+
authenticate?: AuthenticateFn,
|
|
215
|
+
maxMemory: number = Infinity
|
|
189
216
|
): Promise<S3MDatasetEntry> {
|
|
190
217
|
const existing = this.datasets.get(ds.id);
|
|
191
218
|
if (existing) return existing;
|
|
@@ -216,10 +243,13 @@ export class DatasetManager {
|
|
|
216
243
|
maxTiles: ds.maxTiles ?? maxTiles,
|
|
217
244
|
maxConcurrent: maxConcurrent,
|
|
218
245
|
maxTriangles: ds.maxTriangles ?? maxTriangles,
|
|
246
|
+
maxMemory: ds.maxMemory ?? maxMemory,
|
|
219
247
|
maxImageCache: ds.maxImageCache ?? maxImageCache,
|
|
220
248
|
lodRangeScale: ds.lodRangeScale ?? defaultLodRangeScale,
|
|
221
249
|
buildTile: buildTile ?? null,
|
|
222
250
|
polygonOffset: ds.polygonOffset,
|
|
251
|
+
// 数据集 > 图层 > 模块级默认(undefined 一路传下去,由 createStyledMaterial 回落)
|
|
252
|
+
materialQuality: ds.materialQuality ?? this.defaultMaterialQuality,
|
|
223
253
|
useWorker: ds.useWorker,
|
|
224
254
|
authenticate: ds.authenticate ?? authenticate,
|
|
225
255
|
unsupported: ds.unsupported,
|
|
@@ -323,6 +323,7 @@ export interface TileBuildStats {
|
|
|
323
323
|
/** 该瓦片的高斯实例数。泼溅瓦片才有值,LOD 判据用它算屏幕覆盖率 */
|
|
324
324
|
splatCount?: number;
|
|
325
325
|
/** 被跳过的东西,加载完可以直接在控制台看 */
|
|
326
|
+
memory?: number;
|
|
326
327
|
skipped: string[];
|
|
327
328
|
}
|
|
328
329
|
|
|
@@ -359,6 +360,13 @@ export interface BuildTileOptions {
|
|
|
359
360
|
* 偏移量随斜率/距离缩放,量级在微米级,正常相邻面不受影响。
|
|
360
361
|
*/
|
|
361
362
|
polygonOffset?: { factor: number; units: number };
|
|
363
|
+
/**
|
|
364
|
+
* 光照模型档位,透传给 `createStyledMaterial`。
|
|
365
|
+
*
|
|
366
|
+
* 不传 = 读模块级默认(`getMaterialQuality()`),与旧行为一致。
|
|
367
|
+
* 由 `S3MTileset` 从图层 / 数据集一路带下来,所以同页多个图层可以各用各的。
|
|
368
|
+
*/
|
|
369
|
+
materialQuality?: 'pbr' | 'lambert';
|
|
362
370
|
}
|
|
363
371
|
|
|
364
372
|
export interface BuildTileResult {
|
|
@@ -540,6 +548,8 @@ export async function buildTileObjects(
|
|
|
540
548
|
// uv 第三分量(custom0):几何带了这条流,材质才走官方的「按 z 决定采样」分支
|
|
541
549
|
texCoordZ: hasTexCoordZ && !!map,
|
|
542
550
|
secMap,
|
|
551
|
+
// 图层 / 数据集的档位;undefined 时 createStyledMaterial 自己回落到模块级默认
|
|
552
|
+
materialQuality: options.materialQuality,
|
|
543
553
|
});
|
|
544
554
|
// 用户配置的深度偏移(共面摩尔纹 / 重合面归属的人工定序,见 BuildTileOptions 注释)
|
|
545
555
|
if (options.polygonOffset) {
|
|
@@ -769,6 +779,26 @@ export async function buildTileObjects(
|
|
|
769
779
|
|
|
770
780
|
if (!group.children.length) stats.skipped.push('tile 无可用几何');
|
|
771
781
|
|
|
782
|
+
let totalGeoBytes = 0;
|
|
783
|
+
for (const obj of objects) {
|
|
784
|
+
const mesh = obj as THREE.Mesh;
|
|
785
|
+
const geo = mesh.geometry;
|
|
786
|
+
if (geo) {
|
|
787
|
+
if (geo.attributes) {
|
|
788
|
+
for (const attr of Object.values(geo.attributes)) {
|
|
789
|
+
if (attr && attr.array) totalGeoBytes += attr.array.byteLength;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (geo.index && geo.index.array) totalGeoBytes += geo.index.array.byteLength;
|
|
793
|
+
}
|
|
794
|
+
if ((mesh as THREE.InstancedMesh).isInstancedMesh) {
|
|
795
|
+
const inst = mesh as THREE.InstancedMesh;
|
|
796
|
+
if (inst.instanceMatrix && inst.instanceMatrix.array) totalGeoBytes += inst.instanceMatrix.array.byteLength;
|
|
797
|
+
if (inst.instanceColor && inst.instanceColor.array) totalGeoBytes += inst.instanceColor.array.byteLength;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
stats.memory = totalGeoBytes;
|
|
801
|
+
|
|
772
802
|
group.userData.textureKeys = usedTextureKeys;
|
|
773
803
|
return { group, stats, objects, textureKeys: usedTextureKeys };
|
|
774
804
|
}
|
package/src/render/S3MViewer.ts
CHANGED
|
@@ -26,7 +26,7 @@ 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
28
|
import { setMaxAnisotropy, setCompressedTexturesSupported, setTextureOffThreadDecode, setMaxTextureSize as setTextureSizeCap } from './TextureFactory.js';
|
|
29
|
-
import { getMaterialQuality,
|
|
29
|
+
import { getMaterialQuality, normalizeMaterialQuality } from './StyleMaterial.js';
|
|
30
30
|
import type { LightingPreset } from './lightingPreset.js';
|
|
31
31
|
import { LightRig } from './viewer/LightRig.js';
|
|
32
32
|
import { HeightIndex } from './viewer/HeightIndex.js';
|
|
@@ -249,6 +249,14 @@ export class S3MViewer {
|
|
|
249
249
|
readonly groundManager: GroundManager;
|
|
250
250
|
/** 多数据集生命周期与锚点管理器 */
|
|
251
251
|
private readonly _datasetManager = new DatasetManager();
|
|
252
|
+
/**
|
|
253
|
+
* 本图层的光照模型档位。
|
|
254
|
+
*
|
|
255
|
+
* `undefined` = 没指定,跟随模块级默认(`getMaterialQuality()`)—— 老代码
|
|
256
|
+
* `setMaterialQuality('lambert')` 一行改全场的用法靠这条继续有效。
|
|
257
|
+
* 传了值就只认自己,多图层同页各用各的,互不覆盖。
|
|
258
|
+
*/
|
|
259
|
+
private _materialQuality: MaterialQuality | undefined;
|
|
252
260
|
/** 单体样式、专题着色与状态纹理管理器 */
|
|
253
261
|
private readonly _featureStyleManager: FeatureStyleManager;
|
|
254
262
|
/** GPU 路径动画管理器 */
|
|
@@ -352,7 +360,11 @@ export class S3MViewer {
|
|
|
352
360
|
this._unsupported = options.unsupported ?? 'warn';
|
|
353
361
|
this._lodPixelRatioCap = options.lodPixelRatioCap ?? Infinity;
|
|
354
362
|
setTextureSizeCap(options.maxTextureSize ?? Infinity);
|
|
355
|
-
setMaterialQuality(
|
|
363
|
+
// 只记本层的档位,**不写模块级全局**:以前这里 setMaterialQuality(...) 会把全局
|
|
364
|
+
// 改掉,于是同页后创建的图层顺手把先创建的图层也打回去了(多层互相覆盖的根因)。
|
|
365
|
+
this._materialQuality = normalizeMaterialQuality(options.materialQuality);
|
|
366
|
+
// 之后 addDataset 的数据集按这个默认建材质(数据集自己指定则用自己的)
|
|
367
|
+
this._datasetManager.defaultMaterialQuality = this._materialQuality;
|
|
356
368
|
if (options.totalMaxTiles) {
|
|
357
369
|
this._datasetManager.totalMaxTiles = options.totalMaxTiles;
|
|
358
370
|
}
|
|
@@ -717,7 +729,8 @@ export class S3MViewer {
|
|
|
717
729
|
},
|
|
718
730
|
this.options.maxTriangles ?? Infinity,
|
|
719
731
|
this.options.maxImageCache ?? 128,
|
|
720
|
-
this._authenticate
|
|
732
|
+
this._authenticate,
|
|
733
|
+
this.options.maxMemory ?? Infinity
|
|
721
734
|
);
|
|
722
735
|
return entry;
|
|
723
736
|
}
|
|
@@ -2081,22 +2094,25 @@ export class S3MViewer {
|
|
|
2081
2094
|
}
|
|
2082
2095
|
|
|
2083
2096
|
/**
|
|
2084
|
-
*
|
|
2097
|
+
* 运行时切换**本图层**的材质质感(`'pbr'` | `'lambert'`)。
|
|
2085
2098
|
*
|
|
2086
|
-
*
|
|
2087
|
-
*
|
|
2088
|
-
*
|
|
2089
|
-
*
|
|
2099
|
+
* 档位是图层级的:只改这一层,同页其它图层不受影响(也不会去动模块级默认)。
|
|
2100
|
+
* 已加载瓦片的材质在这里**原位重建**:不重拉瓦片、不重建几何,按创建时记录的
|
|
2101
|
+
* 参数(`userData.s3mOpts`)换一个新材质实例,并保留逐材质状态(选中编号、
|
|
2102
|
+
* 瓦片淡入、批量状态纹理)。切换后 program 需要重新编译一次(lambert / pbr 是
|
|
2103
|
+
* 不同着色器),这是唯一开销。
|
|
2090
2104
|
*/
|
|
2091
2105
|
setMaterialQuality(q: MaterialQuality): void {
|
|
2092
|
-
|
|
2106
|
+
this._materialQuality = normalizeMaterialQuality(q) ?? 'pbr';
|
|
2107
|
+
// 已建好的数据集也要跟着换,否则之后新加载的瓦片还是旧档位(和重建结果打架)
|
|
2108
|
+
this._datasetManager.setDefaultMaterialQuality(this._materialQuality);
|
|
2093
2109
|
this._rebuildStyledMaterials();
|
|
2094
2110
|
this._requestRepaint();
|
|
2095
2111
|
}
|
|
2096
2112
|
|
|
2097
|
-
/**
|
|
2113
|
+
/** 当前材质质感档位(本图层指定了就用本图层的,否则是模块级默认) */
|
|
2098
2114
|
get materialQuality(): MaterialQuality {
|
|
2099
|
-
return getMaterialQuality();
|
|
2115
|
+
return this._materialQuality ?? getMaterialQuality();
|
|
2100
2116
|
}
|
|
2101
2117
|
|
|
2102
2118
|
/** 按创建参数原位重建所有 styled 材质(materialQuality 切换用) */
|
|
@@ -2118,7 +2134,9 @@ export class S3MViewer {
|
|
|
2118
2134
|
const old = raw as StyledMaterial | undefined;
|
|
2119
2135
|
if (!old?.userData?.isStyled || !old.userData?.s3mOpts) continue;
|
|
2120
2136
|
const opts = old.userData.s3mOpts as StyledMaterialOptions;
|
|
2121
|
-
|
|
2137
|
+
// 显式传本层档位:s3mOpts 里记的是创建时的参数,可能是 undefined(跟随全局),
|
|
2138
|
+
// 重建时必须钉成本层当前档位,否则「别处改了全局」会把这一层带跑。
|
|
2139
|
+
const fresh = createStyledMaterial({ ...opts, uniforms: this.uniforms, materialQuality: this.materialQuality });
|
|
2122
2140
|
for (const key of PRESERVED) {
|
|
2123
2141
|
const src = old.userData[key] as { value: unknown } | undefined;
|
|
2124
2142
|
const dst = fresh.userData[key] as { value: unknown } | undefined;
|
|
@@ -2461,6 +2479,29 @@ export class S3MViewer {
|
|
|
2461
2479
|
}
|
|
2462
2480
|
}
|
|
2463
2481
|
|
|
2482
|
+
/** 当前每个数据集的驻留显存/内存字节预算。Infinity = 不限制 */
|
|
2483
|
+
get maxMemory(): number {
|
|
2484
|
+
return this.options.maxMemory ?? Infinity;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
/**
|
|
2488
|
+
* 调整每个数据集驻留显存字节数的软上限(默认 Infinity = 不限制)。
|
|
2489
|
+
* 不传 `datasetId` 时对所有已存在数据集立即生效,并作为之后新增数据集的默认值。
|
|
2490
|
+
* 实际裁剪发生在每帧调度末尾(make-before-break),不会当场开洞。
|
|
2491
|
+
*/
|
|
2492
|
+
setMaxMemory(bytes: number, datasetId?: string): void {
|
|
2493
|
+
const next = Number.isFinite(bytes) && bytes > 0 ? bytes : Infinity;
|
|
2494
|
+
if (datasetId) {
|
|
2495
|
+
const entry = this.datasets.get(datasetId);
|
|
2496
|
+
if (entry?.tileset) (entry.tileset as any).setMaxMemory(next);
|
|
2497
|
+
} else {
|
|
2498
|
+
this.options.maxMemory = next;
|
|
2499
|
+
for (const entry of this.datasets.values()) {
|
|
2500
|
+
if (entry.tileset) (entry.tileset as any).setMaxMemory(next);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2464
2505
|
/** 当前各向异性过滤上限。Infinity = 不封顶 */
|
|
2465
2506
|
get anisotropyCap(): number {
|
|
2466
2507
|
return this.options.anisotropyCap ?? Infinity;
|
|
@@ -272,13 +272,29 @@ export const DEFAULT_STYLE: S3MStyle = {
|
|
|
272
272
|
export type MaterialQuality = 'pbr' | 'lambert';
|
|
273
273
|
|
|
274
274
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
275
|
+
* 模块级默认档位。**只是默认值**,不是全局开关:
|
|
276
|
+
* - `S3MViewer` / `S3MLayer` 构造时传了 `materialQuality` 的,只定自己那一层,
|
|
277
|
+
* 不读也不写这里(多图层同页互不干扰);
|
|
278
|
+
* - 没传的图层、以及直接调 `createStyledMaterial` / `buildTileObjects` 的场景,
|
|
279
|
+
* 仍然读这里 —— 老代码 `setMaterialQuality('lambert')` 一行改全场的用法照旧有效。
|
|
280
|
+
*
|
|
281
|
+
* 只影响**之后创建**的材质;已加载的瓦片要重载或在 viewer 上调
|
|
282
|
+
* `setMaterialQuality` 才会换。
|
|
277
283
|
*/
|
|
278
284
|
let materialQuality: MaterialQuality = 'pbr';
|
|
279
285
|
|
|
280
286
|
/**
|
|
281
|
-
*
|
|
287
|
+
* 归一档位:只认 'pbr' / 'lambert',其余(含 undefined)返回 undefined,
|
|
288
|
+
* 表示「没指定,去读模块级默认」。
|
|
289
|
+
*/
|
|
290
|
+
export function normalizeMaterialQuality(q: unknown): MaterialQuality | undefined {
|
|
291
|
+
if (q === 'lambert') return 'lambert';
|
|
292
|
+
if (q === 'pbr') return 'pbr';
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 设置模块级默认档位(不是全局开关,见上面的 `materialQuality` 注释)。
|
|
282
298
|
*
|
|
283
299
|
* 'pbr'(默认)= `MeshStandardMaterial`,GGX + 环境球谐,质感最好也最贵。
|
|
284
300
|
* 'lambert' = `MeshLambertMaterial`,只保留漫反射 + 平行光,片元开销约省一半;
|
|
@@ -1105,6 +1121,13 @@ export interface StyledMaterialOptions {
|
|
|
1105
1121
|
texCoordZ?: boolean;
|
|
1106
1122
|
/** 图集材质的第二套纹理(官方 BakeTex,光照烘焙),与 base 相乘 */
|
|
1107
1123
|
secMap?: THREE.Texture | null;
|
|
1124
|
+
/**
|
|
1125
|
+
* 光照模型档位。不传 = 读模块级默认(`getMaterialQuality()`),行为与旧版一致。
|
|
1126
|
+
*
|
|
1127
|
+
* 传了就按传的建 —— 图层级隔离靠这条:`S3MLayer` / `S3MViewer` 把自己的档位
|
|
1128
|
+
* 一路透传到这里,不再让所有图层共享同一份模块级状态。
|
|
1129
|
+
*/
|
|
1130
|
+
materialQuality?: MaterialQuality;
|
|
1108
1131
|
}
|
|
1109
1132
|
|
|
1110
1133
|
/**
|
|
@@ -1152,6 +1175,9 @@ const FRAG_SURFACE = /* glsl */ `
|
|
|
1152
1175
|
|
|
1153
1176
|
export function createStyledMaterial(opts: StyledMaterialOptions): StyledMaterial {
|
|
1154
1177
|
const uniforms = opts.uniforms;
|
|
1178
|
+
// 档位解析:显式传参 > 模块级默认。图层级隔离就靠这一行 —— 各图层把自己的
|
|
1179
|
+
// 档位透传进来,不再共享同一份模块级状态。
|
|
1180
|
+
const quality: MaterialQuality = normalizeMaterialQuality(opts.materialQuality) ?? getMaterialQuality();
|
|
1155
1181
|
// 两种光照模型共用的参数。注入的 chunk 只动 diffuseColor / normal / transformed,
|
|
1156
1182
|
// 在 Standard 与 Lambert 的 shader 里都存在同名 include,所以模板不用分支。
|
|
1157
1183
|
const baseParams = {
|
|
@@ -1170,7 +1196,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
1170
1196
|
alphaTest: opts.alphaTest ?? 0,
|
|
1171
1197
|
};
|
|
1172
1198
|
const mat = (
|
|
1173
|
-
|
|
1199
|
+
quality === 'lambert'
|
|
1174
1200
|
? new THREE.MeshLambertMaterial(baseParams)
|
|
1175
1201
|
: new THREE.MeshStandardMaterial({
|
|
1176
1202
|
...baseParams,
|
|
@@ -1311,6 +1337,8 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
1311
1337
|
mat.userData.uFlatShading = uFlatShading;
|
|
1312
1338
|
// 记录创建参数:materialQuality 运行时切换时按原参数重建材质(换类不换外观)
|
|
1313
1339
|
mat.userData.s3mOpts = opts;
|
|
1340
|
+
// 记录实际生效的档位:重建 / 排查时不用再猜当时读的是哪一层设置
|
|
1341
|
+
mat.userData.s3mQuality = quality;
|
|
1314
1342
|
mat.userData.isStyled = true;
|
|
1315
1343
|
return mat;
|
|
1316
1344
|
}
|
|
@@ -138,6 +138,12 @@ export interface S3MViewerOptions {
|
|
|
138
138
|
* 见 `S3MTilesetOptions.maxTriangles`:瓦片数管不住的场景(精模、大面片)用这条刹车。
|
|
139
139
|
*/
|
|
140
140
|
maxTriangles?: number;
|
|
141
|
+
/**
|
|
142
|
+
* 每个数据集驻留显存/内存字节总数的软上限(默认 Infinity = 不限制)。
|
|
143
|
+
* 包含几何顶点缓冲(精确字节)与纹理显存估计。
|
|
144
|
+
* 超限时在每帧末尾(make-before-break)主动回收非显示的瓦片及其独占纹理。
|
|
145
|
+
*/
|
|
146
|
+
maxMemory?: number;
|
|
141
147
|
/**
|
|
142
148
|
* 纹理缓存条数上限(默认 128)。一条对应一张已解码的瓦片纹理,
|
|
143
149
|
* 1024² 的 RGBA 一张约 4MB,128 条就是 512MB 显存 —— 低端设备建议 32~48。
|
|
@@ -162,8 +168,10 @@ export interface S3MViewerOptions {
|
|
|
162
168
|
* - `'lambert'`:`MeshLambertMaterial`,只保留漫反射 + 平行光,片元开销约省一半;
|
|
163
169
|
* 白模 / 纯色 / 按高度着色这类不需要高光的模式肉眼差别很小
|
|
164
170
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
171
|
+
* **图层级**:只定这一层,同页多个图层各用各的,互不覆盖;构造时不会写模块级
|
|
172
|
+
* 默认(`setMaterialQuality`)。不传 = 跟随模块级默认,老用法照旧有效。
|
|
173
|
+
* 只影响**之后创建**的材质 —— 已加载的瓦片要重载,或在图层上调
|
|
174
|
+
* `setMaterialQuality()` 原位重建。
|
|
167
175
|
*/
|
|
168
176
|
materialQuality?: 'pbr' | 'lambert';
|
|
169
177
|
/** 自定义瓦片构建器(测试 / 服务端渲染时注入) */
|