OpenGL怪异线线性过滤

OpenGL weird line linear filtering

我编写了以下着色器来测试线性过滤在 OpenGL 中的工作原理。

这里我们有一个 5x1 的纹理散布到立方体的面上(megenta 区域只是背景的颜色)。

纹理是这个(很小)。

左下角对应uv=(0, 0)右上对应uv=(1, 1)。 启用线性过滤。

着色器将 v 坐标垂直分成 5 行(从上到下):

  1. 连续采样。正常采样即可。
  2. 如果你在 [0, 1] 中则为绿色,否则为红色。仅供测试。
  3. 灰度中的u坐标。
  4. 在纹素左侧采样。
  5. 在纹素中心采样。

问题是在34之间有一排像素在闪烁。闪烁随着相机距离的改变而变化,有时甚至可以让它消失。问题似乎出在处理第四行的着色器代码中。

// sample at the left of the pixel
// the following line can fix the problem if I add any number different from 0
tc.y += 0.000000;  // replace by any number other than 0 and works fine
tc.x = floor(5 * tc.x) * 0.2;
c = texture(tex0, tc);

我觉得这很奇怪,因为在那个区域 v 坐标不靠近纹理的任何边缘。

您的代码在纹理获取期间依赖于未定义的值

第 8.9 节纹理函数(强调我的)中的 GLSL 4.60 specification 状态:

Some texture functions (non-“Lod” and non-“Grad” versions) may require implicit derivatives. Implicit derivatives are undefined within non-uniform control flow and for non-fragment-shader texture fetches.

虽然大多数人认为这些导数只是 mip-mapping 所必需的,但这是不正确的。还需要 LOD 因子来确定纹理是放大还是缩小(以及在非 mipmapped 情况下的各向异性过滤,但这里不感兴趣)。

GPU 通常通过 2x2 像素四边形中相邻像素之间的有限差分来近似导数。 发生的情况是,在您的各种选项之间的边缘,您有不统一的控制流,其中一行您进行纹理过滤,而在上面的一行中,您不这样做。有限差分将导致尝试访问上一行纹理采样操作的纹理坐标,这根本不能保证已经计算,因为着色器调用没有主动执行该代码路径 - 这就是为什么规范将它们视为未定义。

现在取决于你的边缘在 2x2 像素四边形中的位置,你会得到正确的结果,或者你没有。对于您没有得到正确结果的情况,一种可能的结果可能是 GL 使用缩小过滤器,在您的示例中为 GL_NEAREST

将两个过滤器都设置为 GL_LINEAR 可能会有所帮助。但是,这仍然不是正确的代码,因为按照规范,结果仍未定义。

唯一正确的解决方案是将纹理采样移出非均匀控制流,例如

vec4 c1=texture(tex, tc); // sample directly at tc
vec4 c2=texture(tex, some_function_of(tc)); // sample somewhere else
vec4 c3=texture(tex, ...);

// select output color in some non-uniform way
if (foo) {
   c=c1;
} else if (bar) {
   c=c2;
} else {
   c=c3;
}