处理扫描模型或手绘贴图时,纹理接缝几乎无法完全避免。传统方式是在图像编辑软件里手工修复,但面对复杂UV、多象限贴图或程序化纹理时,效率很低。投影绘制和纹理缝合是两条更可控的技术路线。前者从投射角度直接覆盖接缝两侧,后者用图像算法把接缝处像素过渡成一致。把它们组合起来,能显著减少可见裂缝和颜色跳变。

纹理接缝的成因与投影绘制思路
纹理接缝的根源通常不在贴图本身,而在采样阶段。模型展开UV后,相邻面片可能落在不同UV壳中,光栅化时GPU会沿着三角形边缘读取纹素。一旦过滤核跨过UV边界,就可能读到另一个壳体的颜色,尤其开启mipmap后,低级别纹理中不同壳体的边缘相互渗透,形成裂纹。
投影绘制(projection painting)的思路是:把屏幕空间或相机视角的图片直接投射到三维模型表面。射线从视点穿过像素,与模型碰撞得到表面点,再根据该点的UV坐标写入纹理。这个过程不依赖UV壳的邻接关系,颜色可以连续地覆盖接缝两侧。哪怕两个面在UV布局中离得很远,只要它们在投影视角下相邻,就能一次性绘制过去。
投影绘制最适合处理结构型接缝,例如头部扫描模型侧面与正面的法线跳变、衣物褶皱的跨UV色差。但它也有局限:当投射方向与表面夹角过小时,纹素拉伸会比较明显;单一视角只能覆盖可见区域,需要多角度反复投射。
投影绘制工作流与关键参数设置
在Blender纹理绘制模式或Substance Painter的投影工具中,核心工作流可以拆成三步:创建投影相机、选择源图像、设置衰减与混合模式。投影相机最好贴近最终渲染视角,避免因为透视变形导致绘制到不可见区域。
关键参数之一是Ray Distance和bleed。Ray Distance控制射线检测距离,太短可能在复杂模型上漏掉表面;太长容易把背景也投到模型上。还有一个参数是衰减角度,它决定投影边缘的羽化程度。比如给头部模型画脸部贴图时,衰减角度可以设为65到75度,让正脸到侧脸的过渡自然一些。
下面是一段简化的Python示例,演示如何把屏幕坐标反投影到世界坐标,并查询模型UV来生成纹理,核心是利用视图矩阵和投影矩阵的逆矩阵。
import numpy as np
def unproject_to_world(screen_xy, depth, inv_view_proj):
# screen_xy: Nx2 像素坐标
# depth: N 对应的深度值
# inv_view_proj: 4x4 视图投影逆矩阵
ndc_x = (screen_xy[:, 0] / width) * 2.0 - 1.0
ndc_y = 1.0 - (screen_xy[:, 1] / height) * 2.0
clip = np.stack([ndc_x, ndc_y, depth * 2.0 - 1.0, np.ones_like(depth)], axis=-1)
world = clip @ inv_view_proj.T
world /= world[:, 3:4]
return world[:, :3]
这段代码只是投影绘制中最基础的数学步骤。实际工程里还需要处理遮挡、背面剔除和纹理写入的过滤。写成C++或Python的插件时,通常会先在着色器里输出一张临时深度图,再用同一套逆矩阵做批量反投影。
另外一个容易忽略的点是投影源图像的分辨率。如果源图像分辨率低于目标纹理,投影绘制后也会出现模糊。建议源图像至少是目标UV区域像素密度的1.5倍以上,这样在后续缝合和扩展边缘时才有可操作空间。
纹理缝合算法与边缘像素扩展
缝合解决的是颜色接缝。它的主要动作可以理解为在UV边界两侧做局部修复,让相邻纹素在视觉上连续。最基础的方法是羽化:取接缝两侧一定宽度的像素,按照距离混合。更稳定的方案是多频带混合,它先构建高斯金字塔,把低频颜色差异在较宽范围消除,高频细节保留在原位,避免直接羽化导致模糊。
在图像处理中,泊松图像编辑也经常被用作纹理缝合。它把接缝一侧的梯度场复制到另一侧,然后求解泊松方程,使颜色变化尽量平滑同时不破坏原有纹理细节。OpenCV和scikit-image都有现成函数,但对于贴图缝合,通常要提前把UV壳的边界mask和膨胀区域算出来。
下面这个示例演示了在UV纹理空间中对一条接缝两侧进行高斯羽化融合。假设tex是一张Numpy数组,mask标记需要融合的过渡带。
import cv2
import numpy as np
def feather_seam(tex, seam_mask, radius=12):
# tex: HxWx3 浮点纹理
# seam_mask: HxW 0/1 接缝区域
kernel = cv2.getGaussianKernel(radius * 2 + 1, -1)
weight = kernel @ kernel.T
weight = weight / weight.max()
blurred = cv2.filter2D(tex, -1, weight)
mask_blur = cv2.filter2D(seam_mask.astype(np.float32), -1, weight)
mask_blur = np.clip(mask_blur, 1e-6, None)
# 只在过渡带混合,避免整张图被平滑
result = tex.copy()
blend = mask_blur[..., None]
result = (tex * (1.0 - blend) + blurred * blend).astype(np.uint8)
return result
这个函数只做基础羽化,对细小接缝有效。如果接缝两侧纹理方向差异很大,建议先用光流或块匹配做对齐,再执行泊松融合,否则羽化后可能出现重影。另一个实用技巧是扩展UV边界像素,也叫padding或bleed。将接缝外侧若干像素复制出去,可以防止mipmap低级别时颜色渗出。很多引擎导入贴图时都能自动生成padding,但自动生成的宽度往往不够,接缝在远景依然可见。
投影绘制与缝合的组合落地流程
单独使用投影绘制或单独缝合,都很难完全消除纹理接缝。投影绘制擅长消除大范围颜色断裂,但对高频细节的接缝可能不细腻;缝合擅长过渡细节,却无法修正UV布局本身带来的结构错位。实际项目中,通常会先投影绘制几轮,把基础色和阴影连续覆盖到接缝两侧,再对残余接缝做局部缝合。
一个典型流程是:先在低分辨率代理模型上投影颜色,生成一张基础贴图;然后回到高分辨率模型,在接缝附近用克隆印章或修复画笔做局部缝合;最后导出时打开padding,至少保留8到16像素的边缘扩展。如果目标平台使用mipmap,还要逐级检查mip级别中的接缝表现,必要时单独对低级别做模糊。
还要注意UV岛的texel密度差异。如果两个相邻面在UV中比例不同,投影绘制得再均匀,缝合后仍然会出现一侧清晰一侧模糊。此时可以在展UV阶段锁定相邻区域的密度,或者给不同UV壳设置独立的纹理分区,减少跨壳采样。
纹理接缝问题没有一次性消失的银弹,但把投影绘制和缝合组合起来,能覆盖绝大多数扫描模型、角色面部和硬表面模型的常见情况。关键是先判断接缝类型,再选择工具,而不是盲目提高整张贴图分辨率。