投影立方体贴图Shader问题:面片间出现白线
立方体贴图转等矩形投影Shader面片间白线问题解决

我正在编写一款将Cube Map/Cube Texture渲染为Equirectangular Projection的Shader,主体功能已实现,但面片间出现了白线问题。
实现步骤:
- 从UV([0,1]x[0,1])开始
- 转换至[-1,1]x[-1,1],再转为[-180,180]x[-90,90]的经纬度坐标
- 将经纬度转换为3D坐标(xyz)
- 获取该坐标所属的立方体面片,以及在面片内的位置([-1,1]x[-1,1])
- 将面片内位置转换为立方体贴图对应的UV
起初认为是步骤4的输出错误导致采样超出纹理范围,但即使将面片坐标乘以1/2,白线问题依然存在。
问题根源
白线的核心诱因是GPU浮点精度误差和纹理采样逻辑的边界处理缺陷:
- 用
abs(xyz.x) == m这类精确相等判断在浮点运算中完全不可靠,精度误差会导致本该归属某一面片的点被错误分配到相邻面片,甚至落入默认分支生成异常UV。 - 直接将面片内坐标映射到[0,1]边界时,纹理过滤会采样到面片边缘的空白区域(或跨面片采样),产生白色缝隙。
- 中途的
clamp操作会强制拉回边缘UV,反而加剧边界处的采样偏差。
修复方案
关键修改点
- 添加浮点容差的面片归属判断:用范围比较替代精确相等,避免精度误差导致的面片分类错误。
- 收缩UV采样范围:将面片内UV从[0,1]收缩到[margin, 1-margin],避开纹理边缘的过滤盲区。
- 优化UV clamp时机:只在最终全局UV计算完成后做必要的clamp,避免中途破坏边界采样逻辑。
修改后的完整代码
float max3(vec3 v) { return max(max(v.x, v.y), v.z); } vec2 sample_cube_map_1(vec3 xyz, out float faceIndex) { xyz /= length(xyz); float m = max3(abs(xyz)); float eps = 1e-6; // 浮点精度容差 if (abs(xyz.x) >= m - eps) { faceIndex = sign(xyz.x); return xyz.yz / abs(xyz.x); } if (abs(xyz.y) >= m - eps) { faceIndex = 2. * sign(xyz.y); return xyz.xz / abs(xyz.y); } if (abs(xyz.z) >= m - eps) { faceIndex = 3. * sign(xyz.z); return xyz.xy / abs(xyz.z); } faceIndex = 1.0; return vec2(0., 0.); } vec2 sample_cube_map(vec3 xyz) { float face; vec2 xy = sample_cube_map_1(xyz, face); // 收缩UV范围,避免采样到面片边缘 float margin = 0.005; xy = (xy + 1.) / 2.; // [-1,1] -> [0,1] xy = clamp(xy, margin, 1. - margin); // 避开纹理边界 if (face == 1.) { // front xy += vec2(1., 1.); } else if (face == -1.) { //back xy.x = 1. - xy.x; xy += vec2(3., 1.); } else if (face == 2.) { // right xy.x = 1. - xy.x; xy += vec2(2., 1.); } else if (face == -2.) { // left xy += vec2(0., 1.); } else if (face == 3.) { // top xy = vec2(xy.y, 1. - xy.x); xy += vec2(1., 2.); } else if (face == -3.) { // bottom xy = xy.yx; xy += vec2(1., 0.); } else { xy += vec2(1., 0.); } vec2 finalUv = xy / vec2(4., 3.); // [0,4]x[0,3] -> [0,1]x[0,1] return clamp(finalUv, 0. + margin/4., 1. - margin/4.); // 全局UV微小收缩 } // projects // uv:([0,1] x [0,1]) // to // xy:([ -2, 2 ] x [ -1, 1 ]) vec2 uv_2_xy(vec2 uv) { return vec2(uv.x * 4. - 2., uv.y * 2. - 1.); } // projects // xy:([ -2, 2 ] x [ -1, 1 ]) // to // longlat: ([ -pi, pi ] x [-pi/2,pi/2]) vec2 xy_2_longlat(vec2 xy) { float pi = 3.1415926535897932384626433832795; return xy * pi / 2.; } vec3 longlat_2_xyz(vec2 longlat) { return vec3(cos(longlat.x) * cos(longlat.y), sin(longlat.x) * cos(longlat.y), sin(longlat.y)); } vec3 uv_2_xyz(vec2 uv) { return longlat_2_xyz(xy_2_longlat(uv_2_xy(uv))); } vec3 roty(vec3 xyz, float alpha) { return vec3(cos(alpha) * xyz.x + sin(alpha) * xyz.z, xyz.y, cos(alpha) * xyz.z - sin(alpha) * xyz.x); } varying vec2 vUv; uniform sampler2D image; uniform float time; void main() { vec3 xyz = uv_2_xyz(vUv); xyz = roty(xyz, time); vec2 uv = sample_cube_map(xyz); vec4 texturePixel = texture2D(image, uv); gl_FragColor = texturePixel; }
额外优化建议
- 将立方体贴图的采样模式设置为Clamp to Edge,避免边缘采样时重复相邻面片内容。
- 若使用压缩纹理,可尝试用未压缩格式测试,排除压缩算法导致的边缘精度损失。
内容的提问来源于stack exchange,提问作者Sam Coutteau
相关产品推荐
相关产品推荐

