three-base 2.0.188 → 2.0.192
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/MeshBase.d.ts +23 -0
- package/MeshBase.js +47 -1
- package/MeshBase.ts +301 -257
- package/MktMesh/MktMesh.d.ts +36 -3
- package/MktMesh/MktMesh.js +72 -5
- package/MktMesh/MktMesh.ts +1437 -1368
- package/MktMesh/imagePlane.d.ts +4 -0
- package/MktMesh/imagePlane.js +8 -0
- package/MktMesh/imagePlane.ts +7 -0
- package/MktMesh/mktMappingShader.d.ts +5 -3
- package/MktMesh/mktMappingShader.js +7 -5
- package/MktMesh/mktMappingShader.ts +7 -5
- package/MktMesh/mktShader.d.ts +58 -54
- package/MktMesh/mktShader.js +61 -57
- package/MktMesh/mktShader.ts +68 -64
- package/MktMesh/modelSlot.d.ts +8 -0
- package/MktMesh/modelSlot.js +26 -2
- package/MktMesh/modelSlot.ts +26 -2
- package/MktMesh/panoTileStitcher.d.ts +8 -6
- package/MktMesh/panoTileStitcher.js +36 -25
- package/MktMesh/panoTileStitcher.ts +37 -26
- package/MktMesh/types.d.ts +2 -1
- package/MktMesh/types.ts +2 -1
- package/ThreeBase.d.ts +4 -1
- package/ThreeBase.js +51 -42
- package/ThreeBase.ts +51 -42
- package/instanced/InstancedMeshBase.js +3 -0
- package/instanced/InstancedMeshBase.ts +3 -0
- package/package.json +1 -1
package/MktMesh/modelSlot.js
CHANGED
|
@@ -107,6 +107,12 @@ export class ModelSlot extends Group {
|
|
|
107
107
|
_staged = null;
|
|
108
108
|
/** 载入令牌:换地址/删除后迟到的回调靠它作废(否则旧模型会挂进新槽) */
|
|
109
109
|
_loadToken = 0;
|
|
110
|
+
/**
|
|
111
|
+
* 内容是否已落定:真模型挂进了 `_contentRoot`,或这一颗压根没在等什么(空地址 / 取回失败 ⇒ 停在占位小立方体)。
|
|
112
|
+
* ⚠️ 落定**不等于**已经画进 GPU —— 顶点缓冲与首次 program 编译发生在「亮出来的那一帧」,
|
|
113
|
+
* 所以逐帧限流必须闸在点亮这一下(`MktMesh.tickProgressiveReveal`),闸在挂载那一下拦不住 GPU 侧的账。
|
|
114
|
+
*/
|
|
115
|
+
_loaded = false;
|
|
110
116
|
_disposed = false;
|
|
111
117
|
constructor(name) {
|
|
112
118
|
super();
|
|
@@ -126,6 +132,8 @@ export class ModelSlot extends Group {
|
|
|
126
132
|
get naturalSize() { return this._naturalSize; }
|
|
127
133
|
/** 是否已经挂上真模型(false = 仍是占位小立方体) */
|
|
128
134
|
get hasModel() { return this._model !== null; }
|
|
135
|
+
/** 这一颗的内容落定没有 —— 库的逐帧点亮闸只认这一条(`MktMesh.tickProgressiveReveal`)。 */
|
|
136
|
+
get loaded() { return this._loaded; }
|
|
129
137
|
/** 改尺寸档(米,最长边):立刻重缩内容 */
|
|
130
138
|
setSize(size) {
|
|
131
139
|
this._size = size > 0 ? size : this._size;
|
|
@@ -161,13 +169,25 @@ export class ModelSlot extends Group {
|
|
|
161
169
|
async loadModel(url) {
|
|
162
170
|
this._url = url;
|
|
163
171
|
const token = ++this._loadToken;
|
|
172
|
+
this._loaded = false;
|
|
164
173
|
this._detachModel();
|
|
165
174
|
// 先立刻退回占位小立方体:GLB 在路上那段窗口(慢网/大模型)⛔ 不许让这一槽凭空消失
|
|
166
175
|
// (取回失败那条支路本来也是停在占位上,两段时间里画面一致)
|
|
167
176
|
this._layout();
|
|
168
|
-
if (!url)
|
|
177
|
+
if (!url) {
|
|
178
|
+
// 空地址 = 这一颗压根没在等什么 ⇒ 当场落定(占位小立方体就是它的最终内容)
|
|
179
|
+
this._loaded = true;
|
|
169
180
|
return true;
|
|
170
|
-
|
|
181
|
+
}
|
|
182
|
+
// 取回失败(404 / 解析报错都从这一档走):`threeLoader.loadModel` 是会 reject 的,⚠️ 必须当「落定在占位方块」处理
|
|
183
|
+
// —— 整层重建时这一颗是压着不亮的,不落定就永远隐形(旧写法里它至少还露得出占位方块)。
|
|
184
|
+
let obj;
|
|
185
|
+
try {
|
|
186
|
+
obj = await threeLoader.loadModel(url);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
obj = undefined;
|
|
190
|
+
}
|
|
171
191
|
// 迟到回调:这一槽已经改指向别的地址、或已经删除 ⇒ 当场释放,别留一份没人认领的显存
|
|
172
192
|
if (this._disposed || token !== this._loadToken) {
|
|
173
193
|
if (obj)
|
|
@@ -175,6 +195,7 @@ export class ModelSlot extends Group {
|
|
|
175
195
|
return false;
|
|
176
196
|
}
|
|
177
197
|
if (!obj) {
|
|
198
|
+
this._loaded = true;
|
|
178
199
|
this._layout();
|
|
179
200
|
return false;
|
|
180
201
|
}
|
|
@@ -189,6 +210,7 @@ export class ModelSlot extends Group {
|
|
|
189
210
|
this._staged = null;
|
|
190
211
|
this._model = obj;
|
|
191
212
|
this._contentRoot.add(obj);
|
|
213
|
+
this._loaded = true;
|
|
192
214
|
this._layout();
|
|
193
215
|
return true;
|
|
194
216
|
}
|
|
@@ -220,6 +242,8 @@ export class ModelSlot extends Group {
|
|
|
220
242
|
this._size = d.size > 0 ? d.size : MODEL_SLOT_DEFAULT_SIZE;
|
|
221
243
|
this._naturalSize = d.naturalSize;
|
|
222
244
|
this._url = d.url || "";
|
|
245
|
+
// 落定判据跟着地址走:空地址 = 没在等什么(占位方块就是最终内容);有地址 = 等 GLB 那一拍
|
|
246
|
+
this._loaded = !this._url;
|
|
223
247
|
if (!this._url)
|
|
224
248
|
this._detachModel();
|
|
225
249
|
this._layout();
|
package/MktMesh/modelSlot.ts
CHANGED
|
@@ -137,6 +137,12 @@ export class ModelSlot extends Group {
|
|
|
137
137
|
private _staged: Object3D | null = null;
|
|
138
138
|
/** 载入令牌:换地址/删除后迟到的回调靠它作废(否则旧模型会挂进新槽) */
|
|
139
139
|
private _loadToken = 0;
|
|
140
|
+
/**
|
|
141
|
+
* 内容是否已落定:真模型挂进了 `_contentRoot`,或这一颗压根没在等什么(空地址 / 取回失败 ⇒ 停在占位小立方体)。
|
|
142
|
+
* ⚠️ 落定**不等于**已经画进 GPU —— 顶点缓冲与首次 program 编译发生在「亮出来的那一帧」,
|
|
143
|
+
* 所以逐帧限流必须闸在点亮这一下(`MktMesh.tickProgressiveReveal`),闸在挂载那一下拦不住 GPU 侧的账。
|
|
144
|
+
*/
|
|
145
|
+
private _loaded = false;
|
|
140
146
|
private _disposed = false;
|
|
141
147
|
|
|
142
148
|
constructor(name?: string) {
|
|
@@ -164,6 +170,8 @@ export class ModelSlot extends Group {
|
|
|
164
170
|
get naturalSize(): { x: number; y: number; z: number } | undefined { return this._naturalSize; }
|
|
165
171
|
/** 是否已经挂上真模型(false = 仍是占位小立方体) */
|
|
166
172
|
get hasModel(): boolean { return this._model !== null; }
|
|
173
|
+
/** 这一颗的内容落定没有 —— 库的逐帧点亮闸只认这一条(`MktMesh.tickProgressiveReveal`)。 */
|
|
174
|
+
get loaded(): boolean { return this._loaded; }
|
|
167
175
|
|
|
168
176
|
/** 改尺寸档(米,最长边):立刻重缩内容 */
|
|
169
177
|
setSize(size: number): void {
|
|
@@ -201,18 +209,31 @@ export class ModelSlot extends Group {
|
|
|
201
209
|
async loadModel(url: string): Promise<boolean> {
|
|
202
210
|
this._url = url;
|
|
203
211
|
const token = ++this._loadToken;
|
|
212
|
+
this._loaded = false;
|
|
204
213
|
this._detachModel();
|
|
205
214
|
// 先立刻退回占位小立方体:GLB 在路上那段窗口(慢网/大模型)⛔ 不许让这一槽凭空消失
|
|
206
215
|
// (取回失败那条支路本来也是停在占位上,两段时间里画面一致)
|
|
207
216
|
this._layout();
|
|
208
|
-
if (!url)
|
|
209
|
-
|
|
217
|
+
if (!url) {
|
|
218
|
+
// 空地址 = 这一颗压根没在等什么 ⇒ 当场落定(占位小立方体就是它的最终内容)
|
|
219
|
+
this._loaded = true;
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
// 取回失败(404 / 解析报错都从这一档走):`threeLoader.loadModel` 是会 reject 的,⚠️ 必须当「落定在占位方块」处理
|
|
223
|
+
// —— 整层重建时这一颗是压着不亮的,不落定就永远隐形(旧写法里它至少还露得出占位方块)。
|
|
224
|
+
let obj: Object3D | undefined;
|
|
225
|
+
try {
|
|
226
|
+
obj = await threeLoader.loadModel(url);
|
|
227
|
+
} catch {
|
|
228
|
+
obj = undefined;
|
|
229
|
+
}
|
|
210
230
|
// 迟到回调:这一槽已经改指向别的地址、或已经删除 ⇒ 当场释放,别留一份没人认领的显存
|
|
211
231
|
if (this._disposed || token !== this._loadToken) {
|
|
212
232
|
if (obj) disposeSubtree(obj);
|
|
213
233
|
return false;
|
|
214
234
|
}
|
|
215
235
|
if (!obj) {
|
|
236
|
+
this._loaded = true;
|
|
216
237
|
this._layout();
|
|
217
238
|
return false;
|
|
218
239
|
}
|
|
@@ -227,6 +248,7 @@ export class ModelSlot extends Group {
|
|
|
227
248
|
this._staged = null;
|
|
228
249
|
this._model = obj;
|
|
229
250
|
this._contentRoot.add(obj);
|
|
251
|
+
this._loaded = true;
|
|
230
252
|
this._layout();
|
|
231
253
|
return true;
|
|
232
254
|
}
|
|
@@ -257,6 +279,8 @@ export class ModelSlot extends Group {
|
|
|
257
279
|
this._size = d.size > 0 ? d.size : MODEL_SLOT_DEFAULT_SIZE;
|
|
258
280
|
this._naturalSize = d.naturalSize;
|
|
259
281
|
this._url = d.url || "";
|
|
282
|
+
// 落定判据跟着地址走:空地址 = 没在等什么(占位方块就是最终内容);有地址 = 等 GLB 那一拍
|
|
283
|
+
this._loaded = !this._url;
|
|
260
284
|
if (!this._url) this._detachModel();
|
|
261
285
|
this._layout();
|
|
262
286
|
}
|
|
@@ -6,7 +6,7 @@ import { type TileLayout } from "./mktShader";
|
|
|
6
6
|
*/
|
|
7
7
|
export interface PanoStitcher {
|
|
8
8
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
|
|
9
|
-
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景
|
|
9
|
+
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 8×4 / 2 槽 / 6144×6144,2026-10-27 需求);
|
|
10
10
|
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。 */
|
|
11
11
|
ensure(urls: string[], layout?: TileLayout): void;
|
|
12
12
|
/**
|
|
@@ -59,7 +59,7 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
59
59
|
private _camera;
|
|
60
60
|
private _mat;
|
|
61
61
|
/** 几何是 2×2(铺满正交相机的 [-1,1]²),每格按 `1/slotCellCols × 1/slotCellRows` 缩放再挪过去
|
|
62
|
-
* ⇒ 全景一格 = 槽的 1/
|
|
62
|
+
* ⇒ 全景一格 = 槽的 1/4 宽 × 1/4 高(横 4 竖 4,2026-10-27 口径),图片平面一格 = 1/4(横 2 竖 2) */
|
|
63
63
|
private _quad;
|
|
64
64
|
/** 缺格那一路 `uMap` 也要有个合法纹理可绑(1×1,从不真采样) */
|
|
65
65
|
private _blank;
|
|
@@ -67,10 +67,12 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
67
67
|
private _rtTextures;
|
|
68
68
|
constructor();
|
|
69
69
|
/** 确保批次已建组、加载已发起。幂等:同批已在册 ⇒ 直接返回(一片瓦片终身只下载、只解码一次)。
|
|
70
|
-
* ⭐ 09-29「不足的涂灰」+ 10-
|
|
70
|
+
* ⭐ 09-29「不足的涂灰」+ 2026-10-27「8×4 = 32 片」:1~32 条皆可入组,内部补空串占位到 32;
|
|
71
71
|
* 空串 / 死链的格子拼合时涂灰(一眼看出该全景数据不全)。
|
|
72
|
-
* ⛔ 旧版
|
|
73
|
-
* 超
|
|
72
|
+
* ⛔ 旧版 18 条(6×3)、16 条(4×4)、64 条(8×8)与 9 条(3×3)的数据**都不做兼容**(用户「无须兼容正常使用, 有异常我会重传」):
|
|
73
|
+
* 超 32 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
|
|
74
|
+
* ⚠️ 唯一按张数**能混进来**的是 10-06 那版同为 32 片的老档(切片顺序与槽分组两版一致 ⇒ 不会错位),
|
|
75
|
+
* 但它的瓦片是 1024²、被画进 1536² 的格子 = 1.5× 放大 ⇒ 发虚,那类老点仍要重传一次。
|
|
74
76
|
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null]` —— 槽数再变会漏改)。 */
|
|
75
77
|
ensure(urls: string[], layout?: TileLayout): void;
|
|
76
78
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
@@ -121,7 +123,7 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
121
123
|
* (与 `MktMesh._uploadTileTexture` 的「排上队之后已被清掉 ⇒ 本项作废」同一口径。) */
|
|
122
124
|
private _uploadTile;
|
|
123
125
|
/** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(每槽 `slotCellCols × slotCellRows` 格里的一格,
|
|
124
|
-
* 10-
|
|
126
|
+
* 2026-10-27 起全景是「横 4 × 竖 4」= 16 格、图片平面是 2×2 = 4 格)。
|
|
125
127
|
* ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 cellsPerSlot 帧**,
|
|
126
128
|
* 清一次就把前面画好的格子抹了 ⇒ 槽纹理只剩最后一格。清屏交给我们自己(见 `_clearRt`)。 */
|
|
127
129
|
private _stitchCell;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* @Description:
|
|
2
|
+
* @Description: 32 瓦片全景的「离屏拼合器」= **shader 逐格画进 WebGLRenderTarget**(本项目唯一一份拼合实现)。
|
|
3
3
|
*
|
|
4
4
|
* 三道旨意叠成现在这一份:
|
|
5
5
|
* ① 2026-09-29:一张全景切多片(每片所有 GPU 都装得下),运行时离屏拼回若干张固定尺寸的「槽纹理」,
|
|
@@ -28,9 +28,16 @@
|
|
|
28
28
|
* ⇒ 改 **6×3=18 片 / 左右 2 槽**,每槽 = 原图半幅(横 3 格 × 竖 3 格)= **6144×6144**、与原图 1:1。
|
|
29
29
|
* ⭐ 净效果:单全景有效分辨率与显存**一字未动**(仍是 75.5Mpx / ≈384MB),sampler = 点数 × 2 ⇒
|
|
30
30
|
* **同屏全景点数 5 → 8**(8×2=16 正好用满硬顶)。旧存档 9 条地址**不兼容**,用户重传即可。
|
|
31
|
+
* ⑥ 2026-10-27(需求,逐字):「目前全景的 2个RT 是 3*3=9张的对吧, 我想要改成之前的 4*4=16张的逻辑, 数据你不用管,
|
|
32
|
+
* 我会重新上传, 你把代码改正确就行」
|
|
33
|
+
* ⇒ 改 **8×4=32 片 / 左右 2 槽**,每槽 = 原图半幅(横 4 格 × 竖 4 格 = 16 格)= **6144×6144**、与原图 1:1。
|
|
34
|
+
* ⭐ 净效果:单全景有效分辨率与显存**一字未动**(有效分辨率仍是 **12288×6144**、与 ⑤ 那版 18 片**逐像素同量**;
|
|
35
|
+
* 显存仍 ≈384MB 含 mipmap),sampler 账不变(每点 2 张 × 同屏 8 点 = 16 = 本机硬顶)⇒ 变的只有「切几刀」:
|
|
36
|
+
* 每格像素 2048×2048 → **1536×1536**、一组待画格 18 → **32**。
|
|
37
|
+
* ⚠️ 旧存档 18 / 9 / 64 / 16 条地址**不兼容**(张数不符被判废)⇒ 用户「数据你不用管,我会重新上传」。
|
|
31
38
|
*
|
|
32
39
|
* ⚠️ **仍然有 canvas,但只在上传切图那一步**(`src/views/MktMeshEdit/panoramaUpload.ts` 用 2D 画布把原图
|
|
33
|
-
* 切成
|
|
40
|
+
* 切成 32 个 JPEG 传 OSS)—— 那是**离线、一次性、产出的是数据**;运行时这条路上一个 canvas 都没有。
|
|
34
41
|
*
|
|
35
42
|
* ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
|
|
36
43
|
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
|
|
@@ -40,49 +47,51 @@
|
|
|
40
47
|
* 到 GPU 里面去」)。这一趟**只上传、不画** ⇒ 上传与绘制永不落在同一拍(09-29 那句「两个大头别在同一
|
|
41
48
|
* 帧」的延续),而且第 2 步天然只能画「上一帧及更早」传上去的图 ⇒ 每帧 = 1 上传 + 1 绘制(稳态)。
|
|
42
49
|
*
|
|
43
|
-
* ⚠️ 于是「一帧一片」的帧数随网格走:16 片 → 32 片 → 9 片 →
|
|
44
|
-
* 10-
|
|
45
|
-
* 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等
|
|
50
|
+
* ⚠️ 于是「一帧一片」的帧数随网格走:16 片 → 32 片 → 9 片 → 18 片 → **32 片/组 = 32 帧**(30ms/帧 ⇒ 约 1.0s,
|
|
51
|
+
* 2026-10-27 需求)。每格像素 = `RT_STITCH_SLOT_W/4 × RT_STITCH_SLOT_H/4` = **1536×1536**(原图切出来那片多大就多大,一比一)。
|
|
52
|
+
* 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等 32 帧。
|
|
46
53
|
*
|
|
47
54
|
* ⭐ 部分就绪就先喂(同日旨「1. 需要先喂给 shader」):`peek()` 不再等一个槽的 `cellsPerSlot` 格画满,
|
|
48
55
|
* **画过 ≥1 格就交出去**。为了保证交出去的那张 RT 里绝无未定义像素,RT 建好后的第一件事是
|
|
49
56
|
* 整张涂成缺失灰(`_clearRt`)⇒ 观感 = 该象限从左上角起逐格显影,缺数据的格子一直是灰的。
|
|
50
57
|
*
|
|
51
58
|
* ── 画质四条(⛔ 改任何一条都是改画质,先请旨)──────────────────────────────
|
|
52
|
-
* 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **6144×6144**、每槽「横
|
|
59
|
+
* 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **6144×6144**、每槽「横 4 格 × 竖 4 格」、原图**横 8 × 竖 4** 片,
|
|
53
60
|
* 全在 `./mktShader`(单一来源)。⭐ 10-23 旨后格子尺寸 = 那片瓦片的尺寸 ⇒ **一比一**:11968×5984 那类原图
|
|
54
|
-
* 切
|
|
55
|
-
* ⇒ 单全景有效分辨率 33.5Mpx → **75.5Mpx
|
|
61
|
+
* 切 8×4 得 ~1496×1496 的片,铺进 1536×1536 的格子几乎不缩(缩放比 1.03,与上一版同一档)
|
|
62
|
+
* ⇒ 单全景有效分辨率 33.5Mpx → **75.5Mpx**(= 12288×6144,本次与上一版**逐像素同量**),是**升**画质那一档定下的,
|
|
63
|
+
* 与「千万不要降低质量」红线同向。
|
|
56
64
|
* ⛔ 不许拿槽尺寸当省显存的旋钮:现口径每槽覆盖**横 180° × 竖 180°**(原图左/右半幅),
|
|
57
|
-
* 宽高比 1:1 与「半幅(
|
|
65
|
+
* 宽高比 1:1 与「半幅(4 列 × 4 行)」一致;更早按列分槽那一版一槽装一整列时是 2:3,两版各自都对,⛔ 别混着抄。
|
|
58
66
|
* 2. **色彩**:瓦片纹理 `colorSpace = SRGBColorSpace` ⇒ WebGL2 内部格式 `SRGB8_ALPHA8`,采样时硬件解码成
|
|
59
67
|
* linear;RT 纹理同样标 sRGB ⇒ 写入时硬件再编码回 sRGB。一进一出**逐字节还原**。
|
|
60
68
|
* ⚠️ 另一半证据在 three 里:写进非 XR 的 render target 时片元末尾的 `linearToOutputTexel` 恒按
|
|
61
69
|
* LinearSRGB(= 不转换),编码完全交给硬件 ⇒ ⛔ 我们的片元绝不 `#include <colorspace_fragment>`,
|
|
62
70
|
* 也不带 tone mapping 的 chunk(那两处都会再转一遍 ⇒ 画面发白)。
|
|
63
71
|
* 3. **摩尔纹**:两层都开 mipmap ——
|
|
64
|
-
* ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:一片原图(~
|
|
72
|
+
* ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:一片原图(~1496×1496)铺进 1536×1536
|
|
65
73
|
* 的格子是**一比一**,但墙面在远处仍会缩到格子以下 ⇒ ⛔ 单点双线性会采出锯齿,mipmap 照旧开
|
|
66
74
|
* (WebGL2 对 NPOT 也能生成 mipmap,three r163 起只支持 WebGL2)。
|
|
67
75
|
* ② 槽 RT 标 `generateMipmaps`:three 每次渲染进 RT 后自己 `glGenerateMipmap`(`WebGLRenderer.js`
|
|
68
76
|
* `updateRenderTargetMipmap`)⇒ 与当年 CanvasTexture 上传后生成 mipmap 同一口径。
|
|
69
77
|
* ⚠️ 代价:每格一次(旧版每格也是一次),都在 GPU 上、不占主线程。
|
|
70
|
-
* 4. **接缝**:槽内「横
|
|
71
|
-
* 槽与槽之间另有 u=1/2 **一道**跨纹理竖缝,那道治不了(见 `PanoTexMode`
|
|
78
|
+
* 4. **接缝**:槽内「横 4 × 竖 4」⇒ 一张槽内部的格子线是 **3 横 3 竖**(上一版 3×3 是 2 横 2 竖;更早一槽一整列时只有 2 横 0 竖);
|
|
79
|
+
* 槽与槽之间另有 u=1/2 **一道**跨纹理竖缝,那道治不了(见 `PanoTexMode` 说明②;更早按列 3 槽那版才是两道)。治法 = 上传时每片向四边**多切**
|
|
72
80
|
* `RT_STITCH_SEAM_MARGIN_PX` 像素、拼合时**只采中间那块**(`uSrcRect`)⇒ 边界插值拿得到真邻居像素,
|
|
73
81
|
* 不再复制自己的边缘。⛔ 这条不许「顺手简化」成直接铺满格子:几何会挤歪,缝也还在。
|
|
74
82
|
* 详见 `./mktShader` 里那三个 `rtStitch*` 纯函数 —— **上传侧与拼合侧必须共用同一份几何**。
|
|
75
83
|
*
|
|
76
|
-
* 资源账(⚠️ 如实记;10-
|
|
84
|
+
* 资源账(⚠️ 如实记;2026-10-27 需求改 8×4=32 片 / 左右 2 槽 / 6144×6144 后重算 —— 槽尺寸一字未动 ⇒ 显存那一栏不变):
|
|
77
85
|
* - 常驻显存:2 槽 × (6144×**6144**×4B + mipmap≈33%) ≈ **384MB/全景**(不含 mipmap 288MB —— 与上一版**一字不差**)
|
|
78
86
|
* —— 更早那版 8×4=32 片 / 2 槽 4096² 只有 **170MB/全景** ⇒ 单全景 **2.26 倍**,代价来自「槽与原图一比一」那次**升分辨率**,
|
|
79
87
|
* 不是浪费:33.5Mpx → 75.5Mpx 的有效像素全在 RT 里。
|
|
80
|
-
* ⚠️ 组**运行中不淘汰**(见文末那条需求)⇒ 13 张全景全常驻 ≈ **4.9GB
|
|
88
|
+
* ⚠️ 组**运行中不淘汰**(见文末那条需求)⇒ 13 张全景全常驻 ≈ **4.9GB**(上一版同数 —— 单全景显存一字未动)。同屏景点数 8,
|
|
81
89
|
* 那只省 sampler 绑定、**不省显存**。⚠️ 这是这一轮最大的风险项,已另记待用户裁决(见「RT 不回收」)。
|
|
82
|
-
* - 上传总量:同一张原图切完 ⇒ **总像素与上一版逐字节同量**,片数
|
|
83
|
-
* ⇒ 下载发起数比
|
|
90
|
+
* - 上传总量:同一张原图切完 ⇒ **总像素与上一版逐字节同量**,片数 18 → 32、每片 2048×2048 → 1536×1536(单片像素减到 ≈0.56 倍)
|
|
91
|
+
* ⇒ 下载发起数比 18 片那版**多 14 条**(与 10-06 那版 32 片同数)、单请求更小。
|
|
84
92
|
* - 瓦片纹理画完即弃 ⇒ 同时在册的只有「下载中 + 已解码待上传」那些,上限 8+8 片;
|
|
85
|
-
* ⚠️ 每片解码位图
|
|
93
|
+
* ⚠️ 每片解码位图 1536×1536×4B = **9MB**(上一版每片 2048×2048 = 16MB)⇒ 闸内在制品峰值 CPU 侧 ≈ **144MB**(原 256MB);
|
|
94
|
+
* 一组 32 片若全解码则是 ≈**288MB**(上一版 18 片 × 16MB = 288MB —— **同一量**)。
|
|
86
95
|
*
|
|
87
96
|
* ⚠️ RT 是 GL 上下文私有资源:`pump(renderer)` 换 renderer 时整组作废(`_switchRenderer`),下一帧消费方
|
|
88
97
|
* 照旧的 `ensure()` 重建(瓦片走浏览器 HTTP 缓存重新下载)。现况是**每个持有者一份拼合器 + 一个 renderer**
|
|
@@ -103,11 +112,11 @@ import { MISSING_TILE_GRAY, rtStitchBatchOf, rtStitchCellPos, rtStitchTileInset,
|
|
|
103
112
|
* ⛔ 别再拿它去写 budget 循环:那正是「一帧上传 2 张」的根因(消费方 onBeforeRender 里 pump 了两次)。
|
|
104
113
|
*/
|
|
105
114
|
const PANO_STITCH_UPLOADS_PER_FRAME = 1;
|
|
106
|
-
/** 每帧最多发起几张下载(现行一批
|
|
115
|
+
/** 每帧最多发起几张下载(现行一批 32 片,⛔ 不加以限制就是一帧甩 32 条 XHR + 32 次 ≈236 万像素解码) */
|
|
107
116
|
const PANO_STITCH_DOWNLOADS_PER_FRAME = 2;
|
|
108
117
|
/** 「下载中」的片数上限(跨所有批次) */
|
|
109
118
|
const PANO_STITCH_MAX_INFLIGHT = 8;
|
|
110
|
-
/** 「已解码、还没上传进 GPU」的片数上限 ⇒ 也是 CPU 侧位图的峰值内存闸(≈8 ×
|
|
119
|
+
/** 「已解码、还没上传进 GPU」的片数上限 ⇒ 也是 CPU 侧位图的峰值内存闸(≈8 × 9MB,每片 1536×1536×4B) */
|
|
111
120
|
const PANO_STITCH_MAX_DECODED_AHEAD = 8;
|
|
112
121
|
/** 瓦片纹理的实际像素宽高(`loadPanoTexture` 交回来的是 `ImageBitmap`,尺寸就是切片区)。
|
|
113
122
|
* 内缩比例按**实际尺寸**算 ⇒ 编码差 1px 也不会露出邻居格子的像素。拿不到尺寸时退回下面那个兜底边长。 */
|
|
@@ -192,7 +201,7 @@ export class PanoTileStitcher {
|
|
|
192
201
|
blending: NoBlending,
|
|
193
202
|
});
|
|
194
203
|
/** 几何是 2×2(铺满正交相机的 [-1,1]²),每格按 `1/slotCellCols × 1/slotCellRows` 缩放再挪过去
|
|
195
|
-
* ⇒ 全景一格 = 槽的 1/
|
|
204
|
+
* ⇒ 全景一格 = 槽的 1/4 宽 × 1/4 高(横 4 竖 4,2026-10-27 口径),图片平面一格 = 1/4(横 2 竖 2) */
|
|
196
205
|
_quad = new Mesh(new PlaneGeometry(2, 2), this._mat);
|
|
197
206
|
/** 缺格那一路 `uMap` 也要有个合法纹理可绑(1×1,从不真采样) */
|
|
198
207
|
_blank = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
|
|
@@ -204,10 +213,12 @@ export class PanoTileStitcher {
|
|
|
204
213
|
this._blank.needsUpdate = true;
|
|
205
214
|
}
|
|
206
215
|
/** 确保批次已建组、加载已发起。幂等:同批已在册 ⇒ 直接返回(一片瓦片终身只下载、只解码一次)。
|
|
207
|
-
* ⭐ 09-29「不足的涂灰」+ 10-
|
|
216
|
+
* ⭐ 09-29「不足的涂灰」+ 2026-10-27「8×4 = 32 片」:1~32 条皆可入组,内部补空串占位到 32;
|
|
208
217
|
* 空串 / 死链的格子拼合时涂灰(一眼看出该全景数据不全)。
|
|
209
|
-
* ⛔ 旧版
|
|
210
|
-
* 超
|
|
218
|
+
* ⛔ 旧版 18 条(6×3)、16 条(4×4)、64 条(8×8)与 9 条(3×3)的数据**都不做兼容**(用户「无须兼容正常使用, 有异常我会重传」):
|
|
219
|
+
* 超 32 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
|
|
220
|
+
* ⚠️ 唯一按张数**能混进来**的是 10-06 那版同为 32 片的老档(切片顺序与槽分组两版一致 ⇒ 不会错位),
|
|
221
|
+
* 但它的瓦片是 1024²、被画进 1536² 的格子 = 1.5× 放大 ⇒ 发虚,那类老点仍要重传一次。
|
|
211
222
|
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null]` —— 槽数再变会漏改)。 */
|
|
212
223
|
ensure(urls, layout) {
|
|
213
224
|
const L = layout ?? PANO_TILE_LAYOUT;
|
|
@@ -442,14 +453,14 @@ export class PanoTileStitcher {
|
|
|
442
453
|
}
|
|
443
454
|
// ───────────────────────── 内部:单格绘制 ─────────────────────────
|
|
444
455
|
/** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(每槽 `slotCellCols × slotCellRows` 格里的一格,
|
|
445
|
-
* 10-
|
|
456
|
+
* 2026-10-27 起全景是「横 4 × 竖 4」= 16 格、图片平面是 2×2 = 4 格)。
|
|
446
457
|
* ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 cellsPerSlot 帧**,
|
|
447
458
|
* 清一次就把前面画好的格子抹了 ⇒ 槽纹理只剩最后一格。清屏交给我们自己(见 `_clearRt`)。 */
|
|
448
459
|
_stitchCell(renderer, g, slot, cell, tex) {
|
|
449
460
|
const rt = this._ensureRt(renderer, g, slot);
|
|
450
461
|
const tileIdx = g.slotTiles[slot][cell];
|
|
451
462
|
const [cc, rr] = rtStitchCellPos(cell, g.layout.slotCellCols);
|
|
452
|
-
// ⚠️ 横竖**分轴**缩放/摆位(旧的单一 `slotGrid`
|
|
463
|
+
// ⚠️ 横竖**分轴**缩放/摆位(旧的单一 `slotGrid` 方形假设整条作废):现行全景一槽 = 原图半幅 ⇒ 4 横 4 竖,图片平面那档 2 横 2 竖
|
|
453
464
|
const cw = g.layout.slotCellCols;
|
|
454
465
|
const ch = g.layout.slotCellRows;
|
|
455
466
|
this._quad.scale.set(1 / cw, 1 / ch, 1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* @Description:
|
|
2
|
+
* @Description: 32 瓦片全景的「离屏拼合器」= **shader 逐格画进 WebGLRenderTarget**(本项目唯一一份拼合实现)。
|
|
3
3
|
*
|
|
4
4
|
* 三道旨意叠成现在这一份:
|
|
5
5
|
* ① 2026-09-29:一张全景切多片(每片所有 GPU 都装得下),运行时离屏拼回若干张固定尺寸的「槽纹理」,
|
|
@@ -28,9 +28,16 @@
|
|
|
28
28
|
* ⇒ 改 **6×3=18 片 / 左右 2 槽**,每槽 = 原图半幅(横 3 格 × 竖 3 格)= **6144×6144**、与原图 1:1。
|
|
29
29
|
* ⭐ 净效果:单全景有效分辨率与显存**一字未动**(仍是 75.5Mpx / ≈384MB),sampler = 点数 × 2 ⇒
|
|
30
30
|
* **同屏全景点数 5 → 8**(8×2=16 正好用满硬顶)。旧存档 9 条地址**不兼容**,用户重传即可。
|
|
31
|
+
* ⑥ 2026-10-27(需求,逐字):「目前全景的 2个RT 是 3*3=9张的对吧, 我想要改成之前的 4*4=16张的逻辑, 数据你不用管,
|
|
32
|
+
* 我会重新上传, 你把代码改正确就行」
|
|
33
|
+
* ⇒ 改 **8×4=32 片 / 左右 2 槽**,每槽 = 原图半幅(横 4 格 × 竖 4 格 = 16 格)= **6144×6144**、与原图 1:1。
|
|
34
|
+
* ⭐ 净效果:单全景有效分辨率与显存**一字未动**(有效分辨率仍是 **12288×6144**、与 ⑤ 那版 18 片**逐像素同量**;
|
|
35
|
+
* 显存仍 ≈384MB 含 mipmap),sampler 账不变(每点 2 张 × 同屏 8 点 = 16 = 本机硬顶)⇒ 变的只有「切几刀」:
|
|
36
|
+
* 每格像素 2048×2048 → **1536×1536**、一组待画格 18 → **32**。
|
|
37
|
+
* ⚠️ 旧存档 18 / 9 / 64 / 16 条地址**不兼容**(张数不符被判废)⇒ 用户「数据你不用管,我会重新上传」。
|
|
31
38
|
*
|
|
32
39
|
* ⚠️ **仍然有 canvas,但只在上传切图那一步**(`src/views/MktMeshEdit/panoramaUpload.ts` 用 2D 画布把原图
|
|
33
|
-
* 切成
|
|
40
|
+
* 切成 32 个 JPEG 传 OSS)—— 那是**离线、一次性、产出的是数据**;运行时这条路上一个 canvas 都没有。
|
|
34
41
|
*
|
|
35
42
|
* ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
|
|
36
43
|
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
|
|
@@ -40,49 +47,51 @@
|
|
|
40
47
|
* 到 GPU 里面去」)。这一趟**只上传、不画** ⇒ 上传与绘制永不落在同一拍(09-29 那句「两个大头别在同一
|
|
41
48
|
* 帧」的延续),而且第 2 步天然只能画「上一帧及更早」传上去的图 ⇒ 每帧 = 1 上传 + 1 绘制(稳态)。
|
|
42
49
|
*
|
|
43
|
-
* ⚠️ 于是「一帧一片」的帧数随网格走:16 片 → 32 片 → 9 片 →
|
|
44
|
-
* 10-
|
|
45
|
-
* 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等
|
|
50
|
+
* ⚠️ 于是「一帧一片」的帧数随网格走:16 片 → 32 片 → 9 片 → 18 片 → **32 片/组 = 32 帧**(30ms/帧 ⇒ 约 1.0s,
|
|
51
|
+
* 2026-10-27 需求)。每格像素 = `RT_STITCH_SLOT_W/4 × RT_STITCH_SLOT_H/4` = **1536×1536**(原图切出来那片多大就多大,一比一)。
|
|
52
|
+
* 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等 32 帧。
|
|
46
53
|
*
|
|
47
54
|
* ⭐ 部分就绪就先喂(同日旨「1. 需要先喂给 shader」):`peek()` 不再等一个槽的 `cellsPerSlot` 格画满,
|
|
48
55
|
* **画过 ≥1 格就交出去**。为了保证交出去的那张 RT 里绝无未定义像素,RT 建好后的第一件事是
|
|
49
56
|
* 整张涂成缺失灰(`_clearRt`)⇒ 观感 = 该象限从左上角起逐格显影,缺数据的格子一直是灰的。
|
|
50
57
|
*
|
|
51
58
|
* ── 画质四条(⛔ 改任何一条都是改画质,先请旨)──────────────────────────────
|
|
52
|
-
* 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **6144×6144**、每槽「横
|
|
59
|
+
* 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **6144×6144**、每槽「横 4 格 × 竖 4 格」、原图**横 8 × 竖 4** 片,
|
|
53
60
|
* 全在 `./mktShader`(单一来源)。⭐ 10-23 旨后格子尺寸 = 那片瓦片的尺寸 ⇒ **一比一**:11968×5984 那类原图
|
|
54
|
-
* 切
|
|
55
|
-
* ⇒ 单全景有效分辨率 33.5Mpx → **75.5Mpx
|
|
61
|
+
* 切 8×4 得 ~1496×1496 的片,铺进 1536×1536 的格子几乎不缩(缩放比 1.03,与上一版同一档)
|
|
62
|
+
* ⇒ 单全景有效分辨率 33.5Mpx → **75.5Mpx**(= 12288×6144,本次与上一版**逐像素同量**),是**升**画质那一档定下的,
|
|
63
|
+
* 与「千万不要降低质量」红线同向。
|
|
56
64
|
* ⛔ 不许拿槽尺寸当省显存的旋钮:现口径每槽覆盖**横 180° × 竖 180°**(原图左/右半幅),
|
|
57
|
-
* 宽高比 1:1 与「半幅(
|
|
65
|
+
* 宽高比 1:1 与「半幅(4 列 × 4 行)」一致;更早按列分槽那一版一槽装一整列时是 2:3,两版各自都对,⛔ 别混着抄。
|
|
58
66
|
* 2. **色彩**:瓦片纹理 `colorSpace = SRGBColorSpace` ⇒ WebGL2 内部格式 `SRGB8_ALPHA8`,采样时硬件解码成
|
|
59
67
|
* linear;RT 纹理同样标 sRGB ⇒ 写入时硬件再编码回 sRGB。一进一出**逐字节还原**。
|
|
60
68
|
* ⚠️ 另一半证据在 three 里:写进非 XR 的 render target 时片元末尾的 `linearToOutputTexel` 恒按
|
|
61
69
|
* LinearSRGB(= 不转换),编码完全交给硬件 ⇒ ⛔ 我们的片元绝不 `#include <colorspace_fragment>`,
|
|
62
70
|
* 也不带 tone mapping 的 chunk(那两处都会再转一遍 ⇒ 画面发白)。
|
|
63
71
|
* 3. **摩尔纹**:两层都开 mipmap ——
|
|
64
|
-
* ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:一片原图(~
|
|
72
|
+
* ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:一片原图(~1496×1496)铺进 1536×1536
|
|
65
73
|
* 的格子是**一比一**,但墙面在远处仍会缩到格子以下 ⇒ ⛔ 单点双线性会采出锯齿,mipmap 照旧开
|
|
66
74
|
* (WebGL2 对 NPOT 也能生成 mipmap,three r163 起只支持 WebGL2)。
|
|
67
75
|
* ② 槽 RT 标 `generateMipmaps`:three 每次渲染进 RT 后自己 `glGenerateMipmap`(`WebGLRenderer.js`
|
|
68
76
|
* `updateRenderTargetMipmap`)⇒ 与当年 CanvasTexture 上传后生成 mipmap 同一口径。
|
|
69
77
|
* ⚠️ 代价:每格一次(旧版每格也是一次),都在 GPU 上、不占主线程。
|
|
70
|
-
* 4. **接缝**:槽内「横
|
|
71
|
-
* 槽与槽之间另有 u=1/2 **一道**跨纹理竖缝,那道治不了(见 `PanoTexMode`
|
|
78
|
+
* 4. **接缝**:槽内「横 4 × 竖 4」⇒ 一张槽内部的格子线是 **3 横 3 竖**(上一版 3×3 是 2 横 2 竖;更早一槽一整列时只有 2 横 0 竖);
|
|
79
|
+
* 槽与槽之间另有 u=1/2 **一道**跨纹理竖缝,那道治不了(见 `PanoTexMode` 说明②;更早按列 3 槽那版才是两道)。治法 = 上传时每片向四边**多切**
|
|
72
80
|
* `RT_STITCH_SEAM_MARGIN_PX` 像素、拼合时**只采中间那块**(`uSrcRect`)⇒ 边界插值拿得到真邻居像素,
|
|
73
81
|
* 不再复制自己的边缘。⛔ 这条不许「顺手简化」成直接铺满格子:几何会挤歪,缝也还在。
|
|
74
82
|
* 详见 `./mktShader` 里那三个 `rtStitch*` 纯函数 —— **上传侧与拼合侧必须共用同一份几何**。
|
|
75
83
|
*
|
|
76
|
-
* 资源账(⚠️ 如实记;10-
|
|
84
|
+
* 资源账(⚠️ 如实记;2026-10-27 需求改 8×4=32 片 / 左右 2 槽 / 6144×6144 后重算 —— 槽尺寸一字未动 ⇒ 显存那一栏不变):
|
|
77
85
|
* - 常驻显存:2 槽 × (6144×**6144**×4B + mipmap≈33%) ≈ **384MB/全景**(不含 mipmap 288MB —— 与上一版**一字不差**)
|
|
78
86
|
* —— 更早那版 8×4=32 片 / 2 槽 4096² 只有 **170MB/全景** ⇒ 单全景 **2.26 倍**,代价来自「槽与原图一比一」那次**升分辨率**,
|
|
79
87
|
* 不是浪费:33.5Mpx → 75.5Mpx 的有效像素全在 RT 里。
|
|
80
|
-
* ⚠️ 组**运行中不淘汰**(见文末那条需求)⇒ 13 张全景全常驻 ≈ **4.9GB
|
|
88
|
+
* ⚠️ 组**运行中不淘汰**(见文末那条需求)⇒ 13 张全景全常驻 ≈ **4.9GB**(上一版同数 —— 单全景显存一字未动)。同屏景点数 8,
|
|
81
89
|
* 那只省 sampler 绑定、**不省显存**。⚠️ 这是这一轮最大的风险项,已另记待用户裁决(见「RT 不回收」)。
|
|
82
|
-
* - 上传总量:同一张原图切完 ⇒ **总像素与上一版逐字节同量**,片数
|
|
83
|
-
* ⇒ 下载发起数比
|
|
90
|
+
* - 上传总量:同一张原图切完 ⇒ **总像素与上一版逐字节同量**,片数 18 → 32、每片 2048×2048 → 1536×1536(单片像素减到 ≈0.56 倍)
|
|
91
|
+
* ⇒ 下载发起数比 18 片那版**多 14 条**(与 10-06 那版 32 片同数)、单请求更小。
|
|
84
92
|
* - 瓦片纹理画完即弃 ⇒ 同时在册的只有「下载中 + 已解码待上传」那些,上限 8+8 片;
|
|
85
|
-
* ⚠️ 每片解码位图
|
|
93
|
+
* ⚠️ 每片解码位图 1536×1536×4B = **9MB**(上一版每片 2048×2048 = 16MB)⇒ 闸内在制品峰值 CPU 侧 ≈ **144MB**(原 256MB);
|
|
94
|
+
* 一组 32 片若全解码则是 ≈**288MB**(上一版 18 片 × 16MB = 288MB —— **同一量**)。
|
|
86
95
|
*
|
|
87
96
|
* ⚠️ RT 是 GL 上下文私有资源:`pump(renderer)` 换 renderer 时整组作废(`_switchRenderer`),下一帧消费方
|
|
88
97
|
* 照旧的 `ensure()` 重建(瓦片走浏览器 HTTP 缓存重新下载)。现况是**每个持有者一份拼合器 + 一个 renderer**
|
|
@@ -135,13 +144,13 @@ import {
|
|
|
135
144
|
*/
|
|
136
145
|
const PANO_STITCH_UPLOADS_PER_FRAME = 1;
|
|
137
146
|
|
|
138
|
-
/** 每帧最多发起几张下载(现行一批
|
|
147
|
+
/** 每帧最多发起几张下载(现行一批 32 片,⛔ 不加以限制就是一帧甩 32 条 XHR + 32 次 ≈236 万像素解码) */
|
|
139
148
|
const PANO_STITCH_DOWNLOADS_PER_FRAME = 2;
|
|
140
149
|
|
|
141
150
|
/** 「下载中」的片数上限(跨所有批次) */
|
|
142
151
|
const PANO_STITCH_MAX_INFLIGHT = 8;
|
|
143
152
|
|
|
144
|
-
/** 「已解码、还没上传进 GPU」的片数上限 ⇒ 也是 CPU 侧位图的峰值内存闸(≈8 ×
|
|
153
|
+
/** 「已解码、还没上传进 GPU」的片数上限 ⇒ 也是 CPU 侧位图的峰值内存闸(≈8 × 9MB,每片 1536×1536×4B) */
|
|
145
154
|
const PANO_STITCH_MAX_DECODED_AHEAD = 8;
|
|
146
155
|
|
|
147
156
|
/** 瓦片纹理的实际像素宽高(`loadPanoTexture` 交回来的是 `ImageBitmap`,尺寸就是切片区)。
|
|
@@ -228,7 +237,7 @@ interface StitchGroup {
|
|
|
228
237
|
*/
|
|
229
238
|
export interface PanoStitcher {
|
|
230
239
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
|
|
231
|
-
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景
|
|
240
|
+
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 8×4 / 2 槽 / 6144×6144,2026-10-27 需求);
|
|
232
241
|
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。 */
|
|
233
242
|
ensure(urls: string[], layout?: TileLayout): void;
|
|
234
243
|
/**
|
|
@@ -296,7 +305,7 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
296
305
|
blending: NoBlending,
|
|
297
306
|
});
|
|
298
307
|
/** 几何是 2×2(铺满正交相机的 [-1,1]²),每格按 `1/slotCellCols × 1/slotCellRows` 缩放再挪过去
|
|
299
|
-
* ⇒ 全景一格 = 槽的 1/
|
|
308
|
+
* ⇒ 全景一格 = 槽的 1/4 宽 × 1/4 高(横 4 竖 4,2026-10-27 口径),图片平面一格 = 1/4(横 2 竖 2) */
|
|
300
309
|
private _quad: Mesh<PlaneGeometry, ShaderMaterial> = new Mesh(new PlaneGeometry(2, 2), this._mat);
|
|
301
310
|
/** 缺格那一路 `uMap` 也要有个合法纹理可绑(1×1,从不真采样) */
|
|
302
311
|
private _blank: DataTexture = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
|
|
@@ -310,10 +319,12 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
310
319
|
}
|
|
311
320
|
|
|
312
321
|
/** 确保批次已建组、加载已发起。幂等:同批已在册 ⇒ 直接返回(一片瓦片终身只下载、只解码一次)。
|
|
313
|
-
* ⭐ 09-29「不足的涂灰」+ 10-
|
|
322
|
+
* ⭐ 09-29「不足的涂灰」+ 2026-10-27「8×4 = 32 片」:1~32 条皆可入组,内部补空串占位到 32;
|
|
314
323
|
* 空串 / 死链的格子拼合时涂灰(一眼看出该全景数据不全)。
|
|
315
|
-
* ⛔ 旧版
|
|
316
|
-
* 超
|
|
324
|
+
* ⛔ 旧版 18 条(6×3)、16 条(4×4)、64 条(8×8)与 9 条(3×3)的数据**都不做兼容**(用户「无须兼容正常使用, 有异常我会重传」):
|
|
325
|
+
* 超 32 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
|
|
326
|
+
* ⚠️ 唯一按张数**能混进来**的是 10-06 那版同为 32 片的老档(切片顺序与槽分组两版一致 ⇒ 不会错位),
|
|
327
|
+
* 但它的瓦片是 1024²、被画进 1536² 的格子 = 1.5× 放大 ⇒ 发虚,那类老点仍要重传一次。
|
|
317
328
|
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null]` —— 槽数再变会漏改)。 */
|
|
318
329
|
ensure(urls: string[], layout?: TileLayout): void {
|
|
319
330
|
const L = layout ?? PANO_TILE_LAYOUT;
|
|
@@ -533,14 +544,14 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
533
544
|
// ───────────────────────── 内部:单格绘制 ─────────────────────────
|
|
534
545
|
|
|
535
546
|
/** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(每槽 `slotCellCols × slotCellRows` 格里的一格,
|
|
536
|
-
* 10-
|
|
547
|
+
* 2026-10-27 起全景是「横 4 × 竖 4」= 16 格、图片平面是 2×2 = 4 格)。
|
|
537
548
|
* ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 cellsPerSlot 帧**,
|
|
538
549
|
* 清一次就把前面画好的格子抹了 ⇒ 槽纹理只剩最后一格。清屏交给我们自己(见 `_clearRt`)。 */
|
|
539
550
|
private _stitchCell(renderer: WebGLRenderer, g: StitchGroup, slot: number, cell: number, tex: Texture | null): void {
|
|
540
551
|
const rt = this._ensureRt(renderer, g, slot);
|
|
541
552
|
const tileIdx = g.slotTiles[slot][cell];
|
|
542
553
|
const [cc, rr] = rtStitchCellPos(cell, g.layout.slotCellCols);
|
|
543
|
-
// ⚠️ 横竖**分轴**缩放/摆位(旧的单一 `slotGrid`
|
|
554
|
+
// ⚠️ 横竖**分轴**缩放/摆位(旧的单一 `slotGrid` 方形假设整条作废):现行全景一槽 = 原图半幅 ⇒ 4 横 4 竖,图片平面那档 2 横 2 竖
|
|
544
555
|
const cw = g.layout.slotCellCols;
|
|
545
556
|
const ch = g.layout.slotCellRows;
|
|
546
557
|
this._quad.scale.set(1 / cw, 1 / ch, 1);
|
package/MktMesh/types.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ export interface MktTextureInfo {
|
|
|
25
25
|
* ⭐ 皇上旨 2026-10-06 晚(逐字)「我希望一次能够展示 8 个全景纹理, 每个全景纹理由左右 2 个 RT 组成,
|
|
26
26
|
* 每个 RT 由 16 个全景瓦片组成, 所以, 全景图在上传的时候, 只需要拆分成 横 8 * 竖 4 = 32 个瓦片图就可以了」
|
|
27
27
|
* ⇒ **现行唯一形态 = 32 张(横 8 × 竖 4)**:行优先从图像左上开始,运行时由
|
|
28
|
-
* `PanoTileStitcher` 用 shader 逐格画进 **2 张
|
|
28
|
+
* `PanoTileStitcher` 用 shader 逐格画进 **2 张 6144×6144** `WebGLRenderTarget`(左半幅 / 右半幅,每槽 4×4 格;
|
|
29
|
+
* 槽尺寸是 10-26 那版定的,2026-10-27 回到 32 片切档时一字未动),
|
|
29
30
|
* 2 槽组成完整全景再喂 shader。⚠️ 同一天早些时候那道「8×8 = 64 片 / 4 张 4096×2048」**已作废**。
|
|
30
31
|
* ⚠️ 每片向四边**多带** `RT_STITCH_SEAM_MARGIN_PX` 像素的邻居余量(治内部接缝;采样窗口由
|
|
31
32
|
* `rtStitchTileInset` 还原),所以片宽 ≠ 原图正好 1/8、片高 ≠ 正好 1/4。
|
package/MktMesh/types.ts
CHANGED
|
@@ -33,7 +33,8 @@ export interface MktTextureInfo {
|
|
|
33
33
|
* ⭐ 皇上旨 2026-10-06 晚(逐字)「我希望一次能够展示 8 个全景纹理, 每个全景纹理由左右 2 个 RT 组成,
|
|
34
34
|
* 每个 RT 由 16 个全景瓦片组成, 所以, 全景图在上传的时候, 只需要拆分成 横 8 * 竖 4 = 32 个瓦片图就可以了」
|
|
35
35
|
* ⇒ **现行唯一形态 = 32 张(横 8 × 竖 4)**:行优先从图像左上开始,运行时由
|
|
36
|
-
* `PanoTileStitcher` 用 shader 逐格画进 **2 张
|
|
36
|
+
* `PanoTileStitcher` 用 shader 逐格画进 **2 张 6144×6144** `WebGLRenderTarget`(左半幅 / 右半幅,每槽 4×4 格;
|
|
37
|
+
* 槽尺寸是 10-26 那版定的,2026-10-27 回到 32 片切档时一字未动),
|
|
37
38
|
* 2 槽组成完整全景再喂 shader。⚠️ 同一天早些时候那道「8×8 = 64 片 / 4 张 4096×2048」**已作废**。
|
|
38
39
|
* ⚠️ 每片向四边**多带** `RT_STITCH_SEAM_MARGIN_PX` 像素的邻居余量(治内部接缝;采样窗口由
|
|
39
40
|
* `rtStitchTileInset` 还原),所以片宽 ≠ 原图正好 1/8、片高 ≠ 正好 1/4。
|
package/ThreeBase.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
44
44
|
maxPixelRatio: number;
|
|
45
45
|
renderScale: number;
|
|
46
46
|
private _resolution;
|
|
47
|
+
/** baseRenderTarget 之外**全部** RT 的倍率(深度 / 法线 / 各效果 mask / 屏幕合成);1 = 不超采样 */
|
|
48
|
+
auxResolution: number;
|
|
47
49
|
get resolution(): number;
|
|
48
50
|
set resolution(value: number);
|
|
49
51
|
/**
|
|
@@ -127,7 +129,8 @@ export default class ThreeBase extends EventDispatcher<ThreeBaseEventTypeMap> {
|
|
|
127
129
|
setContainer(div: HTMLDivElement | null): Promise<void>;
|
|
128
130
|
setSize(width: number, height: number): void;
|
|
129
131
|
/**
|
|
130
|
-
* 按「容器尺寸 × effectiveRatio
|
|
132
|
+
* 按「容器尺寸 × effectiveRatio」重建所有渲染目标尺寸:base 再乘 resolution,
|
|
133
|
+
* 其余(深度 / 法线 / mask / 屏幕合成)再乘 auxResolution。
|
|
131
134
|
* 由 setSize()(容器尺寸变化)与 resolution setter(运行时调倍率)共用 ——
|
|
132
135
|
* 关键:resolution 是运行时可调的性能/质量旋钮,改完必须立刻重建 RT,
|
|
133
136
|
* 否则倍率变了而 RT 尺寸没变,画面毫无变化(历史坑:"调分辨率没感觉")。
|