将 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,因此最低有效位被“丢失”。例如,如果这种情况发生在最重要的八位字节上,我认为存储其余三个不太重要的八位字节没有意义。
我的推理有缺陷吗?我是不是错过了什么?
所讨论的算法似乎是假的;它没有考虑 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 来反转,最后将八位字节移回原来的位置以获得原始浮点数。
此过程准确地再现了原始浮点数。
注释: