Opengl GLSL中统一和常量之间浮点行为的不同
我试图在GLSL中实现模拟的双精度,我观察到一个奇怪的行为差异,导致GLSL中出现微妙的浮点错误 考虑以下片段着色器,写入4浮点纹理以打印输出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
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
vs0x36400000
)值的差异
因此,为了弄清楚发生了什么(以及谁是对的),我尝试在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,因为我从未使用过它
double
和dvec
,但请注意,目前还没有64位插值器(至少据我所知)
要排除由于按纹理传递结果而导致的舍入,请参见:
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中没有对它们的任何支持。中间变量也没有帮助