经纬志
首页 / 实战手册 / three.js 渲染

两个面贴在一起就闪,越远越明显

先看结论

地面模型上叠了一层道路面,近处还好,相机拉远之后整片道路像被噪声覆盖,来回闪烁。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

两个网格在空间上几乎共面,渲染结果随机交替,形成条纹或斑点。 相机距离越远、near/far 跨度越大,条纹越粗。 关闭其中一个网格,闪烁立刻消失。

原因

深度缓冲的精度是有限的。默认透视投影下,深度值与到相机的距离不是线性关系, 远处两个相距几厘米的面,换算到深度缓冲里可能落在同一个整数值上。 显卡无法判断谁在前,就按绘制顺序覆盖,于是出现闪烁。

相机的 far 设得过大(例如 1e7)会进一步压缩有效精度。 场景单位是米时,把 far 设成 10 公里量级就足够。

排查过程

  1. 临时隐藏上层网格,确认闪烁由共面引起,而不是法线或光照问题。
  2. 打印两个网格的世界坐标包围盒,计算它们的最小间距。
  3. 检查相机 near 与 far 的比值,超过 1:100000 就会明显。
var b1 = new THREE.Box3().setFromObject(ground);
var b2 = new THREE.Box3().setFromObject(road);
console.log('上层最小 y', b2.min.y, '下层最大 y', b1.max.y);
// 上层最小 y 0.0004  下层最大 y 0.0000  间距 0.4 毫米

解决方案

方案一:给上层材质加多边形偏移,让它在深度测试中稳定地“靠前”一点。 这是处理贴地面片的标准做法,不改变几何:

var roadMat = new THREE.MeshStandardMaterial({
  map: roadTexture,
  polygonOffset: true,
  polygonOffsetFactor: -1,
  polygonOffsetUnits: -1
});

方案二:把两层真实拉开一点距离。按场景尺度给 1–2 厘米即可, 在米制场景里对视觉没有影响:

road.position.y = ground.position.y + 0.02;

方案三:场景纵深很大(从厘米到几十公里)时改用对数深度缓冲。 它把深度精度按距离重新分配,代价是需要额外的片元计算:

var renderer = new THREE.WebGLRenderer({
  antialias: true,
  logarithmicDepthBuffer: true
});
camera.near = 0.1;
camera.far = 20000;      // 20 公里,够用即可

验证

把相机从 5 米拉到 5 公里,道路面应保持稳定,不再出现条纹。 旋转视角时,上下两层的遮挡关系始终一致,不会随机切换。 用 renderer.info.render.triangles 确认没有因为加面片导致数量暴涨。

优先级:先拉间距,再加 polygonOffset,最后才考虑对数深度缓冲。 前两个几乎没有性能代价,第三个会影响所有材质的片元开销。

引用本文

经纬志(2026)。《两个面贴在一起就闪,越远越明显》。https://www.jingweizhi.com/cookbook/three-z-fighting.html(访问日期 2026-11-03)。