将float打包进vec4是否存在精度损失?我的推理是否有误?
常用打包/解包代码
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; }
问题背景
上述代码可将32位数据打包为4个8位值,但OpenGL会通过除以255并截断的方式将每个通道值压缩为8位无符号整数(unorm),这是有损操作:例如0.11011011(二进制)乘以255后截断会得到11011010,最低有效位丢失。理论上要完整保留字节需乘以256(即左移8位)。
由此产生疑问:若最高有效字节出现此类丢失,存储其余三个低有效字节是否毫无意义?
解答
你的推理存在关键漏洞,忽略了以下几个核心要点:
精度损失的量级差异:最高有效字节的丢失确实会带来最大的绝对误差,但低有效字节的存在能大幅降低相对误差。比如,MSB丢失1个LSB时,保留后续三个字节的还原误差是
1/2^24,若丢弃低字节,误差会直接变成1/2^8,两者的精度差距达到数百万倍。32位Float的精度特性:32位float采用符号位+8位指数+23位尾数的存储结构,精度随数值大小变化——数值越小,相对精度越高。对于深度值这类可能覆盖大范围的数值,低有效字节能在小数值区间提供更精细的还原度,即便MSB有微小损失,低字节依然能让结果更接近原始值。
unorm量化的误差分布:OpenGL将
[0,1]区间映射到[0,255]整数的过程,本质是均匀量化。四个通道的量化误差相互独立,组合后整体误差处于1/(2^32)量级,远优于仅保留MSB时的1/(2^8)量级误差。实际场景的需求价值:在深度缓冲、纹理存储等实际应用中,我们往往不需要完全精确的原始值,只需要足够精度避免视觉瑕疵(如阴影 acne)。保留低有效字节能显著提升还原后的精度,减少这类问题,整体收益远大于丢弃低字节的操作。
简言之,哪怕最高有效字节存在量化损失,低有效字节的存在依然能大幅提升整体还原精度,绝非毫无意义。
内容的提问来源于stack exchange,提问作者Daniel

