three-base 2.0.197 → 2.0.198
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/MktMesh/MktMesh.d.ts +22 -8
- package/MktMesh/MktMesh.js +47 -13
- package/MktMesh/MktMesh.ts +47 -13
- package/MktMesh/imagePlane.d.ts +14 -0
- package/MktMesh/imagePlane.js +79 -3
- package/MktMesh/imagePlane.ts +79 -2
- package/MktMesh/modelSlot.d.ts +2 -0
- package/MktMesh/modelSlot.js +12 -0
- package/MktMesh/modelSlot.ts +12 -0
- package/ThreeBase.js +1 -10
- package/ThreeBase.ts +1 -9
- package/package.json +1 -1
- package/pass/debugDepthState.d.ts +4 -8
- package/pass/debugDepthState.js +0 -4
- package/pass/debugDepthState.ts +4 -10
- package/pass/stroke_pass.js +8 -90
- package/pass/stroke_pass.ts +8 -94
package/MktMesh/MktMesh.d.ts
CHANGED
|
@@ -121,6 +121,12 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
121
121
|
/** 当前全景纹理形态(只读)。消费方(预览立方体等自持材质的路径)据此算 `mktPanoTiledFlag`,
|
|
122
122
|
* 必须与基类喂纹理处用同一个值,否则标记与地址不同源 ⇒ 整图被按 1/2 横向铺满球面(10-06 晚起纵向不分块,四槽时代是 1/4)。 */
|
|
123
123
|
get panoTexMode(): PanoTexMode;
|
|
124
|
+
/** 优先加载的全景个数(存档 `textures` 的前 n 项):**-1 = 全部、0 = 一个都不提前、n = 前 n 个**(2026-11-05 需求)。
|
|
125
|
+
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(=4);宿主想换档在渲染循环跑起来之前 `setPanoPriorityCount(-1)` 一行即可
|
|
126
|
+
* (与 `_ossSize`、`_panoTexMode` 同一套注入路径 ⇒ 编辑器与预览各自的配置各自推下来)。 */
|
|
127
|
+
private _panoPriorityCount;
|
|
128
|
+
/** 当前优先加载个数(只读,回显用)。 */
|
|
129
|
+
get panoPriorityCount(): number;
|
|
124
130
|
private _tmpV3;
|
|
125
131
|
/**
|
|
126
132
|
* 图片平面层(⭐ 10-26 旨入库):对象挂在**本 MktMesh 的子树**里,⛔ 不是场景根
|
|
@@ -338,6 +344,11 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
338
344
|
* 下一帧 `feedFaceMapping` 里共用的那份 binder 见「槽内现有形态 ≠ 目标形态」自动走整档判齐、一次换绑并同帧翻标记。
|
|
339
345
|
* 编辑器 MktDevMesh 走的是自己那路(同一个 binder、同一份策略),本方法对它只是记下形态。 */
|
|
340
346
|
setPanoTexMode(mode: PanoTexMode): void;
|
|
347
|
+
/** 设置优先加载的全景个数:-1 = 全部 / 0 = 不提前 / n = 存档前 n 个(2026-11-05 需求)。
|
|
348
|
+
* 收口:负数(含 -2、NaN 一类)一律归到 -1「全加载」,正数向下取整;非有限值回落默认档。
|
|
349
|
+
* 改档⛔ 需要重新 `importData` —— 名册每帧重排,下一帧 `prefetchHeadPanoramas` 直接用新数点名
|
|
350
|
+
* (`prioritize` 幂等 ⇒ 已在队列里的那批不重复投喂,也不会把已出队的旧批再塞回去)。 */
|
|
351
|
+
setPanoPriorityCount(count: number): void;
|
|
341
352
|
/**
|
|
342
353
|
* 记下「当前激活(名册 0 号槽 = 中心)是第几个全景」。
|
|
343
354
|
* ⭐ 皇上旨 2026-10-03 旨②:预览立方体已与墙**共用同一份材质**,原来那句「把中心那几张抄给立方体单全景材质」
|
|
@@ -499,11 +510,12 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
499
510
|
* @param onModelLoad 每只 GLB 落地时回调一次(可选)——按需渲染的宿主(编辑器)拿它标脏;每帧真画的预览不传 */
|
|
500
511
|
importData(data: MktMeshData, projName?: string, onModelLoad?: () => void): void;
|
|
501
512
|
/**
|
|
502
|
-
* 把**存档里前 `
|
|
513
|
+
* 把**存档里前 `panoPriorityCount` 个全景**(-1 = 整份全加载)按存档顺序登记进拼合器的**加载任务队列**
|
|
503
514
|
*(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
|
|
504
515
|
* 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
|
|
505
516
|
* 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
|
|
506
|
-
*
|
|
517
|
+
* 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」
|
|
518
|
+
* + 2026-11-05 需求「提前加载多少个全景改由配置决定,-1 全加载 / 0 不加载 / n 前 n 个,默认 4」)。
|
|
507
519
|
* 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
|
|
508
520
|
*(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
|
|
509
521
|
*
|
|
@@ -512,21 +524,23 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
512
524
|
* (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
|
|
513
525
|
* 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
|
|
514
526
|
* ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
|
|
515
|
-
* ② 本方法把存档 0
|
|
527
|
+
* ② 本方法把存档 0 号起这 n 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
|
|
516
528
|
* 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
|
|
517
529
|
* 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
|
|
530
|
+
* n = -1(全加载)时这一条变成「整份存档按存档顺序串行」⇒ 距离名册插不到前面,出清速度取决于总片数。
|
|
518
531
|
* ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
|
|
519
532
|
* 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
|
|
520
533
|
* ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
|
|
521
534
|
* 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
|
|
522
|
-
* `ensure` 之前 ⇒
|
|
535
|
+
* `ensure` 之前 ⇒ 每帧把这几记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以存档次序恒不变)。
|
|
523
536
|
* ⚠️ 代价如实记:
|
|
524
|
-
* · 队列非空时**其他全景一片都不发** ⇒
|
|
537
|
+
* · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前 n 颗的那面墙会多灰一会儿。
|
|
525
538
|
* · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
|
|
526
|
-
* (`_dead`),退避重试期间队首不落定 ⇒
|
|
539
|
+
* (`_dead`),退避重试期间队首不落定 ⇒ 后面几颗跟着延后。真实数据里缺片是「地址为空/404」,
|
|
527
540
|
* 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
|
|
528
|
-
* ·
|
|
529
|
-
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)——
|
|
541
|
+
* · 这批全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
|
|
542
|
+
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这些景点迟早要建;
|
|
543
|
+
* ⚠️ n = -1(全加载)时这一条按存档全景总数放大,显存与在途请求都跟着涨。
|
|
530
544
|
* ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
|
|
531
545
|
* 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
|
|
532
546
|
* 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
|
package/MktMesh/MktMesh.js
CHANGED
|
@@ -49,11 +49,13 @@ import { stripAssetHost, absAssetUrl } from "./assetUrl";
|
|
|
49
49
|
// 原先各住在本站两个渲染器里一份 ⇒ 下游直接用包里的 `MktMesh` 时,存档里的 `images`/`models` 两键没人读。
|
|
50
50
|
// 入库之后它们挂在 `MktMesh` **自己的子树**下,与墙同一棵变换 ⇒ 预览那三根 transform 滑杆搬得动整份展品。
|
|
51
51
|
import { ImagePlane } from "./imagePlane";
|
|
52
|
-
import { ModelSlot } from "./modelSlot";
|
|
52
|
+
import { ModelSlot, glbFileName } from "./modelSlot";
|
|
53
53
|
import { ImagePlaneTileLoader } from "./imageTileLoader";
|
|
54
|
-
/** 载入 JSON
|
|
55
|
-
* 口径 = 存档 `textures` 数组的前 n
|
|
56
|
-
|
|
54
|
+
/** 载入 JSON 时**优先**加载几个全景的素材的**默认档**(数值只写这一处)。
|
|
55
|
+
* 口径 = 存档 `textures` 数组的前 n 项(n = -1 视为整份全加载);其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。
|
|
56
|
+
* 2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」立的是 2;
|
|
57
|
+
* 2026-11-05 需求逐字「现在要默认把全景全部都要默认加载, 现在需要通过配置来设置, 提前加载多少个全景 -1 就是全加载 0就是不加载 1就是加载个 2就提前加载2个 一次类推, 默认加载前4个」⇒ 换成 4,并让这一档可被实例覆写。 */
|
|
58
|
+
const PANO_IMPORT_PRIORITY_DEFAULT = 4;
|
|
57
59
|
/** 隐藏期预热那一次绘制用的镜头(模块级共用:它只是个「有合法的投影矩阵可读」的载体,永不动 ⇒ ⛔ 不必每实例造一份)。 */
|
|
58
60
|
const GPU_WARM_CAMERA = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
|
|
59
61
|
/**
|
|
@@ -193,6 +195,14 @@ export default class MktMesh extends Mesh {
|
|
|
193
195
|
get panoTexMode() {
|
|
194
196
|
return this._panoTexMode;
|
|
195
197
|
}
|
|
198
|
+
/** 优先加载的全景个数(存档 `textures` 的前 n 项):**-1 = 全部、0 = 一个都不提前、n = 前 n 个**(2026-11-05 需求)。
|
|
199
|
+
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(=4);宿主想换档在渲染循环跑起来之前 `setPanoPriorityCount(-1)` 一行即可
|
|
200
|
+
* (与 `_ossSize`、`_panoTexMode` 同一套注入路径 ⇒ 编辑器与预览各自的配置各自推下来)。 */
|
|
201
|
+
_panoPriorityCount = PANO_IMPORT_PRIORITY_DEFAULT;
|
|
202
|
+
/** 当前优先加载个数(只读,回显用)。 */
|
|
203
|
+
get panoPriorityCount() {
|
|
204
|
+
return this._panoPriorityCount;
|
|
205
|
+
}
|
|
196
206
|
// 复用临时对象(避免每帧 new,降低 GC 压力)
|
|
197
207
|
_tmpV3 = new Vector3();
|
|
198
208
|
/**
|
|
@@ -284,6 +294,7 @@ export default class MktMesh extends Mesh {
|
|
|
284
294
|
// `updateTexture`(它里面那记 `pump` 是「第一次拿到 renderer ⇒ 在册组整份作废」的现场)之后、
|
|
285
295
|
// `feedFaceMapping`(名册逐槽入册)之前 —— 顺序倒过来就是「名册在前、我们垫底」= 静默失效。
|
|
286
296
|
// 编辑态那份在 `MktDevMesh` 的宿主循环里照同一相对位置点(⛔ 别在这儿加第三处)。
|
|
297
|
+
// 2026-11-05 需求:这一批点几颗改由 `panoPriorityCount` 定(默认 4,-1 = 存档全部)⇒ 本句不动。
|
|
287
298
|
this.prefetchHeadPanoramas();
|
|
288
299
|
this.feedFaceMapping();
|
|
289
300
|
this.tickProgressiveReveal();
|
|
@@ -1069,6 +1080,18 @@ export default class MktMesh extends Mesh {
|
|
|
1069
1080
|
return;
|
|
1070
1081
|
this._panoTexMode = next;
|
|
1071
1082
|
}
|
|
1083
|
+
/** 设置优先加载的全景个数:-1 = 全部 / 0 = 不提前 / n = 存档前 n 个(2026-11-05 需求)。
|
|
1084
|
+
* 收口:负数(含 -2、NaN 一类)一律归到 -1「全加载」,正数向下取整;非有限值回落默认档。
|
|
1085
|
+
* 改档⛔ 需要重新 `importData` —— 名册每帧重排,下一帧 `prefetchHeadPanoramas` 直接用新数点名
|
|
1086
|
+
* (`prioritize` 幂等 ⇒ 已在队列里的那批不重复投喂,也不会把已出队的旧批再塞回去)。 */
|
|
1087
|
+
setPanoPriorityCount(count) {
|
|
1088
|
+
if (!Number.isFinite(count))
|
|
1089
|
+
this._panoPriorityCount = PANO_IMPORT_PRIORITY_DEFAULT;
|
|
1090
|
+
else {
|
|
1091
|
+
const n = Math.floor(count);
|
|
1092
|
+
this._panoPriorityCount = n < 0 ? -1 : n;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1072
1095
|
/**
|
|
1073
1096
|
* 记下「当前激活(名册 0 号槽 = 中心)是第几个全景」。
|
|
1074
1097
|
* ⭐ 皇上旨 2026-10-03 旨②:预览立方体已与墙**共用同一份材质**,原来那句「把中心那几张抄给立方体单全景材质」
|
|
@@ -1189,6 +1212,9 @@ export default class MktMesh extends Mesh {
|
|
|
1189
1212
|
ms.dispose();
|
|
1190
1213
|
}
|
|
1191
1214
|
this.models = [];
|
|
1215
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 这一路(进厅 / 切项目 / 预览的整层重建)几十颗⛔ 不再一颗一行:
|
|
1216
|
+
// 名单先攒着,循环结束打**一条**(文件名口径与 `setModelUrl` 那一记共用 `glbFileName`)⇒ 一颗不落、一行到位。
|
|
1217
|
+
const glbNames = [];
|
|
1192
1218
|
for (const d of datas) {
|
|
1193
1219
|
const ms = new ModelSlot(d.name);
|
|
1194
1220
|
ms.applyData(d);
|
|
@@ -1200,9 +1226,12 @@ export default class MktMesh extends Mesh {
|
|
|
1200
1226
|
//(⚠️ 与旧的「GLB 在路上时这一槽露出占位方块」不同:整层重建这一路一颗都不露,直到轮到自己亮)
|
|
1201
1227
|
ms.visible = false;
|
|
1202
1228
|
this._revealPending.push(ms);
|
|
1229
|
+
glbNames.push(glbFileName(d.url));
|
|
1203
1230
|
void ms.loadModel(d.url).then(() => this._queueModelMount(ms, onEachLoad));
|
|
1204
1231
|
}
|
|
1205
1232
|
}
|
|
1233
|
+
if (glbNames.length > 0)
|
|
1234
|
+
console.log(`[MktMesh] 整层重建加载 ${glbNames.length} 个 glb:${glbNames.join("、")}`);
|
|
1206
1235
|
}
|
|
1207
1236
|
/** 把「备好待挂」的那一槽交给全局队列;⛔ 去重闸门自备(`addWork` 按闭包引用判重,新闭包它判不出来) */
|
|
1208
1237
|
_queueModelMount(ms, onEachLoad) {
|
|
@@ -1641,11 +1670,12 @@ export default class MktMesh extends Mesh {
|
|
|
1641
1670
|
this.setModels((data.models ?? []).map((d) => ({ ...d, url: d.url ? absAssetUrl(d.url, pn) : "" })), onModelLoad);
|
|
1642
1671
|
}
|
|
1643
1672
|
/**
|
|
1644
|
-
* 把**存档里前 `
|
|
1673
|
+
* 把**存档里前 `panoPriorityCount` 个全景**(-1 = 整份全加载)按存档顺序登记进拼合器的**加载任务队列**
|
|
1645
1674
|
*(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
|
|
1646
1675
|
* 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
|
|
1647
1676
|
* 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
|
|
1648
|
-
*
|
|
1677
|
+
* 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」
|
|
1678
|
+
* + 2026-11-05 需求「提前加载多少个全景改由配置决定,-1 全加载 / 0 不加载 / n 前 n 个,默认 4」)。
|
|
1649
1679
|
* 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
|
|
1650
1680
|
*(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
|
|
1651
1681
|
*
|
|
@@ -1654,27 +1684,31 @@ export default class MktMesh extends Mesh {
|
|
|
1654
1684
|
* (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
|
|
1655
1685
|
* 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
|
|
1656
1686
|
* ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
|
|
1657
|
-
* ② 本方法把存档 0
|
|
1687
|
+
* ② 本方法把存档 0 号起这 n 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
|
|
1658
1688
|
* 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
|
|
1659
1689
|
* 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
|
|
1690
|
+
* n = -1(全加载)时这一条变成「整份存档按存档顺序串行」⇒ 距离名册插不到前面,出清速度取决于总片数。
|
|
1660
1691
|
* ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
|
|
1661
1692
|
* 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
|
|
1662
1693
|
* ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
|
|
1663
1694
|
* 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
|
|
1664
|
-
* `ensure` 之前 ⇒
|
|
1695
|
+
* `ensure` 之前 ⇒ 每帧把这几记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以存档次序恒不变)。
|
|
1665
1696
|
* ⚠️ 代价如实记:
|
|
1666
|
-
* · 队列非空时**其他全景一片都不发** ⇒
|
|
1697
|
+
* · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前 n 颗的那面墙会多灰一会儿。
|
|
1667
1698
|
* · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
|
|
1668
|
-
* (`_dead`),退避重试期间队首不落定 ⇒
|
|
1699
|
+
* (`_dead`),退避重试期间队首不落定 ⇒ 后面几颗跟着延后。真实数据里缺片是「地址为空/404」,
|
|
1669
1700
|
* 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
|
|
1670
|
-
* ·
|
|
1671
|
-
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)——
|
|
1701
|
+
* · 这批全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
|
|
1702
|
+
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这些景点迟早要建;
|
|
1703
|
+
* ⚠️ n = -1(全加载)时这一条按存档全景总数放大,显存与在途请求都跟着涨。
|
|
1672
1704
|
* ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
|
|
1673
1705
|
* 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
|
|
1674
1706
|
* 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
|
|
1675
1707
|
*/
|
|
1676
1708
|
prefetchHeadPanoramas() {
|
|
1677
|
-
const
|
|
1709
|
+
const total = this.textures.length;
|
|
1710
|
+
// -1 = 整份;0 = 一个都不提前(这一句直接空转,其余全景仍按距离名册排队 = 回到 10-29 之前的行为)
|
|
1711
|
+
const n = this._panoPriorityCount < 0 ? total : Math.min(this._panoPriorityCount, total);
|
|
1678
1712
|
// 正着点:0 号先入队 ⇒ 队首是它,落定后才轮到 1 号(`prioritize` 对已在队列里的键不挪位置)
|
|
1679
1713
|
for (let i = 0; i < n; i++) {
|
|
1680
1714
|
const info = this.textures[i];
|
package/MktMesh/MktMesh.ts
CHANGED
|
@@ -75,12 +75,14 @@ import { stripAssetHost, absAssetUrl } from "./assetUrl";
|
|
|
75
75
|
// 原先各住在本站两个渲染器里一份 ⇒ 下游直接用包里的 `MktMesh` 时,存档里的 `images`/`models` 两键没人读。
|
|
76
76
|
// 入库之后它们挂在 `MktMesh` **自己的子树**下,与墙同一棵变换 ⇒ 预览那三根 transform 滑杆搬得动整份展品。
|
|
77
77
|
import { ImagePlane, type ImagePlaneData } from "./imagePlane";
|
|
78
|
-
import { ModelSlot, type ModelSlotData } from "./modelSlot";
|
|
78
|
+
import { ModelSlot, glbFileName, type ModelSlotData } from "./modelSlot";
|
|
79
79
|
import { ImagePlaneTileLoader } from "./imageTileLoader";
|
|
80
80
|
|
|
81
|
-
/** 载入 JSON
|
|
82
|
-
* 口径 = 存档 `textures` 数组的前 n
|
|
83
|
-
|
|
81
|
+
/** 载入 JSON 时**优先**加载几个全景的素材的**默认档**(数值只写这一处)。
|
|
82
|
+
* 口径 = 存档 `textures` 数组的前 n 项(n = -1 视为整份全加载);其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。
|
|
83
|
+
* 2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」立的是 2;
|
|
84
|
+
* 2026-11-05 需求逐字「现在要默认把全景全部都要默认加载, 现在需要通过配置来设置, 提前加载多少个全景 -1 就是全加载 0就是不加载 1就是加载个 2就提前加载2个 一次类推, 默认加载前4个」⇒ 换成 4,并让这一档可被实例覆写。 */
|
|
85
|
+
const PANO_IMPORT_PRIORITY_DEFAULT = 4;
|
|
84
86
|
|
|
85
87
|
/** 隐藏期预热那一次绘制用的镜头(模块级共用:它只是个「有合法的投影矩阵可读」的载体,永不动 ⇒ ⛔ 不必每实例造一份)。 */
|
|
86
88
|
const GPU_WARM_CAMERA = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
|
|
@@ -231,6 +233,15 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
231
233
|
return this._panoTexMode;
|
|
232
234
|
}
|
|
233
235
|
|
|
236
|
+
/** 优先加载的全景个数(存档 `textures` 的前 n 项):**-1 = 全部、0 = 一个都不提前、n = 前 n 个**(2026-11-05 需求)。
|
|
237
|
+
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(=4);宿主想换档在渲染循环跑起来之前 `setPanoPriorityCount(-1)` 一行即可
|
|
238
|
+
* (与 `_ossSize`、`_panoTexMode` 同一套注入路径 ⇒ 编辑器与预览各自的配置各自推下来)。 */
|
|
239
|
+
private _panoPriorityCount: number = PANO_IMPORT_PRIORITY_DEFAULT;
|
|
240
|
+
/** 当前优先加载个数(只读,回显用)。 */
|
|
241
|
+
get panoPriorityCount(): number {
|
|
242
|
+
return this._panoPriorityCount;
|
|
243
|
+
}
|
|
244
|
+
|
|
234
245
|
// 复用临时对象(避免每帧 new,降低 GC 压力)
|
|
235
246
|
private _tmpV3: Vector3 = new Vector3();
|
|
236
247
|
|
|
@@ -323,6 +334,7 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
323
334
|
// `updateTexture`(它里面那记 `pump` 是「第一次拿到 renderer ⇒ 在册组整份作废」的现场)之后、
|
|
324
335
|
// `feedFaceMapping`(名册逐槽入册)之前 —— 顺序倒过来就是「名册在前、我们垫底」= 静默失效。
|
|
325
336
|
// 编辑态那份在 `MktDevMesh` 的宿主循环里照同一相对位置点(⛔ 别在这儿加第三处)。
|
|
337
|
+
// 2026-11-05 需求:这一批点几颗改由 `panoPriorityCount` 定(默认 4,-1 = 存档全部)⇒ 本句不动。
|
|
326
338
|
this.prefetchHeadPanoramas();
|
|
327
339
|
this.feedFaceMapping();
|
|
328
340
|
this.tickProgressiveReveal();
|
|
@@ -1108,6 +1120,18 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1108
1120
|
this._panoTexMode = next;
|
|
1109
1121
|
}
|
|
1110
1122
|
|
|
1123
|
+
/** 设置优先加载的全景个数:-1 = 全部 / 0 = 不提前 / n = 存档前 n 个(2026-11-05 需求)。
|
|
1124
|
+
* 收口:负数(含 -2、NaN 一类)一律归到 -1「全加载」,正数向下取整;非有限值回落默认档。
|
|
1125
|
+
* 改档⛔ 需要重新 `importData` —— 名册每帧重排,下一帧 `prefetchHeadPanoramas` 直接用新数点名
|
|
1126
|
+
* (`prioritize` 幂等 ⇒ 已在队列里的那批不重复投喂,也不会把已出队的旧批再塞回去)。 */
|
|
1127
|
+
setPanoPriorityCount(count: number): void {
|
|
1128
|
+
if (!Number.isFinite(count)) this._panoPriorityCount = PANO_IMPORT_PRIORITY_DEFAULT;
|
|
1129
|
+
else {
|
|
1130
|
+
const n = Math.floor(count);
|
|
1131
|
+
this._panoPriorityCount = n < 0 ? -1 : n;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1111
1135
|
/**
|
|
1112
1136
|
* 记下「当前激活(名册 0 号槽 = 中心)是第几个全景」。
|
|
1113
1137
|
* ⭐ 皇上旨 2026-10-03 旨②:预览立方体已与墙**共用同一份材质**,原来那句「把中心那几张抄给立方体单全景材质」
|
|
@@ -1237,6 +1261,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1237
1261
|
ms.dispose();
|
|
1238
1262
|
}
|
|
1239
1263
|
this.models = [];
|
|
1264
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 这一路(进厅 / 切项目 / 预览的整层重建)几十颗⛔ 不再一颗一行:
|
|
1265
|
+
// 名单先攒着,循环结束打**一条**(文件名口径与 `setModelUrl` 那一记共用 `glbFileName`)⇒ 一颗不落、一行到位。
|
|
1266
|
+
const glbNames: string[] = [];
|
|
1240
1267
|
for (const d of datas) {
|
|
1241
1268
|
const ms = new ModelSlot(d.name);
|
|
1242
1269
|
ms.applyData(d);
|
|
@@ -1248,9 +1275,11 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1248
1275
|
//(⚠️ 与旧的「GLB 在路上时这一槽露出占位方块」不同:整层重建这一路一颗都不露,直到轮到自己亮)
|
|
1249
1276
|
ms.visible = false;
|
|
1250
1277
|
this._revealPending.push(ms);
|
|
1278
|
+
glbNames.push(glbFileName(d.url));
|
|
1251
1279
|
void ms.loadModel(d.url).then(() => this._queueModelMount(ms, onEachLoad));
|
|
1252
1280
|
}
|
|
1253
1281
|
}
|
|
1282
|
+
if (glbNames.length > 0) console.log(`[MktMesh] 整层重建加载 ${glbNames.length} 个 glb:${glbNames.join("、")}`);
|
|
1254
1283
|
}
|
|
1255
1284
|
|
|
1256
1285
|
/** 把「备好待挂」的那一槽交给全局队列;⛔ 去重闸门自备(`addWork` 按闭包引用判重,新闭包它判不出来) */
|
|
@@ -1683,11 +1712,12 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1683
1712
|
}
|
|
1684
1713
|
|
|
1685
1714
|
/**
|
|
1686
|
-
* 把**存档里前 `
|
|
1715
|
+
* 把**存档里前 `panoPriorityCount` 个全景**(-1 = 整份全加载)按存档顺序登记进拼合器的**加载任务队列**
|
|
1687
1716
|
*(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
|
|
1688
1717
|
* 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
|
|
1689
1718
|
* 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
|
|
1690
|
-
*
|
|
1719
|
+
* 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」
|
|
1720
|
+
* + 2026-11-05 需求「提前加载多少个全景改由配置决定,-1 全加载 / 0 不加载 / n 前 n 个,默认 4」)。
|
|
1691
1721
|
* 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
|
|
1692
1722
|
*(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
|
|
1693
1723
|
*
|
|
@@ -1696,27 +1726,31 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1696
1726
|
* (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
|
|
1697
1727
|
* 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
|
|
1698
1728
|
* ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
|
|
1699
|
-
* ② 本方法把存档 0
|
|
1729
|
+
* ② 本方法把存档 0 号起这 n 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
|
|
1700
1730
|
* 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
|
|
1701
1731
|
* 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
|
|
1732
|
+
* n = -1(全加载)时这一条变成「整份存档按存档顺序串行」⇒ 距离名册插不到前面,出清速度取决于总片数。
|
|
1702
1733
|
* ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
|
|
1703
1734
|
* 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
|
|
1704
1735
|
* ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
|
|
1705
1736
|
* 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
|
|
1706
|
-
* `ensure` 之前 ⇒
|
|
1737
|
+
* `ensure` 之前 ⇒ 每帧把这几记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以存档次序恒不变)。
|
|
1707
1738
|
* ⚠️ 代价如实记:
|
|
1708
|
-
* · 队列非空时**其他全景一片都不发** ⇒
|
|
1739
|
+
* · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前 n 颗的那面墙会多灰一会儿。
|
|
1709
1740
|
* · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
|
|
1710
|
-
* (`_dead`),退避重试期间队首不落定 ⇒
|
|
1741
|
+
* (`_dead`),退避重试期间队首不落定 ⇒ 后面几颗跟着延后。真实数据里缺片是「地址为空/404」,
|
|
1711
1742
|
* 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
|
|
1712
|
-
* ·
|
|
1713
|
-
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)——
|
|
1743
|
+
* · 这批全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
|
|
1744
|
+
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这些景点迟早要建;
|
|
1745
|
+
* ⚠️ n = -1(全加载)时这一条按存档全景总数放大,显存与在途请求都跟着涨。
|
|
1714
1746
|
* ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
|
|
1715
1747
|
* 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
|
|
1716
1748
|
* 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
|
|
1717
1749
|
*/
|
|
1718
1750
|
prefetchHeadPanoramas(): void {
|
|
1719
|
-
const
|
|
1751
|
+
const total = this.textures.length;
|
|
1752
|
+
// -1 = 整份;0 = 一个都不提前(这一句直接空转,其余全景仍按距离名册排队 = 回到 10-29 之前的行为)
|
|
1753
|
+
const n = this._panoPriorityCount < 0 ? total : Math.min(this._panoPriorityCount, total);
|
|
1720
1754
|
// 正着点:0 号先入队 ⇒ 队首是它,落定后才轮到 1 号(`prioritize` 对已在队列里的键不挪位置)
|
|
1721
1755
|
for (let i = 0; i < n; i++) {
|
|
1722
1756
|
const info = this.textures[i];
|
package/MktMesh/imagePlane.d.ts
CHANGED
|
@@ -39,6 +39,10 @@ export declare class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
39
39
|
private _data;
|
|
40
40
|
/** 内容是否已落定:4 格里至少有一格真的绑上了纹理。**只朝 true 翻一次**(之后逐张显影继续,但不再回退成「没内容」)。 */
|
|
41
41
|
private _loaded;
|
|
42
|
+
/** 此刻是不是编辑器的选中态(决定上不上黄、边框亮不亮) */
|
|
43
|
+
private _selected;
|
|
44
|
+
/** 选中边框:懒建(预览/发布那一路从不选中 ⇒ 一颗都不建) */
|
|
45
|
+
private _outline;
|
|
42
46
|
constructor(name?: string);
|
|
43
47
|
get imageWidth(): number;
|
|
44
48
|
get imageHeight(): number;
|
|
@@ -57,6 +61,16 @@ export declare class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
57
61
|
tilesBindingStamp(): string;
|
|
58
62
|
highlight(): void;
|
|
59
63
|
restoreColor(): void;
|
|
64
|
+
/**
|
|
65
|
+
* 选中态的三样东西一起算(颜色 + 边框),一处规则:
|
|
66
|
+
* - 黄只在**还没上传图**那一档:片元里贴图是乘进 diffuse 的(`diffuseColor *= imgTexel`),有图还套黄 ⇒ 整张图发黄。
|
|
67
|
+
* - 边框在有图/没图两档都亮:没图那四格是整格 `discard`(一个像素都不落),只靠黄等于没有反馈,看不见也找不着。
|
|
68
|
+
*/
|
|
69
|
+
private _syncSelection;
|
|
70
|
+
/** 选中边框懒建:预览/发布那一路从不选中 ⇒ 一颗 LineSegments2 都不建(与模型槽那圈 `BoxLine2` 同口径) */
|
|
71
|
+
private _ensureOutline;
|
|
72
|
+
/** 四条边按当前几何的宽高铺在平面那一平面上(局部 z=0,中心为原点) */
|
|
73
|
+
private _layoutOutline;
|
|
60
74
|
toData(): ImagePlaneData;
|
|
61
75
|
applyData(d: ImagePlaneData): void;
|
|
62
76
|
dispose(): void;
|
package/MktMesh/imagePlane.js
CHANGED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* 而是直接 discrad 丢弃」⇒ 默认图与缺图灰两条都已删,没就绪就是透明没有。)
|
|
17
17
|
* - UV 标准 [0,1]²(PlaneGeometry 自带),4 张瓦片按 2×2 象限铺满 ⇒ 不需要投影 shader,也不需要离屏 RT。
|
|
18
18
|
*/
|
|
19
|
-
import { Color, DataTexture, FrontSide, Mesh, MeshBasicMaterial, PlaneGeometry, RGBAFormat, Vector4, } from "three";
|
|
19
|
+
import { Color, DataTexture, FrontSide, Mesh, MeshBasicMaterial, PlaneGeometry, RGBAFormat, Vector2, Vector4, } from "three";
|
|
20
|
+
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
|
|
21
|
+
import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
|
|
22
|
+
import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry.js";
|
|
20
23
|
/*
|
|
21
24
|
* ⭐ 2026-10-22 晚旨:图片平面**不用拼合器 RT**,直接把上传切好的 4 张瓦片纹理绑到材质,
|
|
22
25
|
* 片元按 UV 象限选片 + 用每片 `rtStitchTileInset` 窗口采样(与上传切片共用同一份几何 ⇒ 接缝不回潮)。
|
|
@@ -61,6 +64,9 @@ const IMG_PLANE_MAP_FRAG = `
|
|
|
61
64
|
const DEFAULT_WIDTH = 2;
|
|
62
65
|
const DEFAULT_HEIGHT = 1.5;
|
|
63
66
|
const HIGHLIGHT_COLOR = "#ffd24a";
|
|
67
|
+
/** 选中边框:颜色/粗细与模型槽那圈 `BoxLine2` 的朝向相机的棱同一档(0x00ff00 / 2 屏幕像素) */
|
|
68
|
+
const SELECT_LINE_COLOR = 0x00ff00;
|
|
69
|
+
const SELECT_LINE_WIDTH_PX = 2;
|
|
64
70
|
export class ImagePlane extends Mesh {
|
|
65
71
|
_originalColor = "#ffffff";
|
|
66
72
|
_uTiles;
|
|
@@ -72,6 +78,10 @@ export class ImagePlane extends Mesh {
|
|
|
72
78
|
};
|
|
73
79
|
/** 内容是否已落定:4 格里至少有一格真的绑上了纹理。**只朝 true 翻一次**(之后逐张显影继续,但不再回退成「没内容」)。 */
|
|
74
80
|
_loaded = false;
|
|
81
|
+
/** 此刻是不是编辑器的选中态(决定上不上黄、边框亮不亮) */
|
|
82
|
+
_selected = false;
|
|
83
|
+
/** 选中边框:懒建(预览/发布那一路从不选中 ⇒ 一颗都不建) */
|
|
84
|
+
_outline = null;
|
|
75
85
|
constructor(name) {
|
|
76
86
|
const geo = new PlaneGeometry(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
77
87
|
const mat = new MeshBasicMaterial({
|
|
@@ -115,6 +125,7 @@ export class ImagePlane extends Mesh {
|
|
|
115
125
|
setPlaneSize(w, h) {
|
|
116
126
|
this.geometry.dispose();
|
|
117
127
|
this.geometry = new PlaneGeometry(w, h);
|
|
128
|
+
this._layoutOutline(); // 边框跟着宽高走,否则面板改完尺寸框还停在旧那一圈
|
|
118
129
|
}
|
|
119
130
|
setPlaneData(d) {
|
|
120
131
|
this._data = { url: d.url, tiles: d.tiles, naturalW: d.naturalW, naturalH: d.naturalH };
|
|
@@ -125,6 +136,7 @@ export class ImagePlane extends Mesh {
|
|
|
125
136
|
get naturalH() { return this._data.naturalH; }
|
|
126
137
|
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒ 那一片整格 discard,不落像素)。uTiles 用持久对象引用 ⇒ 改 value 实时生效 */
|
|
127
138
|
setTileTextures(texs, insets) {
|
|
139
|
+
const hadContent = this._loaded;
|
|
128
140
|
for (let i = 0; i < 4; i++) {
|
|
129
141
|
const t = texs[i] || null;
|
|
130
142
|
this._uTiles.value[i] = t || PLACEHOLDER_TEX;
|
|
@@ -136,6 +148,10 @@ export class ImagePlane extends Mesh {
|
|
|
136
148
|
if (t)
|
|
137
149
|
this._loaded = true;
|
|
138
150
|
}
|
|
151
|
+
// 上传前这颗是选中态(黄),第一张图一到就把黄摘掉 ⇒ 否则「有图不上色」只在再次选中时才生效,
|
|
152
|
+
// 而他报的正是「上传后」那一刻。
|
|
153
|
+
if (!hadContent && this._loaded)
|
|
154
|
+
this._syncSelection();
|
|
139
155
|
}
|
|
140
156
|
/** 只读「此刻材质里绑了什么」的戳记:4 格就绪位 + 已绑纹理的版本号。
|
|
141
157
|
* 按需渲染的宿主拿它比对画面:逐帧绑定只写 uniform 值,⛔ 不动 `material.version`、也不动几何 ⇒ 场景指纹看不见这一变。 */
|
|
@@ -148,10 +164,66 @@ export class ImagePlane extends Mesh {
|
|
|
148
164
|
return s;
|
|
149
165
|
}
|
|
150
166
|
highlight() {
|
|
151
|
-
this.
|
|
167
|
+
this._selected = true;
|
|
168
|
+
this._syncSelection();
|
|
152
169
|
}
|
|
153
170
|
restoreColor() {
|
|
154
|
-
this.
|
|
171
|
+
this._selected = false;
|
|
172
|
+
this._syncSelection();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 选中态的三样东西一起算(颜色 + 边框),一处规则:
|
|
176
|
+
* - 黄只在**还没上传图**那一档:片元里贴图是乘进 diffuse 的(`diffuseColor *= imgTexel`),有图还套黄 ⇒ 整张图发黄。
|
|
177
|
+
* - 边框在有图/没图两档都亮:没图那四格是整格 `discard`(一个像素都不落),只靠黄等于没有反馈,看不见也找不着。
|
|
178
|
+
*/
|
|
179
|
+
_syncSelection() {
|
|
180
|
+
this.material.color.set(this._selected && !this._loaded ? HIGHLIGHT_COLOR : this._originalColor);
|
|
181
|
+
if (!this._selected) {
|
|
182
|
+
if (this._outline)
|
|
183
|
+
this._outline.visible = false;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this._ensureOutline();
|
|
187
|
+
this._layoutOutline();
|
|
188
|
+
}
|
|
189
|
+
/** 选中边框懒建:预览/发布那一路从不选中 ⇒ 一颗 LineSegments2 都不建(与模型槽那圈 `BoxLine2` 同口径) */
|
|
190
|
+
_ensureOutline() {
|
|
191
|
+
if (this._outline)
|
|
192
|
+
return;
|
|
193
|
+
const material = new LineMaterial({
|
|
194
|
+
color: SELECT_LINE_COLOR,
|
|
195
|
+
worldUnits: false,
|
|
196
|
+
linewidth: SELECT_LINE_WIDTH_PX,
|
|
197
|
+
// ⚠️ `depthTest: false`:这一圈就躺在平面自己那一平面上,留着深度测试必与图打摆(闪烁)
|
|
198
|
+
depthTest: false,
|
|
199
|
+
resolution: new Vector2(1, 1),
|
|
200
|
+
});
|
|
201
|
+
const outline = new LineSegments2(new LineSegmentsGeometry(), material);
|
|
202
|
+
outline.name = `${this.name}-选中边框`;
|
|
203
|
+
outline.renderOrder = 100; // 与 `BoxLine2` 那圈同档:压在图/墙之后
|
|
204
|
+
outline.frustumCulled = false;
|
|
205
|
+
outline.userData.pickIgnored = true;
|
|
206
|
+
outline.raycast = () => { }; // 拾取打平面本体,边框不是可点的东西
|
|
207
|
+
outline.onBeforeRender = (renderer) => {
|
|
208
|
+
renderer.getSize(material.resolution); // 屏幕像素粗细要每帧拿画布尺寸
|
|
209
|
+
};
|
|
210
|
+
this.add(outline);
|
|
211
|
+
this._outline = outline;
|
|
212
|
+
}
|
|
213
|
+
/** 四条边按当前几何的宽高铺在平面那一平面上(局部 z=0,中心为原点) */
|
|
214
|
+
_layoutOutline() {
|
|
215
|
+
if (!this._outline)
|
|
216
|
+
return;
|
|
217
|
+
const w = this.imageWidth / 2;
|
|
218
|
+
const h = this.imageHeight / 2;
|
|
219
|
+
const c = [
|
|
220
|
+
[-w, -h, 0], [w, -h, 0],
|
|
221
|
+
[w, -h, 0], [w, h, 0],
|
|
222
|
+
[w, h, 0], [-w, h, 0],
|
|
223
|
+
[-w, h, 0], [-w, -h, 0],
|
|
224
|
+
].flat();
|
|
225
|
+
this._outline.geometry.setPositions(c);
|
|
226
|
+
this._outline.visible = true;
|
|
155
227
|
}
|
|
156
228
|
toData() {
|
|
157
229
|
return {
|
|
@@ -177,6 +249,10 @@ export class ImagePlane extends Mesh {
|
|
|
177
249
|
dispose() {
|
|
178
250
|
this.geometry.dispose();
|
|
179
251
|
this.material.dispose();
|
|
252
|
+
if (this._outline) {
|
|
253
|
+
this._outline.geometry.dispose();
|
|
254
|
+
this._outline.material.dispose();
|
|
255
|
+
}
|
|
180
256
|
}
|
|
181
257
|
}
|
|
182
258
|
export const IMAGE_PLANE_DEFAULTS = { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT };
|
package/MktMesh/imagePlane.ts
CHANGED
|
@@ -25,8 +25,13 @@ import {
|
|
|
25
25
|
PlaneGeometry,
|
|
26
26
|
RGBAFormat,
|
|
27
27
|
Texture,
|
|
28
|
+
Vector2,
|
|
28
29
|
Vector4,
|
|
30
|
+
WebGLRenderer,
|
|
29
31
|
} from "three";
|
|
32
|
+
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
|
|
33
|
+
import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
|
|
34
|
+
import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry.js";
|
|
30
35
|
|
|
31
36
|
/*
|
|
32
37
|
* ⭐ 2026-10-22 晚旨:图片平面**不用拼合器 RT**,直接把上传切好的 4 张瓦片纹理绑到材质,
|
|
@@ -92,6 +97,9 @@ export interface ImagePlaneData {
|
|
|
92
97
|
const DEFAULT_WIDTH = 2;
|
|
93
98
|
const DEFAULT_HEIGHT = 1.5;
|
|
94
99
|
const HIGHLIGHT_COLOR = "#ffd24a";
|
|
100
|
+
/** 选中边框:颜色/粗细与模型槽那圈 `BoxLine2` 的朝向相机的棱同一档(0x00ff00 / 2 屏幕像素) */
|
|
101
|
+
const SELECT_LINE_COLOR = 0x00ff00;
|
|
102
|
+
const SELECT_LINE_WIDTH_PX = 2;
|
|
95
103
|
|
|
96
104
|
export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
97
105
|
private _originalColor: string = "#ffffff";
|
|
@@ -104,6 +112,10 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
104
112
|
};
|
|
105
113
|
/** 内容是否已落定:4 格里至少有一格真的绑上了纹理。**只朝 true 翻一次**(之后逐张显影继续,但不再回退成「没内容」)。 */
|
|
106
114
|
private _loaded = false;
|
|
115
|
+
/** 此刻是不是编辑器的选中态(决定上不上黄、边框亮不亮) */
|
|
116
|
+
private _selected = false;
|
|
117
|
+
/** 选中边框:懒建(预览/发布那一路从不选中 ⇒ 一颗都不建) */
|
|
118
|
+
private _outline: LineSegments2 | null = null;
|
|
107
119
|
|
|
108
120
|
constructor(name?: string) {
|
|
109
121
|
const geo = new PlaneGeometry(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
@@ -153,6 +165,7 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
153
165
|
setPlaneSize(w: number, h: number): void {
|
|
154
166
|
this.geometry.dispose();
|
|
155
167
|
this.geometry = new PlaneGeometry(w, h);
|
|
168
|
+
this._layoutOutline(); // 边框跟着宽高走,否则面板改完尺寸框还停在旧那一圈
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
setPlaneData(d: Pick<ImagePlaneData, "url" | "tiles" | "naturalW" | "naturalH">): void {
|
|
@@ -166,6 +179,7 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
166
179
|
|
|
167
180
|
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒ 那一片整格 discard,不落像素)。uTiles 用持久对象引用 ⇒ 改 value 实时生效 */
|
|
168
181
|
setTileTextures(texs: (Texture | null)[], insets: Vector4[]): void {
|
|
182
|
+
const hadContent = this._loaded;
|
|
169
183
|
for (let i = 0; i < 4; i++) {
|
|
170
184
|
const t = texs[i] || null;
|
|
171
185
|
this._uTiles.value[i] = t || PLACEHOLDER_TEX;
|
|
@@ -175,6 +189,9 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
175
189
|
// 「逐张就绪逐张显影」是本文件 10-22 晚定下的口径,等到全到齐会让先到的那几格白等几帧)。
|
|
176
190
|
if (t) this._loaded = true;
|
|
177
191
|
}
|
|
192
|
+
// 上传前这颗是选中态(黄),第一张图一到就把黄摘掉 ⇒ 否则「有图不上色」只在再次选中时才生效,
|
|
193
|
+
// 而他报的正是「上传后」那一刻。
|
|
194
|
+
if (!hadContent && this._loaded) this._syncSelection();
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
/** 只读「此刻材质里绑了什么」的戳记:4 格就绪位 + 已绑纹理的版本号。
|
|
@@ -189,11 +206,67 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
189
206
|
}
|
|
190
207
|
|
|
191
208
|
highlight(): void {
|
|
192
|
-
this.
|
|
209
|
+
this._selected = true;
|
|
210
|
+
this._syncSelection();
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
restoreColor(): void {
|
|
196
|
-
this.
|
|
214
|
+
this._selected = false;
|
|
215
|
+
this._syncSelection();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 选中态的三样东西一起算(颜色 + 边框),一处规则:
|
|
220
|
+
* - 黄只在**还没上传图**那一档:片元里贴图是乘进 diffuse 的(`diffuseColor *= imgTexel`),有图还套黄 ⇒ 整张图发黄。
|
|
221
|
+
* - 边框在有图/没图两档都亮:没图那四格是整格 `discard`(一个像素都不落),只靠黄等于没有反馈,看不见也找不着。
|
|
222
|
+
*/
|
|
223
|
+
private _syncSelection(): void {
|
|
224
|
+
this.material.color.set(this._selected && !this._loaded ? HIGHLIGHT_COLOR : this._originalColor);
|
|
225
|
+
if (!this._selected) {
|
|
226
|
+
if (this._outline) this._outline.visible = false;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this._ensureOutline();
|
|
230
|
+
this._layoutOutline();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** 选中边框懒建:预览/发布那一路从不选中 ⇒ 一颗 LineSegments2 都不建(与模型槽那圈 `BoxLine2` 同口径) */
|
|
234
|
+
private _ensureOutline(): void {
|
|
235
|
+
if (this._outline) return;
|
|
236
|
+
const material = new LineMaterial({
|
|
237
|
+
color: SELECT_LINE_COLOR,
|
|
238
|
+
worldUnits: false,
|
|
239
|
+
linewidth: SELECT_LINE_WIDTH_PX,
|
|
240
|
+
// ⚠️ `depthTest: false`:这一圈就躺在平面自己那一平面上,留着深度测试必与图打摆(闪烁)
|
|
241
|
+
depthTest: false,
|
|
242
|
+
resolution: new Vector2(1, 1),
|
|
243
|
+
});
|
|
244
|
+
const outline = new LineSegments2(new LineSegmentsGeometry(), material);
|
|
245
|
+
outline.name = `${this.name}-选中边框`;
|
|
246
|
+
outline.renderOrder = 100; // 与 `BoxLine2` 那圈同档:压在图/墙之后
|
|
247
|
+
outline.frustumCulled = false;
|
|
248
|
+
outline.userData.pickIgnored = true;
|
|
249
|
+
outline.raycast = (): void => { }; // 拾取打平面本体,边框不是可点的东西
|
|
250
|
+
outline.onBeforeRender = (renderer: WebGLRenderer): void => {
|
|
251
|
+
renderer.getSize(material.resolution); // 屏幕像素粗细要每帧拿画布尺寸
|
|
252
|
+
};
|
|
253
|
+
this.add(outline);
|
|
254
|
+
this._outline = outline;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 四条边按当前几何的宽高铺在平面那一平面上(局部 z=0,中心为原点) */
|
|
258
|
+
private _layoutOutline(): void {
|
|
259
|
+
if (!this._outline) return;
|
|
260
|
+
const w = this.imageWidth / 2;
|
|
261
|
+
const h = this.imageHeight / 2;
|
|
262
|
+
const c = [
|
|
263
|
+
[-w, -h, 0], [w, -h, 0],
|
|
264
|
+
[w, -h, 0], [w, h, 0],
|
|
265
|
+
[w, h, 0], [-w, h, 0],
|
|
266
|
+
[-w, h, 0], [-w, -h, 0],
|
|
267
|
+
].flat();
|
|
268
|
+
this._outline.geometry.setPositions(c);
|
|
269
|
+
this._outline.visible = true;
|
|
197
270
|
}
|
|
198
271
|
|
|
199
272
|
toData(): ImagePlaneData {
|
|
@@ -221,6 +294,10 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
221
294
|
dispose(): void {
|
|
222
295
|
this.geometry.dispose();
|
|
223
296
|
this.material.dispose();
|
|
297
|
+
if (this._outline) {
|
|
298
|
+
this._outline.geometry.dispose();
|
|
299
|
+
this._outline.material.dispose();
|
|
300
|
+
}
|
|
224
301
|
}
|
|
225
302
|
}
|
|
226
303
|
|
package/MktMesh/modelSlot.d.ts
CHANGED
package/MktMesh/modelSlot.js
CHANGED
|
@@ -32,6 +32,10 @@ import { Box3, BoxGeometry, Color, Group, Mesh, MeshBasicMaterial, Vector3, } fr
|
|
|
32
32
|
import threeLoader from "../threeLoader";
|
|
33
33
|
import threeUtils from "../threeUtils";
|
|
34
34
|
import BoxLine2 from "./boxLine2";
|
|
35
|
+
/** glb 地址 → 文件名(日志唯一口径:去掉查询串取最后一段;⛔ 别在打印点各写一份 split) */
|
|
36
|
+
export function glbFileName(url) {
|
|
37
|
+
return url.split("?")[0].split("/").pop() || url;
|
|
38
|
+
}
|
|
35
39
|
/** 占位小立方体的棱长(米)—— 皇上要的是「小立方体」,比「加立方体」那颗 1m 的单位方块小一档 */
|
|
36
40
|
export const MODEL_SLOT_PLACEHOLDER = 0.3;
|
|
37
41
|
/** 默认尺寸档(米,最长边):还没上传模型时占位方块用不上它;上传成功后 `useNativeSize()` 会把它填成模型原最长边 ⇒ 一比一 */
|
|
@@ -179,6 +183,9 @@ export class ModelSlot extends Group {
|
|
|
179
183
|
this._loaded = true;
|
|
180
184
|
return true;
|
|
181
185
|
}
|
|
186
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 这一颗的取回入口⛔ 不出声:整层重建一次几十颗,逐颗一行会刷屏。
|
|
187
|
+
// 文件名改由两个批次入口各打一条 —— 单颗动作(上传/复制/换地址)在下面的 `setModelUrl`,
|
|
188
|
+
// 整层重建在 `MktMesh.setModels`(那边一条列全)⇒ 一颗不落,但一拍一条。
|
|
182
189
|
// 取回失败(404 / 解析报错都从这一档走):`threeLoader.loadModel` 是会 reject 的,⚠️ 必须当「落定在占位方块」处理
|
|
183
190
|
// —— 整层重建时这一颗是压着不亮的,不落定就永远隐形(旧写法里它至少还露得出占位方块)。
|
|
184
191
|
let obj;
|
|
@@ -224,6 +231,11 @@ export class ModelSlot extends Group {
|
|
|
224
231
|
* @returns 是否真的挂上了(地址无效/载入失败/这次结果已作废 ⇒ false;空地址 = 退回占位,返回 true)
|
|
225
232
|
*/
|
|
226
233
|
async setModelUrl(url) {
|
|
234
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 逐颗一行从 `loadModel` 挪到这里:这一路一次动作只动一/几颗
|
|
235
|
+
// (上传 .glb、复制模型、换同名 glb),一条就是他能对上号的那一颗;整层重建那几十颗由
|
|
236
|
+
// `MktMesh.setModels` 攒成一条 ⇒ 文件名仍「一颗不落」,行数从 N 降到 1。空地址 = 退回占位,不打。
|
|
237
|
+
if (url)
|
|
238
|
+
console.log(`[MktMesh] 加载 glb「${this.name}」:${glbFileName(url)}`);
|
|
227
239
|
if (!(await this.loadModel(url)))
|
|
228
240
|
return false;
|
|
229
241
|
if (this._staged)
|
package/MktMesh/modelSlot.ts
CHANGED
|
@@ -42,6 +42,11 @@ import threeLoader from "../threeLoader";
|
|
|
42
42
|
import threeUtils from "../threeUtils";
|
|
43
43
|
import BoxLine2 from "./boxLine2";
|
|
44
44
|
|
|
45
|
+
/** glb 地址 → 文件名(日志唯一口径:去掉查询串取最后一段;⛔ 别在打印点各写一份 split) */
|
|
46
|
+
export function glbFileName(url: string): string {
|
|
47
|
+
return url.split("?")[0].split("/").pop() || url;
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
/** 模型槽的持久化数据(进 exportData / importData / 预览 loadData) */
|
|
46
51
|
export interface ModelSlotData {
|
|
47
52
|
name?: string;
|
|
@@ -219,6 +224,9 @@ export class ModelSlot extends Group {
|
|
|
219
224
|
this._loaded = true;
|
|
220
225
|
return true;
|
|
221
226
|
}
|
|
227
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 这一颗的取回入口⛔ 不出声:整层重建一次几十颗,逐颗一行会刷屏。
|
|
228
|
+
// 文件名改由两个批次入口各打一条 —— 单颗动作(上传/复制/换地址)在下面的 `setModelUrl`,
|
|
229
|
+
// 整层重建在 `MktMesh.setModels`(那边一条列全)⇒ 一颗不落,但一拍一条。
|
|
222
230
|
// 取回失败(404 / 解析报错都从这一档走):`threeLoader.loadModel` 是会 reject 的,⚠️ 必须当「落定在占位方块」处理
|
|
223
231
|
// —— 整层重建时这一颗是压着不亮的,不落定就永远隐形(旧写法里它至少还露得出占位方块)。
|
|
224
232
|
let obj: Object3D | undefined;
|
|
@@ -263,6 +271,10 @@ export class ModelSlot extends Group {
|
|
|
263
271
|
* @returns 是否真的挂上了(地址无效/载入失败/这次结果已作废 ⇒ false;空地址 = 退回占位,返回 true)
|
|
264
272
|
*/
|
|
265
273
|
async setModelUrl(url: string): Promise<boolean> {
|
|
274
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 逐颗一行从 `loadModel` 挪到这里:这一路一次动作只动一/几颗
|
|
275
|
+
// (上传 .glb、复制模型、换同名 glb),一条就是他能对上号的那一颗;整层重建那几十颗由
|
|
276
|
+
// `MktMesh.setModels` 攒成一条 ⇒ 文件名仍「一颗不落」,行数从 N 降到 1。空地址 = 退回占位,不打。
|
|
277
|
+
if (url) console.log(`[MktMesh] 加载 glb「${this.name}」:${glbFileName(url)}`);
|
|
266
278
|
if (!(await this.loadModel(url))) return false;
|
|
267
279
|
if (this._staged) this.mountStaged();
|
|
268
280
|
return true;
|
package/ThreeBase.js
CHANGED
|
@@ -971,10 +971,8 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
971
971
|
// compare → R=全局 base 深度,G=最近一次 glow/stroke 选区的 select 深度。两路任一全白即该侧写入失败。
|
|
972
972
|
// judge → R=base 原始深度、G=select 原始深度、B=判定结果(1=判为被遮挡应 discard)。
|
|
973
973
|
// strokeSelect → 【描边步骤 1】选区本体深度(selectDepthTexture),白色=空背景、灰=物体本体。
|
|
974
|
-
// strokeSelectBlur → 【描边步骤 5.5】描边外扩后的选区深度(selectDepthBlurTextureTarget),
|
|
975
|
-
// 物体轮廓比本体档更胖一圈即外扩生效。
|
|
976
974
|
// 通用控制台入口(任意页面可用,优先级高于面板属性):
|
|
977
|
-
// `window.__TB_DEBUG_DEPTH = "global" | "ao" | "compare" | "judge" | "strokeSelect"
|
|
975
|
+
// `window.__TB_DEBUG_DEPTH = "global" | "ao" | "compare" | "judge" | "strokeSelect"`,
|
|
978
976
|
// 设回 "none" 恢复。
|
|
979
977
|
const dbgWin = window.__TB_DEBUG_DEPTH;
|
|
980
978
|
const dbgMode = window.__BASE_DEPTH_VIS
|
|
@@ -1011,13 +1009,6 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
1011
1009
|
debugDepthState.lastSelectDepth ?? this.baseDepthTexture;
|
|
1012
1010
|
fsQuad.render(this.renderer, baseDepthVisShader);
|
|
1013
1011
|
}
|
|
1014
|
-
else if (dbgMode === "strokeSelectBlur") {
|
|
1015
|
-
// 描边步骤 5.5:外扩后的选区深度(RGBA8 目标,对数深度存在 .r 通道)。
|
|
1016
|
-
// 与本体档对比看:外扩生效时物体轮廓会「胖一圈」,被遮挡的外扩环则被判 discard 后不参与。
|
|
1017
|
-
baseDepthVisShader.uniforms.tDepth.value =
|
|
1018
|
-
debugDepthState.lastSelectBlurDepth ?? this.baseDepthTexture;
|
|
1019
|
-
fsQuad.render(this.renderer, baseDepthVisShader);
|
|
1020
|
-
}
|
|
1021
1012
|
flushLensflareOverlays(this.renderer, camera);
|
|
1022
1013
|
return;
|
|
1023
1014
|
}
|
package/ThreeBase.ts
CHANGED
|
@@ -1071,10 +1071,8 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
1071
1071
|
// compare → R=全局 base 深度,G=最近一次 glow/stroke 选区的 select 深度。两路任一全白即该侧写入失败。
|
|
1072
1072
|
// judge → R=base 原始深度、G=select 原始深度、B=判定结果(1=判为被遮挡应 discard)。
|
|
1073
1073
|
// strokeSelect → 【描边步骤 1】选区本体深度(selectDepthTexture),白色=空背景、灰=物体本体。
|
|
1074
|
-
// strokeSelectBlur → 【描边步骤 5.5】描边外扩后的选区深度(selectDepthBlurTextureTarget),
|
|
1075
|
-
// 物体轮廓比本体档更胖一圈即外扩生效。
|
|
1076
1074
|
// 通用控制台入口(任意页面可用,优先级高于面板属性):
|
|
1077
|
-
// `window.__TB_DEBUG_DEPTH = "global" | "ao" | "compare" | "judge" | "strokeSelect"
|
|
1075
|
+
// `window.__TB_DEBUG_DEPTH = "global" | "ao" | "compare" | "judge" | "strokeSelect"`,
|
|
1078
1076
|
// 设回 "none" 恢复。
|
|
1079
1077
|
const dbgWin = (window as unknown as { __TB_DEBUG_DEPTH?: DebugDepthMode }).__TB_DEBUG_DEPTH;
|
|
1080
1078
|
const dbgMode = (window as unknown as { __BASE_DEPTH_VIS?: boolean }).__BASE_DEPTH_VIS
|
|
@@ -1106,12 +1104,6 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
1106
1104
|
baseDepthVisShader.uniforms.tDepth.value =
|
|
1107
1105
|
debugDepthState.lastSelectDepth ?? this.baseDepthTexture;
|
|
1108
1106
|
fsQuad.render(this.renderer, baseDepthVisShader);
|
|
1109
|
-
} else if (dbgMode === "strokeSelectBlur") {
|
|
1110
|
-
// 描边步骤 5.5:外扩后的选区深度(RGBA8 目标,对数深度存在 .r 通道)。
|
|
1111
|
-
// 与本体档对比看:外扩生效时物体轮廓会「胖一圈」,被遮挡的外扩环则被判 discard 后不参与。
|
|
1112
|
-
baseDepthVisShader.uniforms.tDepth.value =
|
|
1113
|
-
debugDepthState.lastSelectBlurDepth ?? this.baseDepthTexture;
|
|
1114
|
-
fsQuad.render(this.renderer, baseDepthVisShader);
|
|
1115
1107
|
}
|
|
1116
1108
|
flushLensflareOverlays(this.renderer, camera);
|
|
1117
1109
|
return;
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DepthTexture
|
|
1
|
+
import type { DepthTexture } from "three";
|
|
2
2
|
/**
|
|
3
3
|
* 深度调试预览模式(面板按钮与控制台 `window.__TB_DEBUG_DEPTH` 共用同一枚举):
|
|
4
4
|
* - "none" :正常渲染(默认)
|
|
@@ -7,22 +7,18 @@ import type { DepthTexture, Texture } from "three";
|
|
|
7
7
|
* - "compare" :R=base 深度、G=选区 select 深度(对数反解后归一化)
|
|
8
8
|
* - "judge" :R=base 原始深度、G=select 原始深度、B=判定结果(1=判为被遮挡应 discard)
|
|
9
9
|
* - "strokeSelect" :【描边步骤 1】选区本体深度(selectDepthTexture,mask 那趟光栅化顺带写入)
|
|
10
|
-
* - "strokeSelectBlur":【描边步骤 5.5】描边外扩后的选区深度(selectDepthBlurTextureTarget,
|
|
11
|
-
* RGBA8 的 R 通道存对数深度,外扩环取邻域最近深度)
|
|
12
10
|
* ⚠️ 2026-10-18 皇上旨「所有的对象都要进入到深度图里面了,不需要用参数过滤掉了」⇒ 原 "excluded"
|
|
13
11
|
* 档(把被排除的面叠红)连同 ssao_pass 的排除判据整条删除,已无「写不进深度图的面」可标。
|
|
12
|
+
* ⚠️ 2026-11-04 描边回到年初口径 ⇒ 原 "strokeSelectBlur" 档(步骤 5.5 外扩后的选区深度)连同
|
|
13
|
+
* 那条 min-blur 整条删除,管线里已无「外扩深度」可供显示。
|
|
14
14
|
*/
|
|
15
|
-
export type DebugDepthMode = "none" | "global" | "ao" | "compare" | "judge" | "strokeSelect"
|
|
15
|
+
export type DebugDepthMode = "none" | "global" | "ao" | "compare" | "judge" | "strokeSelect";
|
|
16
16
|
/**
|
|
17
17
|
* 调试用全局状态:记录最近一次 glow_pass / stroke_pass 渲染的「选区自身深度图」(selectDepth)。
|
|
18
18
|
* 供 ThreeBase 渲染末尾的「辉光深度比对」调试视图读取(R=全局 base 深度,G=选区 select 深度),
|
|
19
19
|
* 让用户一眼看出到底是「全局 base 深度没写进」还是「选区自身深度没写进」。
|
|
20
20
|
* 由各 pass 在渲染完 selectDepth 后写入;每帧会被最后一个被处理的可见对象覆盖(调试够用)。
|
|
21
|
-
*
|
|
22
|
-
* lastSelectBlurDepth:描边专属(步骤 5.5 外扩后的选区深度)。它是 RGBA8 目标(不是 DepthTexture),
|
|
23
|
-
* 深度值存在 .r 通道里 —— 可视化时同样取 .r 即可。glow/cover/frosted 没有这一步,故只有 stroke_pass 写。
|
|
24
21
|
*/
|
|
25
22
|
export declare const debugDepthState: {
|
|
26
23
|
lastSelectDepth: DepthTexture | null;
|
|
27
|
-
lastSelectBlurDepth: Texture | null;
|
|
28
24
|
};
|
package/pass/debugDepthState.js
CHANGED
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
* 供 ThreeBase 渲染末尾的「辉光深度比对」调试视图读取(R=全局 base 深度,G=选区 select 深度),
|
|
4
4
|
* 让用户一眼看出到底是「全局 base 深度没写进」还是「选区自身深度没写进」。
|
|
5
5
|
* 由各 pass 在渲染完 selectDepth 后写入;每帧会被最后一个被处理的可见对象覆盖(调试够用)。
|
|
6
|
-
*
|
|
7
|
-
* lastSelectBlurDepth:描边专属(步骤 5.5 外扩后的选区深度)。它是 RGBA8 目标(不是 DepthTexture),
|
|
8
|
-
* 深度值存在 .r 通道里 —— 可视化时同样取 .r 即可。glow/cover/frosted 没有这一步,故只有 stroke_pass 写。
|
|
9
6
|
*/
|
|
10
7
|
export const debugDepthState = {
|
|
11
8
|
lastSelectDepth: null,
|
|
12
|
-
lastSelectBlurDepth: null,
|
|
13
9
|
};
|
package/pass/debugDepthState.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DepthTexture
|
|
1
|
+
import type { DepthTexture } from "three";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 深度调试预览模式(面板按钮与控制台 `window.__TB_DEBUG_DEPTH` 共用同一枚举):
|
|
@@ -8,10 +8,10 @@ import type { DepthTexture, Texture } from "three";
|
|
|
8
8
|
* - "compare" :R=base 深度、G=选区 select 深度(对数反解后归一化)
|
|
9
9
|
* - "judge" :R=base 原始深度、G=select 原始深度、B=判定结果(1=判为被遮挡应 discard)
|
|
10
10
|
* - "strokeSelect" :【描边步骤 1】选区本体深度(selectDepthTexture,mask 那趟光栅化顺带写入)
|
|
11
|
-
* - "strokeSelectBlur":【描边步骤 5.5】描边外扩后的选区深度(selectDepthBlurTextureTarget,
|
|
12
|
-
* RGBA8 的 R 通道存对数深度,外扩环取邻域最近深度)
|
|
13
11
|
* ⚠️ 2026-10-18 皇上旨「所有的对象都要进入到深度图里面了,不需要用参数过滤掉了」⇒ 原 "excluded"
|
|
14
12
|
* 档(把被排除的面叠红)连同 ssao_pass 的排除判据整条删除,已无「写不进深度图的面」可标。
|
|
13
|
+
* ⚠️ 2026-11-04 描边回到年初口径 ⇒ 原 "strokeSelectBlur" 档(步骤 5.5 外扩后的选区深度)连同
|
|
14
|
+
* 那条 min-blur 整条删除,管线里已无「外扩深度」可供显示。
|
|
15
15
|
*/
|
|
16
16
|
export type DebugDepthMode =
|
|
17
17
|
| "none"
|
|
@@ -19,22 +19,16 @@ export type DebugDepthMode =
|
|
|
19
19
|
| "ao"
|
|
20
20
|
| "compare"
|
|
21
21
|
| "judge"
|
|
22
|
-
| "strokeSelect"
|
|
23
|
-
| "strokeSelectBlur";
|
|
22
|
+
| "strokeSelect";
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* 调试用全局状态:记录最近一次 glow_pass / stroke_pass 渲染的「选区自身深度图」(selectDepth)。
|
|
27
26
|
* 供 ThreeBase 渲染末尾的「辉光深度比对」调试视图读取(R=全局 base 深度,G=选区 select 深度),
|
|
28
27
|
* 让用户一眼看出到底是「全局 base 深度没写进」还是「选区自身深度没写进」。
|
|
29
28
|
* 由各 pass 在渲染完 selectDepth 后写入;每帧会被最后一个被处理的可见对象覆盖(调试够用)。
|
|
30
|
-
*
|
|
31
|
-
* lastSelectBlurDepth:描边专属(步骤 5.5 外扩后的选区深度)。它是 RGBA8 目标(不是 DepthTexture),
|
|
32
|
-
* 深度值存在 .r 通道里 —— 可视化时同样取 .r 即可。glow/cover/frosted 没有这一步,故只有 stroke_pass 写。
|
|
33
29
|
*/
|
|
34
30
|
export const debugDepthState: {
|
|
35
31
|
lastSelectDepth: DepthTexture | null;
|
|
36
|
-
lastSelectBlurDepth: Texture | null;
|
|
37
32
|
} = {
|
|
38
33
|
lastSelectDepth: null,
|
|
39
|
-
lastSelectBlurDepth: null,
|
|
40
34
|
};
|
package/pass/stroke_pass.js
CHANGED
|
@@ -38,12 +38,6 @@ const selectDepthTestTextureTarget = new WebGLRenderTarget(1024, 1024, {
|
|
|
38
38
|
});
|
|
39
39
|
const blur1TextureTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
40
40
|
const blur2TextureTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
41
|
-
// 外扩深度临时/结果目标:depthMinBlurShader 把本体深度图随描边一起外扩,
|
|
42
|
-
// 外扩区取邻域最近深度(忽略背景 1.0),使外扩区 selD<1.0,depthTest 才能正确判遮挡(不漏光)。
|
|
43
|
-
const selectDepthBlurTmpTextureTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
44
|
-
const selectDepthBlurTextureTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
45
|
-
// 外扩描边经深度测试后的结果(步骤 5.6):独立 target,避免回写 blur1 触发历史回归 bug
|
|
46
|
-
const blurDepthTestTextureTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
47
41
|
const size = new Vector2();
|
|
48
42
|
const depthTestShader = new ShaderMaterial({
|
|
49
43
|
uniforms: {
|
|
@@ -383,52 +377,6 @@ const strokeShader = new RawShaderMaterial({
|
|
|
383
377
|
depthWrite: false,
|
|
384
378
|
transparent: true
|
|
385
379
|
});
|
|
386
|
-
// 深度 min-blur(最近深度,忽略背景 1.0):随描边一起把深度图向外扩,
|
|
387
|
-
// 外扩区 selD = 物体最近深度(<1.0) 而非清屏 1.0,depthTest 在外扩区才能正确判遮挡(不漏光),
|
|
388
|
-
// 同时本体区仍靠 depthTestShader 的 EPSILON 容差防自遮挡闪烁(无缝)。separable(direction 控制 H/V)。
|
|
389
|
-
const depthMinBlurShader = new ShaderMaterial({
|
|
390
|
-
uniforms: {
|
|
391
|
-
tDepth: { value: null },
|
|
392
|
-
resolution: { value: new Vector2(1 / 512, 1 / 512) },
|
|
393
|
-
radius: { value: 1.0 },
|
|
394
|
-
direction: { value: new Vector2(1, 0) },
|
|
395
|
-
},
|
|
396
|
-
vertexShader: `
|
|
397
|
-
${ShaderChunk.common}
|
|
398
|
-
${ShaderChunk.logdepthbuf_pars_vertex}
|
|
399
|
-
varying vec2 vUv;
|
|
400
|
-
void main() {
|
|
401
|
-
vUv = uv;
|
|
402
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
403
|
-
${ShaderChunk.logdepthbuf_vertex}
|
|
404
|
-
}
|
|
405
|
-
`,
|
|
406
|
-
fragmentShader: `
|
|
407
|
-
${ShaderChunk.logdepthbuf_pars_fragment}
|
|
408
|
-
uniform sampler2D tDepth;
|
|
409
|
-
uniform vec2 resolution;
|
|
410
|
-
uniform float radius;
|
|
411
|
-
uniform vec2 direction;
|
|
412
|
-
varying vec2 vUv;
|
|
413
|
-
void main() {
|
|
414
|
-
float minD = 1.0;
|
|
415
|
-
for (int i = -10; i <= 10; i++) {
|
|
416
|
-
float x = float(i);
|
|
417
|
-
vec2 off = direction * (x * radius * resolution);
|
|
418
|
-
float d = texture2D(tDepth, vUv + off).r;
|
|
419
|
-
if (d < 0.9999) {
|
|
420
|
-
minD = min(minD, d);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
gl_FragColor = vec4(minD, 0.0, 0.0, 1.0);
|
|
424
|
-
${ShaderChunk.logdepthbuf_fragment}
|
|
425
|
-
}
|
|
426
|
-
`,
|
|
427
|
-
blending: NoBlending,
|
|
428
|
-
depthTest: false,
|
|
429
|
-
depthWrite: false,
|
|
430
|
-
transparent: true,
|
|
431
|
-
});
|
|
432
380
|
const filterSelectShader = new RawShaderMaterial({
|
|
433
381
|
uniforms: {
|
|
434
382
|
selectTexture: { value: null },
|
|
@@ -508,9 +456,6 @@ export default function stroke_pass(renderer, camera, object3d, baseDepthTexture
|
|
|
508
456
|
selectDepthTestTextureTarget.setSize(size.x, size.y);
|
|
509
457
|
blur1TextureTarget.setSize(size.x, size.y);
|
|
510
458
|
blur2TextureTarget.setSize(size.x, size.y);
|
|
511
|
-
selectDepthBlurTmpTextureTarget.setSize(size.x, size.y);
|
|
512
|
-
selectDepthBlurTextureTarget.setSize(size.x, size.y);
|
|
513
|
-
blurDepthTestTextureTarget.setSize(size.x, size.y);
|
|
514
459
|
// 1. 渲染物体本体 mask(灰度)。⭐ 本体深度由同一趟光栅化顺带写进 selectDepthTexture
|
|
515
460
|
// (挂在 selectTextureTarget 的 depth attachment 上)—— 原先这里还有第 2 趟「只渲深度」,
|
|
516
461
|
// 同材质/同相机/同几何 ⇒ 深度逐位相同,纯重复遍历,2026-10-11 皇上旨简化时删除。
|
|
@@ -532,7 +477,8 @@ export default function stroke_pass(renderer, camera, object3d, baseDepthTexture
|
|
|
532
477
|
depthTestShader.uniforms.cameraFar.value = camera.far;
|
|
533
478
|
depthTestShader.uniforms.uDebugVis.value = (window.__STROKE_DEPTH_VIS) ? 1.0 : 0.0;
|
|
534
479
|
fsQuad.render(renderer, depthTestShader);
|
|
535
|
-
// 4.
|
|
480
|
+
// 4. 从「深度测试后的本体描边」开始外扩(外扩的源头是本体判过的结果,被挡掉的本体不会再长出描边;
|
|
481
|
+
// 但外扩出来那圈像素本身不再判深度 ⇒ 贴着遮挡物时会压到遮挡物前面,见步骤 6 的代价点名),
|
|
536
482
|
// 描边粗细由 stroke_width 控制的迭代次数决定。
|
|
537
483
|
strokeShader.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
|
|
538
484
|
FXAAShaderMaterial.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
|
|
@@ -557,42 +503,14 @@ export default function stroke_pass(renderer, camera, object3d, baseDepthTexture
|
|
|
557
503
|
renderer.setRenderTarget(blur1TextureTarget);
|
|
558
504
|
FXAAShaderMaterial.uniforms.tDiffuse.value = blur2TextureTarget.texture;
|
|
559
505
|
fsQuad.render(renderer, FXAAShaderMaterial);
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
depthMinBlurShader.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
|
|
566
|
-
depthMinBlurShader.uniforms.radius.value = Math.max(1, stroke_width);
|
|
567
|
-
// H:直接采 DepthTexture(selectDepthTexture),其 .r 即对数深度
|
|
568
|
-
depthMinBlurShader.uniforms.direction.value.set(1, 0);
|
|
569
|
-
depthMinBlurShader.uniforms.tDepth.value = selectDepthTexture;
|
|
570
|
-
renderer.setRenderTarget(selectDepthBlurTmpTextureTarget);
|
|
571
|
-
fsQuad.render(renderer, depthMinBlurShader);
|
|
572
|
-
// V
|
|
573
|
-
depthMinBlurShader.uniforms.direction.value.set(0, 1);
|
|
574
|
-
depthMinBlurShader.uniforms.tDepth.value = selectDepthBlurTmpTextureTarget.texture;
|
|
575
|
-
renderer.setRenderTarget(selectDepthBlurTextureTarget);
|
|
576
|
-
fsQuad.render(renderer, depthMinBlurShader);
|
|
577
|
-
// 调试:记录「外扩后的选区深度」,供面板「深度图(分步)」的步骤 5.5 档读取。
|
|
578
|
-
// 它是 RGBA8 目标、对数深度存在 .r 里(与 DepthTexture 取 .r 的读法一致)。
|
|
579
|
-
debugDepthState.lastSelectBlurDepth = selectDepthBlurTextureTarget.texture;
|
|
580
|
-
// 5.6 对外扩后的描边判遮挡:被更近几何挡住的外扩环像素 discard。
|
|
581
|
-
// ⚠️ 输出到独立 target,绝不回写 blur1(历史回归 bug:blur1 被 depthTest 结果覆盖
|
|
582
|
-
// 会导致步骤 6 相减归零、描边全不显示)。
|
|
583
|
-
renderer.setRenderTarget(blurDepthTestTextureTarget);
|
|
584
|
-
renderer.clear();
|
|
585
|
-
depthTestShader.uniforms.map.value = blur1TextureTarget.texture;
|
|
586
|
-
depthTestShader.uniforms.baseDepth.value = baseDepthTexture;
|
|
587
|
-
depthTestShader.uniforms.selectDepth.value = selectDepthBlurTextureTarget.texture;
|
|
588
|
-
depthTestShader.uniforms.cameraFar.value = camera.far;
|
|
589
|
-
depthTestShader.uniforms.uDebugVis.value = 0.0;
|
|
590
|
-
fsQuad.render(renderer, depthTestShader);
|
|
591
|
-
// 6. 合成:最终外扩描边(blurDepthTest,已判遮挡) 减去 深度测试后的本体描边
|
|
592
|
-
// = 仅保留描边外扩环;本体被减掉。filterSelectShader 用 NormalBlending 叠到场景上。
|
|
506
|
+
// 6. 合成:FXAA 后的外扩描边(blur1) 减去 深度测试后的本体 = 仅保留描边外扩环;本体被减掉。
|
|
507
|
+
// ⚠️ 2026-11-04 决定回到「年初口径」:原先这里有 5.5(本体深度随描边一起 separable min-blur 外扩)
|
|
508
|
+
// 与 5.6(用外扩后的深度再判一次遮挡)两趟,作用是外扩环也参与遮挡判定 ⇒ 物体被挡住时描边不漏光。
|
|
509
|
+
// 撤掉换来的是每个描边对象每帧全屏绘制 10 趟降 7 趟(min-blur 那两趟是 ±10 共 21 次采样/像素,最贵);
|
|
510
|
+
// 代价点名:只有本体参与判遮挡,外扩环不参与 ⇒ 物体贴着遮挡物边缘时,一圈描边会压在遮挡物前面。
|
|
593
511
|
renderer.setRenderTarget(originRenderTarget);
|
|
594
512
|
filterSelectShader.uniforms.selectTexture.value = selectDepthTestTextureTarget.texture;
|
|
595
|
-
filterSelectShader.uniforms.blurTexture.value =
|
|
513
|
+
filterSelectShader.uniforms.blurTexture.value = blur1TextureTarget.texture;
|
|
596
514
|
filterSelectShader.blending = NormalBlending;
|
|
597
515
|
fsQuad.render(renderer, filterSelectShader);
|
|
598
516
|
}
|
package/pass/stroke_pass.ts
CHANGED
|
@@ -44,12 +44,6 @@ const selectDepthTestTextureTarget: WebGLRenderTarget = new WebGLRenderTarget(10
|
|
|
44
44
|
const blur1TextureTarget: WebGLRenderTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
45
45
|
|
|
46
46
|
const blur2TextureTarget: WebGLRenderTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
47
|
-
// 外扩深度临时/结果目标:depthMinBlurShader 把本体深度图随描边一起外扩,
|
|
48
|
-
// 外扩区取邻域最近深度(忽略背景 1.0),使外扩区 selD<1.0,depthTest 才能正确判遮挡(不漏光)。
|
|
49
|
-
const selectDepthBlurTmpTextureTarget: WebGLRenderTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
50
|
-
const selectDepthBlurTextureTarget: WebGLRenderTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
51
|
-
// 外扩描边经深度测试后的结果(步骤 5.6):独立 target,避免回写 blur1 触发历史回归 bug
|
|
52
|
-
const blurDepthTestTextureTarget: WebGLRenderTarget = new WebGLRenderTarget(1024, 1024, { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat });
|
|
53
47
|
|
|
54
48
|
const size: Vector2 = new Vector2();
|
|
55
49
|
|
|
@@ -394,53 +388,6 @@ const strokeShader: RawShaderMaterial = new RawShaderMaterial({
|
|
|
394
388
|
transparent: true
|
|
395
389
|
});
|
|
396
390
|
|
|
397
|
-
// 深度 min-blur(最近深度,忽略背景 1.0):随描边一起把深度图向外扩,
|
|
398
|
-
// 外扩区 selD = 物体最近深度(<1.0) 而非清屏 1.0,depthTest 在外扩区才能正确判遮挡(不漏光),
|
|
399
|
-
// 同时本体区仍靠 depthTestShader 的 EPSILON 容差防自遮挡闪烁(无缝)。separable(direction 控制 H/V)。
|
|
400
|
-
const depthMinBlurShader: ShaderMaterial = new ShaderMaterial({
|
|
401
|
-
uniforms: {
|
|
402
|
-
tDepth: { value: null },
|
|
403
|
-
resolution: { value: new Vector2(1 / 512, 1 / 512) },
|
|
404
|
-
radius: { value: 1.0 },
|
|
405
|
-
direction: { value: new Vector2(1, 0) },
|
|
406
|
-
},
|
|
407
|
-
vertexShader: `
|
|
408
|
-
${ShaderChunk.common}
|
|
409
|
-
${ShaderChunk.logdepthbuf_pars_vertex}
|
|
410
|
-
varying vec2 vUv;
|
|
411
|
-
void main() {
|
|
412
|
-
vUv = uv;
|
|
413
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
414
|
-
${ShaderChunk.logdepthbuf_vertex}
|
|
415
|
-
}
|
|
416
|
-
`,
|
|
417
|
-
fragmentShader: `
|
|
418
|
-
${ShaderChunk.logdepthbuf_pars_fragment}
|
|
419
|
-
uniform sampler2D tDepth;
|
|
420
|
-
uniform vec2 resolution;
|
|
421
|
-
uniform float radius;
|
|
422
|
-
uniform vec2 direction;
|
|
423
|
-
varying vec2 vUv;
|
|
424
|
-
void main() {
|
|
425
|
-
float minD = 1.0;
|
|
426
|
-
for (int i = -10; i <= 10; i++) {
|
|
427
|
-
float x = float(i);
|
|
428
|
-
vec2 off = direction * (x * radius * resolution);
|
|
429
|
-
float d = texture2D(tDepth, vUv + off).r;
|
|
430
|
-
if (d < 0.9999) {
|
|
431
|
-
minD = min(minD, d);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
gl_FragColor = vec4(minD, 0.0, 0.0, 1.0);
|
|
435
|
-
${ShaderChunk.logdepthbuf_fragment}
|
|
436
|
-
}
|
|
437
|
-
`,
|
|
438
|
-
blending: NoBlending,
|
|
439
|
-
depthTest: false,
|
|
440
|
-
depthWrite: false,
|
|
441
|
-
transparent: true,
|
|
442
|
-
});
|
|
443
|
-
|
|
444
391
|
const filterSelectShader: RawShaderMaterial = new RawShaderMaterial({
|
|
445
392
|
uniforms: {
|
|
446
393
|
selectTexture: { value: null },
|
|
@@ -536,9 +483,6 @@ export default function stroke_pass(
|
|
|
536
483
|
selectDepthTestTextureTarget.setSize(size.x, size.y);
|
|
537
484
|
blur1TextureTarget.setSize(size.x, size.y);
|
|
538
485
|
blur2TextureTarget.setSize(size.x, size.y);
|
|
539
|
-
selectDepthBlurTmpTextureTarget.setSize(size.x, size.y);
|
|
540
|
-
selectDepthBlurTextureTarget.setSize(size.x, size.y);
|
|
541
|
-
blurDepthTestTextureTarget.setSize(size.x, size.y);
|
|
542
486
|
|
|
543
487
|
// 1. 渲染物体本体 mask(灰度)。⭐ 本体深度由同一趟光栅化顺带写进 selectDepthTexture
|
|
544
488
|
// (挂在 selectTextureTarget 的 depth attachment 上)—— 原先这里还有第 2 趟「只渲深度」,
|
|
@@ -563,7 +507,8 @@ export default function stroke_pass(
|
|
|
563
507
|
depthTestShader.uniforms.uDebugVis.value = ((window as unknown as { __STROKE_DEPTH_VIS?: boolean }).__STROKE_DEPTH_VIS) ? 1.0 : 0.0;
|
|
564
508
|
fsQuad.render(renderer, depthTestShader);
|
|
565
509
|
|
|
566
|
-
// 4.
|
|
510
|
+
// 4. 从「深度测试后的本体描边」开始外扩(外扩的源头是本体判过的结果,被挡掉的本体不会再长出描边;
|
|
511
|
+
// 但外扩出来那圈像素本身不再判深度 ⇒ 贴着遮挡物时会压到遮挡物前面,见步骤 6 的代价点名),
|
|
567
512
|
// 描边粗细由 stroke_width 控制的迭代次数决定。
|
|
568
513
|
strokeShader.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
|
|
569
514
|
FXAAShaderMaterial.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
|
|
@@ -590,45 +535,14 @@ export default function stroke_pass(
|
|
|
590
535
|
FXAAShaderMaterial.uniforms.tDiffuse.value = blur2TextureTarget.texture;
|
|
591
536
|
fsQuad.render(renderer, FXAAShaderMaterial);
|
|
592
537
|
|
|
593
|
-
//
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
//
|
|
597
|
-
//
|
|
598
|
-
depthMinBlurShader.uniforms.resolution.value.set(1 / size.x, 1 / size.y);
|
|
599
|
-
depthMinBlurShader.uniforms.radius.value = Math.max(1, stroke_width);
|
|
600
|
-
// H:直接采 DepthTexture(selectDepthTexture),其 .r 即对数深度
|
|
601
|
-
depthMinBlurShader.uniforms.direction.value.set(1, 0);
|
|
602
|
-
depthMinBlurShader.uniforms.tDepth.value = selectDepthTexture;
|
|
603
|
-
renderer.setRenderTarget(selectDepthBlurTmpTextureTarget);
|
|
604
|
-
fsQuad.render(renderer, depthMinBlurShader);
|
|
605
|
-
// V
|
|
606
|
-
depthMinBlurShader.uniforms.direction.value.set(0, 1);
|
|
607
|
-
depthMinBlurShader.uniforms.tDepth.value = selectDepthBlurTmpTextureTarget.texture;
|
|
608
|
-
renderer.setRenderTarget(selectDepthBlurTextureTarget);
|
|
609
|
-
fsQuad.render(renderer, depthMinBlurShader);
|
|
610
|
-
|
|
611
|
-
// 调试:记录「外扩后的选区深度」,供面板「深度图(分步)」的步骤 5.5 档读取。
|
|
612
|
-
// 它是 RGBA8 目标、对数深度存在 .r 里(与 DepthTexture 取 .r 的读法一致)。
|
|
613
|
-
debugDepthState.lastSelectBlurDepth = selectDepthBlurTextureTarget.texture;
|
|
614
|
-
|
|
615
|
-
// 5.6 对外扩后的描边判遮挡:被更近几何挡住的外扩环像素 discard。
|
|
616
|
-
// ⚠️ 输出到独立 target,绝不回写 blur1(历史回归 bug:blur1 被 depthTest 结果覆盖
|
|
617
|
-
// 会导致步骤 6 相减归零、描边全不显示)。
|
|
618
|
-
renderer.setRenderTarget(blurDepthTestTextureTarget);
|
|
619
|
-
renderer.clear();
|
|
620
|
-
depthTestShader.uniforms.map.value = blur1TextureTarget.texture;
|
|
621
|
-
depthTestShader.uniforms.baseDepth.value = baseDepthTexture;
|
|
622
|
-
depthTestShader.uniforms.selectDepth.value = selectDepthBlurTextureTarget.texture;
|
|
623
|
-
depthTestShader.uniforms.cameraFar.value = (camera as PerspectiveCamera).far;
|
|
624
|
-
depthTestShader.uniforms.uDebugVis.value = 0.0;
|
|
625
|
-
fsQuad.render(renderer, depthTestShader);
|
|
626
|
-
|
|
627
|
-
// 6. 合成:最终外扩描边(blurDepthTest,已判遮挡) 减去 深度测试后的本体描边
|
|
628
|
-
// = 仅保留描边外扩环;本体被减掉。filterSelectShader 用 NormalBlending 叠到场景上。
|
|
538
|
+
// 6. 合成:FXAA 后的外扩描边(blur1) 减去 深度测试后的本体 = 仅保留描边外扩环;本体被减掉。
|
|
539
|
+
// ⚠️ 2026-11-04 决定回到「年初口径」:原先这里有 5.5(本体深度随描边一起 separable min-blur 外扩)
|
|
540
|
+
// 与 5.6(用外扩后的深度再判一次遮挡)两趟,作用是外扩环也参与遮挡判定 ⇒ 物体被挡住时描边不漏光。
|
|
541
|
+
// 撤掉换来的是每个描边对象每帧全屏绘制 10 趟降 7 趟(min-blur 那两趟是 ±10 共 21 次采样/像素,最贵);
|
|
542
|
+
// 代价点名:只有本体参与判遮挡,外扩环不参与 ⇒ 物体贴着遮挡物边缘时,一圈描边会压在遮挡物前面。
|
|
629
543
|
renderer.setRenderTarget(originRenderTarget);
|
|
630
544
|
filterSelectShader.uniforms.selectTexture.value = selectDepthTestTextureTarget.texture;
|
|
631
|
-
filterSelectShader.uniforms.blurTexture.value =
|
|
545
|
+
filterSelectShader.uniforms.blurTexture.value = blur1TextureTarget.texture;
|
|
632
546
|
filterSelectShader.blending = NormalBlending;
|
|
633
547
|
fsQuad.render(renderer, filterSelectShader);
|
|
634
548
|
}
|