Opengl着色器:如果条件错误地评估为假

Posted

技术标签:

【中文标题】Opengl着色器:如果条件错误地评估为假【英文标题】:Opengl shader: if condition wrongly evaluated to false 【发布时间】:2016-05-27 14:53:26 【问题描述】:

我最近购买了“opengl 着色语言食谱”。 我正在尝试编写其中一个示例:特别是反射立方体贴图。

现在,我的软件崩溃了。

我可以将问题缩小到一个假设条件为真,但“假”部分似乎已被执行(更多细节即将到来)。

目标是:

画一个天空盒。 绘制一个物体(茶壶),它反映了天空盒。

开发环境: 我在 2 台带有 QT5.6.0 的 PC(工作笔记本电脑和家庭游戏设备)上以及带有 QT 5.3.0 的笔记本电脑上进行了尝试。 QT 5.6.0是mingw编译的32位版本,5.3.0版本不清楚。

两台电脑都有英伟达卡。我不会详细介绍,因为我认为问题不在于那里(除非答案中另有说明)。

我已经移除了茶壶的drawcall。 我目前只画天空盒。这意味着“DrawSkyBox”布尔统一始终为真。这很重要。

顶点着色器是问题所在(或者至少是引发崩溃的那个)。 代码在这里(这是书中的代码/项目,不是我的,反正是复制/粘贴):

#version 430

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;
layout (location = 2) in vec2 VertexTexCoord;

out vec3 ReflectDir;

uniform bool DrawSkyBox;
uniform vec3 WorldCameraPosition;

uniform mat4 ModelViewMatrix;
uniform mat4 ModelMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;

void main()

    if( DrawSkyBox ) 
        ReflectDir = VertexPosition;
     else 
        vec3 worldPos = vec3( ModelMatrix * vec4(VertexPosition,1.0) );
        vec3 worldNorm = vec3(ModelMatrix * vec4(VertexNormal, 0.0));
        vec3 worldView = normalize( WorldCameraPosition - worldPos );

        ReflectDir = reflect(-worldView, worldNorm );
    

    gl_Position = MVP * vec4(VertexPosition,1.0);

片段着色器在这里:

#version 430

in vec3 ReflectDir;

layout(binding=0) uniform samplerCube CubeMapTex;

uniform bool DrawSkyBox;
uniform float ReflectFactor;
uniform vec4 MaterialColor;

layout( location = 0 ) out vec4 FragColor;

void main() 
    // Access the cube map texture
    vec4 cubeMapColor = texture(CubeMapTex, ReflectDir);

    if( DrawSkyBox )
        FragColor = cubeMapColor;
    else
        FragColor = mix( MaterialColor, cubeMapColor, ReflectFactor);

那么我做了什么来研究这个问题:

1) 在片段着色器中,我不获取立方体贴图纹理,而是设置颜色(在 if/else 中)。 我清楚地看到红色的天空盒和绿色的茶壶(在这个测试中我仍然画了两者)-> 绘制调用和对象数据是正确的。

2) 我尝试使用“标准”2D 纹理。我通过了标准纹理坐标,片段着色器只是获取纹理(没有 if/else)-> 工作正常。

3) 我发现一些驱动程序/API 可以将布尔值转换为 int(这可能是过去的问题,但我也消除了这种可能性): 我尝试发送一个 int 并将条件更改为 if( DrawSkyBox > 0) ... 并将 1 传递给制服:

mProgram->setUniformValue("DrawSkyBox", 1);

即使是类型转换:

mProgram->setUniformValue("DrawSkyBox", (int) 1);

着色器仍在执行“else”部分。

4) 我移动了特定函数中的执行部分,结果相同。

void CalcSkyBox()

    ReflectDir = VertexPosition;


void ObjReflect()

    vec3 worldPos  = vec3(ModelMatrix * vec4(VertexPosition, 1.0));
    vec3 worldNorm = vec3(ModelMatrix * vec4(VertexNormal, 0.0));
    vec3 worldView = normalize(WorldCameraPosition - worldPos);

    ReflectDir = reflect(-worldView, worldNorm );    


void main()


    if (DrawSkyBox) 
        CalcSkyBox();
     else 
        ObjReflect();
    

    gl_Position = MVP * vec4(VertexPosition, 1.0);

为什么我说它从一开始就执行“else”部分?嗯……:

最后我发现,通过修改顶点着色器其他部分中的“ReflectDir”(-> ReflectDir = reflect(-worldView, worldNorm ); 替换为ReflectDir = VertexPosition;),它可以完美运行! (我的意思是没有崩溃并显示天空盒)。

所以我重申问题是: 为什么着色器执行 else 部分?

伊马里翁

PS:这是一篇很长的文章,所以如果您达到这一点并提供任何答案,请提前感谢。

【问题讨论】:

如果“crash”是指“应用程序强制关闭”,那么在核心转储或 IDE 的调试器(如果您使用的话)中发现了什么? 感谢您的编辑 :)。我使用 QT Creator。它说“应用程序停止工作”,所以是的,这是一种强制关闭。这是绘图调用中的分段错误。 (在我的 cpp 中:glDrawElements(...) -> 在 qopenglfunctions.h 中:d_ptr->DrawElements(mode, count, type, indices); 请注意,d_ptr 已正确初始化(DrawElements 0x58790c60 void (GLenum, GLsizei, GLenum, const GLvoid *)) Qt 处于相当不稳定的状态,我无法获得核心转储。 做了一个新的测试,见我原来的消息中的4。 【参考方案1】:

某些 GPU 可能会评估分支的两侧,然后丢弃其中一侧。见:http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html

这并不能解释为什么会发生崩溃。您归一化的向量是否可以为零,因此归一化会导致除以零?

【讨论】:

我怀疑是某种优化。至于标准化,那是正确的。我修改了这样的计算: ReflectDir = reflect(-worldView, vec3(0.0, 1.0, 0.0));它不会崩溃。我稍后会更深入地调查,我现在没有时间。无论如何谢谢你:) 也就是说,它不应该执行这部分。存在这个问题是因为对于 Skybox,这些向量没有被传递......它们没有被使用。【参考方案2】:

我创建了 2 个程序来摆脱顶点着色器中的“if”。 我使用具有“if”的完全相同的片段着色器。

然后一切正常。

考虑到这是来自 Nvidia 顶点着色器编译器的优化错误,我很想关闭此问题。

【讨论】:

以上是关于Opengl着色器:如果条件错误地评估为假的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL几何着色器三角形不会没有错误地绘制

未触发opengl片段着色器条件语句

为啥在使用 OpenGL 编译顶点着色器时会出现着色器编译器错误 #143、#160 和 #216?

OpenGL 3着色器错误[关闭]

OpenGL着色器编译错误

OpenGL 程序着色器链接错误