从 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.
我尝试修复在 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.