GLSL - 片段着色器不同部分的精度不同

GLSL - different precision in different parts of fragment shader

我有一个绘制测试网格图案的简单片段着色器。

我真的没有问题 - 但我注意到一个我无法解释的奇怪行为。不要介意奇怪的常量——它们会在编译前的着色器组装过程中被填充。另外,vertexPosition 是世界中的实际计算位置 space,因此我可以在网格本身移动时移动着色器纹理。

这是我的着色器代码:

#version 300 es

precision highp float;

in highp vec3 vertexPosition;

out mediump vec4 fragColor;

const float squareSize = __CONSTANT_SQUARE_SIZE;
const vec3 color_base = __CONSTANT_COLOR_BASE;
const vec3 color_l1 = __CONSTANT_COLOR_L1;

float minWidthX;
float minWidthY;

vec3 color_green = vec3(0.0,1.0,0.0);

void main()
{
    // calculate l1 border positions
    float dimention = squareSize;
    int roundX = int(vertexPosition.x / dimention);
    int roundY = int(vertexPosition.z / dimention);
    float remainderX = vertexPosition.x - float(roundX)*dimention;
    float remainderY = vertexPosition.z - float(roundY)*dimention;

    vec3 dyX = dFdy(vec3(vertexPosition.x, vertexPosition.y, 0));
    vec3 dxX = dFdx(vec3(vertexPosition.x, vertexPosition.y, 0));
    minWidthX = max(length(dxX),length(dyX));
    vec3 dyY = dFdy(vec3(0, vertexPosition.y, vertexPosition.z));
    vec3 dxY = dFdx(vec3(0, vertexPosition.y, vertexPosition.z));
    minWidthY = max(length(dxY),length(dyY));


    //Fill l1 suqares
    if (remainderX <= minWidthX)
    {
        fragColor = vec4(color_l1, 1.0);
        return;
    }

    if (remainderY <= minWidthY)
    {
        fragColor = vec4(color_l1, 1.0);
        return;
    }

    // fill base color
    fragColor = vec4(color_base, 1.0);
    return;
}

因此,使用此代码一切正常。 然后我想通过在绘制垂直线之后移动只涉及水平线的计算来稍微优化它。因为如果垂直线检查为真,这些计算就没有用了。像这样:

#version 300 es

precision highp float;

in highp vec3 vertexPosition;

out mediump vec4 fragColor;

const float squareSize = __CONSTANT_SQUARE_SIZE;
const vec3 color_base = __CONSTANT_COLOR_BASE;
const vec3 color_l1 = __CONSTANT_COLOR_L1;

float minWidthX;
float minWidthY;

vec3 color_green = vec3(0.0,1.0,0.0);

void main()
{
    // calculate l1 border positions
    float dimention = squareSize;
    int roundX = int(vertexPosition.x / dimention);
    int roundY = int(vertexPosition.z / dimention);
    float remainderX = vertexPosition.x - float(roundX)*dimention;
    float remainderY = vertexPosition.z - float(roundY)*dimention;

    vec3 dyX = dFdy(vec3(vertexPosition.x, vertexPosition.y, 0));
    vec3 dxX = dFdx(vec3(vertexPosition.x, vertexPosition.y, 0));
    minWidthX = max(length(dxX),length(dyX));

    //Fill l1 suqares
    if (remainderX <= minWidthX)
    {
        fragColor = vec4(color_l1, 1.0);
        return;
    }

    vec3 dyY = dFdy(vec3(0, vertexPosition.y, vertexPosition.z));
    vec3 dxY = dFdx(vec3(0, vertexPosition.y, vertexPosition.z));
    minWidthY = max(length(dxY),length(dyY));

    if (remainderY <= minWidthY)
    {
        fragColor = vec4(color_l1, 1.0);
        return;
    }

    // fill base color
    fragColor = vec4(color_base, 1.0);
    return;
}

但是即使这看起来不应该影响结果 - 它确实如此。相当多。 下面是两个截图。第一个是原始代码,第二个是 "optimized" 代码。哪个效果不好。

原版:

优化版(看起来差很多):

注意线条是如何变成 "fuzzy" 的,尽管看起来根本没有数字应该改变。

注意:这不是因为 minwidthX/Y 是全球性的。我最初通过将它们设为本地来进行优化。 我最初也将 RoundY 和 remainderY 计算也移到了 X 检查下面,结果是一样的。

注意 2:我尝试专门为每个计算添加 highp 关键字,但这并没有改变任何东西(不是我期望的那样,但我还是尝试了)

谁能给我解释一下为什么会这样?我想知道我未来的着色器,实际上我也想优化这个着色器。我想在这里了解precision loss背后的原理,因为它对我来说没有任何意义。

关于这一点,我将参考 OpenGL ES Shading Language 3.20 Specification, which is the same as OpenGL ES Shading Language 3.00 Specification 的答案。

8.14.1. Derivative Functions

[...] Derivatives are undefined within non-uniform control flow.

进一步

3.9.2. Uniform and Non-Uniform Control Flow

When executing statements in a fragment shader, control flow starts as uniform control flow; all fragments enter the same control path into main(). Control flow becomes non-uniform when different fragments take different paths through control-flow statements (selection, iteration, and jumps).[...]

这意味着,第一种情况(你的问题)的导数函数的结果是明确定义的。

但第二种情况不是:

if (remainderX <= minWidthX)
{
   fragColor = vec4(color_l1, 1.0);
   return;
}

vec3 dyY = dFdy(vec3(0, vertexPosition.y, vertexPosition.z));
vec3 dxY = dFdx(vec3(0, vertexPosition.y, vertexPosition.z));

因为 return 语句就像一个选择。并且 return 语句的代码块之后的所有代码都处于非统一控制流中。