从 iPad Air 开始的 GLSL 小浮点值截断

GLSL small float value truncation starting from iPad Air

我尝试修复在 iPad3(PowerVR SGX543MP4, iOS 8.1) 和旧硬件上运行良好但在 iPad Air(PowerVR G6430, iOS 8.1)...我偶然发现 "curious" 对后者的小浮点值的处理。它归结为这样的测试用例:

片段着色器 1(写入测试值):

precision highp float;
const highp float addValue = 0.000013;

uniform highp sampler2D maskField;
varying mediump vec2 pixelUv;

void main()
{
    highp vec4 mask = texture2D(maskField, pixelUv);

    gl_FragColor = vec4(addValue * mask.x);
}

片段着色器 2(检查值并输出红色)

precision highp float;
uniform highp sampler2D testTexture;
varying highp vec2 pixelUv;

void main()
{
    highp vec4 test = texture2D(testTexture, pixelUv);

    gl_FragColor = vec4(test.a > 0.000012, 0.0, 0.0, 1.0);
}

第一个着色器正在写入 HALF_FLOAT_OES 纹理。 结果是 Ipad3 上的红色和 ipad 空气上的黑色。并且不要急于发布 mask.x 值。通过两个设备上的调试器验证正确(1.0)。

更奇怪的是,如果我将 "addValue" 的值增加到 0.000062,它会成功写入。但如果它低于该值并且变量与常量以外的任何东西交替,它会在 iPad Air 上截断为 0.0。所以...这有效:

const highp float addValue = 0.000013;

void main()
{
    gl_FragColor = vec4(addValue * 1.0);
}

但这不是:

uniform highp float one; //= 1.0
const highp float addValue = 0.000013;

void main()
{
    gl_FragColor = vec4(addValue * one);
}

与添加相同。如果我尝试像这样积累价值,它什么也不会增加:

const highp float addValue = 0.000013;
uniform highp sampler2D pressureField;

varying mediump vec2 pixelUv;

void main()
{
    highp vec4 pressureData = texture2D(pressureField, pixelUv);
    pressureData.a += addValue;
    gl_FragColor = vec4(addValue);
}

欢迎就正在发生的事情以及如何修复它提出任何建议!使用的算法确实需要高精度浮点数。现在我正处于向苹果提交错误报告并等待永恒的边缘。

将 0.000013 存储为 GL_HALF_FLOAT 时,您处理的是非规范化数字。标准 half float (IEEE 754-2008) 可以表示的最小标准化数字是 2^-14,大约为 0.000061,或大于您所表示的值。

OpenGL 规范在如何处理非规范化 16 位浮点数方面为实现留出了一定的自由度。这在 ES 2.0 的 extension spec 和 ES 3.0 规范中都有非常相似的记录。

扩展规范使用此作为指数为 0 的情况的主要定义,这是非规范化数字的情况:

(-1)^S * 2^-14 * (M / 2^10),         if E == 0 and M != 0,

这个精度足以解决 0.000012 和 0.000013 之间的差异。但它也说 "Implementations are also allowed to use any of the following alternative encodings":

(-1)^S * 0.0,                        if E == 0 and M != 0,

使用这种编码,0.000012 和 0.000013 都四舍五入为 0.0,因此变得相等。

ES 3.0 规范有第一个定义,但随后在文本中添加:

Providing a denormalized number or negative zero to GL must yield predictable results, whereby the value is either preserved or forced to positive or negative zero.

这也允许将这些值四舍五入为 0.0。

至于为什么这在不同代的硬件之间表现不同,我没有任何详细的见解。但在我看来,这两种实现都符合规范,因为它们使用了规范明确允许的两种不同变体。

如果您想确保这些值用您需要的 range/precision 表示,您将必须使用 GL_FLOAT 类型的纹理,它将以完整的 32-位 range/precision.