【问题标题】:Strange kernel artifacts when writing to fragment out often经常写入碎片时出现奇怪的内核工件
【发布时间】:2017-12-26 04:32:35
【问题描述】:

我正在开发一个游戏引擎并致力于延迟渲染管道。完成(第二遍)(着色)着色器后,我开始在我拥有的各种其他计算机上测试管道。有趣的是,在我的旧笔记本电脑上,每个 4x8 像素组都会出现这种奇怪的伪影(示例如下)。看起来着色器正在执行并最终返回正确的颜色,但是以非常随机的方式。

此问题不是错误报告或解决方案请求。我已经用下面的代码补丁解决了这个问题。此线程旨在更好地了解为什么会发生这种情况,并为可能受同一问题影响的其他任何人提供见解。

更详细地描述效果:
大约 50% 的屏幕具有一组 4x8 的像素,可以对实际生成的颜色进行高度着色。
这些 4x8 组在每帧屏幕上的随机位置,造成“静态”效果。
某些型号有不同的颜色。正如您在下面看到的,反射兔子是蓝色的,但折射球是黄色的。这似乎不是 Gbuffer 问题,但是因为它们都从相同的纹理中采样,我确信这是正确的(因为我可以同时在屏幕上看到它)。
不同对象的 4x8 块显示正确颜色的比率更高。您可以看到折射兔子大部分是正确的,但反射地板和折射球体只是白色和黄色。
4x8 块的色调会根据 GPU 上运行的其他程序而发生巨大变化。

图像应如下所示:

损坏的着色器的伪代码类似于

out vec3 FragColor; // Out pixel of fragment shader

void main() {
    for (int i=0; i<NumberOfPointLights; i++) {
        ... Lighting calculation code...
        FragColor += lighting;
    }

    for (int i=0; i<NumberOfSpotLights; i++) {
        ... Lighting calculation code...
        FragColor += lighting;
    }

    for (int i=0; i<NumberOfDirectionalLights; i++) {
        ... Lighting calculation code...
        FragColor += lighting;
    }
}

为了解决这个问题,我简单地初始化了一个临时变量来保存输出颜色,在光照计算期间写入该变量,然后在最后将其写入片段输出。如下:

out vec3 FragColor; // Out pixel of fragment shader

void main() {
    vec3 outcolor = vec3(0);
    for (int i=0; i<NumberOfPointLights; i++) {
        ... Lighting calculation code...
        outcolor += lighting;
    }

    for (int i=0; i<NumberOfSpotLights; i++) {
        ... Lighting calculation code...
        outcolor += lighting;
    }

    for (int i=0; i<NumberOfDirectionalLights; i++) {
        ... Lighting calculation code...
        outcolor += lighting;
    }

    FragColor = outcolor;
}

我很惊讶这能奏效,因为我认为这种行为是默认假设的。写入片段输出实际上并不是每次都写入 VRAM,只是在最后。我的印象是在着色器执行后会读取片段输出变量,因此它是全局变量。

怀疑和问题

  1. 根据我的研究,我了解到 4x8 像素组是 nVidia GPU(我正在使用)上的一个“工作组”或“核心”的大小,而 AMD 使用 8x8 像素工作组。所以有些东西会导致随机工作组的输出颜色永久受到影响,直到它被重新分配到屏幕上的不同位置。
  2. 颜色的变化取决于 GPU 的其他用途,这告诉我 GPU 具有非常复杂的内存分配方案并且它正在从其他程序的内存中读取(我对此表示怀疑),或者着色器未初始化记忆每一帧。但是每次写入纹理的内存肯定是相同的吗?
  3. 每次写入片段输出变量都会写入 VRAM,每个工作组写入太多次会导致工作组退出,留下混合的结果。这可以解释为什么临时/局部变量有效。
  4. 因为我一直使用 +=(先读后写),所以临时变量初始化充当显式指令,从黑色开始并添加到它,而将片段写入直接添加到该像素的最后一种颜色.如果是这种情况,为什么它可以在高端 PC 上正常工作?
其他详情

我的旧笔记本电脑使用的是带有集成 Intel 3000 显卡的 Optimus 技术的 GT540m(此处未使用)
我较新的台式电脑使用的是 GTX1070。 两个 GPU 在运行应用程序期间使用的 VRAM 都非常少,不到 100MB。 正在使用 #version 400 core 编译着色器

【问题讨论】:

  • 抱歉,我可能在这里遗漏了一些东西,但是......在第一个(失败的)代码示例中,FragColor 的初始化位置在哪里?
  • 添加到上一条评论。如果未显式初始化,则输出变量的初始值未定义。见stackoverflow.com/a/29206537/99279
  • 这对@sterin 很有帮助,这是损坏的着色器所做的另一种模式。所以你们俩都说的是片段输出的默认值本质上是随机的,我不能相信它默认是 0,0,0。这得到了另一个快速测试的支持,我刚刚做了 where 而不是第二个(固定)代码。我只是为片段设置了一个默认值并且它起作用了。
  • 没错。还发现this on the Khronos wiki——“片段着色器的输出变量不必写入。但是,未写入的输出将具有未定义的值。如果当前帧缓冲区绘制缓冲区设置将丢弃该值(通过使用 GL_NONE),则可以。 "

标签: opengl glsl shader artifacts


【解决方案1】:

这是一个驱动程序错误。这里没有什么可研究的了。

可以读取和写入输出变量。这是 GLSL 的一部分。所以看起来驱动程序只是搞砸了它的实现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多