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