将 float 打包到 vec4 中是有损的吗?

问题描述 投票:0回答:1

将 32 位 float(例如深度值)打包/解包为

vec4
格式的
gl_RGBA8
众所周知的过程
如下:

vec4 pack(const in float depth)
{
    const vec4 bit_shift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);
    const vec4 bit_mask  = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);
    vec4 res = fract(depth * bit_shift);
    res -= res.xxyz * bit_mask;
    return res;
}

float unpack(const in vec4 rgba_depth)
{
    const vec4 bit_shift = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0);
    float depth = dot(rgba_depth, bit_shift);
    return depth;
}

pack
例程正确地将 32 位打包为 4 x 8 位值。然而,opengl 使用除以 255 加截断的方式将每个压缩为 8 位无符号整数 (unorm)。这是一个有损操作;例如,0.11011011 乘以 255 截断后得到 11011010,因此最低有效位被“丢失”。例如,如果这种情况发生在最重要的八位字节上,我认为存储其余三个不太重要的八位字节没有意义。

我的推理有缺陷吗?我是不是错过了什么?

glsl shader
1个回答
0
投票

所讨论的算法似乎是假的;它没有考虑 opengl 除以 255 并截断的事实。 (D3D的FLOAT-to-UNORM基本一样,只是在截断前加了0.5,但还是有损的。)

为了使事情顺利进行,我们需要将八位字节编码为整数(而不是pack那样的

分数
),然后除以255。

说明:

pack()
返回
vec4
,其组成部分是表示组成原始数字的四个八位字节的分数。例如,假设原始浮点数是 16 位分数
0.1101101110111101
pack
的输出将是
vec2
,其组成部分为:
res=[0.10111101, 0.11011011]
res[0]
中最高有效的八位字节和
res[1]
中最低有效八位字节,均编码为分数)。

我们需要整数而不是分数:

[10111101, 11011011]
。然后将每个除以 255 得到两个分数,但由于 opengl 乘以 255 转换为
gl_RGBA8
,它基本上颠倒了我们的除法,准确地存储原始整数。 (这些是整数,因此不会执行截断/舍入。)

继续我们的示例,在

unpack
一侧,opengl 首先将每个分量除以 255,然后传递我们的函数
[0.10111101..., 0.11011011...]
。然后我们需要乘以 255 来反转,最后将八位字节移回原来的位置以获得原始浮点数。

此过程准确地再现了原始浮点数。


注释

  1. 32 位浮点数有 24 位有效数,因此它没有足够的精度来准确表示 32 位小数。因此保留 32 位精度是毫无意义的。但例如,如果我们想保留 16 位精度,则此方法可以很好地工作。
  2. 乘除 255(反之亦然)是浮点运算,可能会导致舍入误差。因此,第二个操作并不完全反转第一个操作,但它不会影响八位字节的位,因为操作是以 32 位 FP 精度执行的。
© www.soinside.com 2019 - 2024. All rights reserved.