three-base 2.0.145 → 2.0.168

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.
@@ -31,7 +31,7 @@
31
31
  * 兼容注意:为兼容 Windows / ANGLE 的 GLSL 编译器,本文件刻意不使用全局 const 常量与自定义函数,
32
32
  * 所有常量均以字面量内联书写;ShaderMaterial 为 GLSL ES 1.00,用 texture2D / gl_FragColor。
33
33
  */
34
- import { Color, DataTexture, DoubleSide, Matrix4, ShaderMaterial, SRGBColorSpace, Vector3, Vector4 } from "three";
34
+ import { Color, DataTexture, DoubleSide, Matrix4, ShaderMaterial, SRGBColorSpace, Vector3 } from "three";
35
35
  /**
36
36
  * 🔬 瓦片象限诊断开关(2026-09-21)。
37
37
  * 背景:皇上为「全景 2×2 瓦片上下方向」连调六轮,当时把「位置」与「块内朝向」绑在一起调
@@ -66,7 +66,8 @@ function panoDebugOn(name) {
66
66
  return false;
67
67
  }
68
68
  }
69
- const TILE_DEBUG_GLSL = panoDebugOn("panoTileDebug") ? "1.0" : "0.0";
69
+ /** ⭐ 2026-10-09 随blend 材质搬去编辑器,故改为导出(那边拼片元 GLSL 时要用)。 */
70
+ export const TILE_DEBUG_GLSL = panoDebugOn("panoTileDebug") ? "1.0" : "0.0";
70
71
  /**
71
72
  * 🔬 归属诊断配色(皇上旨 2026-09-25「你为每个全景球设置不同的颜色, 然后把全景瓦片映射在墙面上时, 叠加该全景图的颜色,
72
73
  * 这样我就能看清楚整个墙面是不是叠加了同一个颜色 —— 若是同一个颜色, 说明整面都映射了同一张瓦片, 根本没按最近取」)。
@@ -100,7 +101,8 @@ export const MKT_PANO_TINT_ON = PANO_TINT_ON;
100
101
  * 照片本身看不清);皇上旨 2026-09-26 晚「叠加的颜色比例少一点」⇒ 取 **0.5**:色相仍是主色、一眼分得出归属,
101
102
  * 但底下那张全景图的明暗与颜色能同时读出来。两份材质(融合 / 单全景)**共用这一个值**。
102
103
  */
103
- const PANO_TINT_MIX = 0.5;
104
+ /** ⭐ 2026-10-09 同上:随 blend 材质搬去编辑器,故改为导出。 */
105
+ export const PANO_TINT_MIX = 0.5;
104
106
  /**
105
107
  * 🔬 **距离明暗诊断图**(皇上旨 2026-09-26 晚「你计一下每个点距离最近的一个全景点的距离, 然后距离越小颜色就是白色,
106
108
  * 越远颜色就是黑色」)。
@@ -138,7 +140,9 @@ const PANO_TINT_MIX = 0.5;
138
140
  * ⛔ 不许再回到「模块顶层翻全局变量」那一路:那份 GLSL 是建材质时一次性拼出来的,谁先谁后取决于 import 顺序,
139
141
  * 改一次 import 就悄悄漏一面(且 `统一性检查` 会当场报不是同一份源码)。
140
142
  */
141
- const PANO_DIST_ON = panoDebugOn("panoDist");
143
+ /** ⭐ 2026-10-09 皇上旨「MktMesh 只有映射一种,blend/selected 归编辑器」⇒ 本常量改为导出:
144
+ * 编辑器那份 `mktMeshFloorBlendShader.ts` 要用它当默认值。库内的映射材质仍照常用。 */
145
+ export const PANO_DIST_ON = panoDebugOn("panoDist");
142
146
  /**
143
147
  * 🔬 **编辑器这一页**该不该有近白远黑图:**默认关**(皇上旨 2026-09-27 晚「把颜色换成纹理,全景的纹理」——
144
148
  * 灰阶诊断盖住了真投影,默认让位给全景纹理;要看诊断图 URL 显式写 `panoDist=1`,`#` 前后都认)。
@@ -182,7 +186,8 @@ function panoDistGlsl(colorVar, distExpr) {
182
186
  * 已整链删除 ⇒ 两边都是「纯水平距离最近」,圆与图永远一致。⛔ 本函数只动颜色,不动取图。
183
187
  * ⚠️ 动态下标只碰普通 uniform 数组(`vec4[]`),不碰 sampler ⇒ ANGLE 那条「sampler 必须常量下标」的限制管不到这里。
184
188
  */
185
- function panoDistLoopGlsl(colorVar, maxPano) {
189
+ /** ⭐ 2026-10-09 同上:随 blend 材质一起搬去编辑器,故改为导出(那边拼片元 GLSL 时要用)。 */
190
+ export function panoDistLoopGlsl(colorVar, maxPano) {
186
191
  return /* glsl */ `{
187
192
  float distNear = 1e9;
188
193
  for (int i = 0; i < ${maxPano}; i++) {
@@ -842,407 +847,3 @@ export function createMktShaderMaterial() {
842
847
  * - textureMatrices[i]: 第 i 张全景的世界矩阵(位置=投影中心,旋转=朝向)
843
848
  * - weights[i] : 第 i 张是否活着(camW,0 = 该槽未绑全景 ⇒ 不进候选池)
844
849
  */
845
- /**
846
- * 生成多全景片元着色器(地板 / 墙 / 家具共用)。
847
- * ⚠️ 关键约束:Windows/ANGLE(D3D) 后端**禁止用循环变量/非常量索引 sampler 数组**(报错
848
- * "array index for samplers must be constant integral expressions"),故这里在 JS 侧把循环
849
- * 完全展开成「字面量下标」的若干代码段(panoramas[0]…panoramas[N-1]),所有数组访问均为常量下标。
850
- * 未参与融合的槽位权重=0,被 if(w>0.0) 跳过(常量分支,零开销)。
851
- */
852
- function buildFloorBlendFragment(maxPano, panoDistOn) {
853
- // 每个全景点占 **2** 张拼合槽纹理(32 片每 16 片拼一张:左半幅 / 右半幅)⇒ 8 × 2 = 16 = sampler 硬顶
854
- const tileCount = maxPano * RT_STITCH_SLOT_COUNT;
855
- let slotCalc = ""; // 每槽各自算出:颜色 linK / 水平距离 dK / 夹角 angK(acos+背面+180°)/ 分值 scoreK = d−ang
856
- let slotReduceLive = ""; // 每槽参与「score 最小值」归约(候选池 = 槽里活着的那几张,score<1e8 即活)
857
- let slotOne = ""; // 每槽对「只取 score 最小那一张」的贡献(唯一取图口径,地板/墙/家具共用)
858
- let slotOwner = ""; // 🔬 每槽对「归属诊断色」的贡献(仅 ?panoTint=1 时生成,正常态为空串)
859
- for (let k = 0; k < maxPano; k++) {
860
- const base = k * RT_STITCH_SLOT_COUNT; // 本点占 2 个连续纹理槽:base=k*2 .. base+1(顺序 [左半幅, 右半幅])
861
- slotCalc += `
862
- // ── 点槽 ${k}(纹理 ${base}..${base + 1})──
863
- vec3 lin${k} = vec3(0.0);
864
- float d${k} = 1e9; // 本面这点到本槽全景中心的**水平**距离(score 被减数)
865
- // float camD${k} —— 14:24 旨曾加「全景中心到相机距离」项,15:07 旨去掉(连声明一并去,注释存档)
866
- float cosA${k} = -1.0; // 「点→全景」向量 与 正面法线 的夹角余弦(acos 后 = 夹角弧度)
867
- float ang${k} = 1e9; // 夹角(acos 后 0..180°,背面 +180° ⇒ 270°..360°),score 的减数
868
- float score${k} = 1e9; // ⭐主判据(皇上 09-28 15:07 旨七修):score = 顶点与全景的距离 − 夹角,取**最小**者定纹理
869
- // ── 旧原理存档(注释保留不删)──
870
- // 09-28 14:24 旨「score = 顶点距离 + 相机距离 − 夹角」:camD 项已整条撤(见下方槽内注释行)
871
- // 09-28 00:44 旨「score = 距离 × 夹角²,取积最小」:见下方槽内注释行
872
- // 09-28 01:56 旨「按夹角从小到大排序取最小、打平比水平距离」:bestAng/bestDAtAng 两段归约(已撤,git 可查)
873
- {
874
- float camW = weights[${k}]; // 该全景的「锚点距离权重」(JS 按**名册那条水平距离**反比算,未归一化;⭐ 10-07 第二十一道旨后那把尺量的端点 = 控制器中心点,未注入时才是相机位置)
875
- if (camW > 0.0) {
876
- mat4 tm = textureMatrices[${k}];
877
- vec3 texPos = tm[3].xyz; // 投影中心 = 纹理矩阵位置
878
- mat3 texRot = mat3(tm); // 纹理朝向 = 旋转分量
879
- vec3 dir = normalize(vProjPos - texPos);
880
- dir = texRot * dir; // 转到纹理局部方向
881
- float lon = atan(dir.z, dir.x);
882
- float u = lon * 0.15915494309189535 + 0.5; // 经度 -> u
883
- float lat = asin(clamp(dir.y, -1.0, 1.0));
884
- // 等距柱状线性 v(= 全图 UV),与单全景分支**逐字同口径**,理由见单全景分支说明
885
- float v = 0.5 + lat * 0.3183098861837907; // 1/PI
886
- // 左右两半 -> 拼合槽下标 [左半幅=0, 右半幅=1](每槽 = 8×4 网格里 4×4=16 片拼成,见 RT_STITCH_SLOT_TILES)
887
- // ⭐ 10-06 晚旨「每个全景纹理由左右 2 个 RT 组成」:竖着不再分块 ⇒ 槽只随 u 走,与单全景分支逐字同口径
888
- int tileIdx = (u >= 0.5) ? 1 : 0;
889
- float localU = (u < 0.5) ? (u * 2.0) : (u * 2.0 - 1.0);
890
- // ⛔ 纵向不再减半(槽就是全高)⇒ localV 恒等于 v;写成 2v/2v-1 = 纵向分辨率白丢一半
891
- float localV = v;
892
- // 🧩 该点这 2 槽装的是整图(用户切了 whole 形态,或该点压根没切过片)⇒ 还原成全图 UV 再采样,与单全景分支逐字同口径
893
- // (方向已写死,故横向还原式恒等于 u、纵向 localV 本就等于 v,见单全景分支推导)
894
- float panoU = (uPanoTiled[${k}] > 0.5) ? localU : u;
895
- float panoV = (uPanoTiled[${k}] > 0.5) ? localV : v;
896
- // ⚠️ ANGLE(D3D) 禁止动态索引 sampler 数组:base 为本点连槽起点(JS 常量 k*RT_STITCH_SLOT_COUNT),
897
- // 以下 2 个采样下标均为「base + 字面量 0/1」的常量表达式,编译安全。
898
- // 🔬 诊断直通(见文件头 TILE_DEBUG_GLSL 说明;正常态内联为 0.0,整段被编译器消除)
899
- if (${TILE_DEBUG_GLSL} > 0.5) {
900
- vec3 dbgCol;
901
- if (tileIdx == 0) dbgCol = vec3(1.0, 0.0, 0.0); // 红 = 左半幅槽 0
902
- else dbgCol = vec3(0.0, 1.0, 0.0); // 绿 = 右半幅槽 1
903
- gl_FragColor = vec4(dbgCol, 1.0);
904
- #include <colorspace_fragment>
905
- #include <logdepthbuf_fragment>
906
- return;
907
- }
908
-
909
- // ⭐ 采样即线性(与单全景分支同一条口径,10-07 旨「色彩空间统一使用 liner」):纹理都标了
910
- // colorSpace = SRGBColorSpace ⇒ 硬件上传成 SRGB8_ALPHA8、texture2D 已解码一次;
911
- // 这里**不再手动 pow 解码**(那遍是二次解码 = 整体压暗),末端只编码一次 ⇒ 与源图逐字节还原。
912
- if (tileIdx == 0) lin${k} = texture2D(panoramas[${base}], vec2(panoU, panoV)).rgb;
913
- else lin${k} = texture2D(panoramas[${base + 1}], vec2(panoU, panoV)).rgb;
914
- // ⭐ 09-28 15:07 旨定版(七修):score = **顶点到全景的距离 − 夹角**,取最小者。
915
- // 夹角 = acos(cosA)(0..180°),cosA = dot(normalize(全景中心 − 本点), N)(N 已按 gl_FrontFacing 翻到观察面)。
916
- // 背面一刀 +180°(00:26 旨「背面的夹角该大于 180 度」)保留。
917
- // 14:24 那版曾加「全景与相机的距离 camD」,15:07 旨「把全景到相机的那个逻辑去掉」,注释保留不删。
918
- vec3 toPano${k} = texPos - vProjPos;
919
- float lenP${k} = length(toPano${k});
920
- cosA${k} = (lenP${k} > 1e-4) ? dot(toPano${k} / lenP${k}, N) : 1.0;
921
- d${k} = distance(vProjPos.xz, texPos.xz);
922
- // camD${k} = distance(cameraPosition, texPos); // ⛌ 14:24 旨「全景与相机距离」项,15:07 旨去掉,注释保留不删
923
- ang${k} = acos(clamp(cosA${k}, -1.0, 1.0));
924
- if (cosA${k} < 0.0) ang${k} += 3.14159265; // 背面 +180°(π)
925
- score${k} = d${k} - ang${k}; // ⭐ 15:07 旨:距离 − 角度,取最小
926
- // score${k} = d${k} + camD${k} - ang${k}; // ⛌ 旧原理(14:24 旨:两距离之和 − 角度),注释保留不删
927
- // score${k} = d${k} * ang${k} * ang${k}; // ⛌ 旧原理(00:44 旨:距离 × 夹角² 取积最小),注释保留不删
928
- }
929
- }`;
930
- // ⭐ 归约(皇上 09-28 15:07 旨七修「顶点距离 − 夹角」):bestScore = 各活槽分值最小值。
931
- // score == bestScore 是同值浮点精确比较(min 存的就是同一份 bit 值,== 可靠,与历代口径同一技巧)。
932
- slotReduceLive += `
933
- if (score${k} < 1e8) {
934
- bestScore = min(bestScore, score${k});
935
- }`;
936
- // ⭐ 胜者(皇上 09-28 15:07 旨七修):score = 顶点距离 − 角度 最小的那张。
937
- slotOne += `
938
- if (score${k} < 1e8 && score${k} == bestScore) {
939
- wsum += 1.0; acc += lin${k}; cover += 1.0;
940
- }`;
941
- // 🔬 归属诊断(皇上旨 2026-09-25「就在最后算出来的颜色上再叠颜色,不用想那么复杂」):
942
- // 这里只**记账**(这点归哪个景点),染色动作留到 main 末尾对最终颜色做一次。
943
- // 归属判据与 slotOne **逐字同一条**(距离−角度最小),末了统一「打平归编号小的那个」。
944
- if (PANO_TINT_ON) {
945
- slotOwner += `
946
- if (score${k} < 1e8 && score${k} == bestScore) {
947
- // ⚠️ tintCnt == 0.0 这道闸 = **打平归编号小的那个**:两张一样近的那条分界带(实测两景点连线的中垂面与墙交线那一带)
948
- // 会同时命中两个槽,平均出来是「红+绿=橄榄」—— 那不是没上色,但肉眼读不出归属,故只记第一个。
949
- if (tintCnt == 0.0) { tintAcc += uPanoTintColors[${k}]; tintCnt += 1.0; }
950
- }`;
951
- }
952
- // 🔬 距离明暗图**不在这儿逐槽拼**了(皇上旨 2026-09-27 晚:要一句真循环遍历 4 个坐标)⇒ 见 `panoDistLoopGlsl`。
953
- }
954
- return /* glsl */ `
955
- #include <logdepthbuf_pars_fragment>
956
-
957
- uniform sampler2D panoramas[${tileCount}];${PANO_TINT_ON ? `
958
- // 🔬 归属诊断色(线性空间,**仅 ?panoTint=1 时存在**):第 i 个纹理槽里躺的那个景点的固定色,
959
- // 由应用层每次换绑时写入(见 setMktPanoTint)。⚠️ 刻意不按槽位编译期写死:
960
- // 「仅映射选中的那一个」模式下地板永远只喂 0 号槽,按槽写死 = 选第几个点都涂同一个红(皇上 09-25 反馈)。
961
- uniform vec3 uPanoTintColors[${maxPano}];` : ""}${panoDistOn ? `
962
- // 🔬 距离明暗诊断(这份材质建时 panoDist 选项为真才存在):「多远算全黑」的米数,由应用层每次喂投影时写
963
- // (见 setMktPanoDistRange)⇒ 全屋同一把尺子,跨面才比得出远近。
964
- uniform float uPanoDistMax;
965
- // 🔬 距离明暗诊断的**圆心清单**(皇上旨 2026-09-27「shader 里面有四个全景点的坐标, 每个像素判断距离 4 个点里最小的
966
- // 那个值, 再用这个值设置颜色」):xyz = 该点的世界坐标,w = 1 ⇒ 这一格**真有一个全景点**。
967
- // ⛔ 与 weights 的分工(两者刻意不同,别合并):weights 管「这格的**图**能不能取」(瓦片没绑好就 0,防止拿
968
- // 1×1 占位图刷一片素灰),uPanoPoints 管「这格**有没有点**」⇒ 圆按景点个数画,图按传到位的那几张取。
969
- // ⚠️ 09-27 晚起明暗那段**只读这一个数组**(纯水平距离最近)⇒ 与取图同把尺(夹角门已废);坐标与上面
970
- // textureMatrices[k][3].xyz 同源同值(同一批矩阵喂进来,见 setMktPanoPoints),不是第二套数据。
971
- uniform vec4 uPanoPoints[${maxPano}];` : ""}
972
- uniform mat4 textureMatrices[${maxPano}];
973
- uniform float weights[${maxPano}]; // 锚点距离权重 camW(JS 由名册那条水平距离写入,未归一化;着色器内与空间因子相乘后整体归一化)
974
- uniform vec3 uFloorBaseColor; // 地板底色(线性空间,深灰):只在「一个候选都没有」(maxPano 张全景全未绑定)时露出来
975
- // ⚠️ 下面两根覆盖半径旋钮**自 09-25 晚「都要一起改」起不再参与取图**(覆盖度融合那一路已删,每个像素纯按最近一张)。
976
- // 控件与 uniform 暂留(未奉旨删控件),值仍由应用层写入,但着色器不读 ⇒ 调它们没有视觉变化。
977
- uniform float uFloorCoreRadius;
978
- uniform float uFloorFadeRange;
979
- uniform float uFloorOpacity; // 地板全景投影「整体透明度」(0~1,全局对所有全景一起生效):仅作用于全景图映射的可见度,越小全景越淡、越透出地板灰底;地板灰色本身不变
980
- // 全景投射显隐:1=显示全景(投射到墙/地板),0=仅显示素色(墙体回落中性色,地板恒为 1 不受影响)
981
- uniform float uPanoramaVisible;
982
- // 色彩空间只有一条路(10-07 旨「统一使用 liner」):原 uniform float uColorSpace; + 那段 sRGB pow 解码分支已整条撤除,
983
- // 采样值即线性(纹理标了 SRGBColorSpace ⇒ 硬件解过一次),末端 <colorspace_fragment> 只编码一次。
984
- // ⭐ 取图方式只有一条(皇上旨 2026-09-25 晚「都要一起改」→ 09-27 晚「取用距离最近的那个全景点的瓦片纹理」定版):
985
- // 地板 / 墙 / 家具**同一份着色器、同一条判据** —— 候选里只取水平距离最近那**一张**,满权重。
986
- // 旧的 uPanoPickNearest(0=地板覆盖度融合 / 1=模型表面取最近)已随融合那一路一起删除;
987
- // 09-24~09-25 的夹角门 + 兜底也已按 09-27 晚旨整链删除(它是「明暗圆对、纹理不对」的第二来源)。
988
- // ⚠️ 候选池是「槽里活着的全景」(weights>0,瓦片绑好了的),**不受覆盖半径筛掉**。
989
- // 全景上下方向**已写死**,无 uniform(与单全景材质同口径,见文件头 🎛️ 段)。
990
- // 每个全景点**各自**的「这 2 槽是瓦片(1)还是同一张整图(0)」—— 融合时不同点可能来源不同(上传的有瓦片、
991
- // 旧数据/种子图没有),故按点存数组;为 0 的点走全图 UV,理由见单全景分支同注释。
992
- uniform float uPanoTiled[${maxPano}];
993
-
994
- // 取样坐标(已由顶点着色器的 uProjInvMatrix 抵消掉 MktMesh 的整体变换,与 textureMatrices 一样对变换免疫)
995
- varying vec3 vProjPos;
996
- // 全景序号映射模式:本三角面归属的全局全景序号(顶点着色器从 geometry 的 aPanoIndex 传来;无此 attribute 的对象恒为 0)
997
- varying float vPanoIndex;
998
- // 几何法线(顶点着色器按同一条矩阵链变换到与 vProjPos 同一空间):取图「正面夹角」判据用
999
- varying vec3 vWNormal;
1000
- // 实例染色(仅当 InstancedMesh 带 instanceColor 时启用):默认白=全景真实色,选中黄=高亮染色
1001
- #ifdef USE_INSTANCING_COLOR
1002
- varying vec3 vInstanceColor;
1003
- #endif
1004
-
1005
- void main() {
1006
- // ⭐ 正面法线 N(分值公式里「夹角」的基准,皇上 09-28 凌晨旨「距离 × 夹角」):几何法线归一化后,
1007
- // 按 gl_FrontFacing 翻到**观察者看的这一面**(墙零厚度 DoubleSide,两面各自以看到的这面算夹角——
1008
- // 同一面墙从两侧看,分值不同是应该的)。地板/封盖/立方体同理。
1009
- vec3 N = normalize(vWNormal);
1010
- if (!gl_FrontFacing) N = -N;
1011
- ${PANO_TINT_ON ? ` // 🔬 归属诊断配色(?panoTint=1):本面这点归哪个景点(tintAcc 累加其固定色,tintCnt 计数)
1012
- vec3 tintAcc = vec3(0.0);
1013
- float tintCnt = 0.0;` : ""}
1014
- ${slotCalc}
1015
- // ⭐ 取图口径(距离+夹角融合,钦定公式):score = 顶点距离 − 角度,取最小的那**一张**(地板/家具/编辑态墙同一份 shader)。
1016
- // (原「uFaceMappingMode 0/1 运行时分支」已按皇上旨 2026-09-29「三模式三 shader」拆出:
1017
- // 序号直取在 createMktFloorMappingShaderMaterial、仅显示选中在 createMktFloorSelectedShaderMaterial。)
1018
- vec3 acc = vec3(0.0);
1019
- float wsum = 0.0;
1020
- float cover = 0.0;
1021
- // 「取图胜者」归约(皇上 09-28 15:07 旨七修定版:score = 顶点距离 − 角度,取最小)
1022
- float bestScore = 1e9;
1023
- ${slotReduceLive}
1024
- ${PANO_TINT_ON ? " // 🔬 先记账:这点按「距离减夹角最小」归哪个景点(与下面 slotOne 的取图判据同一条)\n" + slotOwner : ""}
1025
- // 一面只显一张:score 最小的那张(不论多远,绝不留空面)
1026
- ${slotOne}
1027
- if (wsum > 0.0) acc /= wsum; // 按总权重归一化(总混合值=1)
1028
- // 覆盖度:有候选即 1(09-25 晚起不再有「渐隐/露灰」的中间态),只在「一个候选都没有」时 = 0
1029
- // ⇒ 露出地板/墙体本来的底色。
1030
- float coverage = clamp(cover, 0.0, 1.0);
1031
- // 整体透明度仅作用于全景图映射:把覆盖度按 uFloorOpacity 缩放后再混合,
1032
- // 灰底始终不透明(alpha=1),opacity 越小全景越淡、越透出地板灰色;地板灰色本身不变。
1033
- float coverageEff = clamp(coverage * uFloorOpacity, 0.0, 1.0);
1034
- vec3 col = mix(uFloorBaseColor, acc, coverageEff);
1035
- // 全景投射显隐:关闭时(仅墙体,地板恒为 1)回落中性素色,不投射全景
1036
- if (uPanoramaVisible < 0.5) {
1037
- col = vec3(0.78, 0.78, 0.80);
1038
- }
1039
- // 实例染色:默认白=原全景色;选中黄(FurnitureObject.highlight 写入 instanceColor)染黄高亮。
1040
- // 普通 Mesh(墙/地板)无 instanceColor,USE_INSTANCING_COLOR 不定义,此分支被编译剔除。
1041
- #ifdef USE_INSTANCING_COLOR
1042
- col *= vInstanceColor;
1043
- #endif
1044
- ${PANO_TINT_ON ? ` // 🔬 归属诊断配色(皇上旨 2026-09-25「就在最后算出来的颜色再叠加上颜色,不用想那么复杂」):
1045
- // 整段只在 ?panoTint=1 时生成,默认关 ⇒ 正常态一行都没有。色相全交给所属景点,只保留明暗,
1046
- // 所以亮部/暗部仍看得出是原照片的纹理,但一眼能分「这块面归哪个景点」。
1047
- // ⚠️ 与上一版(逐槽染 linK)的区别:这里染的是**最终颜色** ⇒ 即使那一片压根没有候选(一个全景都没绑上、
1048
- // 露底色)之外的任何区域也照样按归属上色;归属判据与取图判据同一条 = 「存活口径最近一张」。
1049
- // ⚠️ 刻意不用「采样色 × 淡色」:那种叠法叠在亮图上几乎发白,肉眼看不出(09-25 已被打回过一次)。
1050
- // ⚠️ 强度 = PANO_TINT_MIX(皇上旨 09-26 晚「叠加的颜色比例少一点」):原来整块换成色相 ⇒ 只剩明暗、
1051
- // 底下一张全景图完全读不出来;现在按 mix(原色, 染色, 本值) 叠,色相仍占主但照片看得见了。
1052
- if (tintCnt > 0.0) {
1053
- vec3 tintHue = tintAcc / tintCnt;
1054
- float tintLum = dot(col, vec3(0.2126, 0.7152, 0.0722));
1055
- vec3 tintTo = tintHue * (0.55 + 0.45 * clamp(tintLum * 3.0, 0.0, 1.0));
1056
- col = mix(col, tintTo, ${PANO_TINT_MIX});
1057
- }` : ""}${panoDistOn ? `
1058
- // 🔬 距离明暗诊断(皇上旨 09-26 晚「距离越小越白、越远越黑」+ 09-27 早「按我有 4 个全景点来写代码」+
1059
- // 09-27 晚「我一开始的需求就是要把 4 个全景点的坐标传给 shader,按最近取用」):
1060
- // **一句真循环**遍历 uPanoPoints[0..${maxPano - 1}],每个像素只留「到最近那个景点」的水平距离。
1061
- // ⚠️ 纯水平距离最近(09-27 晚定版后与取图**同把尺**,夹角门已废)⇒ 墙背后的景点也往这面墙上画圆,
1062
- // 一个面能看满 N 个渐变圆;代价 = 明暗图不分身前身后(与取图口径一致)。
1063
- ${panoDistLoopGlsl("col", maxPano)}` : ""}
1064
- gl_FragColor = vec4(col, 1.0);
1065
- // 输出颜色空间编码:渲染到线性 RT(主编辑器后处理链)时 passthrough,渲染到 sRGB 屏幕(预览弹窗)时自动编码 sRGB
1066
- #include <colorspace_fragment>
1067
- #include <logdepthbuf_fragment>
1068
- }
1069
- `;
1070
- }
1071
- /** 融合材质的全景点数**上限**:每点占 **2** 张拼合槽纹理(32 片 8×4 → 左右 2 槽,每槽 4×4 格)⇒ **8 × 2 = 16 个 sampler**,再多样本编译不过。
1072
- * ⭐ 2026-10-06 晚皇上旨「一次能够展示 8 个全景纹理」:靠的就是「每点从 4 槽降到 2 槽」——
1073
- * sampler 总数 16 是硬件硬顶(皇上本机 `MAX_TEXTURE_IMAGE_UNITS` 实测 16,一直没变),
1074
- * 原来 16 ÷ 4 槽 = 4 个景点,现在 16 ÷ 2 槽 = **8 个景点**,画质与单全景显存**分毫未动**。 */
1075
- export const MKT_PANO_MAX_SLOTS = 8;
1076
- /**
1077
- * 创建「多全景融合」着色器材质:每帧由外部喂入最近 maxPano 个全景点(⭐ 10-06 晚旨后每点 2 张拼合槽纹理)。
1078
- * 同一份着色器同时服务普通 Mesh(墙/地板)与 InstancedMesh(家具立方体):
1079
- * 实例化与否由 three.js 按对象类型自动注入 USE_INSTANCING 宏决定,无需外部传 instanced 开关。
1080
- * 数组 uniform 在创建时即按 maxPano 长度初始化(占位纹理 + 单位矩阵 + 权重 0),运行时由渲染器每帧改写对应槽位。
1081
- * ⚠️ 取图口径只有一条(皇上旨 2026-09-25 晚「都要一起改」→ 09-27 晚「取用距离最近的那个全景点的瓦片纹理」定版):
1082
- * 地板 / 墙 / 家具**全部**按「水平距离最近的一张」取图(09-24~09-25 的夹角门已整链删除),
1083
- * 不再有「地板按覆盖度融合」这条路,故也**没有** `pickNearest` 选项(旧的 0/1 分流 uniform 已删)。
1084
- */
1085
- export function createMktFloorBlendShaderMaterial(options = {}) {
1086
- // 2026-09-20:每个全景点占 N 张纹理槽,故「最多融合点数」maxPano 与纹理上限挂钩:
1087
- // maxPano × N 张纹理须 ≤ 16(shader sampler 上限)。
1088
- // 2026-09-24 皇上旨「同一时间要准备 4 个全景的瓦片纹理,一共刚好 16 个」(当时 N=4 ⇒ 上限 4)。
1089
- // ⭐ 2026-10-06 晚旨「一次能够展示 8 个全景纹理」:N 从 4 降到 **2**(每个全景只占左右两张 RT)
1090
- // ⇒ 16 ÷ 2 = **上限 8 个景点**,默认就取满 8。画质 / 单全景显存**分毫未动**。
1091
- // ⚠️ 10-06 切 32 片之后这两个槽各是「16 片拼出来的一张 4096² RT」,sampler 总数仍是 16(硬顶)。
1092
- // ⚠️ 所有喂同一批数据的材质必须同档:某处写成 3 而喂料方按 8 喂,会往 panos[6..15] 越界写、着色器读到 undefined sampler。
1093
- const maxPano = Math.min(options.maxPano ?? MKT_PANO_MAX_SLOTS, MKT_PANO_MAX_SLOTS);
1094
- // 🔬 这一份材质是否带「近白远黑」图:显式传了听他的(编辑器),没传读 URL(发布页/预览默认关)。
1095
- const panoDistOn = options.panoDist ?? PANO_DIST_ON;
1096
- const tileCount = maxPano * RT_STITCH_SLOT_COUNT; // 总纹理槽数 = 点数 × 每点槽数(8 × 2 = 16 = sampler 硬顶)
1097
- const panoramas = [];
1098
- const matrices = [];
1099
- const weights = new Float32Array(maxPano); // 默认全 0(未参与)
1100
- for (let i = 0; i < tileCount; i++) {
1101
- panoramas.push(createPlaceholderTexture());
1102
- }
1103
- for (let i = 0; i < maxPano; i++) {
1104
- matrices.push(new Matrix4());
1105
- }
1106
- return new ShaderMaterial({
1107
- uniforms: {
1108
- panoramas: { value: panoramas },
1109
- textureMatrices: { value: matrices },
1110
- weights: { value: weights },
1111
- // 地板底色(线性空间深灰,约 sRGB 0.27):远处露出此色
1112
- uFloorBaseColor: { value: new Vector3(0.06, 0.06, 0.06) },
1113
- // 🔬 归属诊断色(每槽一个 vec3 = 该槽里那个景点的固定色):**只在 ?panoTint=1 时创建**,
1114
- // 关态连 uniform 都不存在(JS 侧三元)⇒ 线上正常路径零开销。默认白 = 未被应用层写过。
1115
- ...(PANO_TINT_ON
1116
- ? { uPanoTintColors: { value: Array.from({ length: maxPano }, () => new Vector3(1, 1, 1)) } }
1117
- : {}),
1118
- // 🔬 距离明暗诊断的「黑端米数」:**这份材质 panoDist 为真才创建**(默认 16 ≈ 常见房间对角线,
1119
- // 应用层每次喂投影时按现场量出的尺子写实际值,见 setMktPanoDistRange);
1120
- // uPanoPoints = 同一张图的**圆心清单**(每格一个 vec4,w=1 表示这格有个全景点,见 setMktPanoPoints)
1121
- ...(panoDistOn ? {
1122
- uPanoDistMax: { value: 16 },
1123
- uPanoPoints: { value: Array.from({ length: maxPano }, () => new Vector4(0, 0, 0, 0)) },
1124
- } : {}),
1125
- // ⚠️ 下面两根覆盖半径:着色器自 09-25 晚「都要一起改」起**不再读它们**(覆盖度融合那一路已删),
1126
- // uniform 与应用层写入暂留 ⇒ 面板上那两根旋钮调了没有视觉变化,删不删等皇上旨意。
1127
- uFloorCoreRadius: { value: 4 },
1128
- uFloorFadeRange: { value: 4 },
1129
- // 整体透明度(0~1):仅缩放全景图映射的可见度(覆盖度×此值),地板灰底不变;全局对所有全景一起生效;1=全景完全可见
1130
- uFloorOpacity: { value: 1 },
1131
- // 全景投射显隐:1=显示全景,0=仅显示素色(墙体回落中性色,地板恒为 1)
1132
- uPanoramaVisible: { value: 1 },
1133
- // 色彩空间只有一条路(10-07 旨「统一使用 liner」)⇒ 原 `uColorSpace: { value: 0 }` 已随那段 pow 分支整条撤除
1134
- // 全景上下方向**已写死**,无 uniform(见文件头 🎛️ 段)。
1135
- // 每点各自的「这 2 槽是瓦片(1)/同一张整图(0)」,由喂料处按「PanoTexMode + 真实 tiles」写(setMktPanoTiled);
1136
- // 默认全 1 = 维持改造前的瓦片口径(真实上传的图都是四瓦片)。
1137
- uPanoTiled: { value: new Float32Array(maxPano).fill(1) },
1138
- // 投影空间逆矩阵:默认单位阵(= 世界坐标,行为与改造前一致)。
1139
- // 融合材质默认**永不**写入它 —— 编辑器地板/立方体、预览立方体因此完全不受 MktMesh 变换影响。
1140
- uProjInvMatrix: { value: new Matrix4() },
1141
- },
1142
- vertexShader: floorVertexShader,
1143
- fragmentShader: buildFloorBlendFragment(maxPano, panoDistOn),
1144
- side: DoubleSide,
1145
- transparent: false,
1146
- toneMapped: false,
1147
- });
1148
- }
1149
- /**
1150
- * 「仅显示选中全景」片元(皇上旨 2026-09-29「三模式三 shader」之三):
1151
- * 整面恒显示 **0 号点槽**那一张全景 —— 不看距离、不看夹角、不看序号,槽里是谁就投谁
1152
- * (喂料侧保证 0 号槽 = 当前选中的全景、其余槽留空)。
1153
- * uniform 形状与 blend 同档 ⇒ 喂料复用 feedBlendProjection 槽位指派路零改动;
1154
- * GLSL 只读 0 号点槽,其余槽是声明了没人采的死重(开销可忽略)。
1155
- * 槽没绑好(weights[0]≤0,选中全景瓦片还在路上 / 没选中任何全景)→ 露 uFloorBaseColor 深灰底。
1156
- */
1157
- function buildFloorSelectedFragment(maxPano) {
1158
- const tileCount = maxPano * RT_STITCH_SLOT_COUNT; // 形状与 blend 同档(喂料共用)
1159
- return /* glsl */ `
1160
- #include <logdepthbuf_pars_fragment>
1161
- uniform sampler2D panoramas[${tileCount}]; // 形状与 blend 同档(喂料共用);本 shader 只采 0 号点槽(纹理 0..1)
1162
- uniform mat4 textureMatrices[${maxPano}];
1163
- uniform float weights[${maxPano}]; // 0 号槽 > 0 = 选中全景的瓦片已绑好
1164
- uniform float uPanoTiled[${maxPano}];
1165
- uniform vec3 uFloorBaseColor; // 槽没绑好时露出的深灰底(线性空间,约 sRGB 0.27)
1166
- uniform float uFloorOpacity; // 整体透明度(与 blend 同口径:只缩放全景可见度,底色不变)
1167
- // 色彩空间只有一条路(10-07 旨「统一使用 liner」):原 uniform float uColorSpace; + 那段 pow 分支已整条撤除(与 blend 同口径)
1168
- uniform float uPanoramaVisible;
1169
- varying vec3 vProjPos;
1170
- #ifdef USE_INSTANCING_COLOR
1171
- varying vec3 vInstanceColor;
1172
- #endif
1173
- void main() {
1174
- // ── 0 号点槽(纹理 0..1,即左右那 2 张拼合槽):选中的全景独占(采样段与 blend 逐字同口径)──
1175
- vec3 lin0 = vec3(0.0);
1176
- if (weights[0] > 0.0) {
1177
- mat4 tm = textureMatrices[0];
1178
- vec3 texPos = tm[3].xyz;
1179
- mat3 texRot = mat3(tm);
1180
- vec3 dir = normalize(vProjPos - texPos);
1181
- dir = texRot * dir;
1182
- float lon = atan(dir.z, dir.x);
1183
- float u = lon * 0.15915494309189535 + 0.5; // 经度 -> u
1184
- float lat = asin(clamp(dir.y, -1.0, 1.0));
1185
- float v = 0.5 + lat * 0.3183098861837907; // 1/PI(等距柱状线性 v,与 blend 逐字同口径)
1186
- // ⭐ 10-06 晚旨:竖着不再分块 ⇒ 槽只随 u 走(左右两半:[左半幅=0, 右半幅=1]),与 blend 逐字同口径
1187
- int tileIdx = (u >= 0.5) ? 1 : 0;
1188
- float localU = (u < 0.5) ? (u * 2.0) : (u * 2.0 - 1.0);
1189
- // ⛔ 纵向不再减半(槽就是全高)⇒ localV 恒等于 v
1190
- float localV = v;
1191
- float panoU = (uPanoTiled[0] > 0.5) ? localU : u;
1192
- float panoV = (uPanoTiled[0] > 0.5) ? localV : v;
1193
- // ⭐ 采样即线性(与单全景 / blend 那两路同一条口径,10-07 旨「色彩空间统一使用 liner」):纹理都标了
1194
- // colorSpace = SRGBColorSpace ⇒ 硬件上传成 SRGB8_ALPHA8、texture2D 已解码一次;
1195
- // 这里**不再手动 pow 解码**(那遍是二次解码 = 整体压暗),末端只编码一次 ⇒ 与源图逐字节还原。
1196
- if (tileIdx == 0) lin0 = texture2D(panoramas[0], vec2(panoU, panoV)).rgb;
1197
- else lin0 = texture2D(panoramas[1], vec2(panoU, panoV)).rgb;
1198
- }
1199
- float coverage = weights[0] > 0.0 ? 1.0 : 0.0;
1200
- float coverageEff = clamp(coverage * uFloorOpacity, 0.0, 1.0);
1201
- vec3 col = mix(uFloorBaseColor, lin0, coverageEff);
1202
- // 全景投射显隐:关闭时回落中性素色(与 blend 一致)
1203
- if (uPanoramaVisible < 0.5) col = vec3(0.78, 0.78, 0.80);
1204
- #ifdef USE_INSTANCING_COLOR
1205
- col *= vInstanceColor;
1206
- #endif
1207
- gl_FragColor = vec4(col, 1.0);
1208
- #include <colorspace_fragment>
1209
- #include <logdepthbuf_fragment>
1210
- }
1211
- `;
1212
- }
1213
- /**
1214
- * 创建「仅显示选中全景」着色器材质(皇上旨 2026-09-29「三模式三 shader」之三):仅编辑器使用。
1215
- * uniform 形状与 blend 工厂同档 ⇒ 喂料复用 feedBlendProjection 槽位指派路零改动;
1216
- * 本 shader 只读 0 号点槽(GLSL 写死),uFloorCoreRadius/uFloorFadeRange 喂料会写但不读(静默忽略)。
1217
- */
1218
- export function createMktFloorSelectedShaderMaterial(options = {}) {
1219
- const maxPano = Math.min(options.maxPano ?? MKT_PANO_MAX_SLOTS, MKT_PANO_MAX_SLOTS);
1220
- const tileCount = maxPano * RT_STITCH_SLOT_COUNT; // 与 blend / mapping 同档(8 点 × 2 槽 = 16)
1221
- const panoramas = [];
1222
- const matrices = [];
1223
- for (let i = 0; i < tileCount; i++)
1224
- panoramas.push(createPlaceholderTexture());
1225
- for (let i = 0; i < maxPano; i++)
1226
- matrices.push(new Matrix4());
1227
- return new ShaderMaterial({
1228
- uniforms: {
1229
- panoramas: { value: panoramas },
1230
- textureMatrices: { value: matrices },
1231
- weights: { value: new Float32Array(maxPano) },
1232
- uPanoTiled: { value: new Float32Array(maxPano).fill(1) },
1233
- uFloorBaseColor: { value: new Vector3(0.06, 0.06, 0.06) },
1234
- // 喂料统一入口会写;本 shader 不读 uFloorCoreRadius/uFloorFadeRange(留 JS 项 = 喂料零分支)
1235
- uFloorCoreRadius: { value: 4 },
1236
- uFloorFadeRange: { value: 4 },
1237
- uFloorOpacity: { value: 1 },
1238
- uPanoramaVisible: { value: 1 },
1239
- // 色彩空间只有一条路(10-07 旨「统一使用 liner」)⇒ 原 `uColorSpace: { value: 0 }` 已随那段 pow 分支整条撤除
1240
- uProjInvMatrix: { value: new Matrix4() },
1241
- },
1242
- vertexShader: floorVertexShader,
1243
- fragmentShader: buildFloorSelectedFragment(maxPano),
1244
- side: DoubleSide,
1245
- transparent: false,
1246
- toneMapped: false,
1247
- });
1248
- }