three-base 2.0.172 → 2.0.175

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.
Files changed (115) hide show
  1. package/FPScontrol.d.ts +15 -24
  2. package/FPScontrol.js +63 -87
  3. package/FPScontrol.ts +64 -88
  4. package/Ground1/Ground1.d.ts +4 -2
  5. package/Ground1/Ground1.js +77 -57
  6. package/Ground1/Ground1.ts +77 -57
  7. package/Mirror.js +0 -4
  8. package/Mirror.ts +0 -4
  9. package/MktMesh/MktMesh.d.ts +3 -2
  10. package/MktMesh/MktMesh.js +27 -8
  11. package/MktMesh/MktMesh.ts +24 -8
  12. package/MktMesh/mktMappingShader.d.ts +6 -5
  13. package/MktMesh/mktMappingShader.js +26 -19
  14. package/MktMesh/mktMappingShader.ts +28 -19
  15. package/MktMesh/mktShader.d.ts +127 -58
  16. package/MktMesh/mktShader.js +192 -117
  17. package/MktMesh/mktShader.ts +251 -127
  18. package/MktMesh/panoSlotRoster.d.ts +2 -2
  19. package/MktMesh/panoSlotRoster.js +9 -7
  20. package/MktMesh/panoSlotRoster.ts +9 -7
  21. package/MktMesh/panoTileStitcher.d.ts +20 -13
  22. package/MktMesh/panoTileStitcher.js +97 -76
  23. package/MktMesh/panoTileStitcher.ts +119 -90
  24. package/PipeMesh/PipeJoint.d.ts +22 -0
  25. package/PipeMesh/PipeJoint.js +33 -0
  26. package/PipeMesh/PipeJoint.ts +38 -0
  27. package/PipeMesh/PipeMesh.d.ts +94 -22
  28. package/PipeMesh/PipeMesh.js +192 -53
  29. package/PipeMesh/PipeMesh.ts +223 -76
  30. package/PipeMesh/PipeStraight.d.ts +19 -0
  31. package/PipeMesh/PipeStraight.js +34 -0
  32. package/PipeMesh/PipeStraight.ts +45 -0
  33. package/PipeMesh/axisSnap.d.ts +52 -0
  34. package/PipeMesh/axisSnap.js +52 -0
  35. package/PipeMesh/axisSnap.ts +93 -0
  36. package/PipeMesh/directRoute.d.ts +85 -0
  37. package/PipeMesh/directRoute.js +126 -0
  38. package/PipeMesh/directRoute.ts +190 -0
  39. package/PipeMesh/index.d.ts +6 -0
  40. package/PipeMesh/index.js +6 -0
  41. package/PipeMesh/index.ts +7 -1
  42. package/PipeMesh/pipeMaterial.d.ts +16 -0
  43. package/PipeMesh/pipeMaterial.js +23 -0
  44. package/PipeMesh/pipeMaterial.ts +26 -0
  45. package/ThreeBase.d.ts +1 -39
  46. package/ThreeBase.js +38 -147
  47. package/ThreeBase.ts +1176 -1302
  48. package/instanced/InstancedBatch.d.ts +14 -1
  49. package/instanced/InstancedBatch.js +53 -3
  50. package/instanced/InstancedBatch.ts +62 -3
  51. package/instanced/InstancedChild.d.ts +24 -0
  52. package/instanced/InstancedChild.js +40 -0
  53. package/instanced/InstancedChild.ts +41 -0
  54. package/instanced/InstancedGroup.d.ts +48 -0
  55. package/instanced/InstancedGroup.js +45 -1
  56. package/instanced/InstancedGroup.ts +85 -0
  57. package/package.json +2 -7
  58. package/pass/debugDepthState.d.ts +16 -8
  59. package/pass/debugDepthState.js +4 -0
  60. package/pass/debugDepthState.ts +27 -9
  61. package/pass/glow_pass.js +11 -94
  62. package/pass/glow_pass.ts +11 -97
  63. package/pass/ssao_pass.d.ts +9 -23
  64. package/pass/ssao_pass.js +41 -128
  65. package/pass/ssao_pass.ts +36 -166
  66. package/pass/stroke_pass.js +3 -0
  67. package/pass/stroke_pass.ts +4 -0
  68. package/shaders/debugDepthCompareShader.d.ts +1 -1
  69. package/shaders/debugDepthCompareShader.js +1 -1
  70. package/shaders/debugDepthCompareShader.ts +1 -1
  71. package/shaders/debugDepthJudgeShader.d.ts +1 -1
  72. package/shaders/debugDepthJudgeShader.js +1 -1
  73. package/shaders/debugDepthJudgeShader.ts +1 -1
  74. package/PipeMesh/PathGeometry.d.ts +0 -44
  75. package/PipeMesh/PathGeometry.js +0 -307
  76. package/PipeMesh/PathGeometry.ts +0 -343
  77. package/PipeMesh/PathPoint.d.ts +0 -17
  78. package/PipeMesh/PathPoint.js +0 -31
  79. package/PipeMesh/PathPoint.ts +0 -36
  80. package/PipeMesh/PathPointList.d.ts +0 -31
  81. package/PipeMesh/PathPointList.js +0 -209
  82. package/PipeMesh/PathPointList.ts +0 -227
  83. package/PipeMesh/PathTubeGeometry.d.ts +0 -25
  84. package/PipeMesh/PathTubeGeometry.js +0 -130
  85. package/PipeMesh/PathTubeGeometry.ts +0 -146
  86. package/WaterFlow/FloatingDuck.d.ts +0 -88
  87. package/WaterFlow/FloatingDuck.js +0 -307
  88. package/WaterFlow/FloatingDuck.ts +0 -445
  89. package/WaterFlow/GPUFluidSimulation.d.ts +0 -56
  90. package/WaterFlow/GPUFluidSimulation.js +0 -785
  91. package/WaterFlow/GPUFluidSimulation.ts +0 -827
  92. package/WaterFlow/ReservoirSystem.d.ts +0 -106
  93. package/WaterFlow/ReservoirSystem.js +0 -621
  94. package/WaterFlow/ReservoirSystem.ts +0 -754
  95. package/WaterFlow/SPHField.d.ts +0 -35
  96. package/WaterFlow/SPHField.js +0 -204
  97. package/WaterFlow/SPHField.ts +0 -228
  98. package/WaterFlow/ScreenSpaceFluidPass.d.ts +0 -69
  99. package/WaterFlow/ScreenSpaceFluidPass.js +0 -736
  100. package/WaterFlow/ScreenSpaceFluidPass.ts +0 -789
  101. package/WaterFlow/ShallowWaterSurface.d.ts +0 -45
  102. package/WaterFlow/ShallowWaterSurface.js +0 -441
  103. package/WaterFlow/ShallowWaterSurface.ts +0 -510
  104. package/WaterFlow/WaterFlow.d.ts +0 -87
  105. package/WaterFlow/WaterFlow.js +0 -171
  106. package/WaterFlow/WaterFlow.ts +0 -219
  107. package/WaterFlow/types.d.ts +0 -161
  108. package/WaterFlow/types.js +0 -1
  109. package/WaterFlow/types.ts +0 -189
  110. package/WaterFlow/waterFlowPass.d.ts +0 -3
  111. package/WaterFlow/waterFlowPass.js +0 -20
  112. package/WaterFlow/waterFlowPass.ts +0 -40
  113. package/shaders/debugExcludedShader.d.ts +0 -15
  114. package/shaders/debugExcludedShader.js +0 -48
  115. package/shaders/debugExcludedShader.ts +0 -50
@@ -2,45 +2,40 @@
2
2
  * @Author: xiaosihan
3
3
  * @Date: 2022-09-20 23:06:00
4
4
  * @Last Modified by: xiaosihan
5
- * @Last Modified time: 2024-08-17 02:16:56
5
+ * @Last Modified time: 2026-10-12 00:00:00
6
6
  *
7
- * ⭐ 2026-09-20 皇上旨两改:
8
- * ① 原「科技感装饰地板」(三张格子贴图 + 紫色扩散动画 `#8D108E` + 格子线 `#263C56`)
9
- * → 深灰地板(每帧 3 次贴图采样、贴图平铺无各向异性 → 摩尔纹 + home 页卡顿元凶之一)。
10
- * ② 皇上再旨「地板就是**随机渐变**的深灰色、**不透明**」:
11
- * → 深灰区间内用 **value noise 做随机渐变**(无贴图、零采样,天然无摩尔纹);
12
- * → `transparent: false`、alpha 恒 1(不透明)。
7
+ * ⭐ 装饰地板 = 科技感格子贴图 + 紫色扩散动画(2026-10-12 皇上旨「整条退回旧科技感地板」+「装饰地板需要透明」)。
8
+ * 历史:2026-09-20 皇上曾两旨把它改成「随机渐变的深灰、不透明」,理由是每帧 3 次贴图采样 +
9
+ * 平铺无各向异性 → 摩尔纹,且是 home 页卡顿元凶之一。**当年那个摩尔纹根因现在已经在
10
+ * `threeLoader.getTexture` 里根治了**(anisotropy=16 + generateMipmaps + LinearMipmapLinear),
11
+ * 所以贴图路线的重画成本比 09-20 那版低一档,本次按皇上旨意恢复旧外观。
13
12
  *
14
- * ⚠️ 不透明带来的变化与对策:
15
- * - 原靠「径向 alpha 淡出」柔化 500×500 地板的边界;不透明后改用**径向压暗**(乘 0.72~1.0)
16
- * 弱化硬边 —— 若哪天皇上要「边界完全消失」,只能改回 alpha 淡出(即恢复 transparent)。
17
- * - 调试线(FPScontrol)两条口径分家(2026-09-25 皇上逐条定案):**10 条射线辅助线开深度测试**,
18
- * 正是靠本地板「不透明 + depthWrite:true」才挡得住埋进地里的那半截线;**8/9 两条回弹向量线
19
- * 保持穿透显示**(关深度测试 + renderOrder:9999),照旧常显在最前。
20
- * ⚠️ 若哪天把本地板改回 transparent + depthWrite:false,射线辅助线会重新穿地浮出来。
13
+ * ⭐ 2026-10-12 皇上旨「装饰地板需要透明」⇒ 恢复旧版的**外观**口径(逐字照抄 09-20 之前的写法):
14
+ * - `transparent: true` + 径向 alpha 淡出 `mapColor.a = 1 - dist(center)*2`(中心实、外缘全透)。
15
+ * - **但 `depthWrite: true` 保留**(旧版本来就是 transparent + depthWrite:true):主渲染的深度缓冲
16
+ * 照写 ⇒ FPScontrol 那 10 条射线辅助线仍被地板挡住埋进地里的那半截(09-25 定案口径不受影响)。
21
17
  *
22
- * ⚠️ 改动红线(这几条被别处依赖,**不许顺手改**):
23
- * - `userData.fpsCollision = true`:FPScontrol 把地板当碰撞体(见 FPScontrol.ts 地面处理段)。
24
- * - ⚠️ 2026-10-11 皇上旨「物体的深度都要进深度图」⇒ 原第三条红线 `userData.aoFloor = true`
25
- * (把地板从可见深度图里排除的那条例外)**整条拆除**:它的前提是地板半透明可透视,与本文件
26
- * ② 的「不透明改造」自相矛盾。地板现在按普通实心面写进深度图,被它挡住的描边/辉光/覆盖色/
27
- * 磨砂会被裁掉。见 three-base/pass/ssao_pass.ts 文件头。
28
- * - ShaderMaterial 必须含 logdepthbuf chunk;linear 输出必须 `#include <colorspace_fragment>`;
29
- * 否则末端 sRGB 编码会二次编码 → 发白。
18
+ * ⭐ 2026-10-18 皇上旨「所有的对象都要进入到深度图里面了,不需要用参数过滤掉了」⇒ 当年为「可透视
19
+ * 地板」加的 `userData.aoFloor`(把地板从后处理的可见深度图里排除)连同 ssao_pass 里那整条过滤判据
20
+ * 一并删除。现在地板两张深度图都写 ⇒ 透过地板看到的实体,其描边/辉光/覆盖色/磨砂会被地板裁掉,
21
+ * 包括外缘 alpha≈0 那圈「看得见空、看不见地板」的区域。判据出处 three-base/pass/ssao_pass.ts 文件头,
22
+ * 护栏 test/depthDebug.test.js J 组。
30
23
  */
31
- import { Color, DoubleSide, Matrix4, Mesh, PlaneGeometry, ShaderChunk, ShaderMaterial } from "three";
24
+ import { Clock, Color, DoubleSide, BufferGeometry, Camera, Group, Material, Matrix4, Mesh, PlaneGeometry, Scene, ShaderChunk, ShaderMaterial, Vector2, WebGLRenderer } from "three";
32
25
  import { MathUtils } from "three";
33
26
  const { degToRad } = MathUtils;
27
+ import floorMap1PNG from "./floorMap1.png";
28
+ import floorMap2PNG from "./floorMap2.png";
29
+ import floorMap3PNG from "./floorMap3.png";
30
+ import threeLoader from "../threeLoader";
34
31
 
35
- /** 装饰地板(随机渐变的深灰、不透明) */
32
+ /** 装饰地板(科技感格子贴图 + 紫色扩散动画,半透明可透视外观) */
36
33
  export default class Ground1 extends Mesh {
37
34
  tempMatrix: Matrix4 = new Matrix4();
38
35
  constructor() {
39
36
  super();
40
37
  // 标记为自己参与 FPS 碰撞检测
41
38
  this.userData.fpsCollision = true;
42
- // ⚠️ 这里原来还有 `this.userData.aoFloor = true;`(把本地板从可见深度图里排除的特例标记),
43
- // 2026-10-11 皇上旨「物体的深度都要进深度图」⇒ 连同 ssao_pass 的特例分支一起整条删除。
44
39
  }
45
40
  renderOrder: number = -1;
46
41
  geometry: PlaneGeometry = (() => {
@@ -52,14 +47,22 @@ export default class Ground1 extends Mesh {
52
47
  setSize(width: number, height: number): void {
53
48
  this.scale.set(width, 1, height);
54
49
  }
50
+ clock: Clock = new Clock();
51
+ onBeforeRender: (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void = (renderer, scene, camera, geometry, material, group) => {
52
+ const elapsedTime: number = this.clock.getElapsedTime();
53
+ this.material.uniforms.speed.value = (elapsedTime * 0.1) % 0.6;
54
+ this.material.uniforms.speed2.value = ((elapsedTime * 0.1) % 0.6) - 0.1;
55
+ };
55
56
  material: ShaderMaterial = new ShaderMaterial({
56
57
  uniforms: {
57
- /** 随机渐变的**暗端**深灰(three 的 Color 存 linear,末端 colorspace_fragment 编码回 sRGB) */
58
- colorDark: { value: new Color("#2A2E33") },
59
- /** 随机渐变的**亮端**深灰 */
60
- colorLight: { value: new Color("#3D434B") },
61
- /** 低频噪声格数(越大斑块越碎;9 → 500×500 地板上每块约 55m 的大块渐变) */
62
- noiseScale: { value: 9 },
58
+ size: { value: new Vector2(10, 10) }, // 格子平铺格数
59
+ speed: { value: 0 }, // 扩散值0-1
60
+ speed2: { value: 0 }, // 扩散值0-1
61
+ speedColor: { value: new Color("#8D108E") }, // 格子扩散颜色
62
+ lineColor: { value: new Color("#263C56") }, // 格子线的颜色
63
+ map1: { value: threeLoader.getTexture(floorMap1PNG) }, // 贴图纹理
64
+ map2: { value: threeLoader.getTexture(floorMap2PNG) }, // 贴图纹理
65
+ map3: { value: threeLoader.getTexture(floorMap3PNG) }, // 贴图纹理
63
66
  },
64
67
  vertexShader: `
65
68
  ${ShaderChunk.common}
@@ -73,42 +76,59 @@ export default class Ground1 extends Mesh {
73
76
  `,
74
77
  fragmentShader: `
75
78
  ${ShaderChunk.logdepthbuf_pars_fragment}
76
- uniform vec3 colorDark;
77
- uniform vec3 colorLight;
78
- uniform float noiseScale;
79
+ uniform sampler2D map1;
80
+ uniform sampler2D map2;
81
+ uniform sampler2D map3;
82
+ uniform vec2 size;
83
+ uniform vec3 lineColor;
84
+ uniform float speed;
85
+ uniform float speed2;
86
+ uniform vec3 speedColor;
79
87
  varying vec2 vUv;
88
+ void main() {
80
89
 
81
- // 伪随机 hash → value noise:无贴图、零采样,天然没有平铺摩尔纹
82
- float hash21(vec2 p) {
83
- return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
84
- }
85
- float valueNoise(vec2 p) {
86
- vec2 i = floor(p);
87
- vec2 f = fract(p);
88
- vec2 u = f * f * (3.0 - 2.0 * f); // smoothstep 插值 → 柔和渐变,不是硬格子
89
- return mix(mix(hash21(i), hash21(i + vec2(1.0, 0.0)), u.x),
90
- mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0, 1.0)), u.x), u.y);
91
- }
90
+ // 中心点
91
+ vec2 center = vec2(0.5,0.5);
92
92
 
93
- void main() {
94
- // 两个八度叠加:低频大块渐变 + 中频细节 → 深灰区间内的**随机渐变**
95
- float n = valueNoise(vUv * noiseScale) * 0.65
96
- + valueNoise(vUv * noiseScale * 3.1) * 0.35;
97
- vec3 col = mix(colorDark, colorLight, clamp(n, 0.0, 1.0));
93
+ vec4 map1Color = texture2D(map1, fract( vUv * size) );
94
+
95
+ // 格子中心渐变颜色
96
+ if(distance( vUv , center ) < speed){
97
+ map1Color = mix(map1Color, vec4(speedColor, 1.0), clamp((1.0 - (distance(distance( vUv , center ), speed) * 8.0) ) * map1Color.a, 0.0, 1.0 ) );
98
+ }
99
+
100
+ vec4 map2Color = texture2D(map2, fract( vUv * size) );
101
+
102
+ vec4 map3Color = texture2D(map3, fract( vUv * size) );
103
+
104
+ // 正方形装饰中心渐变颜色
105
+ if(distance( vUv , center ) < speed2){
106
+ map3Color = mix(map3Color, map3Color * 3.0, clamp((1.0 - (distance(distance( vUv , center ), speed) * 4.0) ) * map3Color.a, 0.0, 1.0 ) );
107
+ }
108
+
109
+ vec4 mapColor = mix(map1Color, map2Color, map2Color.a);
110
+
111
+ mapColor = mix(mapColor, map3Color, map3Color.a);
112
+
113
+ //添加背景色
114
+ mapColor = mix(mapColor, vec4(lineColor.rgb, 1.0 ), 1.0 - mapColor.a);
115
+
116
+ // 设置透明度(中心实、外缘全透的径向淡出 —— 500×500 大地板的硬边靠它消失)
117
+ mapColor.a = 1.0 - (distance( vUv , center ) * 2.0);
98
118
 
99
- // ⚠️ 不透明地板无法用 alpha 淡出边界 → 改用径向压暗弱化 500×500 的硬边
100
- float edge = 1.0 - smoothstep(0.34, 0.5, distance(vUv, vec2(0.5, 0.5)));
101
- col *= mix(0.72, 1.0, edge);
119
+ gl_FragColor = mapColor;
102
120
 
103
- gl_FragColor = vec4(col, 1.0); // 不透明
104
121
  ${ShaderChunk.logdepthbuf_fragment}
105
122
  #include <colorspace_fragment>
106
123
  }
107
124
  `,
108
125
  depthTest: true,
109
126
  depthWrite: true,
110
- // ⭐ 皇上 2026-09-20 旨:地板**不透明**
111
- transparent: false,
127
+ // ⭐ 皇上 2026-10-12 旨「装饰地板需要透明」:恢复旧版 transparent:true(半透可透视)。
128
+ // ⚠️ depthWrite 保持 true —— 主渲染的深度缓冲照写,FPScontrol 的 10 条射线辅助线才挡得住
129
+ // 埋进地里的那半截(09-25 定案)。后处理特效的遮挡判定走 baseDepthTexture,2026-10-18
130
+ // 皇上旨「所有的对象都要进入到深度图里面了」⇒ 地板也写进那张图,会裁掉身后特效。
131
+ transparent: true,
112
132
  side: DoubleSide,
113
133
  });
114
134
  }
package/Mirror.js CHANGED
@@ -9,10 +9,6 @@ import { Color, DoubleSide, HalfFloatType, Matrix4, Mesh, PerspectiveCamera, Pla
9
9
  export default class Mirror extends Mesh {
10
10
  constructor(geometry = Mirror.geometry) {
11
11
  super(geometry);
12
- // 镜子虽标了 transparent:true(为渲染顺序),但 fragment shader 实际输出 alpha=1.0,
13
- // 是实心反射面、并非可透视表面 → 必须参与 AO 与描边/辉光的深度遮挡。
14
- // SSAO 的 AO 数据准备会排除所有 transparent 物体,故此处显式声明保留。
15
- this.userData.aoOpaque = true;
16
12
  }
17
13
  // 正方形网格
18
14
  static geometry = (() => {
package/Mirror.ts CHANGED
@@ -10,10 +10,6 @@ import { Color, DoubleSide, HalfFloatType, Matrix4, Mesh, PerspectiveCamera, Pla
10
10
  export default class Mirror extends Mesh {
11
11
  constructor(geometry: BufferGeometry = Mirror.geometry) {
12
12
  super(geometry);
13
- // 镜子虽标了 transparent:true(为渲染顺序),但 fragment shader 实际输出 alpha=1.0,
14
- // 是实心反射面、并非可透视表面 → 必须参与 AO 与描边/辉光的深度遮挡。
15
- // SSAO 的 AO 数据准备会排除所有 transparent 物体,故此处显式声明保留。
16
- this.userData.aoOpaque = true;
17
13
  }
18
14
 
19
15
  // 正方形网格
@@ -61,8 +61,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
61
61
  * ⇒ 与编辑态 `mktMeshEditRenderer._slotPanos` **同一份算法、同一个常数**(预览看到的那几张 = 编辑器里的那几张,均为 8 个全景槽)。
62
62
  * 存**对象引用**不存下标(删点会让下标整体前移,引用不会认错人)。
63
63
  * 消费方 = `feedFaceMapping`(墙 8 格);⭐ 10-03 旨② 后预览立方体与墙**共用同一份材质**,
64
- * 立方体几何无 `aPanoIndex` ⇒ `vPanoIndex ≡ 0` ⇒ 它显示的就是 0 号槽那张(墙与立方体天然同源,
65
- * 原来那条「把中心那几张抄给立方体单全景材质」的 `currentTiles` 旁路已整条删除)。
64
+ * ⭐ 10-12 旨后立方体**逐颗自带序号**(`InstancedChild.panoIndex` → 逐实例 `aPanoIndex`),
65
+ * 不再是从前那句「几何没这条 attribute ⇒ `vPanoIndex ≡ 0` ⇒ 全体看 0 号槽那张」;
66
+ * 原来那条「把中心那几张抄给立方体单全景材质」的 `currentTiles` 旁路已整条删除。
66
67
  */
67
68
  private _slotPanos;
68
69
  /**
@@ -107,8 +107,9 @@ export default class MktMesh extends Mesh {
107
107
  * ⇒ 与编辑态 `mktMeshEditRenderer._slotPanos` **同一份算法、同一个常数**(预览看到的那几张 = 编辑器里的那几张,均为 8 个全景槽)。
108
108
  * 存**对象引用**不存下标(删点会让下标整体前移,引用不会认错人)。
109
109
  * 消费方 = `feedFaceMapping`(墙 8 格);⭐ 10-03 旨② 后预览立方体与墙**共用同一份材质**,
110
- * 立方体几何无 `aPanoIndex` ⇒ `vPanoIndex ≡ 0` ⇒ 它显示的就是 0 号槽那张(墙与立方体天然同源,
111
- * 原来那条「把中心那几张抄给立方体单全景材质」的 `currentTiles` 旁路已整条删除)。
110
+ * ⭐ 10-12 旨后立方体**逐颗自带序号**(`InstancedChild.panoIndex` → 逐实例 `aPanoIndex`),
111
+ * 不再是从前那句「几何没这条 attribute ⇒ `vPanoIndex ≡ 0` ⇒ 全体看 0 号槽那张」;
112
+ * 原来那条「把中心那几张抄给立方体单全景材质」的 `currentTiles` 旁路已整条删除。
112
113
  */
113
114
  _slotPanos = [];
114
115
  /**
@@ -801,6 +802,10 @@ export default class MktMesh extends Mesh {
801
802
  * ⭐ `rt:` 虚拟键(32 瓦片拼合槽)走拼合器 peek;⛔ 不在这里 pump(每帧驱动点唯一,见 `updateTexture`,
802
803
  * 否则一帧画好几格 = 把「每帧只载入一张新纹理」那道闸从后门拆了)。 */
803
804
  _peekTileAt(key, original) {
805
+ // ⭐ 2026-10-25:空地址 = 「这个点还没设图片」,不是一条能加载的 URL ⇒ 直接判没有。
806
+ // 放过去会让 ensureTileTexture 拿空串请求当前文档地址、失败后每帧重试。
807
+ if (!key)
808
+ return null;
804
809
  if (isRtStitchKey(key)) {
805
810
  const p = rtStitchKeyParts(key);
806
811
  if (!p)
@@ -838,6 +843,9 @@ export default class MktMesh extends Mesh {
838
843
  * ⚠️ 皇上旨 2026-09-28「加载瓦片的时候, 瓦片纹理不需要添加 oss 的参数」⇒ 真瓦片(`original=true`,现行 32 片/全景)走原图,
839
844
  * 整图档才按 ossSize 档缩(`resolveOssUrl`)。 */
840
845
  ensureTileTexture(key, original = false) {
846
+ // ⭐ 2026-10-25:空地址直接判「没有」,不起载(与编辑态 ensureFloorTexture 同一道闸)。
847
+ if (!key)
848
+ return null;
841
849
  const cached = this._texCache.get(key);
842
850
  if (cached)
843
851
  return cached;
@@ -887,8 +895,9 @@ export default class MktMesh extends Mesh {
887
895
  if (this._glRenderer)
888
896
  this._glRenderer.initTexture(tex);
889
897
  }
890
- catch (e) {
891
- console.warn("[MktMesh] 瓦片预上传失败,交给常规渲染路径自行上传:", e);
898
+ catch {
899
+ // 皇上旨 2026-10-12「日志都去掉」⇒ 这里不再出声;降级路径本身不变(下面照记「已处理」,
900
+ // 最坏退回 three 在绑定那一帧自己上传的老路径)。
892
901
  }
893
902
  // ⚠️ 成败都记「已处理」。不记 ⇒ 这张永远进不了 `_texUploaded` ⇒ 那一格永远换不上新图,
894
903
  // 比「卡一下」严重得多;记了最坏也只是退回 three 在绑定那一帧自己上传的老路径。
@@ -932,6 +941,18 @@ export default class MktMesh extends Mesh {
932
941
  continue;
933
942
  }
934
943
  idxU[s] = this.textures.indexOf(info);
944
+ // ⭐ 2026-10-25 皇上旨「把默认图去掉,图片没有加载的时候就显示纯灰色」:
945
+ // 这个点**没设图片**(url 空且没有切片)⇒ 按空槽喂料,权重 0 ⇒ 片元回落 uFaceMissingColor 中性灰。
946
+ // ⛔ 别把空地址放下去:空 key 会走到 `_peekTileAt` → `ensureTileTexture("")` 发一次空请求
947
+ // (解析成当前文档地址),失败后下一帧再试 = 每帧一个废请求。序号照留(归属还在,只是没图)。
948
+ const hasSource = !!info.url || !!(info.tiles && info.tiles.length > 0);
949
+ if (!hasSource) {
950
+ for (let k = 0; k < RT_STITCH_SLOT_COUNT; k++)
951
+ panos[base + k] = this._placeholder;
952
+ mats[s].identity();
953
+ ws[s] = 0;
954
+ continue;
955
+ }
935
956
  // 各槽喂什么地址 = 「用户选的形态 + 该点有没有切过片」共同决定(whole 或无 tiles ⇒ 各槽同为一张整图)
936
957
  const tiles = mktPanoTexUrls(info.url, info.tiles, this._panoTexMode);
937
958
  // ⚠️ 「是不是真瓦片」只算一次:喂地址(走不走 OSS 尺寸档)与写 uPanoTiled 共用同一个值
@@ -1042,12 +1063,10 @@ export default class MktMesh extends Mesh {
1042
1063
  // setWallPoints 已按位置重算过映射(旧为空 → 全 -1),此处用存档里的逐面指派覆盖。
1043
1064
  // 几何拓扑由同一份 wallPoints 重建,面索引与导出时一致 ⇒ 逐面写入即可对齐。
1044
1065
  // ⭐ 皇上旨 2026-10-03「补」:长度不符**不再整份丢弃**——按较小长度逐面灌回(存档多出的面丢掉,
1045
- // 墙变长多出来的那些面保持 setWallPoints 按位置继承到的值),并把两个数 warn 出来(不静默吞)。
1066
+ // 墙变长多出来的那些面保持 setWallPoints 按位置继承到的值)。
1067
+ // 2026-10-12 旨「日志都去掉」⇒ 这一条不再 warn,差值只按较小者静默还原。
1046
1068
  const imported = (data.panoFaceMapping && data.panoFaceMapping.length ? data.panoFaceMapping : []);
1047
1069
  const faceCount = this.panoFaceMapping.length;
1048
- if (imported.length && imported.length !== faceCount) {
1049
- console.warn(`[MktMesh] 存档序号映射面数 ${imported.length} ≠ 当前墙体面数 ${faceCount},按较小者逐面还原(差值来自墙点/墙高变化)`);
1050
- }
1051
1070
  const restore = Math.min(imported.length, faceCount);
1052
1071
  if (restore > 0) {
1053
1072
  for (let f = 0; f < restore; f++)
@@ -130,8 +130,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
130
130
  * ⇒ 与编辑态 `mktMeshEditRenderer._slotPanos` **同一份算法、同一个常数**(预览看到的那几张 = 编辑器里的那几张,均为 8 个全景槽)。
131
131
  * 存**对象引用**不存下标(删点会让下标整体前移,引用不会认错人)。
132
132
  * 消费方 = `feedFaceMapping`(墙 8 格);⭐ 10-03 旨② 后预览立方体与墙**共用同一份材质**,
133
- * 立方体几何无 `aPanoIndex` ⇒ `vPanoIndex ≡ 0` ⇒ 它显示的就是 0 号槽那张(墙与立方体天然同源,
134
- * 原来那条「把中心那几张抄给立方体单全景材质」的 `currentTiles` 旁路已整条删除)。
133
+ * ⭐ 10-12 旨后立方体**逐颗自带序号**(`InstancedChild.panoIndex` → 逐实例 `aPanoIndex`),
134
+ * 不再是从前那句「几何没这条 attribute ⇒ `vPanoIndex ≡ 0` ⇒ 全体看 0 号槽那张」;
135
+ * 原来那条「把中心那几张抄给立方体单全景材质」的 `currentTiles` 旁路已整条删除。
135
136
  */
136
137
  private _slotPanos: (MktTextureInfo | undefined)[] = [];
137
138
  /**
@@ -833,6 +834,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
833
834
  * ⭐ `rt:` 虚拟键(32 瓦片拼合槽)走拼合器 peek;⛔ 不在这里 pump(每帧驱动点唯一,见 `updateTexture`,
834
835
  * 否则一帧画好几格 = 把「每帧只载入一张新纹理」那道闸从后门拆了)。 */
835
836
  private _peekTileAt(key: string, original: boolean): Texture | null {
837
+ // ⭐ 2026-10-25:空地址 = 「这个点还没设图片」,不是一条能加载的 URL ⇒ 直接判没有。
838
+ // 放过去会让 ensureTileTexture 拿空串请求当前文档地址、失败后每帧重试。
839
+ if (!key) return null;
836
840
  if (isRtStitchKey(key)) {
837
841
  const p = rtStitchKeyParts(key);
838
842
  if (!p) return null;
@@ -869,6 +873,8 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
869
873
  * ⚠️ 皇上旨 2026-09-28「加载瓦片的时候, 瓦片纹理不需要添加 oss 的参数」⇒ 真瓦片(`original=true`,现行 32 片/全景)走原图,
870
874
  * 整图档才按 ossSize 档缩(`resolveOssUrl`)。 */
871
875
  private ensureTileTexture(key: string, original = false): Texture | null {
876
+ // ⭐ 2026-10-25:空地址直接判「没有」,不起载(与编辑态 ensureFloorTexture 同一道闸)。
877
+ if (!key) return null;
872
878
  const cached = this._texCache.get(key);
873
879
  if (cached) return cached;
874
880
  if (cached === null) return null; // 加载中
@@ -915,8 +921,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
915
921
  // `_glRenderer` 还没被 `onBeforeRender` 捕获到(构造后尚未渲染过一帧)⇒ 跳过预上传,
916
922
  // 交给 three 在绑定那一帧自己上传 —— 那是改造前的老路径,不会报错,只是那一张没享受「错峰」。
917
923
  if (this._glRenderer) this._glRenderer.initTexture(tex);
918
- } catch (e) {
919
- console.warn("[MktMesh] 瓦片预上传失败,交给常规渲染路径自行上传:", e);
924
+ } catch {
925
+ // 皇上旨 2026-10-12「日志都去掉」⇒ 这里不再出声;降级路径本身不变(下面照记「已处理」,
926
+ // 最坏退回 three 在绑定那一帧自己上传的老路径)。
920
927
  }
921
928
  // ⚠️ 成败都记「已处理」。不记 ⇒ 这张永远进不了 `_texUploaded` ⇒ 那一格永远换不上新图,
922
929
  // 比「卡一下」严重得多;记了最坏也只是退回 three 在绑定那一帧自己上传的老路径。
@@ -959,6 +966,17 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
959
966
  continue;
960
967
  }
961
968
  idxU[s] = this.textures.indexOf(info);
969
+ // ⭐ 2026-10-25 皇上旨「把默认图去掉,图片没有加载的时候就显示纯灰色」:
970
+ // 这个点**没设图片**(url 空且没有切片)⇒ 按空槽喂料,权重 0 ⇒ 片元回落 uFaceMissingColor 中性灰。
971
+ // ⛔ 别把空地址放下去:空 key 会走到 `_peekTileAt` → `ensureTileTexture("")` 发一次空请求
972
+ // (解析成当前文档地址),失败后下一帧再试 = 每帧一个废请求。序号照留(归属还在,只是没图)。
973
+ const hasSource = !!info.url || !!(info.tiles && info.tiles.length > 0);
974
+ if (!hasSource) {
975
+ for (let k = 0; k < RT_STITCH_SLOT_COUNT; k++) panos[base + k] = this._placeholder;
976
+ mats[s].identity();
977
+ ws[s] = 0;
978
+ continue;
979
+ }
962
980
  // 各槽喂什么地址 = 「用户选的形态 + 该点有没有切过片」共同决定(whole 或无 tiles ⇒ 各槽同为一张整图)
963
981
  const tiles = mktPanoTexUrls(info.url, info.tiles, this._panoTexMode);
964
982
  // ⚠️ 「是不是真瓦片」只算一次:喂地址(走不走 OSS 尺寸档)与写 uPanoTiled 共用同一个值
@@ -1075,12 +1093,10 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
1075
1093
  // setWallPoints 已按位置重算过映射(旧为空 → 全 -1),此处用存档里的逐面指派覆盖。
1076
1094
  // 几何拓扑由同一份 wallPoints 重建,面索引与导出时一致 ⇒ 逐面写入即可对齐。
1077
1095
  // ⭐ 皇上旨 2026-10-03「补」:长度不符**不再整份丢弃**——按较小长度逐面灌回(存档多出的面丢掉,
1078
- // 墙变长多出来的那些面保持 setWallPoints 按位置继承到的值),并把两个数 warn 出来(不静默吞)。
1096
+ // 墙变长多出来的那些面保持 setWallPoints 按位置继承到的值)。
1097
+ // 2026-10-12 旨「日志都去掉」⇒ 这一条不再 warn,差值只按较小者静默还原。
1079
1098
  const imported = (data.panoFaceMapping && data.panoFaceMapping.length ? data.panoFaceMapping : []) as number[];
1080
1099
  const faceCount = this.panoFaceMapping.length;
1081
- if (imported.length && imported.length !== faceCount) {
1082
- console.warn(`[MktMesh] 存档序号映射面数 ${imported.length} ≠ 当前墙体面数 ${faceCount},按较小者逐面还原(差值来自墙点/墙高变化)`);
1083
- }
1084
1100
  const restore = Math.min(imported.length, faceCount);
1085
1101
  if (restore > 0) {
1086
1102
  for (let f = 0; f < restore; f++) this.panoFaceMapping[f] = imported[f];
@@ -1,18 +1,19 @@
1
1
  import { ShaderMaterial } from "three";
2
2
  /**
3
- * ⭐ 融合材质的全景点数**上限**(皇上旨 10-06「一次能够展示 8 个全景纹理」):每点占 **2** 张拼合槽纹理
4
- * (32 片 8×4 → 左右 2 槽,每槽 4×4 格)⇒ **8 × 2 = 16 个 sampler**,再多样本编译不过。
5
- * (sampler 总数 16 是硬件硬顶,本机 `MAX_TEXTURE_IMAGE_UNITS` 实测 16。)
3
+ * ⭐ 融合材质的全景点数**上限**:每点占 **3** 张拼合槽纹理(皇上旨 2026-10-23「全景的 RT 由原来的 2 个分成 3 个、
4
+ * 每个 4096×6144、竖直排列 3 个瓦片」⇒ 原图切 3×3=9 片,按**列**分 3 槽)⇒ 点数 × 3 必须 ≤ sampler 硬顶。
5
+ * 本机 `MAX_TEXTURE_IMAGE_UNITS` 实测 **16**:旧版 8 点 × 2 槽 = 16 正好用满,现每点 3 槽 ⇒ 只能取 ⌊16/3⌋ = **5**
6
+ * (5 × 3 = 15)。皇上旨 2026-10-23 在「A」里拍板:保 3 槽不降画质,**同屏全景点数 8 → 5**。
6
7
  *
7
8
  * ⭐⭐ 2026-10-09 皇上旨「MktMesh 只有映射一种,blend/selected 归编辑器」⇒ 该常量从 `mktShader.ts`
8
9
  * **搬到这里**(映射材质的所在地)。它同时是库与编辑器那份 blend 的**共同上限**,两边必须同值。
9
10
  */
10
- export declare const MKT_PANO_MAX_SLOTS = 8;
11
+ export declare const MKT_PANO_MAX_SLOTS = 5;
11
12
  /**
12
13
  * 创建「全景序号直取」着色器材质(皇上旨 2026-09-29「三模式三 shader」之二):
13
14
  * 编辑器「映射设置模式」的墙材质 + **基类 MktMesh(发布/预览)的固定墙材质**
14
15
  * (皇上旨:mktmesh 就只有按照 attribute 配置来显示全景这一种固定的 shader)。
15
- * uniform 形状与 blend 工厂同档(panoramas[16]/textureMatrices[8]/weights[8]/uPanoTiled[8])⇒
16
+ * uniform 形状与 blend 工厂同档(panoramas[15]/textureMatrices[5]/weights[5]/uPanoTiled[5])⇒
16
17
  * 喂料复用 feedBlendProjection 的槽位指派路(feedFaceMapping),零新喂料路。
17
18
  * uFloorCoreRadius/uFloorFadeRange/uFloorOpacity 三根本 shader 不读,但喂料统一入口会写 ⇒ JS 侧留着(静默忽略)。
18
19
  */
@@ -8,33 +8,42 @@
8
8
  import { BackSide, Matrix4, ShaderMaterial, Vector3 } from "three";
9
9
  import { createPlaceholderTexture, floorVertexShader, RT_STITCH_SLOT_COUNT } from "./mktShader";
10
10
  /**
11
- * ⭐ 融合材质的全景点数**上限**(皇上旨 10-06「一次能够展示 8 个全景纹理」):每点占 **2** 张拼合槽纹理
12
- * (32 片 8×4 → 左右 2 槽,每槽 4×4 格)⇒ **8 × 2 = 16 个 sampler**,再多样本编译不过。
13
- * (sampler 总数 16 是硬件硬顶,本机 `MAX_TEXTURE_IMAGE_UNITS` 实测 16。)
11
+ * ⭐ 融合材质的全景点数**上限**:每点占 **3** 张拼合槽纹理(皇上旨 2026-10-23「全景的 RT 由原来的 2 个分成 3 个、
12
+ * 每个 4096×6144、竖直排列 3 个瓦片」⇒ 原图切 3×3=9 片,按**列**分 3 槽)⇒ 点数 × 3 必须 ≤ sampler 硬顶。
13
+ * 本机 `MAX_TEXTURE_IMAGE_UNITS` 实测 **16**:旧版 8 点 × 2 槽 = 16 正好用满,现每点 3 槽 ⇒ 只能取 ⌊16/3⌋ = **5**
14
+ * (5 × 3 = 15)。皇上旨 2026-10-23 在「A」里拍板:保 3 槽不降画质,**同屏全景点数 8 → 5**。
14
15
  *
15
16
  * ⭐⭐ 2026-10-09 皇上旨「MktMesh 只有映射一种,blend/selected 归编辑器」⇒ 该常量从 `mktShader.ts`
16
17
  * **搬到这里**(映射材质的所在地)。它同时是库与编辑器那份 blend 的**共同上限**,两边必须同值。
17
18
  */
18
- export const MKT_PANO_MAX_SLOTS = 8;
19
+ export const MKT_PANO_MAX_SLOTS = 5;
19
20
  /**
20
21
  * 序号直取片元(皇上旨 2026-09-29「三模式三 shader」之二):
21
22
  * 按三角面 aPanoIndex 在 panoIndices 槽里找对应全景纹理 —— **不看距离、不看夹角**(blend 那套打分/归约整段没有)。
22
- * 槽内采样段(等距柱状投射 + 左右 2 张拼合槽 + 采样即线性、不再手动 pow 解码)与 blend **逐字同口径**;改采样口径两处同改。
23
+ * 槽内采样段(等距柱状投射 + 左/中/右 3 张拼合槽 + 采样即线性、不再手动 pow 解码)与 blend **逐字同口径**;改采样口径两处同改。
23
24
  * 未设序号 / 序号不在槽里 / 该槽瓦片没绑好(weights≤0)→ uFaceMissingColor 深灰兜底
24
25
  * (皇上旨 09-28「装不下就深灰」;weights 闸是 09-29 拆分时补的 —— 原运行时分支没这道闸,
25
26
  * 槽没绑好但序号命中会采到 vec3(0) 纯黑,深灰才是钦定兜底色)。
26
27
  */
27
28
  function buildFloorMappingFragment(maxPano) {
28
- // 每个全景点占 **2** 张拼合槽纹理(32 片每 16 片拼一张:左半幅 / 右半幅)⇒ 8 × 2 = 16 = sampler 硬顶
29
+ // 每个全景点占 **3** 张拼合槽纹理(9 片 3×3 按列分:左列 / 中列 / 右列,每槽竖叠 3 片)⇒ 5 × 3 = 15 ≤ sampler 硬顶 16
29
30
  const tileCount = maxPano * RT_STITCH_SLOT_COUNT;
30
31
  let slotCalc = ""; // 每槽各自采样出 linK(weights[k]>0 才采;没绑好保持 0,由 facePick 的 weights 闸兜成深灰)
31
32
  let slotFacePick = ""; // 逐槽展开「panoIndices[k]==vPanoIndex 则取 lin{k}」(ANGLE 禁动态索引 ⇒ JS 常量展开)
32
33
  for (let k = 0; k < maxPano; k++) {
33
- const base = k * RT_STITCH_SLOT_COUNT; // 本点占 2 个连续纹理槽:base=k*2 .. base+1(顺序 [左半幅, 右半幅])
34
+ const base = k * RT_STITCH_SLOT_COUNT; // 本点占 3 个连续纹理槽:base=k*3 .. base+2(顺序 [左列, 中列, 右列])
35
+ // ⚠️ ANGLE(D3D) 禁止动态索引 sampler 数组 ⇒ 展开成「base + 字面量」常量下标 if 链;末条裸 else 兜住 u==1 边界
36
+ const slotSamples = [];
37
+ for (let s = 0; s < RT_STITCH_SLOT_COUNT; s++) {
38
+ const cond = s === RT_STITCH_SLOT_COUNT - 1
39
+ ? "else"
40
+ : `${s === 0 ? "if" : "else if"} (tileIdx == ${s})`;
41
+ slotSamples.push(` ${cond} lin${k} = texture2D(panoramas[${base + s}], vec2(panoU, panoV)).rgb;`);
42
+ }
34
43
  slotFacePick += `
35
44
  if (panoIndices[${k}] >= 0.0 && weights[${k}] > 0.0 && abs(panoIndices[${k}] - vPanoIndex) < 0.5) faceCol = lin${k};`;
36
45
  slotCalc += `
37
- // ── 点槽 ${k}(纹理 ${base}..${base + 1})──
46
+ // ── 点槽 ${k}(纹理 ${base}..${base + RT_STITCH_SLOT_COUNT - 1})──
38
47
  vec3 lin${k} = vec3(0.0);
39
48
  if (weights[${k}] > 0.0) {
40
49
  mat4 tm = textureMatrices[${k}];
@@ -47,21 +56,19 @@ function buildFloorMappingFragment(maxPano) {
47
56
  float lat = asin(clamp(dir.y, -1.0, 1.0));
48
57
  // 等距柱状线性 v(= 全图 UV),与 blend / 单全景分支**逐字同口径**
49
58
  float v = 0.5 + lat * 0.3183098861837907; // 1/PI
50
- // 左右两半 -> 拼合槽下标 [左半幅=0, 右半幅=1](每槽 = 8×4 网格里 4×4=16 片拼成,见 RT_STITCH_SLOT_TILES)
51
- // ⭐ 10-06 晚旨:竖着不再分块 ⇒ 槽只随 u 走(与 blend / 单全景分支**逐字同口径**)
52
- int tileIdx = (u >= 0.5) ? 1 : 0;
53
- float localU = (u < 0.5) ? (u * 2.0) : (u * 2.0 - 1.0);
54
- // ⛔ 纵向不再减半(槽就是全高)⇒ localV 恒等于 v;写成 2v/2v-1 = 纵向分辨率白丢一半
59
+ // 三等分经度 -> 拼合槽下标 [左列=0, 中列=1, 右列=2](每槽 = 3×3 网格里一整列、竖 3 片拼成,见 RT_STITCH_SLOT_TILES)
60
+ // ⭐ 10-06 晚旨「竖着不再分块」继续成立、10-23 旨把横向从 2 等分改成 3 等分 ⇒ 槽只随 u 走(与 blend / 单全景分支**逐字同口径**)
61
+ int tileIdx = int(clamp(floor(u * ${RT_STITCH_SLOT_COUNT}.0), 0.0, ${RT_STITCH_SLOT_COUNT - 1}.0));
62
+ float localU = u * ${RT_STITCH_SLOT_COUNT}.0 - float(tileIdx);
63
+ // ⛔ 纵向不再减半(槽就是全高)⇒ localV 恒等于 v;写成 nv/nv-1 = 纵向分辨率白丢一档
55
64
  float localV = v;
56
- // 🧩 该点这 2 槽装的是整图(whole 形态 / 没切过片)⇒ 还原成全图 UV 再采样(与 blend 逐字同口径)
65
+ // 🧩 该点这 3 槽装的是整图(whole 形态 / 没切过片)⇒ 还原成全图 UV 再采样(与 blend 逐字同口径)
57
66
  float panoU = (uPanoTiled[${k}] > 0.5) ? localU : u;
58
67
  float panoV = (uPanoTiled[${k}] > 0.5) ? localV : v;
59
- // ⚠️ ANGLE(D3D) 禁止动态索引 sampler 数组:以下 2 个采样下标均为「base + 字面量」常量表达式,编译安全
60
68
  // ⭐ 采样即线性(与单全景 / blend / selected 三路同一条口径,10-07 旨「色彩空间统一使用 liner」):纹理都标了
61
69
  // colorSpace = SRGBColorSpace ⇒ 硬件上传成 SRGB8_ALPHA8、texture2D 已解码一次;
62
70
  // 这里**不再手动 pow 解码**(那遍是二次解码 = 整体压暗),末端只编码一次 ⇒ 与源图逐字节还原。
63
- if (tileIdx == 0) lin${k} = texture2D(panoramas[${base}], vec2(panoU, panoV)).rgb;
64
- else lin${k} = texture2D(panoramas[${base + 1}], vec2(panoU, panoV)).rgb;
71
+ ${slotSamples.join("\n")}
65
72
  }`;
66
73
  }
67
74
  return /* glsl */ `
@@ -105,13 +112,13 @@ ${slotFacePick}
105
112
  * 创建「全景序号直取」着色器材质(皇上旨 2026-09-29「三模式三 shader」之二):
106
113
  * 编辑器「映射设置模式」的墙材质 + **基类 MktMesh(发布/预览)的固定墙材质**
107
114
  * (皇上旨:mktmesh 就只有按照 attribute 配置来显示全景这一种固定的 shader)。
108
- * uniform 形状与 blend 工厂同档(panoramas[16]/textureMatrices[8]/weights[8]/uPanoTiled[8])⇒
115
+ * uniform 形状与 blend 工厂同档(panoramas[15]/textureMatrices[5]/weights[5]/uPanoTiled[5])⇒
109
116
  * 喂料复用 feedBlendProjection 的槽位指派路(feedFaceMapping),零新喂料路。
110
117
  * uFloorCoreRadius/uFloorFadeRange/uFloorOpacity 三根本 shader 不读,但喂料统一入口会写 ⇒ JS 侧留着(静默忽略)。
111
118
  */
112
119
  export function createMktFloorMappingShaderMaterial(options = {}) {
113
120
  const maxPano = Math.min(options.maxPano ?? MKT_PANO_MAX_SLOTS, MKT_PANO_MAX_SLOTS);
114
- const tileCount = maxPano * RT_STITCH_SLOT_COUNT; // 8 点 × 左/右 2 槽 = 16 = sampler 硬顶
121
+ const tileCount = maxPano * RT_STITCH_SLOT_COUNT; // 5 点 × 左/中/右 3 槽 = 15(sampler 硬顶 16)
115
122
  const panoramas = [];
116
123
  const matrices = [];
117
124
  for (let i = 0; i < tileCount; i++)
@@ -9,34 +9,45 @@ import { BackSide, Matrix4, ShaderMaterial, Vector3 } from "three";
9
9
  import { createPlaceholderTexture, floorVertexShader, RT_STITCH_SLOT_COUNT } from "./mktShader";
10
10
 
11
11
  /**
12
- * ⭐ 融合材质的全景点数**上限**(皇上旨 10-06「一次能够展示 8 个全景纹理」):每点占 **2** 张拼合槽纹理
13
- * (32 片 8×4 → 左右 2 槽,每槽 4×4 格)⇒ **8 × 2 = 16 个 sampler**,再多样本编译不过。
14
- * (sampler 总数 16 是硬件硬顶,本机 `MAX_TEXTURE_IMAGE_UNITS` 实测 16。)
12
+ * ⭐ 融合材质的全景点数**上限**:每点占 **3** 张拼合槽纹理(皇上旨 2026-10-23「全景的 RT 由原来的 2 个分成 3 个、
13
+ * 每个 4096×6144、竖直排列 3 个瓦片」⇒ 原图切 3×3=9 片,按**列**分 3 槽)⇒ 点数 × 3 必须 ≤ sampler 硬顶。
14
+ * 本机 `MAX_TEXTURE_IMAGE_UNITS` 实测 **16**:旧版 8 点 × 2 槽 = 16 正好用满,现每点 3 槽 ⇒ 只能取 ⌊16/3⌋ = **5**
15
+ * (5 × 3 = 15)。皇上旨 2026-10-23 在「A」里拍板:保 3 槽不降画质,**同屏全景点数 8 → 5**。
15
16
  *
16
17
  * ⭐⭐ 2026-10-09 皇上旨「MktMesh 只有映射一种,blend/selected 归编辑器」⇒ 该常量从 `mktShader.ts`
17
18
  * **搬到这里**(映射材质的所在地)。它同时是库与编辑器那份 blend 的**共同上限**,两边必须同值。
18
19
  */
19
- export const MKT_PANO_MAX_SLOTS = 8;
20
+ export const MKT_PANO_MAX_SLOTS = 5;
20
21
 
21
22
  /**
22
23
  * 序号直取片元(皇上旨 2026-09-29「三模式三 shader」之二):
23
24
  * 按三角面 aPanoIndex 在 panoIndices 槽里找对应全景纹理 —— **不看距离、不看夹角**(blend 那套打分/归约整段没有)。
24
- * 槽内采样段(等距柱状投射 + 左右 2 张拼合槽 + 采样即线性、不再手动 pow 解码)与 blend **逐字同口径**;改采样口径两处同改。
25
+ * 槽内采样段(等距柱状投射 + 左/中/右 3 张拼合槽 + 采样即线性、不再手动 pow 解码)与 blend **逐字同口径**;改采样口径两处同改。
25
26
  * 未设序号 / 序号不在槽里 / 该槽瓦片没绑好(weights≤0)→ uFaceMissingColor 深灰兜底
26
27
  * (皇上旨 09-28「装不下就深灰」;weights 闸是 09-29 拆分时补的 —— 原运行时分支没这道闸,
27
28
  * 槽没绑好但序号命中会采到 vec3(0) 纯黑,深灰才是钦定兜底色)。
28
29
  */
29
30
  function buildFloorMappingFragment(maxPano: number): string {
30
- // 每个全景点占 **2** 张拼合槽纹理(32 片每 16 片拼一张:左半幅 / 右半幅)⇒ 8 × 2 = 16 = sampler 硬顶
31
+ // 每个全景点占 **3** 张拼合槽纹理(9 片 3×3 按列分:左列 / 中列 / 右列,每槽竖叠 3 片)⇒ 5 × 3 = 15 ≤ sampler 硬顶 16
31
32
  const tileCount = maxPano * RT_STITCH_SLOT_COUNT;
32
33
  let slotCalc = ""; // 每槽各自采样出 linK(weights[k]>0 才采;没绑好保持 0,由 facePick 的 weights 闸兜成深灰)
33
34
  let slotFacePick = ""; // 逐槽展开「panoIndices[k]==vPanoIndex 则取 lin{k}」(ANGLE 禁动态索引 ⇒ JS 常量展开)
34
35
  for (let k = 0; k < maxPano; k++) {
35
- const base = k * RT_STITCH_SLOT_COUNT; // 本点占 2 个连续纹理槽:base=k*2 .. base+1(顺序 [左半幅, 右半幅])
36
+ const base = k * RT_STITCH_SLOT_COUNT; // 本点占 3 个连续纹理槽:base=k*3 .. base+2(顺序 [左列, 中列, 右列])
37
+ // ⚠️ ANGLE(D3D) 禁止动态索引 sampler 数组 ⇒ 展开成「base + 字面量」常量下标 if 链;末条裸 else 兜住 u==1 边界
38
+ const slotSamples: string[] = [];
39
+ for (let s = 0; s < RT_STITCH_SLOT_COUNT; s++) {
40
+ const cond = s === RT_STITCH_SLOT_COUNT - 1
41
+ ? "else"
42
+ : `${s === 0 ? "if" : "else if"} (tileIdx == ${s})`;
43
+ slotSamples.push(
44
+ ` ${cond} lin${k} = texture2D(panoramas[${base + s}], vec2(panoU, panoV)).rgb;`,
45
+ );
46
+ }
36
47
  slotFacePick += `
37
48
  if (panoIndices[${k}] >= 0.0 && weights[${k}] > 0.0 && abs(panoIndices[${k}] - vPanoIndex) < 0.5) faceCol = lin${k};`;
38
49
  slotCalc += `
39
- // ── 点槽 ${k}(纹理 ${base}..${base + 1})──
50
+ // ── 点槽 ${k}(纹理 ${base}..${base + RT_STITCH_SLOT_COUNT - 1})──
40
51
  vec3 lin${k} = vec3(0.0);
41
52
  if (weights[${k}] > 0.0) {
42
53
  mat4 tm = textureMatrices[${k}];
@@ -49,21 +60,19 @@ function buildFloorMappingFragment(maxPano: number): string {
49
60
  float lat = asin(clamp(dir.y, -1.0, 1.0));
50
61
  // 等距柱状线性 v(= 全图 UV),与 blend / 单全景分支**逐字同口径**
51
62
  float v = 0.5 + lat * 0.3183098861837907; // 1/PI
52
- // 左右两半 -> 拼合槽下标 [左半幅=0, 右半幅=1](每槽 = 8×4 网格里 4×4=16 片拼成,见 RT_STITCH_SLOT_TILES)
53
- // ⭐ 10-06 晚旨:竖着不再分块 ⇒ 槽只随 u 走(与 blend / 单全景分支**逐字同口径**)
54
- int tileIdx = (u >= 0.5) ? 1 : 0;
55
- float localU = (u < 0.5) ? (u * 2.0) : (u * 2.0 - 1.0);
56
- // ⛔ 纵向不再减半(槽就是全高)⇒ localV 恒等于 v;写成 2v/2v-1 = 纵向分辨率白丢一半
63
+ // 三等分经度 -> 拼合槽下标 [左列=0, 中列=1, 右列=2](每槽 = 3×3 网格里一整列、竖 3 片拼成,见 RT_STITCH_SLOT_TILES)
64
+ // ⭐ 10-06 晚旨「竖着不再分块」继续成立、10-23 旨把横向从 2 等分改成 3 等分 ⇒ 槽只随 u 走(与 blend / 单全景分支**逐字同口径**)
65
+ int tileIdx = int(clamp(floor(u * ${RT_STITCH_SLOT_COUNT}.0), 0.0, ${RT_STITCH_SLOT_COUNT - 1}.0));
66
+ float localU = u * ${RT_STITCH_SLOT_COUNT}.0 - float(tileIdx);
67
+ // ⛔ 纵向不再减半(槽就是全高)⇒ localV 恒等于 v;写成 nv/nv-1 = 纵向分辨率白丢一档
57
68
  float localV = v;
58
- // 🧩 该点这 2 槽装的是整图(whole 形态 / 没切过片)⇒ 还原成全图 UV 再采样(与 blend 逐字同口径)
69
+ // 🧩 该点这 3 槽装的是整图(whole 形态 / 没切过片)⇒ 还原成全图 UV 再采样(与 blend 逐字同口径)
59
70
  float panoU = (uPanoTiled[${k}] > 0.5) ? localU : u;
60
71
  float panoV = (uPanoTiled[${k}] > 0.5) ? localV : v;
61
- // ⚠️ ANGLE(D3D) 禁止动态索引 sampler 数组:以下 2 个采样下标均为「base + 字面量」常量表达式,编译安全
62
72
  // ⭐ 采样即线性(与单全景 / blend / selected 三路同一条口径,10-07 旨「色彩空间统一使用 liner」):纹理都标了
63
73
  // colorSpace = SRGBColorSpace ⇒ 硬件上传成 SRGB8_ALPHA8、texture2D 已解码一次;
64
74
  // 这里**不再手动 pow 解码**(那遍是二次解码 = 整体压暗),末端只编码一次 ⇒ 与源图逐字节还原。
65
- if (tileIdx == 0) lin${k} = texture2D(panoramas[${base}], vec2(panoU, panoV)).rgb;
66
- else lin${k} = texture2D(panoramas[${base + 1}], vec2(panoU, panoV)).rgb;
75
+ ${slotSamples.join("\n")}
67
76
  }`;
68
77
  }
69
78
  return /* glsl */ `
@@ -108,13 +117,13 @@ ${slotFacePick}
108
117
  * 创建「全景序号直取」着色器材质(皇上旨 2026-09-29「三模式三 shader」之二):
109
118
  * 编辑器「映射设置模式」的墙材质 + **基类 MktMesh(发布/预览)的固定墙材质**
110
119
  * (皇上旨:mktmesh 就只有按照 attribute 配置来显示全景这一种固定的 shader)。
111
- * uniform 形状与 blend 工厂同档(panoramas[16]/textureMatrices[8]/weights[8]/uPanoTiled[8])⇒
120
+ * uniform 形状与 blend 工厂同档(panoramas[15]/textureMatrices[5]/weights[5]/uPanoTiled[5])⇒
112
121
  * 喂料复用 feedBlendProjection 的槽位指派路(feedFaceMapping),零新喂料路。
113
122
  * uFloorCoreRadius/uFloorFadeRange/uFloorOpacity 三根本 shader 不读,但喂料统一入口会写 ⇒ JS 侧留着(静默忽略)。
114
123
  */
115
124
  export function createMktFloorMappingShaderMaterial(options: { maxPano?: number } = {}): ShaderMaterial {
116
125
  const maxPano = Math.min(options.maxPano ?? MKT_PANO_MAX_SLOTS, MKT_PANO_MAX_SLOTS);
117
- const tileCount = maxPano * RT_STITCH_SLOT_COUNT; // 8 点 × 左/右 2 槽 = 16 = sampler 硬顶
126
+ const tileCount = maxPano * RT_STITCH_SLOT_COUNT; // 5 点 × 左/中/右 3 槽 = 15(sampler 硬顶 16)
118
127
  const panoramas: import("three").Texture[] = [];
119
128
  const matrices: Matrix4[] = [];
120
129
  for (let i = 0; i < tileCount; i++) panoramas.push(createPlaceholderTexture());