s3m-three 0.4.15 → 0.4.17
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 +118 -24
- package/dist/browser/s3m-three.iife.js +118 -24
- package/dist/core/types.d.ts +8 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/render/EdgeLines.d.ts +14 -2
- package/dist/render/EdgeLines.d.ts.map +1 -1
- package/dist/render/EdgeLines.js +45 -8
- package/dist/render/EdgeLines.js.map +1 -1
- package/dist/render/GpuEdgePass.d.ts +58 -0
- package/dist/render/GpuEdgePass.d.ts.map +1 -0
- package/dist/render/GpuEdgePass.js +207 -0
- package/dist/render/GpuEdgePass.js.map +1 -0
- package/dist/render/S3MViewer.d.ts +66 -14
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +327 -87
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +98 -7
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +107 -9
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +15 -1
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/docs/API.md +77 -27
- package/package.json +1 -1
- package/src/core/types.ts +8 -0
- package/src/index.ts +4 -0
- package/src/render/EdgeLines.ts +50 -8
- package/src/render/GpuEdgePass.ts +236 -0
- package/src/render/S3MViewer.ts +352 -84
- package/src/render/StyleMaterial.ts +193 -11
- package/src/tileset/S3MTileset.ts +15 -1
package/src/render/S3MViewer.ts
CHANGED
|
@@ -24,7 +24,7 @@ import * as THREE from 'three';
|
|
|
24
24
|
import { S3MTileset, usableGeoBounds } from '../tileset/S3MTileset.js';
|
|
25
25
|
import type { BuildTileFn, TileNode } from '../tileset/S3MTileset.js';
|
|
26
26
|
import { setMaxAnisotropy, setCompressedTexturesSupported, setTextureOffThreadDecode, setMaxTextureSize as setTextureSizeCap } from './TextureFactory.js';
|
|
27
|
-
import { setMaterialQuality } from './StyleMaterial.js';
|
|
27
|
+
import { getMaterialQuality, setMaterialQuality } from './StyleMaterial.js';
|
|
28
28
|
import { decodeTextureWithWorker } from '../worker/S3MWorkerClient.js';
|
|
29
29
|
import {
|
|
30
30
|
STYLE,
|
|
@@ -32,11 +32,26 @@ import {
|
|
|
32
32
|
SELECT_MODE,
|
|
33
33
|
DEFAULT_STYLE,
|
|
34
34
|
createSharedUniforms,
|
|
35
|
+
createStyledMaterial,
|
|
35
36
|
createOutlineMaterial,
|
|
37
|
+
resolveEdgeModes,
|
|
38
|
+
sameEdgeModes,
|
|
39
|
+
} from './StyleMaterial.js';
|
|
40
|
+
import type {
|
|
41
|
+
OutlineMaterial,
|
|
42
|
+
OutlineStyleUniforms,
|
|
43
|
+
StyledMaterial,
|
|
44
|
+
StyledMaterialOptions,
|
|
45
|
+
StyleMode,
|
|
46
|
+
SelectMode,
|
|
47
|
+
S3MStyle,
|
|
48
|
+
EdgeModes,
|
|
49
|
+
EdgesOption,
|
|
50
|
+
MaterialQuality,
|
|
36
51
|
} from './StyleMaterial.js';
|
|
37
|
-
import type { OutlineMaterial, StyledMaterial, StyleMode, SelectMode, S3MStyle } from './StyleMaterial.js';
|
|
38
52
|
import { applyPointCloudStyle, isOwnedPointCloudMaterial } from './PointCloudMaterial.js';
|
|
39
53
|
import { Picker } from './Picker.js';
|
|
54
|
+
import { GpuEdgePass } from './GpuEdgePass.js';
|
|
40
55
|
import { Animator } from '../anim/Animator.js';
|
|
41
56
|
import type { Channel, TransitionOptions } from '../anim/Animator.js';
|
|
42
57
|
import { Path } from '../anim/Path.js';
|
|
@@ -308,12 +323,18 @@ interface MeshUserData {
|
|
|
308
323
|
isPointCloud?: boolean;
|
|
309
324
|
/** CPU 端现算的棱线对象(等宽线 Mesh) */
|
|
310
325
|
edges?: THREE.Object3D | null;
|
|
326
|
+
/** 背面外扩轮廓壳(edges 含 'hull' 时挂上) */
|
|
327
|
+
edgeHull?: THREE.Mesh | null;
|
|
311
328
|
/** 数据自带棱线包(此时不再用 EdgesGeometry 现算) */
|
|
312
329
|
nativeEdges?: boolean;
|
|
330
|
+
/** 数据本身有没有棱线包(不受 forceCpuEdges 影响,用来运行中切换时重算 nativeEdges) */
|
|
331
|
+
nativeEdgesRaw?: boolean;
|
|
313
332
|
/** 数据自带的棱线对象本身 */
|
|
314
333
|
isEdgeLines?: boolean;
|
|
315
334
|
/** CPU 现算的等宽棱线(与 isEdgeLines 区分,便于刷新样式) */
|
|
316
335
|
isThickEdge?: boolean;
|
|
336
|
+
/** 背面外扩轮廓壳(edges 含 'hull' 时挂上) */
|
|
337
|
+
isEdgeHull?: boolean;
|
|
317
338
|
/** 选中单体的棱线对象 */
|
|
318
339
|
isSelectionEdge?: boolean;
|
|
319
340
|
/** 该网格正在淡入时占用的动画通道 key,瓦片淘汰时要回收 */
|
|
@@ -368,6 +389,8 @@ const STYLE_SCALAR_KEYS = [
|
|
|
368
389
|
'ambientGain',
|
|
369
390
|
'edgeWidth',
|
|
370
391
|
'edgeOpacity',
|
|
392
|
+
'edgeOffset',
|
|
393
|
+
'edgeHullWidth',
|
|
371
394
|
'outlineWidth',
|
|
372
395
|
] as const;
|
|
373
396
|
|
|
@@ -432,6 +455,8 @@ export class S3MViewer {
|
|
|
432
455
|
private readonly _edgeStyleUniforms: ThickLineStyleUniforms;
|
|
433
456
|
/** 选中棱线的外观 uniform */
|
|
434
457
|
private readonly _selEdgeStyleUniforms: ThickLineStyleUniforms;
|
|
458
|
+
/** 全局外扩轮廓的外观 uniform:逐网格轮廓壳共享这些对象 */
|
|
459
|
+
private readonly _hullStyleUniforms: OutlineStyleUniforms;
|
|
435
460
|
|
|
436
461
|
/** 3D 物理地表面 / 基坑开挖管理器 */
|
|
437
462
|
readonly groundManager: GroundManager;
|
|
@@ -473,6 +498,12 @@ export class S3MViewer {
|
|
|
473
498
|
private _selectionEdges: THREE.Mesh | null = null;
|
|
474
499
|
private _selectionEdgeMeshes: THREE.Mesh[] = [];
|
|
475
500
|
private _edgesEnabled = false;
|
|
501
|
+
/** 当前生效的棱线画法(line / offset / hull / gpu),决定要不要建 / 拆哪种装饰 */
|
|
502
|
+
private _edgeModes: EdgeModes = resolveEdgeModes(false);
|
|
503
|
+
/** GPU 描边后处理(深度 + 法线),只在 edges 含 'gpu' 时按帧执行 */
|
|
504
|
+
private _gpuEdge: GpuEdgePass | null = null;
|
|
505
|
+
/** 给 GPU 描边传色用的临时 Color,避免每帧 new */
|
|
506
|
+
private readonly _gpuEdgeColor = new THREE.Color();
|
|
476
507
|
/** 瓦片淡入通道的自增序号(不用 pickId:pickId 会被回收复用) */
|
|
477
508
|
private _tileFadeSeq = 0;
|
|
478
509
|
/** setMode 编排的代次,用来作废被新调用顶掉的旧编排 */
|
|
@@ -612,6 +643,7 @@ export class S3MViewer {
|
|
|
612
643
|
uColor: { value: new THREE.Color(this.style.edgeColor) },
|
|
613
644
|
uWidth: { value: Math.max(0.1, this.style.edgeWidth ?? 1.5) },
|
|
614
645
|
uOpacity: { value: 0.6 },
|
|
646
|
+
uOffset: { value: this.style.edgeOffset ?? 0.15 },
|
|
615
647
|
};
|
|
616
648
|
this.edgeMaterial = createThickLineMaterial(this.lineShared, {}, this._edgeStyleUniforms);
|
|
617
649
|
this._selEdgeStyleUniforms = {
|
|
@@ -621,11 +653,17 @@ export class S3MViewer {
|
|
|
621
653
|
uElevation: { value: this.style.selectElevation ?? 0 },
|
|
622
654
|
};
|
|
623
655
|
this.selectionEdgeMaterial = createThickLineMaterial(this.lineShared, {}, this._selEdgeStyleUniforms);
|
|
656
|
+
// 全局外扩轮廓的外观 uniform:逐网格轮廓壳复用这几个对象,改一次全场景生效
|
|
657
|
+
this._hullStyleUniforms = {
|
|
658
|
+
uColor: { value: new THREE.Color(this.style.edgeColor) },
|
|
659
|
+
uWidth: { value: Math.max(0, this.style.edgeHullWidth ?? 0.9) },
|
|
660
|
+
uOpacity: { value: 0.6 },
|
|
661
|
+
};
|
|
624
662
|
|
|
625
663
|
// 关键:style.edges 是个开关,但构造函数从没据此启用过棱线,导致
|
|
626
664
|
// `style: { edges: true }` 形同虚设、_edgesEnabled 永远是 false。
|
|
627
665
|
// 这里按 style 实际值调一次 setEdges,让开关真正生效(与 setEdges 后来的手工调用一致)。
|
|
628
|
-
this.setEdges(
|
|
666
|
+
this.setEdges(this.style.edges);
|
|
629
667
|
|
|
630
668
|
// 初始化图层整体不透明度:确保共享 uniform uOpacity、材质混合开关与棱线外观与初始 style.opacity 严格一致
|
|
631
669
|
this._applyOpacity();
|
|
@@ -712,6 +750,9 @@ export class S3MViewer {
|
|
|
712
750
|
this._syncTextureCapabilities(gl);
|
|
713
751
|
this.picker?.dispose();
|
|
714
752
|
this.picker = new Picker(this.renderer);
|
|
753
|
+
// GPU 描边的 RT / 材质绑定在旧上下文上,整体丢弃,下一帧按需重建
|
|
754
|
+
this._gpuEdge?.dispose();
|
|
755
|
+
this._gpuEdge = null;
|
|
715
756
|
// 共享 uniform / 材质引用不变,three 重新编译着色器时会自然接管;
|
|
716
757
|
// 这里只确保下一帧尽快驱动一次渲染。
|
|
717
758
|
this._requestRepaint();
|
|
@@ -725,6 +766,8 @@ export class S3MViewer {
|
|
|
725
766
|
this.picker?.dispose();
|
|
726
767
|
this.picker = null;
|
|
727
768
|
this.outlineMaterial.dispose();
|
|
769
|
+
this._gpuEdge?.dispose();
|
|
770
|
+
this._gpuEdge = null;
|
|
728
771
|
this.edgeMaterial.dispose();
|
|
729
772
|
this.selectionEdgeMaterial.dispose();
|
|
730
773
|
this.scene?.clear();
|
|
@@ -1769,12 +1812,18 @@ export class S3MViewer {
|
|
|
1769
1812
|
node.group.updateMatrixWorld(true);
|
|
1770
1813
|
|
|
1771
1814
|
const hasNativeEdges = node.group.userData.hasNativeEdges === true;
|
|
1815
|
+
// forceCpuEdges:无视数据自带的棱线包,改由 CPU 现算。native 是 1px 的
|
|
1816
|
+
// LineBasicMaterial,edgeWidth / 外扩 / 单体跟随全都吃不上。
|
|
1817
|
+
// gpu 画法由 shader 自己描边,native 线段包同样不能留
|
|
1818
|
+
const useNativeEdges =
|
|
1819
|
+
hasNativeEdges && this.style.forceCpuEdges !== true && !this._edgeModes.gpu;
|
|
1772
1820
|
|
|
1773
1821
|
for (const child of node.group.children) {
|
|
1774
1822
|
// 数据自带的棱线包:只跟着 edges 开关显示,不参与拾取、不参与着色
|
|
1775
1823
|
if ((child as THREE.LineSegments).isLineSegments && (child.userData as MeshUserData).isEdgeLines) {
|
|
1776
|
-
|
|
1824
|
+
// 一律登记进 _nativeEdgeLines:forceCpuEdges 之后又关掉时还要能把它们放出来
|
|
1777
1825
|
this._nativeEdgeLines.add(child as THREE.LineSegments);
|
|
1826
|
+
child.visible = useNativeEdges && this._edgesEnabled;
|
|
1778
1827
|
this._styleEdgeLine(child as THREE.LineSegments);
|
|
1779
1828
|
continue;
|
|
1780
1829
|
}
|
|
@@ -1787,7 +1836,8 @@ export class S3MViewer {
|
|
|
1787
1836
|
const ud = mesh.userData as MeshUserData;
|
|
1788
1837
|
ud.pickId = this._allocPickId();
|
|
1789
1838
|
ud.datasetId = entry.id;
|
|
1790
|
-
ud.nativeEdges =
|
|
1839
|
+
ud.nativeEdges = useNativeEdges;
|
|
1840
|
+
ud.nativeEdgesRaw = hasNativeEdges;
|
|
1791
1841
|
this._pickables.set(ud.pickId, {
|
|
1792
1842
|
datasetId: entry.id,
|
|
1793
1843
|
mesh,
|
|
@@ -1799,8 +1849,11 @@ export class S3MViewer {
|
|
|
1799
1849
|
// 点云没有「棱」:给一堆孤立点建 EdgesGeometry 又慢又没意义
|
|
1800
1850
|
// 点云没有「棱」:给一堆孤立点建 EdgesGeometry 又慢又没意义。
|
|
1801
1851
|
// 自动描边受三道开关控制:全局 style.edges / 数据集 enableEdges / 地形绿地等关键字跳过。
|
|
1802
|
-
if (!
|
|
1803
|
-
|
|
1852
|
+
if (!useNativeEdges && !isPoints && this._datasetEdgesEnabled(entry) && !this._shouldSkipAutoEdges(ud, entry.name)) {
|
|
1853
|
+
// 数据集强制开启(enableEdges: true)时全局画法可能还是关的,退回默认折边线;
|
|
1854
|
+
// 但 gpu 画法本来就不建几何,别在这里多此一举
|
|
1855
|
+
if (this._edgeModes.line || this._edgeModes.hull) this._ensureEdgeDecoration(mesh);
|
|
1856
|
+
else if (!this._edgeModes.gpu) this._ensureEdges(mesh);
|
|
1804
1857
|
}
|
|
1805
1858
|
|
|
1806
1859
|
// 新瓦片要继承当前的不透明度与着色方式
|
|
@@ -1814,8 +1867,11 @@ export class S3MViewer {
|
|
|
1814
1867
|
mat.transparent = o < 0.999;
|
|
1815
1868
|
const keepDepth = this.style.depthWrite !== false;
|
|
1816
1869
|
mat.depthWrite = mat.transparent ? keepDepth : (this.style.depthWrite !== false);
|
|
1817
|
-
mat.flatShading
|
|
1818
|
-
|
|
1870
|
+
// shading 走共享 uniform(uFlatShading),不写 mat.flatShading / needsUpdate——
|
|
1871
|
+
// transparent / depthWrite 也不是 program 决定项,整套样式继承零重编译
|
|
1872
|
+
if (mat.userData.uFlatShading) {
|
|
1873
|
+
mat.userData.uFlatShading.value = this.style.shading !== 'smooth' ? 1 : 0;
|
|
1874
|
+
}
|
|
1819
1875
|
this._featureStyleManager.applyOverridesToMesh(mesh);
|
|
1820
1876
|
this._startTileFade(mesh, mat);
|
|
1821
1877
|
}
|
|
@@ -2039,7 +2095,7 @@ export class S3MViewer {
|
|
|
2039
2095
|
if ('edges' in patch) {
|
|
2040
2096
|
const { edges, ...others } = patch;
|
|
2041
2097
|
rest = others;
|
|
2042
|
-
this.setEdges(
|
|
2098
|
+
this.setEdges(edges as EdgesOption, wantAnim ? transition : undefined);
|
|
2043
2099
|
if (Object.keys(rest).length === 0) {
|
|
2044
2100
|
this._requestRepaint();
|
|
2045
2101
|
return;
|
|
@@ -2157,7 +2213,13 @@ export class S3MViewer {
|
|
|
2157
2213
|
return;
|
|
2158
2214
|
}
|
|
2159
2215
|
this._applyStyleUniforms();
|
|
2160
|
-
if (
|
|
2216
|
+
if (
|
|
2217
|
+
key === 'edgeColor' ||
|
|
2218
|
+
key === 'edgeWidth' ||
|
|
2219
|
+
key === 'edgeOpacity' ||
|
|
2220
|
+
key === 'edgeOffset' ||
|
|
2221
|
+
key === 'edgeHullWidth'
|
|
2222
|
+
) {
|
|
2161
2223
|
this._refreshEdgeLines();
|
|
2162
2224
|
}
|
|
2163
2225
|
}
|
|
@@ -2191,10 +2253,18 @@ export class S3MViewer {
|
|
|
2191
2253
|
// 临界角变了要重建 CPU 棱线(EdgesGeometry 的参数),开 / 关走 setEdges 整体刷新。
|
|
2192
2254
|
// 必须先 _disposeEdges():setEdges 只挑「还没有棱线」的网格来建,
|
|
2193
2255
|
// 不先拆掉的话已经建好的会被全部跳过,临界角改了等于没改。
|
|
2194
|
-
if ('edgeThreshold' in patch && this._edgesEnabled) {
|
|
2256
|
+
if (('edgeThreshold' in patch || 'forceCpuEdges' in patch) && this._edgesEnabled) {
|
|
2195
2257
|
this._disposeEdges();
|
|
2196
|
-
this.setEdges(
|
|
2197
|
-
} else if (
|
|
2258
|
+
this.setEdges(this.style.edges);
|
|
2259
|
+
} else if (
|
|
2260
|
+
'edgeColor' in patch ||
|
|
2261
|
+
'edgeWidth' in patch ||
|
|
2262
|
+
'opacity' in patch ||
|
|
2263
|
+
'edgeOpacity' in patch ||
|
|
2264
|
+
'edgeOffset' in patch ||
|
|
2265
|
+
'edgeHullWidth' in patch
|
|
2266
|
+
) {
|
|
2267
|
+
// 颜色 / 粗细 / 外推距离 / 外扩厚度都只是 uniform,拖着滑块逐帧改也不重建几何
|
|
2198
2268
|
this._refreshEdgeLines();
|
|
2199
2269
|
}
|
|
2200
2270
|
if (this._selection) {
|
|
@@ -2305,26 +2375,74 @@ export class S3MViewer {
|
|
|
2305
2375
|
|
|
2306
2376
|
/** 平面/平滑着色:FLAT_SHADED 是编译期宏,改它必须重编译 */
|
|
2307
2377
|
private _applyShading(): void {
|
|
2308
|
-
|
|
2378
|
+
// shading 现在是共享 uniform(uFlatShading)驱动的运行期分支,不再是
|
|
2379
|
+
// flatShading define:翻一个 float 就完成切换,零重编译、零卡顿。
|
|
2380
|
+
const target = this.style.shading !== 'smooth' ? 1 : 0;
|
|
2309
2381
|
for (const p of this._pickables.values()) {
|
|
2310
2382
|
const m = styledMaterialOf(p.mesh);
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2383
|
+
const u = m?.userData.uFlatShading;
|
|
2384
|
+
if (!u || u.value === target) continue;
|
|
2385
|
+
u.value = target;
|
|
2314
2386
|
}
|
|
2315
2387
|
this._requestRepaint();
|
|
2316
2388
|
}
|
|
2317
2389
|
|
|
2318
2390
|
/**
|
|
2319
|
-
*
|
|
2391
|
+
* 运行时切换材质质感(`'pbr'` | `'lambert'`)。
|
|
2320
2392
|
*
|
|
2321
|
-
*
|
|
2322
|
-
*
|
|
2323
|
-
*
|
|
2393
|
+
* 档位是模块级设置(影响之后新建的材质);已加载瓦片的材质在这里**原位重建**:
|
|
2394
|
+
* 不重拉瓦片、不重建几何,按创建时记录的参数(`userData.s3mOpts`)换一个
|
|
2395
|
+
* 新材质实例,并保留逐材质状态(选中编号、瓦片淡入、批量状态纹理)。
|
|
2396
|
+
* 切换后 program 需要重新编译一次(lambert / pbr 是不同着色器),这是唯一开销。
|
|
2397
|
+
*/
|
|
2398
|
+
setMaterialQuality(q: MaterialQuality): void {
|
|
2399
|
+
setMaterialQuality(q);
|
|
2400
|
+
this._rebuildStyledMaterials();
|
|
2401
|
+
this._requestRepaint();
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
/** 当前材质质感档位 */
|
|
2405
|
+
get materialQuality(): MaterialQuality {
|
|
2406
|
+
return getMaterialQuality();
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
/** 按创建参数原位重建所有 styled 材质(materialQuality 切换用) */
|
|
2410
|
+
private _rebuildStyledMaterials(): void {
|
|
2411
|
+
// 逐材质 own uniform 的状态要在重建时搬过去,否则选中高亮 / 瓦片淡入会闪没
|
|
2412
|
+
const PRESERVED = [
|
|
2413
|
+
'uSelBatch',
|
|
2414
|
+
'uSelWhole',
|
|
2415
|
+
'uBatchTex',
|
|
2416
|
+
'uBatchTex2',
|
|
2417
|
+
'uBatchTexWidth',
|
|
2418
|
+
'uBatchTexActive',
|
|
2419
|
+
'uTileFade',
|
|
2420
|
+
] as const;
|
|
2421
|
+
for (const p of this._pickables.values()) {
|
|
2422
|
+
const mesh = p.mesh;
|
|
2423
|
+
const raw = mesh.material;
|
|
2424
|
+
if (Array.isArray(raw)) continue; // styled 材质恒为单材质;数组材质(点云等)不归这里管
|
|
2425
|
+
const old = raw as StyledMaterial | undefined;
|
|
2426
|
+
if (!old?.userData?.isStyled || !old.userData?.s3mOpts) continue;
|
|
2427
|
+
const opts = old.userData.s3mOpts as StyledMaterialOptions;
|
|
2428
|
+
const fresh = createStyledMaterial({ ...opts, uniforms: this.uniforms });
|
|
2429
|
+
for (const key of PRESERVED) {
|
|
2430
|
+
const src = old.userData[key] as { value: unknown } | undefined;
|
|
2431
|
+
const dst = fresh.userData[key] as { value: unknown } | undefined;
|
|
2432
|
+
if (src && dst) dst.value = src.value;
|
|
2433
|
+
}
|
|
2434
|
+
mesh.material = fresh;
|
|
2435
|
+
old.dispose();
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
/**
|
|
2440
|
+
* 预热着色 program,避免首帧同步编译。
|
|
2324
2441
|
*
|
|
2325
|
-
*
|
|
2326
|
-
*
|
|
2327
|
-
*
|
|
2442
|
+
* shading 改为 `uFlatShading` 共享 uniform 驱动后(见 `_applyShading`),
|
|
2443
|
+
* flat / smooth 共用同一个 program,不再存在「另一种变体」,也不需要
|
|
2444
|
+
* 「翻变体 → 编译 → 翻回来」的那套流程与 `_prewarming` 跳帧。这里只把当前
|
|
2445
|
+
* 唯一的 program 编一次(优先 `compileAsync`,KHR_parallel_shader_compile)。
|
|
2328
2446
|
*/
|
|
2329
2447
|
async prewarmShading(): Promise<void> {
|
|
2330
2448
|
if (this._shadingPrewarmed || this._prewarming) return;
|
|
@@ -2333,23 +2451,17 @@ export class S3MViewer {
|
|
|
2333
2451
|
const camera = this.camera;
|
|
2334
2452
|
if (!r || !scene || !camera) return;
|
|
2335
2453
|
|
|
2336
|
-
|
|
2337
|
-
|
|
2454
|
+
// 场上还没有材质就没必要预热:program 会在首个材质出现时编译
|
|
2455
|
+
let hasMaterial = false;
|
|
2338
2456
|
for (const p of this._pickables.values()) {
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2457
|
+
if (styledMaterialOf(p.mesh)) {
|
|
2458
|
+
hasMaterial = true;
|
|
2459
|
+
break;
|
|
2460
|
+
}
|
|
2343
2461
|
}
|
|
2344
|
-
if (
|
|
2462
|
+
if (!hasMaterial) return;
|
|
2345
2463
|
|
|
2346
2464
|
this._shadingPrewarmed = true;
|
|
2347
|
-
this._prewarming = true;
|
|
2348
|
-
// 当前是 smooth(flatShading = false)就预热 flat,反之预热 smooth
|
|
2349
|
-
const target = this.style.shading === 'smooth';
|
|
2350
|
-
const saved = mats.map((m) => m.flatShading);
|
|
2351
|
-
for (const m of mats) m.flatShading = target;
|
|
2352
|
-
|
|
2353
2465
|
try {
|
|
2354
2466
|
const anyR = r as unknown as {
|
|
2355
2467
|
compileAsync?: (s: THREE.Object3D, c: THREE.Camera) => Promise<unknown>;
|
|
@@ -2357,15 +2469,9 @@ export class S3MViewer {
|
|
|
2357
2469
|
if (typeof anyR.compileAsync === 'function') await anyR.compileAsync(scene, camera);
|
|
2358
2470
|
else r.compile(scene, camera);
|
|
2359
2471
|
} catch {
|
|
2360
|
-
//
|
|
2361
|
-
} finally {
|
|
2362
|
-
for (let i = 0; i < mats.length; i++) {
|
|
2363
|
-
mats[i].flatShading = saved[i];
|
|
2364
|
-
mats[i].needsUpdate = true;
|
|
2365
|
-
}
|
|
2366
|
-
this._prewarming = false;
|
|
2367
|
-
this._requestRepaint();
|
|
2472
|
+
// 预热失败不影响主流程:最坏情况退回「首次渲染时编译一次」
|
|
2368
2473
|
}
|
|
2474
|
+
this._requestRepaint();
|
|
2369
2475
|
}
|
|
2370
2476
|
|
|
2371
2477
|
/**
|
|
@@ -2459,8 +2565,15 @@ export class S3MViewer {
|
|
|
2459
2565
|
/**
|
|
2460
2566
|
* 全局棱线开关。
|
|
2461
2567
|
*
|
|
2462
|
-
*
|
|
2463
|
-
*
|
|
2568
|
+
* 取值是 `boolean | EdgeStyle | EdgeStyle[]`(见 resolveEdgeModes):
|
|
2569
|
+
* - `true` / `'line'` 折边线(旧口径)
|
|
2570
|
+
* - `'offset'` 折边线 + 沿边法线外推 `edgeOffset` 米
|
|
2571
|
+
* - `'hull'` 背面外扩轮廓(选中侧 outlineStyle:'hull' 的整场景版)
|
|
2572
|
+
* - `'gpu'` 纯片元描边:零几何、零额外 pass,线宽约 1 像素
|
|
2573
|
+
* - 数组 叠加,例如 `['line', 'hull']`
|
|
2574
|
+
*
|
|
2575
|
+
* 数据自带棱线包(primitiveType 13)时直接用,成本为零;`style.forceCpuEdges`
|
|
2576
|
+
* 可以无视它,改由 CPU 现算 —— 那是 CPU 端的活,几十万面一次性建会卡住主线程
|
|
2464
2577
|
* (实测 20 个网格 1.9s),所以按 8ms 一帧切片,边建边出。
|
|
2465
2578
|
*
|
|
2466
2579
|
* 棱线本身用「屏幕空间等宽线」画(见 EdgeLines.ts):WebGL 的 lineWidth 基本无效,
|
|
@@ -2470,25 +2583,37 @@ export class S3MViewer {
|
|
|
2470
2583
|
* 开:立刻置位 + 开始分片建线,同时 `uEdgeFade` 0→1 —— 建好一块亮一块,视觉上是渐显;
|
|
2471
2584
|
* 关:`uEdgeFade` 1→0,淡完才 dispose,避免棱线「啪」一下消失。
|
|
2472
2585
|
*/
|
|
2473
|
-
setEdges(
|
|
2586
|
+
setEdges(edges: EdgesOption, transition?: TransitionOptions): void {
|
|
2474
2587
|
const wantAnim = !!transition && ((transition.duration ?? 0) > 0 || !!transition.spring);
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2588
|
+
const modes = resolveEdgeModes(edges);
|
|
2589
|
+
// 换画法(line ↔ hull ↔ offset)是结构变更:旧装饰必须拆掉再建,
|
|
2590
|
+
// 否则会出现「切到 hull 了折边线还留着」这种叠影。
|
|
2591
|
+
// 同一画法重复调用(滑块逐帧 setEdges(true))不重建,几何是复用的。
|
|
2592
|
+
const modesChanged = !sameEdgeModes(this._edgeModes, modes);
|
|
2593
|
+
|
|
2594
|
+
this._edgeModes = modes;
|
|
2595
|
+
this._edgesEnabled = modes.enabled;
|
|
2596
|
+
this.style.edges = edges;
|
|
2597
|
+
this._styleTarget.edges = edges;
|
|
2598
|
+
|
|
2599
|
+
// 数据自带的棱线:直接切可见性(forceCpuEdges 时永远收起,改由 CPU 现算)。
|
|
2600
|
+
// 只遍历注册时收集好的那批 LineSegments —— 全数据集 traverse 在几千节点下太贵。
|
|
2601
|
+
// gpu 模式下 shader 自己描边,native 线段包必须收起,否则叠成双线
|
|
2602
|
+
const nativeVisible = modes.enabled && !modes.gpu && this.style.forceCpuEdges !== true;
|
|
2603
|
+
for (const lines of this._nativeEdgeLines) {
|
|
2604
|
+
lines.visible = nativeVisible;
|
|
2605
|
+
this._styleEdgeLine(lines);
|
|
2488
2606
|
}
|
|
2489
2607
|
this._syncEdgeStyle();
|
|
2490
2608
|
|
|
2491
|
-
|
|
2609
|
+
// 关掉 gpu 画法就把后处理资源还回去(整屏 RT 白占显存没意义)。
|
|
2610
|
+
// 必须放在「整体关闭」的早退之前,否则关棱线时永远轮不到它。
|
|
2611
|
+
if (!modes.gpu && this._gpuEdge) {
|
|
2612
|
+
this._gpuEdge.dispose();
|
|
2613
|
+
this._gpuEdge = null;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
if (!modes.enabled) {
|
|
2492
2617
|
if (wantAnim) {
|
|
2493
2618
|
this.animator.to('style.edgeFade', 0, {
|
|
2494
2619
|
...transition,
|
|
@@ -2506,17 +2631,23 @@ export class S3MViewer {
|
|
|
2506
2631
|
return;
|
|
2507
2632
|
}
|
|
2508
2633
|
|
|
2634
|
+
if (modesChanged) this._disposeEdges();
|
|
2635
|
+
|
|
2509
2636
|
const pending = [...this._pickables.values()]
|
|
2510
2637
|
.map((p) => p.mesh)
|
|
2511
2638
|
.filter((m) => {
|
|
2512
2639
|
const ud = m.userData as MeshUserData;
|
|
2513
2640
|
// 点云没有棱可描
|
|
2514
|
-
|
|
2641
|
+
if (isPointCloudObject(m)) return false;
|
|
2642
|
+
// native 棱线还算不算数,取决于**当前**的 forceCpuEdges(它可以在运行中切换),
|
|
2643
|
+
// 所以这里按注册时记下的原始标记重算,而不是沿用注册那一刻的结论。
|
|
2644
|
+
ud.nativeEdges = ud.nativeEdgesRaw === true && this.style.forceCpuEdges !== true;
|
|
2645
|
+
return !ud.nativeEdges && this._needsEdgeDecoration(ud);
|
|
2515
2646
|
});
|
|
2516
2647
|
let i = 0;
|
|
2517
2648
|
const step = (): void => {
|
|
2518
2649
|
const t0 = performance.now();
|
|
2519
|
-
while (i < pending.length && performance.now() - t0 < 8) this.
|
|
2650
|
+
while (i < pending.length && performance.now() - t0 < 8) this._ensureEdgeDecoration(pending[i++]);
|
|
2520
2651
|
if (i < pending.length) setTimeout(step, 0);
|
|
2521
2652
|
else this._requestRepaint();
|
|
2522
2653
|
};
|
|
@@ -2589,10 +2720,11 @@ export class S3MViewer {
|
|
|
2589
2720
|
}
|
|
2590
2721
|
|
|
2591
2722
|
/**
|
|
2592
|
-
* 让全局棱线材质跟上 style.edgeColor / edgeWidth / edgeOpacity。
|
|
2723
|
+
* 让全局棱线材质跟上 style.edgeColor / edgeWidth / edgeOpacity / edgeOffset。
|
|
2593
2724
|
*
|
|
2594
|
-
* 全程 O(1):只有一份共享 edgeMaterial(所有逐 mesh 棱线都用它)+
|
|
2595
|
-
*
|
|
2725
|
+
* 全程 O(1):只有一份共享 edgeMaterial(所有逐 mesh 棱线都用它)+ 一份预制缓存
|
|
2726
|
+
* + 一批共享外观对象的轮廓壳,所以棱线淡入淡出只要改一个 uniform,
|
|
2727
|
+
* 新建瓦片自动继承当前进度。
|
|
2596
2728
|
*/
|
|
2597
2729
|
private _syncEdgeStyle(): void {
|
|
2598
2730
|
const s = this.style;
|
|
@@ -2605,6 +2737,14 @@ export class S3MViewer {
|
|
|
2605
2737
|
u.uColor.value.set(s.edgeColor);
|
|
2606
2738
|
u.uWidth.value = Math.max(0.1, s.edgeWidth ?? 1.5);
|
|
2607
2739
|
u.uOpacity.value = opacity;
|
|
2740
|
+
// 外推距离只在 offset 画法下非 0;其它画法把线钉在面上(与改动前一致)
|
|
2741
|
+
u.uOffset.value = this._edgeModes.offset ? Math.max(0, s.edgeOffset ?? 0.15) : 0;
|
|
2742
|
+
// 外扩轮廓壳与折边线共用颜色 / 透明度,厚度是米制的单独一项
|
|
2743
|
+
const h = this._hullStyleUniforms;
|
|
2744
|
+
h.uColor.value.set(s.edgeColor);
|
|
2745
|
+
h.uWidth.value = Math.max(0, s.edgeHullWidth ?? 0.9);
|
|
2746
|
+
h.uOpacity.value = opacity;
|
|
2747
|
+
// GPU 描边(edges: 'gpu')是后处理,参数每帧从 style 现取,这里不用同步 uniform
|
|
2608
2748
|
// 预制棱线缓存(LineBasicMaterial)同步颜色 / 不透明度
|
|
2609
2749
|
if (this._creaseLines) {
|
|
2610
2750
|
const m = this._creaseLines.material as THREE.LineBasicMaterial;
|
|
@@ -2664,6 +2804,23 @@ export class S3MViewer {
|
|
|
2664
2804
|
return S3MViewer._EDGE_SKIP_KEYWORDS.some((k) => hay.includes(k));
|
|
2665
2805
|
}
|
|
2666
2806
|
|
|
2807
|
+
/**
|
|
2808
|
+
* 这块网格还缺哪种棱线装饰(缺就建,齐了就不动)。
|
|
2809
|
+
*
|
|
2810
|
+
* setEdges 的分片建线靠它筛「还没建完的」,注册新瓦片也靠它决定要不要动手。
|
|
2811
|
+
*/
|
|
2812
|
+
private _needsEdgeDecoration(ud: MeshUserData): boolean {
|
|
2813
|
+
if (this._edgeModes.line && !ud.edges) return true;
|
|
2814
|
+
if (this._edgeModes.hull && !ud.edgeHull) return true;
|
|
2815
|
+
return false;
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
/** 按当前画法补齐这块网格的棱线装饰 */
|
|
2819
|
+
private _ensureEdgeDecoration(mesh: THREE.Mesh): void {
|
|
2820
|
+
if (this._edgeModes.line) this._ensureEdges(mesh);
|
|
2821
|
+
if (this._edgeModes.hull) this._ensureHull(mesh);
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2667
2824
|
private _ensureEdges(mesh: THREE.Mesh): void {
|
|
2668
2825
|
const ud = mesh.userData as MeshUserData;
|
|
2669
2826
|
if (ud.edges || ud.nativeEdges || !mesh.geometry) return;
|
|
@@ -2683,12 +2840,22 @@ export class S3MViewer {
|
|
|
2683
2840
|
const welded = mergeVerticesByPosition(mesh.geometry, 0.005);
|
|
2684
2841
|
// 2. 调大临界角(默认 55°,可在 style.edgeThreshold 调),过滤浅坡与曲面细分,只保留真正的棱。
|
|
2685
2842
|
const thresholdAngle = this.style.edgeThreshold ?? 55;
|
|
2686
|
-
const source = new THREE.EdgesGeometry(welded, thresholdAngle);
|
|
2687
|
-
welded.dispose();
|
|
2688
2843
|
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2844
|
+
// 'offset' 画法要额外带一条「边法线」才知道往哪推,所以走本地实现(它本来就算好了
|
|
2845
|
+
// 相邻两面法线);其余画法保持 EdgesGeometry 这条老路,几何输出与改动前逐字一致。
|
|
2846
|
+
let segments: Float32Array | null;
|
|
2847
|
+
let normals: Float32Array | undefined;
|
|
2848
|
+
if (this._edgeModes.offset) {
|
|
2849
|
+
const raw: number[] = [];
|
|
2850
|
+
segments = buildFeatureEdgeSegments(welded, null, thresholdAngle, raw);
|
|
2851
|
+
if (raw.length > 0) normals = new Float32Array(raw);
|
|
2852
|
+
} else {
|
|
2853
|
+
const source = new THREE.EdgesGeometry(welded, thresholdAngle);
|
|
2854
|
+
const attr = source.getAttribute('position') as THREE.BufferAttribute | undefined;
|
|
2855
|
+
segments = attr ? (attr.array as Float32Array) : null;
|
|
2856
|
+
source.dispose();
|
|
2857
|
+
}
|
|
2858
|
+
welded.dispose();
|
|
2692
2859
|
if (!segments || segments.length < 6) return;
|
|
2693
2860
|
|
|
2694
2861
|
// 3. 回填每段棱线所属的单体 batchId。
|
|
@@ -2708,7 +2875,7 @@ export class S3MViewer {
|
|
|
2708
2875
|
}
|
|
2709
2876
|
|
|
2710
2877
|
const lines = new THREE.Mesh(
|
|
2711
|
-
buildThickLineGeometry(segments, batchIds),
|
|
2878
|
+
buildThickLineGeometry(segments, batchIds, normals),
|
|
2712
2879
|
this._createEdgeMaterial(mesh)
|
|
2713
2880
|
);
|
|
2714
2881
|
lines.renderOrder = 2;
|
|
@@ -2720,6 +2887,49 @@ export class S3MViewer {
|
|
|
2720
2887
|
mesh.add(lines); // 作为子节点,自动继承 mesh 的世界变换
|
|
2721
2888
|
}
|
|
2722
2889
|
|
|
2890
|
+
/**
|
|
2891
|
+
* 给一块网格挂「背面外扩轮廓」(整场景版)。
|
|
2892
|
+
*
|
|
2893
|
+
* 复用选中侧那套 `createOutlineMaterial`:顶点沿法线鼓 `edgeHullWidth` 米、只画背面,
|
|
2894
|
+
* 于是被本体挡住的部分不显示,露出来的那圈就是描边。与折边线的差别在于——
|
|
2895
|
+
* 这里**不建任何新几何**,成本只是把同一块网格多画一遍,折边判定那套 CPU 活全省了。
|
|
2896
|
+
*
|
|
2897
|
+
* 实例化几何拿不到逐实例变换(轮廓壳是 mesh 的子节点,不吃 instanceMatrix),
|
|
2898
|
+
* 直接跳过:宁可这栋没描边,也不要所有实例的壳叠在原点。
|
|
2899
|
+
*/
|
|
2900
|
+
private _ensureHull(mesh: THREE.Mesh): void {
|
|
2901
|
+
const ud = mesh.userData as MeshUserData;
|
|
2902
|
+
if (ud.edgeHull || !mesh.geometry) return;
|
|
2903
|
+
if ((mesh as unknown as { isInstancedMesh?: boolean }).isInstancedMesh) return;
|
|
2904
|
+
// 地形 / 绿地 / 道路等不参与自动描边(即使全局开棱线也要跳过)
|
|
2905
|
+
if (this._shouldSkipAutoEdges(ud, '')) return;
|
|
2906
|
+
|
|
2907
|
+
// 逐网格一份材质(各绑各的单体状态纹理),外观 uniform 复用共享对象
|
|
2908
|
+
const mat = createOutlineMaterial(
|
|
2909
|
+
this.uniforms,
|
|
2910
|
+
this.style.edgeHullWidth ?? 0.9,
|
|
2911
|
+
this._hullStyleUniforms,
|
|
2912
|
+
true
|
|
2913
|
+
);
|
|
2914
|
+
// 整块几何都要外扩:选中侧靠 uSelBatch 只扩一栋,全局必须置 whole
|
|
2915
|
+
mat.uniforms.uSelWhole.value = 1;
|
|
2916
|
+
mat.uniforms.uSelBatch.value = -1;
|
|
2917
|
+
const styled = styledMaterialOf(mesh);
|
|
2918
|
+
if (styled?.userData?.uBatchTex) {
|
|
2919
|
+
mat.uniforms.uBatchTex.value = styled.userData.uBatchTex.value as THREE.Texture;
|
|
2920
|
+
mat.uniforms.uBatchTexWidth.value = styled.userData.uBatchTexWidth.value as number;
|
|
2921
|
+
mat.uniforms.uBatchTexActive.value = styled.userData.uBatchTexActive.value as number;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
const hull = new THREE.Mesh(mesh.geometry, mat);
|
|
2925
|
+
hull.renderOrder = 2;
|
|
2926
|
+
hull.frustumCulled = false;
|
|
2927
|
+
hull.raycast = () => {};
|
|
2928
|
+
(hull.userData as MeshUserData).isEdgeHull = true;
|
|
2929
|
+
ud.edgeHull = hull;
|
|
2930
|
+
mesh.add(hull); // 作为子节点,自动继承 mesh 的世界变换
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2723
2933
|
/**
|
|
2724
2934
|
* 给一块网格造专属的棱线材质。
|
|
2725
2935
|
*
|
|
@@ -2767,18 +2977,30 @@ export class S3MViewer {
|
|
|
2767
2977
|
|
|
2768
2978
|
private _removeEdges(mesh: THREE.Mesh): void {
|
|
2769
2979
|
const ud = mesh.userData as MeshUserData;
|
|
2980
|
+
|
|
2770
2981
|
const lines = ud.edges;
|
|
2771
|
-
if (
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2982
|
+
if (lines) {
|
|
2983
|
+
mesh.remove(lines);
|
|
2984
|
+
(lines as THREE.Mesh).geometry?.dispose();
|
|
2985
|
+
// 棱线材质现在是逐网格一份(要绑各自的 batch 纹理),得跟着释放
|
|
2986
|
+
const mat = (lines as THREE.Mesh).material;
|
|
2987
|
+
if (Array.isArray(mat)) {
|
|
2988
|
+
mat.forEach((m) => (m as any)?.dispose?.());
|
|
2989
|
+
} else {
|
|
2990
|
+
(mat as any)?.dispose?.();
|
|
2991
|
+
}
|
|
2992
|
+
ud.edges = null;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
// 外扩壳的 geometry 是**复用主网格的**,只能释放材质,不能 dispose 几何
|
|
2996
|
+
const hull = ud.edgeHull;
|
|
2997
|
+
if (hull) {
|
|
2998
|
+
mesh.remove(hull);
|
|
2999
|
+
const hmat = hull.material;
|
|
3000
|
+
if (Array.isArray(hmat)) hmat.forEach((m) => (m as any)?.dispose?.());
|
|
3001
|
+
else (hmat as any)?.dispose?.();
|
|
3002
|
+
ud.edgeHull = null;
|
|
2780
3003
|
}
|
|
2781
|
-
ud.edges = null;
|
|
2782
3004
|
}
|
|
2783
3005
|
|
|
2784
3006
|
/* ---------------------------------------------------------------- 拾取 */
|
|
@@ -3488,6 +3710,36 @@ export class S3MViewer {
|
|
|
3488
3710
|
|
|
3489
3711
|
/* ---------------------------------------------------------------- 渲染 */
|
|
3490
3712
|
|
|
3713
|
+
/**
|
|
3714
|
+
* 执行一帧 GPU 描边(法线 + 深度后处理)。
|
|
3715
|
+
*
|
|
3716
|
+
* 参数全部来自 style,不缓存:`edgeWidth` 在这里是「采样偏移」,线宽 ≈ 2×它;
|
|
3717
|
+
* `edgeThreshold` 是折边敏感度(夹角小于它不描),和 CPU 折边线同一口径。
|
|
3718
|
+
*/
|
|
3719
|
+
private _renderGpuEdges(canvas: HTMLCanvasElement): void {
|
|
3720
|
+
if (!this.renderer || !this.scene || !this.camera) return;
|
|
3721
|
+
const s = this.style;
|
|
3722
|
+
const fade = this.uniforms.uEdgeFade?.value ?? 1;
|
|
3723
|
+
const opacity =
|
|
3724
|
+
Math.min(1, Math.max(0, s.edgeOpacity ?? 0.6)) * clampOpacity(s.opacity) * fade;
|
|
3725
|
+
if (opacity <= 0.004) return;
|
|
3726
|
+
|
|
3727
|
+
if (!this._gpuEdge) this._gpuEdge = new GpuEdgePass();
|
|
3728
|
+
if (!this._gpuEdge.supported(this.renderer)) return;
|
|
3729
|
+
|
|
3730
|
+
// 度数 → 归一化法线差(|Δn| = 2·sin(θ/2)),乘 0.6 让阈值落在「刚好能描出」的位置
|
|
3731
|
+
const deg = Math.min(179, Math.max(1, s.edgeThreshold ?? 55));
|
|
3732
|
+
const normalThreshold = 2 * Math.sin((deg * Math.PI) / 360) * 0.6;
|
|
3733
|
+
|
|
3734
|
+
this._gpuEdge.render(this.renderer, this.scene, this.camera, canvas.width, canvas.height, {
|
|
3735
|
+
color: this._gpuEdgeColor.set(s.edgeColor),
|
|
3736
|
+
opacity,
|
|
3737
|
+
radius: Math.max(1, s.edgeWidth ?? 1.5),
|
|
3738
|
+
normalThreshold,
|
|
3739
|
+
depthThreshold: 0.08,
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3491
3743
|
/** 等宽线要按设备像素换算线宽:uResolution 用绘图缓冲尺寸,uDpr 是缓冲 / CSS 的比值 */
|
|
3492
3744
|
private _updateLineResolution(canvas: HTMLCanvasElement): void {
|
|
3493
3745
|
const w = canvas.width || 1;
|
|
@@ -3734,6 +3986,9 @@ export class S3MViewer {
|
|
|
3734
3986
|
// 这里跳过的是「本层的绘制」,宿主自己的清屏 / 底图照旧。
|
|
3735
3987
|
if ((this.uniforms.uOpacity?.value ?? 1) > OPACITY_EPS) {
|
|
3736
3988
|
this.renderer.render(this.scene, this.camera);
|
|
3989
|
+
// GPU 描边必须画在主场景之后:它的全屏 quad 参与宿主深度测试,
|
|
3990
|
+
// 依赖本帧已经写好的深度(被地形挡住的地方不该描边)
|
|
3991
|
+
if (this._edgeModes.gpu) this._renderGpuEdges(canvas);
|
|
3737
3992
|
}
|
|
3738
3993
|
|
|
3739
3994
|
this._frame++;
|
|
@@ -3776,6 +4031,10 @@ export class S3MViewer {
|
|
|
3776
4031
|
vertices: 0,
|
|
3777
4032
|
triangles: 0,
|
|
3778
4033
|
datasets: this.datasets.size,
|
|
4034
|
+
drawCalls: 0,
|
|
4035
|
+
programs: 0,
|
|
4036
|
+
textures: 0,
|
|
4037
|
+
geometries: 0,
|
|
3779
4038
|
};
|
|
3780
4039
|
for (const entry of this.datasets.values()) {
|
|
3781
4040
|
const s = entry.tileset?.stats;
|
|
@@ -3787,6 +4046,15 @@ export class S3MViewer {
|
|
|
3787
4046
|
total.vertices += s.vertices;
|
|
3788
4047
|
total.triangles += s.triangles;
|
|
3789
4048
|
}
|
|
4049
|
+
// GPU 侧预算观测:draw call / program / 显存驻留。没有渲染器(未 mount /
|
|
4050
|
+
// 纯解析场景)时保持 0,不抛错。
|
|
4051
|
+
const info = this.renderer?.info;
|
|
4052
|
+
if (info) {
|
|
4053
|
+
total.drawCalls = info.render.calls;
|
|
4054
|
+
total.programs = info.programs?.length ?? 0;
|
|
4055
|
+
total.textures = info.memory.textures;
|
|
4056
|
+
total.geometries = info.memory.geometries;
|
|
4057
|
+
}
|
|
3790
4058
|
return total;
|
|
3791
4059
|
}
|
|
3792
4060
|
|