GLSL NVidia 正方形伪影问题

6

当GLSL着色器在以下GPU上生成不正确的图像时,我遇到了一个问题:
GT 430
GT 770
GTX 570
GTX 760

但是在以下GPU上正常工作:
Intel HD Graphics 2500
Intel HD 4000
Intel 4400
GTX 740M
Radeon HD 6310M
Radeon HD 8850

着色器代码如下:

bool PointProjectionInsideTriangle(vec3 p1, vec3 p2, vec3 p3, vec3 point)
{
  vec3 n = cross((p2 - p1), (p3 - p1));

  vec3 n1 = cross((p2 - p1), n);
  vec3 n2 = cross((p3 - p2), n);
  vec3 n3 = cross((p1 - p3), n);

  float proj1 = dot((point - p2), n1);
  float proj2 = dot((point - p3), n2);
  float proj3 = dot((point - p1), n3);

  if(proj1 > 0.0)
    return false;
  if(proj2 > 0.0)
    return false;
  if(proj3 > 0.0)
    return false;
  return true;
}

struct Intersection
{
    vec3 point;
    vec3 norm;
    bool valid;
};

Intersection GetRayTriangleIntersection(vec3 rayPoint, vec3 rayDir, vec3 p1, vec3 p2, vec3 p3)
{
    vec3 norm = normalize(cross(p1 - p2, p1 - p3));

    Intersection res;
    res.norm = norm;
    res.point = vec3(rayPoint.xy, 0.0);
    res.valid = PointProjectionInsideTriangle(p1, p2, p3, res.point);
    return res;
}

struct ColoredIntersection
{
    Intersection geomInt;
    vec4 color;
};

#define raysCount 15
void main(void)
{
    vec2 radius = (gl_FragCoord.xy / vec2(800.0, 600.0)) - vec2(0.5, 0.5);

    ColoredIntersection ints[raysCount];

    vec3 randomPoints[raysCount];
    int i, j;


    for(int i = 0; i < raysCount; i++)
    {
        float theta = 0.5 * float(i);
        float phi = 3.1415 / 2.0;
        float r = 1.0;
        randomPoints[i] = vec3(r * sin(phi) * cos(theta),  r * sin(phi)*sin(theta), r * cos(phi));

        vec3 tangent = normalize(cross(vec3(0.0, 0.0, 1.0), randomPoints[i]));
        vec3 trianglePoint1 = randomPoints[i] * 2.0 + tangent * 0.2;
        vec3 trianglePoint2 = randomPoints[i] * 2.0 - tangent * 0.2;

        ints[i].geomInt = GetRayTriangleIntersection(vec3(radius, -10.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, 0.0), trianglePoint1, trianglePoint2);
        if(ints[i].geomInt.valid)
        {
            float c = length(ints[i].geomInt.point);
            ints[i].color = vec4(c, c, c, 1.0);
        }
    }

    for(i = 0; i < raysCount; i++)
    {
        for(j = i + 1; j < raysCount; j++)
        {
            if(ints[i].geomInt.point.z < ints[i].geomInt.point.z - 10.0)
            {
                gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                ColoredIntersection tmp = ints[j];
                ints[j] = ints[i];
                ints[i] = tmp;
            }
        }
    }

    vec4 resultColor = vec4(0.0, 0.0, 0.0, 0.0);
    for(i = 0; i < raysCount + 0; i++)
    {
        if(ints[i].geomInt.valid)
            resultColor += ints[i].color;
    }

    gl_FragColor = clamp(resultColor, 0.0, 1.0);
}

更新:我已经用内置函数替换了向量归一化,并添加了gl_FragColor的约束,以防万一。

这段代码是一个实际着色器的简化版本,期望得到的图像是:
http://img.owely.com/screens/131316/original_owely20140426-19006-1w4w4ye.?1398554177
但我得到的是:
http://img.owely.com/screens/131315/original_owely20140426-18968-a7fuxu.?1398553652

随机旋转代码可以完全消除伪影。例如,如果我更改以下行:

if(ints[i].geomInt.valid) //1

to

if(ints[i].geomInt.valid == true) //1

这似乎不会以任何方式影响逻辑或完全删除不起作用的双重循环(标记为2)造成的伪像消失。请注意,双重循环根本没有起到任何作用,因为条件不成立。

if(ints[i].geomInt.point.z < ints[i].geomInt.point.z - 10.0)
{
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  return;
  ColoredIntersection tmp = ints[j];
  ints[j] = ints[i];
  ints[i] = tmp;
}

无论如何都无法满足(左右两侧都有索引i而非i、j),且不存在NaN。这段代码实际上什么也没有做,但不知何故却生成了伪影。
您可以使用此项目(包含完整的MSVS 2010项目、源代码、编译后的二进制文件和着色器,并使用包含的SFML)测试着色器和演示:https://dl.dropboxusercontent.com/u/25635148/ShaderTest.zip 我在这个测试项目中使用了SFML,但这完全不重要,因为我遇到这个问题的实际项目没有使用这个库。
我想知道这些伪像为什么出现,以及如何可靠地避免它们。

有趣的现象。在出现问题的设置中,它是否完全可重复?比如你连续运行10次,总是得到相同的现象吗?输出总是完全一样的错误结果,还是有些随机性呢? - Reto Koradi
在大多数现代NVidia GPU上,它是100%可重复的。如果我在Intel内置图形上运行着色器,则没有伪影,如果在gf640m上运行,则会出现伪影。方形伪影本身每帧都会改变,没有任何明显的模式。 - Suslik
您可能还想使用内置函数来进行诸如规范化之类的操作,以避免出现难以跟踪的FP问题。 - Andon M. Coleman
不一定,优化可能会对浮点运算产生奇怪的影响。GPU 不完全遵循 IEEE 754 标准,旧型号的情况更是如此。当涉及到这种类型的问题时,渲染比在 CPU 上运行的通用应用程序更加棘手。使用快速数学与精确 FP 编译通用软件通常不会显示任何差异,但使用快速与精确 FP 编译着色器可能会有天壤之别。而且,您越是自己实现某些基本操作而不是使用内置函数,就越有可能遇到这种情况。 - Andon M. Coleman
我已经使用内置函数替换了所有的规范化,并为gl_FragColor添加了一个保护措施-夹紧。对伪影没有影响。上传了更新的演示并更改了原帖中的源代码。 - Suslik
显示剩余5条评论
3个回答

2

我认为你的着色器没有问题。OpenGL渲染管道渲染到帧缓冲区。如果在呈现完成之前使用该帧缓冲区,则通常会出现您所见到的情况。请记住,glDrawArrays和类似函数是异步的(函数在GPU完成绘制顶点之前返回)。

这些方形图像最常见的用途是将结果帧缓冲区用作纹理,然后用于进一步渲染。

OpenGL驱动程序应该跟踪依赖关系,并且应该知道如何等待依赖项被满足。

但是,如果您在线程之间共享帧缓冲区,则所有赌注都关闭了,那么您可能需要使用诸如fence sync(glFenceSync)之类的东西,以确保一个线程等待另一个线程正在进行的渲染。

作为解决方法,您可能会发现调用glFinish甚至glReadPixels(仅使用一个像素)可以解决问题。

还请记住,此问题与时间有关,简化着色器可能会使问题消失。


不,同步不是原因。至少我无法通过任何同步选项(glFlush()/glFenceSync()/wglSwapInterval(),Sleep())来消除伪影。而且我直接渲染到窗口,所以渲染到纹理的同步问题也不相关。如果你不相信我,如果你有Windows和Nvidia GPU,你可以很容易地自行测试:所有源代码和库都包含在附件中。 - Suslik
那么,这很可能是NVidia OpenGL驱动程序中的一个错误。 - doron

1
如果还有人感兴趣,我在许多专业网站上提出了这个问题,包括opengl.org和devtalk.nvidia.com。我没有得到任何关于我的着色器有什么问题的具体答案,只是一些如何解决我的问题的建议。比如使用if(condition == true)而不是if(condition),尽可能使用简单的算法等等。最后,我选择了我的代码中最简单的旋转方式,摆脱了问题:我只是替换了


struct Intersection  
{  
    vec3 point;  
    vec3 norm;  
    bool valid;  
};  

with

struct Intersection  
{  
    bool valid;  
    vec3 point;  
    vec3 norm;  
};  

还有许多其他的代码旋转可以使文物消失,但我选择了这个,因为我能够在以前遇到问题的大多数其他系统上进行测试。


说实话,如果考虑到GPU的数据对齐偏好,我会尝试将bool valid夹在两个vec3之间。vec3喜欢从16字节边界开始,并且基本上与vec4相同。你可以有效地将一个4字节的变量(是的,在GLSL中,bool是32位的)塞在两个vec3之间,而不会破坏对齐或浪费空间。 - Andon M. Coleman

0
我曾经在GLSL中看到过这种情况发生,当变量没有初始化时。例如,在某些图形卡上,vec3默认为(0,0,0),但在其他图形卡上,它将是不同的值。你确定你没有在未先给变量赋值的情况下使用它吗?具体来说,如果Insersection.valid为false,则您没有初始化ColoredIntersection.color,但我认为您稍后会使用它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接