OpenGL怪异线线性过滤
OpenGL weird line linear filtering
我编写了以下着色器来测试线性过滤在 OpenGL 中的工作原理。
这里我们有一个 5x1 的纹理散布到立方体的面上(megenta 区域只是背景的颜色)。
纹理是这个(很小)。
左下角对应uv=(0, 0)
,右上对应uv=(1, 1)
。
启用线性过滤。
着色器将 v
坐标垂直分成 5 行(从上到下):
- 连续采样。正常采样即可。
- 如果你在 [0, 1] 中则为绿色,否则为红色。仅供测试。
- 灰度中的u坐标。
- 在纹素左侧采样。
- 在纹素中心采样。
问题是在3
和4
之间有一排像素在闪烁。闪烁随着相机距离的改变而变化,有时甚至可以让它消失。问题似乎出在处理第四行的着色器代码中。
// 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;
}
我编写了以下着色器来测试线性过滤在 OpenGL 中的工作原理。
这里我们有一个 5x1 的纹理散布到立方体的面上(megenta 区域只是背景的颜色)。
纹理是这个(很小)。
左下角对应uv=(0, 0)
,右上对应uv=(1, 1)
。
启用线性过滤。
着色器将 v
坐标垂直分成 5 行(从上到下):
- 连续采样。正常采样即可。
- 如果你在 [0, 1] 中则为绿色,否则为红色。仅供测试。
- 灰度中的u坐标。
- 在纹素左侧采样。
- 在纹素中心采样。
问题是在3
和4
之间有一排像素在闪烁。闪烁随着相机距离的改变而变化,有时甚至可以让它消失。问题似乎出在处理第四行的着色器代码中。
// 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;
}