three-base 2.0.168 → 2.0.172
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/FPScontrol.d.ts +41 -7
- package/FPScontrol.js +232 -155
- package/FPScontrol.ts +129 -55
- package/Ground1/Ground1.js +6 -6
- package/Ground1/Ground1.ts +6 -6
- package/MktMesh/MktMesh.d.ts +5 -1
- package/MktMesh/MktMesh.js +20 -7
- package/MktMesh/MktMesh.ts +20 -7
- package/MktMesh/panoSlotBind.js +18 -2
- package/MktMesh/panoSlotBind.ts +18 -1
- package/MktMesh/panoTileStitcher.d.ts +35 -7
- package/MktMesh/panoTileStitcher.js +71 -11
- package/MktMesh/panoTileStitcher.ts +73 -12
- package/MktMesh/types.d.ts +5 -1
- package/MktMesh/types.ts +5 -1
- package/ThreeBase.d.ts +2 -11
- package/ThreeBase.js +14 -26
- package/ThreeBase.ts +14 -26
- package/WaterFlow/WaterFlow.d.ts +1 -1
- package/WaterFlow/WaterFlow.js +1 -1
- package/WaterFlow/WaterFlow.ts +1 -1
- package/package.json +1 -1
- package/pass/bloom_pass.js +7 -14
- package/pass/bloom_pass.ts +7 -16
- package/pass/cover_pass.js +7 -15
- package/pass/cover_pass.ts +7 -17
- package/pass/debugDepthState.d.ts +2 -2
- package/pass/debugDepthState.ts +2 -2
- package/pass/glow_pass.js +8 -16
- package/pass/glow_pass.ts +8 -18
- package/pass/ssao_pass.d.ts +13 -44
- package/pass/ssao_pass.js +60 -78
- package/pass/ssao_pass.ts +74 -88
- package/pass/stroke_pass.js +10 -16
- package/pass/stroke_pass.ts +10 -18
- package/shaders/baseDepthVisShader.d.ts +1 -1
- package/shaders/baseDepthVisShader.js +1 -1
- package/shaders/baseDepthVisShader.ts +1 -1
- package/shaders/debugDepthCompareShader.d.ts +1 -1
- package/shaders/debugDepthCompareShader.js +1 -1
- package/shaders/debugDepthCompareShader.ts +1 -1
- package/shaders/debugExcludedShader.d.ts +6 -4
- package/shaders/debugExcludedShader.js +6 -4
- package/shaders/debugExcludedShader.ts +6 -4
|
@@ -6,8 +6,14 @@ import { Texture, type WebGLRenderer } from "three";
|
|
|
6
6
|
export interface PanoStitcher {
|
|
7
7
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做) */
|
|
8
8
|
ensure(urls: string[]): void;
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
|
|
11
|
+
* ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
|
|
12
|
+
* 先把 RT 建出来(建完即整张涂 `MISSING_TILE_GRAY` 缺失灰)并返回它** ⇒ 调用方切批次的那一帧
|
|
13
|
+
* 拿到的就是**深灰底**而非 null(null 会让它沿用上一批 ⇒ 墙上闪上一个全景)。
|
|
14
|
+
* ⛔ 没传 `renderer`(诊断/测试路径)仍旧返回 null —— 不能在无 GL 上下文时凭空建 RT。
|
|
15
|
+
*/
|
|
16
|
+
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null;
|
|
11
17
|
/** 每帧驱动(消费方每帧只调一次)。`renderer` = 调用方当前的 WebGLRenderer,还没捕获到就传 null
|
|
12
18
|
* ⇒ 这一帧什么都不画(⛔ 不打假读数,也⛔ 不退回任何 CPU 拼合路径)。 */
|
|
13
19
|
pump(renderer: WebGLRenderer | null): void;
|
|
@@ -36,6 +42,12 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
36
42
|
private _retryAt;
|
|
37
43
|
private _fails;
|
|
38
44
|
private _dead;
|
|
45
|
+
/** 已排进 `threeUtils.addWork` 队列、等着「只干上传那一句」的瓦片(皇上 2026-10-09 旨:上传走公共队列)。
|
|
46
|
+
* ⛔ 必须自备:`addWork` 用 `workList.includes(work)` 判重,而每次排的都是**新闭包** ⇒ 它判不出来。
|
|
47
|
+
* 这个 Set 兼作「作废哨兵」:`_uploadTile` 开头 `if (!this._uploadQueued.delete(rec)) return;`
|
|
48
|
+
* —— 该瓦片若已先被 `_dropTile`/`_switchRenderer`清掉(dispose 过),这一项就作废,
|
|
49
|
+
* 否则会对已释放的纹理 `initTexture` ⇒ 凭空建显存且无人回收。 */
|
|
50
|
+
private _uploadQueued;
|
|
39
51
|
/** 建 RT 时用的渲染器(换 renderer ⇒ 在册组全清,见文件头) */
|
|
40
52
|
private _renderer;
|
|
41
53
|
/** 推进计数(`progressStamp`)*/
|
|
@@ -59,7 +71,7 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
59
71
|
ensure(urls: string[]): void;
|
|
60
72
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 16 格画满)。
|
|
61
73
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
62
|
-
peek(batch: string, slot: number): Texture | null;
|
|
74
|
+
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null;
|
|
63
75
|
/** ⭐ 这条路交出去的槽纹理本来就是 GL 渲染目标的附件 —— 像素已经在显存里,没有「上传」这一步。
|
|
64
76
|
* 消费方(`MktMesh._peekTileAt` / 编辑器 `_peekTileAt`)据此**跳过**「每 tick 一张 `initTexture`」那道闸。 */
|
|
65
77
|
resident(tex: Texture): boolean;
|
|
@@ -82,11 +94,27 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
82
94
|
private _drawPass;
|
|
83
95
|
/** 工序 3:每帧至多把**一张**新纹理传进 GPU(旨②),这一趟只上传、不画。
|
|
84
96
|
* 按「槽 0 格 0 → …」的顺序挑第一张已解码没上传的 ⇒ RT 从左上角起逐格显影,观感可预期。 */
|
|
97
|
+
/**
|
|
98
|
+
* 工序 3:**只入队一张**,真正上传交给 `threeUtils.addWork`(皇上 2026-10-09 旨)。
|
|
99
|
+
*
|
|
100
|
+
* ⚠️⛔ 为什么不用「函数内局部 budget」了:那个 `budget` 每次调用都重新初始化为 1,
|
|
101
|
+
* 只管得住「**本次 pump 内**≤1 张」,管不住「一帧被 pump 多次」——
|
|
102
|
+
* 而消费方 `MktMesh.onBeforeRender` 里 `updateTexture` 与 `feedFaceMapping` **各调了一次 pump**
|
|
103
|
+
* (两处注释都以为对方在驱动,实际各驱动一次)⇒ 实际每帧上传 2 张,
|
|
104
|
+
* 违背本文件头「每一帧同样也是只载入一张新纹理」的承诺(实测 Performance 面板一帧看到多次 initTexture)。
|
|
105
|
+
*
|
|
106
|
+
* ⭐ 改挂`threeUtils.addWork` 的三个好处:
|
|
107
|
+
* ① 节流**跨调用天然成立** —— 队列每帧只`splice(0,1)` 取一项,无论 pump 调几次都只上传一张;
|
|
108
|
+
* ② 与库内既有范式同口径(`MktMesh._commitTileToShader` 就是这么排的),上传路径全走同一条队列;
|
|
109
|
+
* ③ 保留皇上 10-05 旨的「占一格」手法(下面 `addWork(() => {})`)⇒ 真活晚一帧执行,
|
|
110
|
+
* 与 `_drawPass` 天然错开(上传与绘制不落同一拍,正是 09-29「两个大头别在同一帧」的延续)。
|
|
111
|
+
*/
|
|
85
112
|
private _uploadPass;
|
|
86
|
-
/**
|
|
87
|
-
* ⚠️
|
|
88
|
-
*
|
|
89
|
-
*
|
|
113
|
+
/** 队列回调的全部内容:把这**一片**瓦片的纹理上传到 GPU(`texImage2D` + mipmap 才是那一下贵的)。
|
|
114
|
+
* ⚠️ 开头那句判空是**作废闸**(皇上 2026-10-09 旨加):本项在队列里等tick 期间,
|
|
115
|
+
* 那片瓦片可能已被 `_dropTile`(画完即弃)/ `_switchRenderer` 清掉并 dispose ⇒ 此时必须作废,
|
|
116
|
+
* 否则会对已释放纹理 `initTexture` ⇒ 凭空把显存建回来且无人回收。
|
|
117
|
+
* (与 `MktMesh._uploadTileTexture` 的「排上队之后已被清掉 ⇒ 本项作废」同一口径。) */
|
|
90
118
|
private _uploadTile;
|
|
91
119
|
/** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(16 格里的一格)。
|
|
92
120
|
* ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 16 帧**,
|
|
@@ -76,8 +76,15 @@
|
|
|
76
76
|
*/
|
|
77
77
|
import { ClampToEdgeWrapping, DataTexture, LinearFilter, LinearMipmapLinearFilter, Mesh, NoBlending, OrthographicCamera, PlaneGeometry, RGBAFormat, Scene, ShaderMaterial, SRGBColorSpace, UnsignedByteType, Vector3, Vector4, WebGLRenderTarget, } from "three";
|
|
78
78
|
import { loadPanoTexture, releasePanoTexture } from "./panoBitmapTexture";
|
|
79
|
+
// ⭐ 2026-10-09 皇上旨「上传也走 threeUtils.addWork 队列」:上传节流改为挂公共队列,见 `_uploadPass`。
|
|
80
|
+
import threeUtils from "../threeUtils";
|
|
79
81
|
import { MISSING_TILE_GRAY, RT_STITCH_CELLS_PER_SLOT, RT_STITCH_SLOT_COUNT, RT_STITCH_SLOT_H, RT_STITCH_SLOT_TILES, RT_STITCH_SLOT_W, RT_STITCH_SLOT_GRID, RT_STITCH_TILES_PER_BATCH, rtStitchBatchOf, rtStitchCellPos, rtStitchTileInset, } from "./mktShader";
|
|
80
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* ⛔⛔ 2026-10-09 皇上旨:「每帧最多几张新纹理进 GPU」这条**节流已不再由此常量实现**,改由
|
|
84
|
+
* `threeUtils.addWork` 队列承担(见 `_uploadPass`)—— 因为函数内局部 budget 管不住「一帧多次 pump」。
|
|
85
|
+
* 此常量保留仅作**文档锚点**,标出「一帧一张」这个不可回退的设计承诺(改队列实现前先看这里)。
|
|
86
|
+
* ⛔ 别再拿它去写 budget 循环:那正是「一帧上传 2 张」的根因(消费方 onBeforeRender 里 pump 了两次)。
|
|
87
|
+
*/
|
|
81
88
|
const PANO_STITCH_UPLOADS_PER_FRAME = 1;
|
|
82
89
|
/** 每帧最多发起几张下载(32 片一批,⛔ 不加以限制就是一帧甩 32 条 XHR + 32 次解码) */
|
|
83
90
|
const PANO_STITCH_DOWNLOADS_PER_FRAME = 2;
|
|
@@ -141,6 +148,12 @@ export class PanoTileStitcher {
|
|
|
141
148
|
_retryAt = new Map();
|
|
142
149
|
_fails = new Map();
|
|
143
150
|
_dead = new Set();
|
|
151
|
+
/** 已排进 `threeUtils.addWork` 队列、等着「只干上传那一句」的瓦片(皇上 2026-10-09 旨:上传走公共队列)。
|
|
152
|
+
* ⛔ 必须自备:`addWork` 用 `workList.includes(work)` 判重,而每次排的都是**新闭包** ⇒ 它判不出来。
|
|
153
|
+
* 这个 Set 兼作「作废哨兵」:`_uploadTile` 开头 `if (!this._uploadQueued.delete(rec)) return;`
|
|
154
|
+
* —— 该瓦片若已先被 `_dropTile`/`_switchRenderer`清掉(dispose 过),这一项就作废,
|
|
155
|
+
* 否则会对已释放的纹理 `initTexture` ⇒ 凭空建显存且无人回收。 */
|
|
156
|
+
_uploadQueued = new Set();
|
|
144
157
|
/** 建 RT 时用的渲染器(换 renderer ⇒ 在册组全清,见文件头) */
|
|
145
158
|
_renderer = null;
|
|
146
159
|
/** 推进计数(`progressStamp`)*/
|
|
@@ -198,10 +211,24 @@ export class PanoTileStitcher {
|
|
|
198
211
|
}
|
|
199
212
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 16 格画满)。
|
|
200
213
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
201
|
-
peek(batch, slot) {
|
|
214
|
+
peek(batch, slot, renderer) {
|
|
202
215
|
const g = this._groups.get(batch);
|
|
203
|
-
if (!g
|
|
216
|
+
if (!g)
|
|
204
217
|
return null;
|
|
218
|
+
if (!g.drawnCount[slot]) {
|
|
219
|
+
// ⭐ 皇上 2026-10-09 旨「切换全景时上一个全景的内容要渲染成深灰」:
|
|
220
|
+
// 换批次的瞬间新全景还没画进任何格,若此处照旧返回 null,消费方 `panoSlotBind` 会
|
|
221
|
+
// **沿用上一批的 RT 纹理** ⇒ 墙上继续显示上一个全景(皇上报的现象)。
|
|
222
|
+
// 这里改为:只要调用方给了 renderer,就**先把这张 RT 建出来**(`_ensureRt` 建完立刻
|
|
223
|
+
// 整张涂 `MISSING_TILE_GRAY` 缺失灰)并交出去 ⇒ 切换瞬间该象限就是**深灰底**,
|
|
224
|
+
// 随后随`pump` 逐格显影。观感 = 深灰 → 逐格填图,不再闪上一个全景的内容。
|
|
225
|
+
// ⛔ 没给 renderer(诊断/测试路径)仍旧返回 null —— 不能在无 GL 上下文时凭空建 RT。
|
|
226
|
+
if (!renderer)
|
|
227
|
+
return null;
|
|
228
|
+
if (!this._renderer)
|
|
229
|
+
return null;
|
|
230
|
+
return this._ensureRt(this._renderer, g, slot).texture;
|
|
231
|
+
}
|
|
205
232
|
const rt = g.rts[slot];
|
|
206
233
|
return rt ? rt.texture : null;
|
|
207
234
|
}
|
|
@@ -247,6 +274,7 @@ export class PanoTileStitcher {
|
|
|
247
274
|
for (const batch of [...this._groups.keys()])
|
|
248
275
|
this.disposeGroup(batch);
|
|
249
276
|
this._texs.clear();
|
|
277
|
+
this._uploadQueued.clear(); // ⭐ 2026-10-09 旨:同上,连队列里那批未执行的项一并作废
|
|
250
278
|
this._wanted.clear();
|
|
251
279
|
this._retryAt.clear();
|
|
252
280
|
this._fails.clear();
|
|
@@ -323,8 +351,28 @@ export class PanoTileStitcher {
|
|
|
323
351
|
}
|
|
324
352
|
/** 工序 3:每帧至多把**一张**新纹理传进 GPU(旨②),这一趟只上传、不画。
|
|
325
353
|
* 按「槽 0 格 0 → …」的顺序挑第一张已解码没上传的 ⇒ RT 从左上角起逐格显影,观感可预期。 */
|
|
354
|
+
/**
|
|
355
|
+
* 工序 3:**只入队一张**,真正上传交给 `threeUtils.addWork`(皇上 2026-10-09 旨)。
|
|
356
|
+
*
|
|
357
|
+
* ⚠️⛔ 为什么不用「函数内局部 budget」了:那个 `budget` 每次调用都重新初始化为 1,
|
|
358
|
+
* 只管得住「**本次 pump 内**≤1 张」,管不住「一帧被 pump 多次」——
|
|
359
|
+
* 而消费方 `MktMesh.onBeforeRender` 里 `updateTexture` 与 `feedFaceMapping` **各调了一次 pump**
|
|
360
|
+
* (两处注释都以为对方在驱动,实际各驱动一次)⇒ 实际每帧上传 2 张,
|
|
361
|
+
* 违背本文件头「每一帧同样也是只载入一张新纹理」的承诺(实测 Performance 面板一帧看到多次 initTexture)。
|
|
362
|
+
*
|
|
363
|
+
* ⭐ 改挂`threeUtils.addWork` 的三个好处:
|
|
364
|
+
* ① 节流**跨调用天然成立** —— 队列每帧只`splice(0,1)` 取一项,无论 pump 调几次都只上传一张;
|
|
365
|
+
* ② 与库内既有范式同口径(`MktMesh._commitTileToShader` 就是这么排的),上传路径全走同一条队列;
|
|
366
|
+
* ③ 保留皇上 10-05 旨的「占一格」手法(下面 `addWork(() => {})`)⇒ 真活晚一帧执行,
|
|
367
|
+
* 与 `_drawPass` 天然错开(上传与绘制不落同一拍,正是 09-29「两个大头别在同一帧」的延续)。
|
|
368
|
+
*/
|
|
326
369
|
_uploadPass(renderer) {
|
|
327
|
-
|
|
370
|
+
// ⛔⛔ 队列里已有待上传项 ⇒ **本次直接不排**。
|
|
371
|
+
// 这一句是本改法的关键闸门:若只靠下面 `has(rec)` 跳过「同一片」,遍历会**继续往下找下一片**
|
|
372
|
+
// 于是 pump 几次就把多张全排进队列(13 全景 × 32 片 = 416 项 ⇒ workList 膨胀、`includes` 退化成 O(n)、
|
|
373
|
+
// 队尾那张要等几百帧才传完)。先判「队列非空就收手」⇒ 队列恒 ≤2 项(一占位+一真活),延迟最低。
|
|
374
|
+
if (this._uploadQueued.size > 0)
|
|
375
|
+
return;
|
|
328
376
|
for (const g of this._groups.values()) {
|
|
329
377
|
if (g.pending <= 0)
|
|
330
378
|
continue;
|
|
@@ -339,18 +387,25 @@ export class PanoTileStitcher {
|
|
|
339
387
|
const rec = this._texs.get(url);
|
|
340
388
|
if (!rec || !rec.tex || rec.uploaded)
|
|
341
389
|
continue;
|
|
342
|
-
|
|
343
|
-
if (
|
|
344
|
-
|
|
390
|
+
// ⛔ 已在队列里 ⇒ 跳过(`addWork` 按引用判重对「新闭包」无效 ⇒ 必须自备 Set,见 `_uploadQueued`)
|
|
391
|
+
if (this._uploadQueued.has(rec))
|
|
392
|
+
continue;
|
|
393
|
+
this._uploadQueued.add(rec);
|
|
394
|
+
threeUtils.addWork(() => { }); // 占一格(什么都不干),把真活推到下一个 tick
|
|
395
|
+
threeUtils.addWork(() => this._uploadTile(rec));
|
|
396
|
+
return; // ⛔ 本次 pump 只排一张:其余名额留给下一帧的队列,绝不在这一帧再排第二张
|
|
345
397
|
}
|
|
346
398
|
}
|
|
347
399
|
}
|
|
348
400
|
}
|
|
349
|
-
/**
|
|
350
|
-
* ⚠️
|
|
351
|
-
*
|
|
352
|
-
*
|
|
401
|
+
/** 队列回调的全部内容:把这**一片**瓦片的纹理上传到 GPU(`texImage2D` + mipmap 才是那一下贵的)。
|
|
402
|
+
* ⚠️ 开头那句判空是**作废闸**(皇上 2026-10-09 旨加):本项在队列里等tick 期间,
|
|
403
|
+
* 那片瓦片可能已被 `_dropTile`(画完即弃)/ `_switchRenderer` 清掉并 dispose ⇒ 此时必须作废,
|
|
404
|
+
* 否则会对已释放纹理 `initTexture` ⇒ 凭空把显存建回来且无人回收。
|
|
405
|
+
* (与 `MktMesh._uploadTileTexture` 的「排上队之后已被清掉 ⇒ 本项作废」同一口径。) */
|
|
353
406
|
_uploadTile(rec) {
|
|
407
|
+
if (!this._uploadQueued.delete(rec))
|
|
408
|
+
return;
|
|
354
409
|
const renderer = this._renderer;
|
|
355
410
|
if (!renderer || !rec.tex)
|
|
356
411
|
return;
|
|
@@ -477,6 +532,8 @@ export class PanoTileStitcher {
|
|
|
477
532
|
for (const batch of [...this._groups.keys()])
|
|
478
533
|
this.disposeGroup(batch);
|
|
479
534
|
this._texs.clear();
|
|
535
|
+
// ⭐ 2026-10-09 旨:在册组整份作废 ⇒ 上传队列里那些项的 rec 也一并作废(否则会拿旧 renderer 上传)
|
|
536
|
+
this._uploadQueued.clear();
|
|
480
537
|
this._wanted.clear();
|
|
481
538
|
this._retryAt.clear();
|
|
482
539
|
this._fails.clear();
|
|
@@ -510,6 +567,9 @@ export class PanoTileStitcher {
|
|
|
510
567
|
const rec = this._texs.get(url);
|
|
511
568
|
if (rec && rec.tex)
|
|
512
569
|
releasePanoTexture(rec.tex);
|
|
570
|
+
// ⭐ 2026-10-09 旨:已排进上传队列的那一片也要**作废**(否则队列回调会对已释放纹理 initTexture)
|
|
571
|
+
if (rec)
|
|
572
|
+
this._uploadQueued.delete(rec);
|
|
513
573
|
this._texs.delete(url);
|
|
514
574
|
this._wanted.delete(url);
|
|
515
575
|
}
|
|
@@ -96,6 +96,8 @@ import {
|
|
|
96
96
|
type WebGLRenderer,
|
|
97
97
|
} from "three";
|
|
98
98
|
import { loadPanoTexture, releasePanoTexture } from "./panoBitmapTexture";
|
|
99
|
+
// ⭐ 2026-10-09 皇上旨「上传也走 threeUtils.addWork 队列」:上传节流改为挂公共队列,见 `_uploadPass`。
|
|
100
|
+
import threeUtils from "../threeUtils";
|
|
99
101
|
import {
|
|
100
102
|
MISSING_TILE_GRAY,
|
|
101
103
|
RT_STITCH_CELLS_PER_SLOT,
|
|
@@ -110,7 +112,12 @@ import {
|
|
|
110
112
|
rtStitchTileInset,
|
|
111
113
|
} from "./mktShader";
|
|
112
114
|
|
|
113
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* ⛔⛔ 2026-10-09 皇上旨:「每帧最多几张新纹理进 GPU」这条**节流已不再由此常量实现**,改由
|
|
117
|
+
* `threeUtils.addWork` 队列承担(见 `_uploadPass`)—— 因为函数内局部 budget 管不住「一帧多次 pump」。
|
|
118
|
+
* 此常量保留仅作**文档锚点**,标出「一帧一张」这个不可回退的设计承诺(改队列实现前先看这里)。
|
|
119
|
+
* ⛔ 别再拿它去写 budget 循环:那正是「一帧上传 2 张」的根因(消费方 onBeforeRender 里 pump 了两次)。
|
|
120
|
+
*/
|
|
114
121
|
const PANO_STITCH_UPLOADS_PER_FRAME = 1;
|
|
115
122
|
|
|
116
123
|
/** 每帧最多发起几张下载(32 片一批,⛔ 不加以限制就是一帧甩 32 条 XHR + 32 次解码) */
|
|
@@ -199,8 +206,14 @@ interface StitchGroup {
|
|
|
199
206
|
export interface PanoStitcher {
|
|
200
207
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做) */
|
|
201
208
|
ensure(urls: string[]): void;
|
|
202
|
-
/**
|
|
203
|
-
|
|
209
|
+
/**
|
|
210
|
+
* 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
|
|
211
|
+
* ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
|
|
212
|
+
* 先把 RT 建出来(建完即整张涂 `MISSING_TILE_GRAY` 缺失灰)并返回它** ⇒ 调用方切批次的那一帧
|
|
213
|
+
* 拿到的就是**深灰底**而非 null(null 会让它沿用上一批 ⇒ 墙上闪上一个全景)。
|
|
214
|
+
* ⛔ 没传 `renderer`(诊断/测试路径)仍旧返回 null —— 不能在无 GL 上下文时凭空建 RT。
|
|
215
|
+
*/
|
|
216
|
+
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null;
|
|
204
217
|
/** 每帧驱动(消费方每帧只调一次)。`renderer` = 调用方当前的 WebGLRenderer,还没捕获到就传 null
|
|
205
218
|
* ⇒ 这一帧什么都不画(⛔ 不打假读数,也⛔ 不退回任何 CPU 拼合路径)。 */
|
|
206
219
|
pump(renderer: WebGLRenderer | null): void;
|
|
@@ -230,6 +243,12 @@ export class PanoTileStitcher implements PanoStitcher {
|
|
|
230
243
|
private _retryAt: Map<string, number> = new Map();
|
|
231
244
|
private _fails: Map<string, number> = new Map();
|
|
232
245
|
private _dead: Set<string> = new Set();
|
|
246
|
+
/** 已排进 `threeUtils.addWork` 队列、等着「只干上传那一句」的瓦片(皇上 2026-10-09 旨:上传走公共队列)。
|
|
247
|
+
* ⛔ 必须自备:`addWork` 用 `workList.includes(work)` 判重,而每次排的都是**新闭包** ⇒ 它判不出来。
|
|
248
|
+
* 这个 Set 兼作「作废哨兵」:`_uploadTile` 开头 `if (!this._uploadQueued.delete(rec)) return;`
|
|
249
|
+
* —— 该瓦片若已先被 `_dropTile`/`_switchRenderer`清掉(dispose 过),这一项就作废,
|
|
250
|
+
* 否则会对已释放的纹理 `initTexture` ⇒ 凭空建显存且无人回收。 */
|
|
251
|
+
private _uploadQueued: Set<TileRec> = new Set();
|
|
233
252
|
/** 建 RT 时用的渲染器(换 renderer ⇒ 在册组全清,见文件头) */
|
|
234
253
|
private _renderer: WebGLRenderer | null = null;
|
|
235
254
|
/** 推进计数(`progressStamp`)*/
|
|
@@ -288,9 +307,21 @@ export class PanoTileStitcher implements PanoStitcher {
|
|
|
288
307
|
|
|
289
308
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 16 格画满)。
|
|
290
309
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
291
|
-
peek(batch: string, slot: number): Texture | null {
|
|
310
|
+
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null {
|
|
292
311
|
const g = this._groups.get(batch);
|
|
293
|
-
if (!g
|
|
312
|
+
if (!g) return null;
|
|
313
|
+
if (!g.drawnCount[slot]) {
|
|
314
|
+
// ⭐ 皇上 2026-10-09 旨「切换全景时上一个全景的内容要渲染成深灰」:
|
|
315
|
+
// 换批次的瞬间新全景还没画进任何格,若此处照旧返回 null,消费方 `panoSlotBind` 会
|
|
316
|
+
// **沿用上一批的 RT 纹理** ⇒ 墙上继续显示上一个全景(皇上报的现象)。
|
|
317
|
+
// 这里改为:只要调用方给了 renderer,就**先把这张 RT 建出来**(`_ensureRt` 建完立刻
|
|
318
|
+
// 整张涂 `MISSING_TILE_GRAY` 缺失灰)并交出去 ⇒ 切换瞬间该象限就是**深灰底**,
|
|
319
|
+
// 随后随`pump` 逐格显影。观感 = 深灰 → 逐格填图,不再闪上一个全景的内容。
|
|
320
|
+
// ⛔ 没给 renderer(诊断/测试路径)仍旧返回 null —— 不能在无 GL 上下文时凭空建 RT。
|
|
321
|
+
if (!renderer) return null;
|
|
322
|
+
if (!this._renderer) return null;
|
|
323
|
+
return this._ensureRt(this._renderer, g, slot).texture;
|
|
324
|
+
}
|
|
294
325
|
const rt = g.rts[slot];
|
|
295
326
|
return rt ? rt.texture : null;
|
|
296
327
|
}
|
|
@@ -338,6 +369,7 @@ export class PanoTileStitcher implements PanoStitcher {
|
|
|
338
369
|
dispose(): void {
|
|
339
370
|
for (const batch of [...this._groups.keys()]) this.disposeGroup(batch);
|
|
340
371
|
this._texs.clear();
|
|
372
|
+
this._uploadQueued.clear(); // ⭐ 2026-10-09 旨:同上,连队列里那批未执行的项一并作废
|
|
341
373
|
this._wanted.clear();
|
|
342
374
|
this._retryAt.clear();
|
|
343
375
|
this._fails.clear();
|
|
@@ -408,8 +440,27 @@ export class PanoTileStitcher implements PanoStitcher {
|
|
|
408
440
|
|
|
409
441
|
/** 工序 3:每帧至多把**一张**新纹理传进 GPU(旨②),这一趟只上传、不画。
|
|
410
442
|
* 按「槽 0 格 0 → …」的顺序挑第一张已解码没上传的 ⇒ RT 从左上角起逐格显影,观感可预期。 */
|
|
443
|
+
/**
|
|
444
|
+
* 工序 3:**只入队一张**,真正上传交给 `threeUtils.addWork`(皇上 2026-10-09 旨)。
|
|
445
|
+
*
|
|
446
|
+
* ⚠️⛔ 为什么不用「函数内局部 budget」了:那个 `budget` 每次调用都重新初始化为 1,
|
|
447
|
+
* 只管得住「**本次 pump 内**≤1 张」,管不住「一帧被 pump 多次」——
|
|
448
|
+
* 而消费方 `MktMesh.onBeforeRender` 里 `updateTexture` 与 `feedFaceMapping` **各调了一次 pump**
|
|
449
|
+
* (两处注释都以为对方在驱动,实际各驱动一次)⇒ 实际每帧上传 2 张,
|
|
450
|
+
* 违背本文件头「每一帧同样也是只载入一张新纹理」的承诺(实测 Performance 面板一帧看到多次 initTexture)。
|
|
451
|
+
*
|
|
452
|
+
* ⭐ 改挂`threeUtils.addWork` 的三个好处:
|
|
453
|
+
* ① 节流**跨调用天然成立** —— 队列每帧只`splice(0,1)` 取一项,无论 pump 调几次都只上传一张;
|
|
454
|
+
* ② 与库内既有范式同口径(`MktMesh._commitTileToShader` 就是这么排的),上传路径全走同一条队列;
|
|
455
|
+
* ③ 保留皇上 10-05 旨的「占一格」手法(下面 `addWork(() => {})`)⇒ 真活晚一帧执行,
|
|
456
|
+
* 与 `_drawPass` 天然错开(上传与绘制不落同一拍,正是 09-29「两个大头别在同一帧」的延续)。
|
|
457
|
+
*/
|
|
411
458
|
private _uploadPass(renderer: WebGLRenderer): void {
|
|
412
|
-
|
|
459
|
+
// ⛔⛔ 队列里已有待上传项 ⇒ **本次直接不排**。
|
|
460
|
+
// 这一句是本改法的关键闸门:若只靠下面 `has(rec)` 跳过「同一片」,遍历会**继续往下找下一片**
|
|
461
|
+
// 于是 pump 几次就把多张全排进队列(13 全景 × 32 片 = 416 项 ⇒ workList 膨胀、`includes` 退化成 O(n)、
|
|
462
|
+
// 队尾那张要等几百帧才传完)。先判「队列非空就收手」⇒ 队列恒 ≤2 项(一占位+一真活),延迟最低。
|
|
463
|
+
if (this._uploadQueued.size > 0) return;
|
|
413
464
|
for (const g of this._groups.values()) {
|
|
414
465
|
if (g.pending <= 0) continue;
|
|
415
466
|
for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
|
|
@@ -420,18 +471,24 @@ export class PanoTileStitcher implements PanoStitcher {
|
|
|
420
471
|
if (!url || this._dead.has(url)) continue;
|
|
421
472
|
const rec = this._texs.get(url);
|
|
422
473
|
if (!rec || !rec.tex || rec.uploaded) continue;
|
|
423
|
-
|
|
424
|
-
if (
|
|
474
|
+
// ⛔ 已在队列里 ⇒ 跳过(`addWork` 按引用判重对「新闭包」无效 ⇒ 必须自备 Set,见 `_uploadQueued`)
|
|
475
|
+
if (this._uploadQueued.has(rec)) continue;
|
|
476
|
+
this._uploadQueued.add(rec);
|
|
477
|
+
threeUtils.addWork(() => {}); // 占一格(什么都不干),把真活推到下一个 tick
|
|
478
|
+
threeUtils.addWork(() => this._uploadTile(rec));
|
|
479
|
+
return; // ⛔ 本次 pump 只排一张:其余名额留给下一帧的队列,绝不在这一帧再排第二张
|
|
425
480
|
}
|
|
426
481
|
}
|
|
427
482
|
}
|
|
428
483
|
}
|
|
429
484
|
|
|
430
|
-
/**
|
|
431
|
-
* ⚠️
|
|
432
|
-
*
|
|
433
|
-
*
|
|
485
|
+
/** 队列回调的全部内容:把这**一片**瓦片的纹理上传到 GPU(`texImage2D` + mipmap 才是那一下贵的)。
|
|
486
|
+
* ⚠️ 开头那句判空是**作废闸**(皇上 2026-10-09 旨加):本项在队列里等tick 期间,
|
|
487
|
+
* 那片瓦片可能已被 `_dropTile`(画完即弃)/ `_switchRenderer` 清掉并 dispose ⇒ 此时必须作废,
|
|
488
|
+
* 否则会对已释放纹理 `initTexture` ⇒ 凭空把显存建回来且无人回收。
|
|
489
|
+
* (与 `MktMesh._uploadTileTexture` 的「排上队之后已被清掉 ⇒ 本项作废」同一口径。) */
|
|
434
490
|
private _uploadTile(rec: TileRec): void {
|
|
491
|
+
if (!this._uploadQueued.delete(rec)) return;
|
|
435
492
|
const renderer = this._renderer;
|
|
436
493
|
if (!renderer || !rec.tex) return;
|
|
437
494
|
try {
|
|
@@ -562,6 +619,8 @@ export class PanoTileStitcher implements PanoStitcher {
|
|
|
562
619
|
this._renderer = renderer;
|
|
563
620
|
for (const batch of [...this._groups.keys()]) this.disposeGroup(batch);
|
|
564
621
|
this._texs.clear();
|
|
622
|
+
// ⭐ 2026-10-09 旨:在册组整份作废 ⇒ 上传队列里那些项的 rec 也一并作废(否则会拿旧 renderer 上传)
|
|
623
|
+
this._uploadQueued.clear();
|
|
565
624
|
this._wanted.clear();
|
|
566
625
|
this._retryAt.clear();
|
|
567
626
|
this._fails.clear();
|
|
@@ -595,6 +654,8 @@ export class PanoTileStitcher implements PanoStitcher {
|
|
|
595
654
|
private _dropTile(url: string): void {
|
|
596
655
|
const rec = this._texs.get(url);
|
|
597
656
|
if (rec && rec.tex) releasePanoTexture(rec.tex);
|
|
657
|
+
// ⭐ 2026-10-09 旨:已排进上传队列的那一片也要**作废**(否则队列回调会对已释放纹理 initTexture)
|
|
658
|
+
if (rec) this._uploadQueued.delete(rec);
|
|
598
659
|
this._texs.delete(url);
|
|
599
660
|
this._wanted.delete(url);
|
|
600
661
|
}
|
package/MktMesh/types.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Matrix4 } from "three";
|
|
2
2
|
/**
|
|
3
|
-
* 单个纹理的运行时信息:url +
|
|
3
|
+
* 单个纹理的运行时信息:url + 矩阵(位置 + 旋转,缩放忽略)。
|
|
4
|
+
* ⚠️ 这个矩阵记在 MktMesh **自身局部(制图)空间**,与 `wallPoints` 同一空间 —— 不是世界矩阵:
|
|
5
|
+
* 着色器里 `vProjPos = uProjInvMatrix·modelMatrix·position` 恰好把顶点拉回局部,片元再拿
|
|
6
|
+
* `vProjPos - 矩阵位置` 求投影方向,两端必须同空间纹理才贴得住(网格整体旋转/平移时尤其要紧,
|
|
7
|
+
* 名册挑图那把尺子也因此先把世界锚点 `worldToLocal` 再比距离,见 `MktMesh.updateTexture`)。
|
|
4
8
|
* 纹理矩阵的「位置」即墨卡托投影的中心点(shader 里以它为原点求方向向量)。
|
|
5
9
|
*/
|
|
6
10
|
export interface MktTextureInfo {
|
package/MktMesh/types.ts
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
import type { Matrix4 } from "three";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* 单个纹理的运行时信息:url +
|
|
11
|
+
* 单个纹理的运行时信息:url + 矩阵(位置 + 旋转,缩放忽略)。
|
|
12
|
+
* ⚠️ 这个矩阵记在 MktMesh **自身局部(制图)空间**,与 `wallPoints` 同一空间 —— 不是世界矩阵:
|
|
13
|
+
* 着色器里 `vProjPos = uProjInvMatrix·modelMatrix·position` 恰好把顶点拉回局部,片元再拿
|
|
14
|
+
* `vProjPos - 矩阵位置` 求投影方向,两端必须同空间纹理才贴得住(网格整体旋转/平移时尤其要紧,
|
|
15
|
+
* 名册挑图那把尺子也因此先把世界锚点 `worldToLocal` 再比距离,见 `MktMesh.updateTexture`)。
|
|
12
16
|
* 纹理矩阵的「位置」即墨卡托投影的中心点(shader 里以它为原点求方向向量)。
|
|
13
17
|
*/
|
|
14
18
|
export interface MktTextureInfo {
|
package/ThreeBase.d.ts
CHANGED
|
@@ -90,8 +90,8 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
90
90
|
/**
|
|
91
91
|
* 调试用:深度预览模式。由面板「深度调试」切换,便于逐一看各深度图排查"深度测试全部失效"根因。
|
|
92
92
|
* - "none" :正常渲染(默认)
|
|
93
|
-
* - "global"
|
|
94
|
-
* - "ao" :仅预览 AO 深度图(aoDepthTexture = 法线 pass
|
|
93
|
+
* - "global" :仅预览可见深度图(baseDepthTexture,含地板)。全白=深度为空→renderVisibleDepthPass 没写进深度。
|
|
94
|
+
* - "ao" :仅预览 AO 深度图(aoDepthTexture = 法线 pass 顺带写入)。用于确认深度管线本身是否工作。
|
|
95
95
|
* - "compare" :R=全局 base 深度,G=最近一次 glow/stroke 选区 select 深度。两路任一全白即该侧写入失败。
|
|
96
96
|
* - "excluded":深度图灰度 + 「被排除出深度图的面」叠红(红=该面写不进深度,永远挡不住后处理)。
|
|
97
97
|
* 物体区域(G 暗)若 B 全黑 → 判定从不触发;再看该区 R/G 谁大即可定位是谁缺深度。
|
|
@@ -99,15 +99,6 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
99
99
|
* 通用控制台入口:任意页面都可 `window.__TB_DEBUG_DEPTH = "judge"`(优先级高于面板属性)。
|
|
100
100
|
*/
|
|
101
101
|
debugDepthPreview: DebugDepthMode;
|
|
102
|
-
/**
|
|
103
|
-
* 可见深度图是否包含「标了 userData.aoFloor 的地板」(如 Ground1)。
|
|
104
|
-
*
|
|
105
|
-
* 默认 false —— 原设计:地板可透视,透过地板看到的物体照常显示(不被地板挡住)。
|
|
106
|
-
* 置 true 后地板会写进 baseDepthTexture,**被地板挡住的描边/辉光/毛玻璃/覆盖色会被正确遮挡**。
|
|
107
|
-
* 排查"怎么都挡不住"时先开这个试一下:若开了就正常,说明挡你的正是可透视地板。
|
|
108
|
-
*/
|
|
109
|
-
get baseDepthIncludeFloor(): boolean;
|
|
110
|
-
set baseDepthIncludeFloor(v: boolean);
|
|
111
102
|
normalRenderTarget: WebGLRenderTarget<Texture>;
|
|
112
103
|
screenRenderTarget: WebGLRenderTarget<Texture>;
|
|
113
104
|
screenCopyShader: import("three").ShaderMaterial;
|
package/ThreeBase.js
CHANGED
|
@@ -18,7 +18,7 @@ import frosted_glass_pass from "./pass/frosted_glass_pass";
|
|
|
18
18
|
import { flushLensflareOverlays } from "./lensflare/LensFlare";
|
|
19
19
|
import glow_pass from "./pass/glow_pass";
|
|
20
20
|
import stroke_pass from "./pass/stroke_pass";
|
|
21
|
-
import ssao_pass, {
|
|
21
|
+
import ssao_pass, { renderVisibleDepthPass, renderExcludedMaskPass } from "./pass/ssao_pass";
|
|
22
22
|
import screenBloomPass from "./pass/screen_bloom_pass";
|
|
23
23
|
import copyShader from "./shaders/copyShader";
|
|
24
24
|
import { baseTextureToRenderShader } from "./shaders/baseTextureToRenderShader";
|
|
@@ -260,12 +260,13 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
260
260
|
stencilBuffer: false,
|
|
261
261
|
depthTexture: this.baseDepthTexture,
|
|
262
262
|
});
|
|
263
|
-
// AO
|
|
264
|
-
// 法线 pass(renderAoNormalPass,
|
|
265
|
-
// (2026-09-14 优化:删掉原 ssao_pass
|
|
266
|
-
// SSAO 的 GTAO
|
|
267
|
-
//
|
|
268
|
-
//
|
|
263
|
+
// AO 深度纹理:**不再单独渲染一遍** —— 它挂在 normalRenderTarget 上当 depth attachment,
|
|
264
|
+
// 法线 pass(renderAoNormalPass,excludeTransparent=true)渲染时深度由光栅器顺带写入,白拿一张。
|
|
265
|
+
// (2026-09-14 优化:删掉原 ssao_pass 内那次独立的含地板深度 pass,每帧省一次全场景遍历。)
|
|
266
|
+
// SSAO 的 GTAO 采样它(与法线同源);合成时再与 depthRenderTarget(可见深度 = 各物体自身材质渲染)
|
|
267
|
+
// 比对:AO 侧更近说明那像素记到屏幕上不可见的面(抠图空隙 / 单面墙背面)→ 作废该像素 AO。
|
|
268
|
+
// ⚠️ 2026-10-11:地板(Ground1)现在两张图都写(旧的 aoFloor 排除特例已整条拆除),
|
|
269
|
+
// 见 three-base/pass/ssao_pass.ts 文件头与复合守卫说明。
|
|
269
270
|
aoDepthTexture = (() => {
|
|
270
271
|
const t = new DepthTexture(4096, 4096);
|
|
271
272
|
t.format = DepthFormat;
|
|
@@ -276,8 +277,8 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
276
277
|
/**
|
|
277
278
|
* 调试用:深度预览模式。由面板「深度调试」切换,便于逐一看各深度图排查"深度测试全部失效"根因。
|
|
278
279
|
* - "none" :正常渲染(默认)
|
|
279
|
-
* - "global"
|
|
280
|
-
* - "ao" :仅预览 AO 深度图(aoDepthTexture = 法线 pass
|
|
280
|
+
* - "global" :仅预览可见深度图(baseDepthTexture,含地板)。全白=深度为空→renderVisibleDepthPass 没写进深度。
|
|
281
|
+
* - "ao" :仅预览 AO 深度图(aoDepthTexture = 法线 pass 顺带写入)。用于确认深度管线本身是否工作。
|
|
281
282
|
* - "compare" :R=全局 base 深度,G=最近一次 glow/stroke 选区 select 深度。两路任一全白即该侧写入失败。
|
|
282
283
|
* - "excluded":深度图灰度 + 「被排除出深度图的面」叠红(红=该面写不进深度,永远挡不住后处理)。
|
|
283
284
|
* 物体区域(G 暗)若 B 全黑 → 判定从不触发;再看该区 R/G 谁大即可定位是谁缺深度。
|
|
@@ -285,20 +286,6 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
285
286
|
* 通用控制台入口:任意页面都可 `window.__TB_DEBUG_DEPTH = "judge"`(优先级高于面板属性)。
|
|
286
287
|
*/
|
|
287
288
|
debugDepthPreview = "none";
|
|
288
|
-
/**
|
|
289
|
-
* 可见深度图是否包含「标了 userData.aoFloor 的地板」(如 Ground1)。
|
|
290
|
-
*
|
|
291
|
-
* 默认 false —— 原设计:地板可透视,透过地板看到的物体照常显示(不被地板挡住)。
|
|
292
|
-
* 置 true 后地板会写进 baseDepthTexture,**被地板挡住的描边/辉光/毛玻璃/覆盖色会被正确遮挡**。
|
|
293
|
-
* 排查"怎么都挡不住"时先开这个试一下:若开了就正常,说明挡你的正是可透视地板。
|
|
294
|
-
*/
|
|
295
|
-
get baseDepthIncludeFloor() {
|
|
296
|
-
return visibleDepthOptions.includeFloor;
|
|
297
|
-
}
|
|
298
|
-
set baseDepthIncludeFloor(v) {
|
|
299
|
-
visibleDepthOptions.includeFloor = v;
|
|
300
|
-
this.needRender = true;
|
|
301
|
-
}
|
|
302
289
|
// 法线专用渲染目标(无 MSAA,存储视图空间法线,供 GTAO 风格 SSAO 使用真实法线)
|
|
303
290
|
normalRenderTarget = new WebGLRenderTarget(128, 128, {
|
|
304
291
|
minFilter: LinearFilter,
|
|
@@ -1019,10 +1006,11 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
1019
1006
|
this.renderer.setRenderTarget(this.baseRenderTarget);
|
|
1020
1007
|
this.renderer.clear();
|
|
1021
1008
|
this.renderer.render(this.scene, camera);
|
|
1022
|
-
//
|
|
1009
|
+
// 可见深度 pass:用各物体自身材质渲染(含地板;sprite/线条/点与显式 aoTransparent 面除外),
|
|
1010
|
+
// 产出 baseDepthTexture 供描边/发光/覆盖/磨砂做遮挡判定。
|
|
1023
1011
|
// 必须在 base render 之后、所有依赖深度的特效(描边/发光/覆盖/磨砂)之前更新,
|
|
1024
1012
|
// 否则这些特效会用到上一帧旧深度 → 遮挡判定错乱。法线 pass 已收进 ssao_pass 内部。
|
|
1025
|
-
|
|
1013
|
+
renderVisibleDepthPass(this.renderer, camera, this.scene, this.depthRenderTarget);
|
|
1026
1014
|
// 切回 baseRenderTarget,后处理 pass 需要把效果叠加到颜色渲染目标上
|
|
1027
1015
|
this.renderer.setRenderTarget(this.baseRenderTarget);
|
|
1028
1016
|
// 效果 mask 灰度图尺寸 = effectiveRatio × resolution,与 baseRenderTarget / baseDepthTexture 同倍率,
|
|
@@ -1088,7 +1076,7 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
1088
1076
|
// 分辨率按实际帧缓冲像素(已乘 dpr)设置,修复高分屏下 FXAA 采样比例偏大失效的问题。
|
|
1089
1077
|
// ⚠️ 深度调试预览(面板「深度调试」切换,定位"深度测试全部失效"根因):
|
|
1090
1078
|
// 旧控制台入口 `window.__BASE_DEPTH_VIS = true` 仍可用,等价于切到 "global"。
|
|
1091
|
-
// global → baseDepthTexture 灰度。全白=深度为空(
|
|
1079
|
+
// global → baseDepthTexture 灰度。全白=深度为空(renderVisibleDepthPass 没写进)→后处理一律显示在前;有灰阶=深度正常,问题在比对侧。
|
|
1092
1080
|
// ao → aoDepthTexture 灰度(含地板)。确认深度管线本身是否工作(GTAO 是否拿到正确深度)。
|
|
1093
1081
|
// compare → R=全局 base 深度,G=最近一次 glow/stroke 选区的 select 深度。两路任一全白即该侧写入失败。
|
|
1094
1082
|
// judge → R=base 原始深度、G=select 原始深度、B=判定结果(1=判为被遮挡应 discard)。
|
package/ThreeBase.ts
CHANGED
|
@@ -19,7 +19,7 @@ import frosted_glass_pass from "./pass/frosted_glass_pass";
|
|
|
19
19
|
import { flushLensflareOverlays } from "./lensflare/LensFlare";
|
|
20
20
|
import glow_pass from "./pass/glow_pass";
|
|
21
21
|
import stroke_pass from "./pass/stroke_pass";
|
|
22
|
-
import ssao_pass, {
|
|
22
|
+
import ssao_pass, { renderVisibleDepthPass, renderExcludedMaskPass } from "./pass/ssao_pass";
|
|
23
23
|
import screenBloomPass from "./pass/screen_bloom_pass";
|
|
24
24
|
import copyShader from "./shaders/copyShader";
|
|
25
25
|
import { baseTextureToRenderShader } from "./shaders/baseTextureToRenderShader";
|
|
@@ -321,12 +321,13 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
321
321
|
stencilBuffer: false,
|
|
322
322
|
depthTexture: this.baseDepthTexture,
|
|
323
323
|
});
|
|
324
|
-
// AO
|
|
325
|
-
// 法线 pass(renderAoNormalPass,
|
|
326
|
-
// (2026-09-14 优化:删掉原 ssao_pass
|
|
327
|
-
// SSAO 的 GTAO
|
|
328
|
-
//
|
|
329
|
-
//
|
|
324
|
+
// AO 深度纹理:**不再单独渲染一遍** —— 它挂在 normalRenderTarget 上当 depth attachment,
|
|
325
|
+
// 法线 pass(renderAoNormalPass,excludeTransparent=true)渲染时深度由光栅器顺带写入,白拿一张。
|
|
326
|
+
// (2026-09-14 优化:删掉原 ssao_pass 内那次独立的含地板深度 pass,每帧省一次全场景遍历。)
|
|
327
|
+
// SSAO 的 GTAO 采样它(与法线同源);合成时再与 depthRenderTarget(可见深度 = 各物体自身材质渲染)
|
|
328
|
+
// 比对:AO 侧更近说明那像素记到屏幕上不可见的面(抠图空隙 / 单面墙背面)→ 作废该像素 AO。
|
|
329
|
+
// ⚠️ 2026-10-11:地板(Ground1)现在两张图都写(旧的 aoFloor 排除特例已整条拆除),
|
|
330
|
+
// 见 three-base/pass/ssao_pass.ts 文件头与复合守卫说明。
|
|
330
331
|
aoDepthTexture: DepthTexture = (() => {
|
|
331
332
|
const t = new DepthTexture(4096, 4096);
|
|
332
333
|
t.format = DepthFormat;
|
|
@@ -337,8 +338,8 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
337
338
|
/**
|
|
338
339
|
* 调试用:深度预览模式。由面板「深度调试」切换,便于逐一看各深度图排查"深度测试全部失效"根因。
|
|
339
340
|
* - "none" :正常渲染(默认)
|
|
340
|
-
* - "global"
|
|
341
|
-
* - "ao" :仅预览 AO 深度图(aoDepthTexture = 法线 pass
|
|
341
|
+
* - "global" :仅预览可见深度图(baseDepthTexture,含地板)。全白=深度为空→renderVisibleDepthPass 没写进深度。
|
|
342
|
+
* - "ao" :仅预览 AO 深度图(aoDepthTexture = 法线 pass 顺带写入)。用于确认深度管线本身是否工作。
|
|
342
343
|
* - "compare" :R=全局 base 深度,G=最近一次 glow/stroke 选区 select 深度。两路任一全白即该侧写入失败。
|
|
343
344
|
* - "excluded":深度图灰度 + 「被排除出深度图的面」叠红(红=该面写不进深度,永远挡不住后处理)。
|
|
344
345
|
* 物体区域(G 暗)若 B 全黑 → 判定从不触发;再看该区 R/G 谁大即可定位是谁缺深度。
|
|
@@ -347,20 +348,6 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
347
348
|
*/
|
|
348
349
|
public debugDepthPreview: DebugDepthMode = "none";
|
|
349
350
|
|
|
350
|
-
/**
|
|
351
|
-
* 可见深度图是否包含「标了 userData.aoFloor 的地板」(如 Ground1)。
|
|
352
|
-
*
|
|
353
|
-
* 默认 false —— 原设计:地板可透视,透过地板看到的物体照常显示(不被地板挡住)。
|
|
354
|
-
* 置 true 后地板会写进 baseDepthTexture,**被地板挡住的描边/辉光/毛玻璃/覆盖色会被正确遮挡**。
|
|
355
|
-
* 排查"怎么都挡不住"时先开这个试一下:若开了就正常,说明挡你的正是可透视地板。
|
|
356
|
-
*/
|
|
357
|
-
public get baseDepthIncludeFloor(): boolean {
|
|
358
|
-
return visibleDepthOptions.includeFloor;
|
|
359
|
-
}
|
|
360
|
-
public set baseDepthIncludeFloor(v: boolean) {
|
|
361
|
-
visibleDepthOptions.includeFloor = v;
|
|
362
|
-
this.needRender = true;
|
|
363
|
-
}
|
|
364
351
|
// 法线专用渲染目标(无 MSAA,存储视图空间法线,供 GTAO 风格 SSAO 使用真实法线)
|
|
365
352
|
normalRenderTarget: WebGLRenderTarget<Texture> = new WebGLRenderTarget(128, 128, {
|
|
366
353
|
minFilter: LinearFilter,
|
|
@@ -1131,10 +1118,11 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
1131
1118
|
this.renderer.setRenderTarget(this.baseRenderTarget);
|
|
1132
1119
|
this.renderer.clear();
|
|
1133
1120
|
this.renderer.render(this.scene, camera);
|
|
1134
|
-
//
|
|
1121
|
+
// 可见深度 pass:用各物体自身材质渲染(含地板;sprite/线条/点与显式 aoTransparent 面除外),
|
|
1122
|
+
// 产出 baseDepthTexture 供描边/发光/覆盖/磨砂做遮挡判定。
|
|
1135
1123
|
// 必须在 base render 之后、所有依赖深度的特效(描边/发光/覆盖/磨砂)之前更新,
|
|
1136
1124
|
// 否则这些特效会用到上一帧旧深度 → 遮挡判定错乱。法线 pass 已收进 ssao_pass 内部。
|
|
1137
|
-
|
|
1125
|
+
renderVisibleDepthPass(this.renderer, camera, this.scene, this.depthRenderTarget);
|
|
1138
1126
|
// 切回 baseRenderTarget,后处理 pass 需要把效果叠加到颜色渲染目标上
|
|
1139
1127
|
this.renderer.setRenderTarget(this.baseRenderTarget);
|
|
1140
1128
|
// 效果 mask 灰度图尺寸 = effectiveRatio × resolution,与 baseRenderTarget / baseDepthTexture 同倍率,
|
|
@@ -1204,7 +1192,7 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
1204
1192
|
// 分辨率按实际帧缓冲像素(已乘 dpr)设置,修复高分屏下 FXAA 采样比例偏大失效的问题。
|
|
1205
1193
|
// ⚠️ 深度调试预览(面板「深度调试」切换,定位"深度测试全部失效"根因):
|
|
1206
1194
|
// 旧控制台入口 `window.__BASE_DEPTH_VIS = true` 仍可用,等价于切到 "global"。
|
|
1207
|
-
// global → baseDepthTexture 灰度。全白=深度为空(
|
|
1195
|
+
// global → baseDepthTexture 灰度。全白=深度为空(renderVisibleDepthPass 没写进)→后处理一律显示在前;有灰阶=深度正常,问题在比对侧。
|
|
1208
1196
|
// ao → aoDepthTexture 灰度(含地板)。确认深度管线本身是否工作(GTAO 是否拿到正确深度)。
|
|
1209
1197
|
// compare → R=全局 base 深度,G=最近一次 glow/stroke 选区的 select 深度。两路任一全白即该侧写入失败。
|
|
1210
1198
|
// judge → R=base 原始深度、G=select 原始深度、B=判定结果(1=判为被遮挡应 discard)。
|
package/WaterFlow/WaterFlow.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ import type { BasinConfig, FluidParameters, FluidQuality, ReservoirState, WaterF
|
|
|
26
26
|
* depthMode: 'logarithmic',
|
|
27
27
|
* });
|
|
28
28
|
* // 每帧:先 renderer.setRenderTarget(baseRenderTarget); renderer.render(scene, camera);
|
|
29
|
-
* // 再
|
|
29
|
+
* // 再 renderVisibleDepthPass(renderer, camera, scene, depthRenderTarget); // 写 baseDepthTexture
|
|
30
30
|
* // 然后 water.update(dt, elapsed);
|
|
31
31
|
* // 最后 setRenderTarget(screenRT); fsQuad.render(renderer, pass.material);
|
|
32
32
|
* // 注意:WaterFlow 的 composite 是纯消费 base 纹理的 fsQuad,可直接放进你的链。
|