three-base 2.0.187 → 2.0.190
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 +2 -2
- package/MktMesh/MktMesh.js +2 -2
- package/MktMesh/MktMesh.ts +2 -2
- 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/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 +52 -42
- package/ThreeBase.ts +52 -42
- package/package.json +1 -1
|
@@ -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 尺寸没变,画面毫无变化(历史坑:"调分辨率没感觉")。
|
package/ThreeBase.js
CHANGED
|
@@ -72,20 +72,23 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
72
72
|
// (纹理被欠采样时的拍频,必须靠提高实际采样率才能消除)。
|
|
73
73
|
// 1 = 关闭超采样(仅设备 pixelRatio);2 = 2 倍超采样(质量最高,片元开销 ×4,需强 GPU)。
|
|
74
74
|
renderScale = 1;
|
|
75
|
-
//
|
|
76
|
-
// effectiveRatio × resolution
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
75
|
+
// 超采样倍率(SSAA)**只作用于 baseRenderTarget**(2026-10-27 需求:「只有 baseRT 的超采样参数是 4,
|
|
76
|
+
// 其他的 RT 超采样参数是 1」):base 按 effectiveRatio × resolution 渲染,深度 / 法线 / 效果 mask /
|
|
77
|
+
// 屏幕合成一律按 effectiveRatio × auxResolution(= 1)渲染。
|
|
78
|
+
// ⚠️ 两条与「全链路同倍率」那版不同、看得见的后果:
|
|
79
|
+
// ① base(4×) → screen(1×) 那次拷贝是**降采样**(RT 不长 mipmap,线性滤波只取到 4 个纹素)
|
|
80
|
+
// ⇒ base 的超采样不会原样带到屏幕上,末端仍靠 FXAA 兜锯齿。
|
|
81
|
+
// ② 描边/发光/覆盖/磨砂的 mask 与 base 深度都从 4× 降到 1×,而它们要合成进 4× 的 base
|
|
82
|
+
// ⇒ 这些效果的边缘按 1× 精度放大,比同倍率那版软;UV 仍严格对齐(错位只发生在精度,不在位置)。
|
|
83
|
+
// 现行 resolution = 4(setter 夹的上限也是 4);上一档 1 出自 2026-10-21「有点卡,分辨率改成 1」。
|
|
84
|
+
// 历史:再早是 base / 深度 / 法线 / mask / 屏幕 **共用同一个 resolution**,2026-10-27 起拆成 base 与其余两档。
|
|
85
|
+
// ⚠️ 必须用 getter/setter,不能是普通字段:RT 尺寸只在 updateRenderTargetSizes()
|
|
86
|
+
// 里按 effectiveRatio × resolution(base)/ × auxResolution(其余)计算,而该方法过去只被 setSize()(容器尺寸变化)调用
|
|
86
87
|
// → 运行时改 resolution 不会重建任何 RT,画面毫无变化(表现为"调分辨率没感觉")。
|
|
87
88
|
// 故 setter 里改动倍率后立刻重建全部 RT。
|
|
88
|
-
_resolution =
|
|
89
|
+
_resolution = 4;
|
|
90
|
+
/** baseRenderTarget 之外**全部** RT 的倍率(深度 / 法线 / 各效果 mask / 屏幕合成);1 = 不超采样 */
|
|
91
|
+
auxResolution = 1;
|
|
89
92
|
get resolution() {
|
|
90
93
|
return this._resolution;
|
|
91
94
|
}
|
|
@@ -171,6 +174,9 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
171
174
|
}
|
|
172
175
|
const w = Math.max(1, Math.round(this.size.x * this.effectiveRatio * this._resolution));
|
|
173
176
|
const h = Math.max(1, Math.round(this.size.y * this.effectiveRatio * this._resolution));
|
|
177
|
+
// 除 base 之外的 RT 走 auxResolution(= 1):屏幕合成目标不跟着 base 超采样
|
|
178
|
+
const aw = Math.max(1, Math.round(this.size.x * this.effectiveRatio * this.auxResolution));
|
|
179
|
+
const ah = Math.max(1, Math.round(this.size.y * this.effectiveRatio * this.auxResolution));
|
|
174
180
|
const opts = {
|
|
175
181
|
minFilter: this._rtOptions.filter,
|
|
176
182
|
magFilter: this._rtOptions.filter,
|
|
@@ -183,23 +189,22 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
183
189
|
this.baseRenderTarget = new WebGLRenderTarget(w, h, { ...opts });
|
|
184
190
|
this.baseRenderTarget.texture.colorSpace = this._rtOptions.colorSpace;
|
|
185
191
|
this.screenRenderTarget.dispose();
|
|
186
|
-
this.screenRenderTarget = new WebGLRenderTarget(
|
|
192
|
+
this.screenRenderTarget = new WebGLRenderTarget(aw, ah, { ...opts });
|
|
187
193
|
this.screenRenderTarget.texture.colorSpace = this._rtOptions.colorSpace;
|
|
188
194
|
this.needRender = true;
|
|
189
195
|
}
|
|
190
|
-
// 效果 mask
|
|
191
|
-
//
|
|
192
|
-
//
|
|
196
|
+
// 效果 mask 像素比硬上限(历史旋钮,当前渲染链未再引用):避免高 dpr 屏(如 2×)上
|
|
197
|
+
// effectiveRatio×resolution 爆到 4×,让效果 mask RT(含辉光多次高斯迭代)填充量失控。
|
|
198
|
+
// 2026-10-27 起 mask/深度/法线/屏幕全部按 auxResolution(= 1)走,超采样只留在 base 一张。
|
|
193
199
|
effectMaxPixelRatio = 2;
|
|
194
|
-
//
|
|
195
|
-
// 辅助通道(深度/法线)分辨率系数。
|
|
200
|
+
// 辅助通道(深度/法线)与各效果 mask、屏幕合成的倍率见下方 auxResolution 字段(= 1,不超采样)。
|
|
196
201
|
// 关键:描边/发光/辉光/毛玻璃都拿 baseDepthTexture 做遮挡比对(deathTestShader 以全分辨率
|
|
197
202
|
// UV 采样半分辨率深度,并与全分辨率物体深度做硬阈值比较)。半分辨率深度(Nearest 滤波)被
|
|
198
203
|
// 放大采样后与锐利的物体深度在边缘逐像素翻转 discard,产生边缘摩尔纹/锯齿——这是这 4 个特效
|
|
199
|
-
//
|
|
200
|
-
// 法线仅 SSAO
|
|
201
|
-
// 之前为"不卡"
|
|
202
|
-
// 辅助通道(深度/法线)
|
|
204
|
+
// 共同的摩尔纹根因。故深度与 mask 必须同倍率(都取 auxResolution),彼此 1:1 采样才无放大错位。
|
|
205
|
+
// 法线仅 SSAO 使用(首页默认关闭、不渲染),无额外开销。
|
|
206
|
+
// 之前为"不卡"把深度单独降到 0.5 反而引入摩尔纹,已恢复为 1。
|
|
207
|
+
// 辅助通道(深度/法线)倍率 = auxResolution,与 base 的 resolution 分离(2026-10-27 需求)。
|
|
203
208
|
// 实际帧缓冲像素比 = min( max(devicePixelRatio, minDevicePixelRatio) * renderScale, maxPixelRatio )
|
|
204
209
|
get effectiveRatio() {
|
|
205
210
|
return Math.min(Math.max(window.devicePixelRatio, this.minDevicePixelRatio) * this.renderScale, this.maxPixelRatio);
|
|
@@ -489,7 +494,8 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
489
494
|
this.orthographicCamera.right = width / height;
|
|
490
495
|
this.renderer.setPixelRatio(Math.max(window.devicePixelRatio, this.minDevicePixelRatio)),
|
|
491
496
|
this.renderer.setSize(width, height);
|
|
492
|
-
//
|
|
497
|
+
// base 按 effectiveRatio × resolution、其余 RT 按 effectiveRatio × auxResolution 重建
|
|
498
|
+
// (容器尺寸变化与 resolution 变化共用)
|
|
493
499
|
this.updateRenderTargetSizes(width, height);
|
|
494
500
|
this.size.set(width, height);
|
|
495
501
|
setTimeout(() => {
|
|
@@ -500,7 +506,8 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
500
506
|
// this.renderer.render(this.scene, this.camera);
|
|
501
507
|
}
|
|
502
508
|
/**
|
|
503
|
-
* 按「容器尺寸 × effectiveRatio
|
|
509
|
+
* 按「容器尺寸 × effectiveRatio」重建所有渲染目标尺寸:base 再乘 resolution,
|
|
510
|
+
* 其余(深度 / 法线 / mask / 屏幕合成)再乘 auxResolution。
|
|
504
511
|
* 由 setSize()(容器尺寸变化)与 resolution setter(运行时调倍率)共用 ——
|
|
505
512
|
* 关键:resolution 是运行时可调的性能/质量旋钮,改完必须立刻重建 RT,
|
|
506
513
|
* 否则倍率变了而 RT 尺寸没变,画面毫无变化(历史坑:"调分辨率没感觉")。
|
|
@@ -509,17 +516,20 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
509
516
|
// 防御 0 尺寸:RT 不接受 0,否则 WebGL 报 framebuffer incomplete
|
|
510
517
|
const w = Math.max(1, Math.round(width * this.effectiveRatio * this._resolution));
|
|
511
518
|
const h = Math.max(1, Math.round(height * this.effectiveRatio * this._resolution));
|
|
512
|
-
|
|
519
|
+
const aw = Math.max(1, Math.round(width * this.effectiveRatio * this.auxResolution));
|
|
520
|
+
const ah = Math.max(1, Math.round(height * this.effectiveRatio * this.auxResolution));
|
|
521
|
+
// 主画面:只有这一张吃 resolution 超采样(纹理摩尔纹在此被根治)
|
|
513
522
|
this.baseRenderTarget.setSize(w, h);
|
|
514
|
-
//
|
|
523
|
+
// 深度/法线辅助通道:按 auxResolution(= 1)。与 base 之间**倍率不同但 UV 一致**,
|
|
524
|
+
// 遮挡判定读的是归一化 UV ⇒ 位置不错位,只是精度按 1× 走(2026-10-27 需求点名要的这一档)。
|
|
515
525
|
// 其 depthTexture(baseDepthTexture) 尺寸由 renderer 在 setRenderTarget 时
|
|
516
526
|
// 强制同步为 depthRenderTarget 尺寸,下面再手动对齐一次避免首帧抖动。
|
|
517
|
-
this.depthRenderTarget.setSize(
|
|
518
|
-
this.normalRenderTarget.setSize(
|
|
519
|
-
//
|
|
520
|
-
this.screenRenderTarget.setSize(
|
|
521
|
-
this.baseDepthTexture.image.width =
|
|
522
|
-
this.baseDepthTexture.image.height =
|
|
527
|
+
this.depthRenderTarget.setSize(aw, ah);
|
|
528
|
+
this.normalRenderTarget.setSize(aw, ah);
|
|
529
|
+
// 屏幕合成目标:同样按 auxResolution,与 base 之间的那次拷贝是降采样
|
|
530
|
+
this.screenRenderTarget.setSize(aw, ah);
|
|
531
|
+
this.baseDepthTexture.image.width = aw;
|
|
532
|
+
this.baseDepthTexture.image.height = ah;
|
|
523
533
|
// 立即渲染一帧,避免静态场景下改了倍率却停在旧画面
|
|
524
534
|
this.needRender = true;
|
|
525
535
|
}
|
|
@@ -899,10 +909,10 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
899
909
|
renderVisibleDepthPass(this.renderer, camera, this.scene, this.depthRenderTarget);
|
|
900
910
|
// 切回 baseRenderTarget,后处理 pass 需要把效果叠加到颜色渲染目标上
|
|
901
911
|
this.renderer.setRenderTarget(this.baseRenderTarget);
|
|
902
|
-
// 效果 mask 灰度图尺寸 = effectiveRatio ×
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
const dpr = this.effectiveRatio * this.
|
|
912
|
+
// 效果 mask 灰度图尺寸 = effectiveRatio × auxResolution(= 1),与 baseDepthTexture 同倍率;
|
|
913
|
+
// baseRenderTarget 是唯一的超采样档(resolution),故 mask/深度 → base 的合成是「1× 精度放大到 4×」。
|
|
914
|
+
// 位置靠归一化 UV 对齐,不会偏移;只是描边/发光/覆盖/磨砂的边缘精度按 1× 走(2026-10-27 需求)。
|
|
915
|
+
const dpr = this.effectiveRatio * this.auxResolution;
|
|
906
916
|
// SSAO 后处理(基于深度计算遮蔽,乘法叠加):数据准备 + 计算全在 ssao_pass 内部,
|
|
907
917
|
// 结果输出到「调用时绑定的渲染目标」。
|
|
908
918
|
// ⚠️ 顺序(2026-09-14 皇上旨):SSAO 必须排在对象级特效(描边/发光/bloom/覆盖/磨砂)之前——
|
|
@@ -913,18 +923,18 @@ export default class ThreeBase extends EventDispatcher {
|
|
|
913
923
|
// 后处理特效渲染(描边,发光,颜色覆盖,bloom,磨砂玻璃):叠加到 baseRenderTarget(此时已含 SSAO)
|
|
914
924
|
this.scene.traverseVisible((child) => {
|
|
915
925
|
if (child instanceof Mesh || child instanceof Line || child instanceof Points) {
|
|
916
|
-
stroke_pass(this.renderer, camera, child, this.baseDepthTexture,
|
|
917
|
-
glow_pass(this.renderer, camera, child, this.baseDepthTexture,
|
|
918
|
-
bloom_pass(this.renderer, camera, child, this.baseRenderTarget, this.baseDepthTexture,
|
|
926
|
+
stroke_pass(this.renderer, camera, child, this.baseDepthTexture, dpr);
|
|
927
|
+
glow_pass(this.renderer, camera, child, this.baseDepthTexture, dpr);
|
|
928
|
+
bloom_pass(this.renderer, camera, child, this.baseRenderTarget, this.baseDepthTexture, dpr);
|
|
919
929
|
}
|
|
920
930
|
});
|
|
921
931
|
// 自定义描边对象(如 InstancedChild 的选择代理):不在 scene 内,单独在效果阶段描边。
|
|
922
932
|
// 代理用实例自身的 matrixWorld + 同款 geometry/material 重建,故其深度/灰度与真实实例严格对齐,
|
|
923
933
|
// 并复用同一张 baseDepthTexture 做遮挡判定(被其他物体挡住的部分不描边)。
|
|
924
934
|
for (const so of this._strokeObjects) {
|
|
925
|
-
stroke_pass(this.renderer, camera, so, this.baseDepthTexture,
|
|
926
|
-
glow_pass(this.renderer, camera, so, this.baseDepthTexture,
|
|
927
|
-
bloom_pass(this.renderer, camera, so, this.baseRenderTarget, this.baseDepthTexture,
|
|
935
|
+
stroke_pass(this.renderer, camera, so, this.baseDepthTexture, dpr);
|
|
936
|
+
glow_pass(this.renderer, camera, so, this.baseDepthTexture, dpr);
|
|
937
|
+
bloom_pass(this.renderer, camera, so, this.baseRenderTarget, this.baseDepthTexture, dpr);
|
|
928
938
|
}
|
|
929
939
|
// 把 base(含 SSAO)+描边/发光/bloom 合成结果拷到屏幕合成目标,后续 颜色叠加/磨砂玻璃
|
|
930
940
|
// 在此目标上叠加;最后统一跑一次 FXAA 终处理输出到屏幕,覆盖全部后处理的锯齿/摩尔纹。
|