一.问题背景
在开发如SLG、开放世界游戏等的小地图或处理超大UI背景时,我们通常会将一张巨大的贴图切分成许多小图块进行动态加载拼接,以优化内存。但当我们在 Unity 中无缝拼接这些切片时,往往会遇到一个缝隙问题,表现如下:

二.问题分析
究其原因,无非是在贴图边缘进行采样时出了问题。根据双线性采样原理,我们在获取每一个uv处的颜色值,需要用其四周像素中心的颜色值进行内插而得,如下:

在我们对整张贴图进行切分时通常是按固定尺寸进行均匀均分的,因而在每个图块边缘采样时,缺乏邻近图块像素信息,进而导致采样内插出错。因此解决这类接缝问题,我们只需要在切分图块时根据其周边邻接情况,多切分一像素,在采样时矫正一下uv即可,如下图:

三.解决方案
针对上述问题分析,本文给出了一个自动贴图切分及uv矫正方案,有效解决了此类接边问题。
1.核心原理:邻接掩码 (Neighbor Mask)
我们不需要无脑地给四周都加Padding。只有当某个方向存在相邻图块时,才需要拷贝那个方向的像素作为“过渡保护”,我们使用4位二进制掩码来表示邻接情况:
- 1 (0001):左侧有邻居
- 2 (0010):右侧有邻居
- 4 (0100):上方有邻居
- 8 (1000):下方有邻居

切分规则:
如果掩码包含“左”,切分起点
X
X
X 减 1,宽度
W
W
W 加 1。其他方向同理。这意味着,中间的图块会被切成
(
W
+
2
)
×
(
H
+
2
)
(W+2) \times (H+2)
(W+2)×(H+2),而边缘图块则是
(
W
+
1
)
×
(
H
+
1
)
(W+1) \times (H+1)
(W+1)×(H+1) 或原始尺寸。核心代码如下:
// 核心切分逻辑片段
var mask = 0;
// 判断邻接关系
if (c > 0) mask |= 1; // Left
if (c < cols - 1) mask |= 2; // Right
if (r < rows - 1) mask |= 4; // Up
if (r > 0) mask |= 8; // Down
// 基础矩形
var readX = c * cellWidth;
var readY = r * cellHeight;
var readW = cellWidth;
var readH = cellHeight;
// 根据邻接智能扩充 (Padding)
if ((mask & 1) != 0) { readX -= 1; readW += 1; } // 左扩
if ((mask & 2) != 0) { readW += 1; } // 右扩
if ((mask & 8) != 0) { readY -= 1; readH += 1; } // 下扩
if ((mask & 4) != 0) { readH += 1; } // 上扩
2.UV矫正原理
切出来的图片变大了,如果我们直接贴在原本大小的 Quad 上,画面会被压缩。我们需要在 Shader 中把多切出来的这 1 像素“推”到 UV 的 0~1 范围之外,仅作为采样缓冲区。
设原始设计尺寸为
W
b
a
s
e
,
H
b
a
s
e
W_{base}, H_{base}
Wbase,Hbase,物理贴图尺寸为
W
r
e
a
l
,
H
r
e
a
l
W_{real}, H_{real}
Wreal,Hreal。输入 UV 为
u
v
i
n
uv_{in}
uvin (0~1)。
我们需要计算像素坐标,然后重新归一化:
P
i
x
e
l
X
=
P
a
d
d
i
n
g
L
e
f
t
+
u
v
i
n
.
x
×
W
b
a
s
e
Pixel_X = Padding_{Left} + uv_{in}.x \times W_{base}
PixelX=PaddingLeft+uvin.x×Wbase
u
v
o
u
t
.
x
=
P
i
x
e
l
X
W
r
e
a
l
uv_{out}.x = \frac{Pixel_X}{W_{real}}
uvout.x=WrealPixelX
通常将UV矫正放在顶点着色其中,性能消耗几乎为零,UV矫正代码如下:
int _NeighborMask;
inline float2 FixUV(float2 _uv)
{
// 解析掩码 (1=左, 2=右, 4=上, 8=下)
float padLeft = (_NeighborMask & 1) ? 1.0 : 0.0;
//float padRight = (_NeighborMask & 2) ? 1.0 : 0.0;
//float padUp = (_NeighborMask & 4) ? 1.0 : 0.0;
float padDown = (_NeighborMask & 8) ? 1.0 : 0.0;
float actualWidth = _MainTex_TexelSize.z;
float actualHeight = _MainTex_TexelSize.w;
float pixelX = padLeft + (_uv.x * _BaseSize.x);
float pixelY = padDown + (_uv.y * _BaseSize.y);
float2 uv = float2(pixelX / actualWidth, pixelY / actualHeight);
return uv;
}
3.接边问题修复效果
通过引入邻接像素缓冲区,双线性插值在边缘处可以获取到正确的像素颜色,接缝彻底消失,拼接效果如同原图一般丝滑。修复前后对比效果,如下:

注:由于切分后的贴图尺寸不是2的幂次方,因此需要修改对应贴图的导入"Non-Power of 2"设置为None。
示例工程如下:shader-art
1333



被折叠的 条评论
为什么被折叠?



