Opengl GLSL中统一和常量之间浮点行为的不同

Opengl GLSL中统一和常量之间浮点行为的不同,opengl,floating-point,glsl,shader,precision,Opengl,Floating Point,Glsl,Shader,Precision,我试图在GLSL中实现模拟的双精度,我观察到一个奇怪的行为差异,导致GLSL中出现微妙的浮点错误 考虑以下片段着色器,写入4浮点纹理以打印输出 layout (location = 0) out vec4 Output uniform float s; void main() { float a = 0.1f; float b = s; const float split = 8193.0; // = 2^13 + 1 float ca = split * a; floa

我试图在GLSL中实现模拟的双精度,我观察到一个奇怪的行为差异,导致GLSL中出现微妙的浮点错误

考虑以下片段着色器,写入4浮点纹理以打印输出

layout (location = 0) out vec4 Output
uniform float s;
void main()
{
  float a = 0.1f;
  float b = s;

  const float split = 8193.0; // = 2^13 + 1

  float ca = split * a;
  float cb = split * b;

  float v1a = ca - (ca - a);
  float v1b = cb - (cb - b);

  Output = vec4(a,b,v1a,v1b);
}
这是我观察到的输出

GLSL输出具有一致性:

a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86129e-06  0x36400497
a = 0.100000     0x3dcccccd
b = 0.000003     0x36400497
v1a = 0.099976   0x3dccc000
v1b = 0.000003   0x36400000
现在,使用给定的
b1
b2
值作为输入,
v2b
的值没有预期的结果。或者至少它没有与CPU上相同的结果(如图所示):

C++输出:

a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86129e-06  0x36400497
a = 0.100000     0x3dcccccd
b = 0.000003     0x36400497
v1a = 0.099976   0x3dccc000
v1b = 0.000003   0x36400000
注意
v1b
0x36400497
vs
0x36400000
)值的差异

因此,为了弄清楚发生了什么(以及谁是对的),我尝试在GLSL中重做计算,用一个常数替换统一值,使用一个稍微修改的着色器,用它替换统一值

layout (location = 0) out vec4 Output
void main()
{
  float a = 0.1f;
  float b = uintBitsToFloat(0x36400497u);

  const float split = 8193.0; // = 2^13 + 1

  float ca = split * a;
  float cb = split * b;

  float v1a = ca - (ca - a);
  float v1b = cb - (cb - b);

  Output = vec4(a,b,v1a,v1b);
}

这次,我得到了与C++版本相同的计算的相同输出。

GLSL输出与常数:

a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86102e-06  0x36400000
a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86102e-06  0x36400000
我的问题是,是什么使浮点计算在统一变量和常量之间表现出不同的行为?这是某种后台编译器优化吗

这是我的笔记本电脑英特尔GPU上的OpenGL供应商字符串,但我在nVidia卡上也观察到了同样的行为

Renderer : Intel(R) HD Graphics 520
Vendor   : Intel
OpenGL   : 4.5.0 - Build 23.20.16.4973
GLSL     : 4.50 - Build 23.20.16.4973

GPU不一定具有/使用IEEE 754某些实现具有较少的位数,因此不需要考虑结果会有所不同。这与您在FPU上比较
float
double
结果相同。但是,如果您的GLSL实现允许,您可以尝试强制执行精度:

  • 不确定它是否也适用于标准GL/GLSL,因为我从未使用过它

在更糟糕的情况下,如果您的GPU允许,请使用
double
dvec
,但请注意,目前还没有64位插值器(至少据我所知)

要排除由于按纹理传递结果而导致的舍入,请参见:

您还可以通过打印检查GPU上的尾数位数

1.0+1.0/2.0
1.0+1.0/4.0
1.0+1.0/8.0
1.0+1.0/16.0
...
1.0+1.0/2.0^i
未打印为
1.0的最后一个数字的
i
是尾数位数。因此,您可以检查它是否为23…

因此,正如在评论中提到的,通过对依赖于严格IEEE754操作的值使用
精确的
修饰符来解决问题:

layout (location = 0) out vec4 Output
uniform float s;
void main()
{
  float a = 0.1f;
  float b = s;

  const float split = 8193.0; // = 2^13 + 1

  precise float ca = split * a;
  precise float cb = split * b;

  precise float v1a = ca - (ca - a);
  precise float v1b = cb - (cb - b);

  Output = vec4(a,b,v1a,v1b);
}
输出:

a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86102e-06  0x36400000
a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86102e-06  0x36400000

编辑:很可能只需要最后的
precise
来约束导致其计算的操作,以避免不必要的优化。

您正在比较不同的东西。如果我做对了,差异在CPU上而不是GLSL上
const float
可能会被编译器优化,并在编译时作为
double
进行计算,因此变量和常量之间存在差异。CPU和GPU结果之间通常也存在差异(但不是您的情况),因为GPU浮点并不总是标准浮点,它们有时具有不同的位数…如果您更改
float v1b=cb-(cb-b)至<代码>浮动温度b=cb-b;浮点数v1b=cb-tempb
(对于
v1a
),行为是否会发生变化?请尝试将
precise
修饰符添加到
v1a
v1b
,因为这看起来像是将浮点重新关联或FMA收缩应用为性能优化的情况(我认为默认情况下允许),更改结果。re:@Spektre:问题是第一个版本的结果不正确(因为它不符合IEEE 754标准,其他两个也遵循)。我也有同样的问题。但是我正在开发WebGL 1.0,而不是2.0,它不支持“精确”限定符。你知道可以做些什么吗?关于
precision highp
?我不想迁移到WebGL 2.0的原因是它在某些移动设备上不受支持:例如,在iPhone上。Shere’s what I Twill Twill(参考对我原始问题的一些评论和回答):-尝试添加中间变量以存储
ca-a
等的结果。-尝试将这些中间变量
设置为volatile
(如果此关键字与OpenGL ES一起工作…),不幸的是,“volatile”只是一个保留字。WebGL2中没有对它们的任何支持。中间变量也没有帮助