【GAD翻译馆】小浮点格式——R11G11B10F精度
bartwronski发表于2017年4月2日
翻译:赵菁菁(轩语轩缘,657459265) 审校:李笑达(DDBC4747)
虽然这篇文章还没有与抖动相关联,但它在某种程度上是这个关于抖动系列的一部分。大家可以点击这里查看所有部分的目录,或点击这里阅读前一部分。
我将在这里讨论使用不是很流行/出名的格式——R11 G11 B10浮点(R11G11B10F)格式——关于其精度、注意事项以及如何提升它。
我想在这里指出,这篇文章不会涉及许多浮点的微妙之处,即NaN、不规范(denorm)和无穷大。很多GPU着色器编译器都使用快速数学算法(除非被要求严格遵从IEEE标准)并忽略它们——程序员在使用结果值时候必须加倍小心。
你可以点击这里下载Mathematica(数学软件)笔记,点击这里获取对应的pdf。
更新: Tom Forsyth和Peter Pike Sloan校正GPU是标准化支持denorm写小浮点数的,在此之后,我更新了关于在denorm范围内丢失动态范围的部分。
问题描述
渲染中最常用的颜色表示不是整数/自然数/有理数表示,而是浮点表示法。浮点数和它们的大范围是有用的,有几个不同的原因,但最重要的是:
- 编码HDR值和光照
- 在操作多个颜色时需要分数值,混合它们,用带分数或负权重的过滤器进行过滤
- 在没有任何伽玛编码的黑暗区域需要更高的精度
- 需要边界相对量化误差(相对于信号幅度的恒定上界)
- 事实上,浮点数是GPU的“自然”表示(很长时间以来,GPU都没有任何整数支持或是利用浮点操作“模拟”……还有一些整数运算比浮点运算慢)。
据说,渲染技术甚至为HDR色彩也很少存储32位浮点值——由于内存存储和性能都需要成本。内存带宽和缓存通常是最神圣的资源,简单的经验法则是“ALU(运算器)很便宜,内存访问很昂贵”。即使是最简单的内存访问操作也有上百个周期的延迟(至少在AMD GCN上是这样)。此外,使用纹理单元时成本会增加——因为过滤操作变得越来越昂贵,操作速度也越来越慢。
因此,渲染程序员通常在内存用较小的浮点格式存储——两种最常见的是RGBA16F(四个16位半浮点通道)和R11G11B10F (通道R和G有11位小浮点,通道B用10位小浮点)。
让我们看看这些格式和全32位IEEE浮点的区别。如果大家对浮点表示感到满意,可以跳过下一节。
浮点——回顾
我在这里假设读者知道浮点数是如何表示的,但作为提醒,典型的浮点值是由一些位表示的:
- 符号位 —— 数字的符号,最大单位值和可选的(之后再讨论)
- 指数位 ——一些位以偏移整数格式表示,在与数字的其余部分相乘之前描述2的有偏指数
- 尾数位 ——在乘指数前,一些位表示数的小数部分。假定有一个开头的1,小数点,所以01011000的尾数对应数字1.01011000表示的二进制(基为2)。
因此最后的典型的数字是符号位( /- 1) * 2解码的指数* 1.尾数。
有很多用特殊指数最小值和最大值的“特殊”案例(denorm,无限,NaN,零),但这篇文章的目的,我们之后只关注一个特殊的情况——零的编码——将所有指数和尾数位置零实现。(注:因为符号位仍然可以设置,有两个零, 0和- 0)。
浮点是一个非常聪明的表示,有很多漂亮的特性(例如:解释为整数的正浮点数可以排序或原子地取最小/最大值!整数零正好对应于正浮点零),然而,伴随着精度的许多问题并不总是最直观的。我只在这里提及其中一些——与讨论问题有关的部分。
普通和小浮点
到目前为止,我试图保持通用,没有指定任何比特数,但在硬件(或软件仿真)中使用浮点,我们需要定义它们。
下面是一个表,显示了常规32位浮点的各种位深度以及图形硬件/标准使用的半浮点和11位和10位浮点:
位深度 | 存在符号位? | 指数位 | 尾数位 |
32 | Yes | 8 | 23 |
16 | Yes | 5 | 10 |
11 | No | 5 | 6 |
10 | No | 5 | 5 |
我们马上就可以看到一些有趣的观察:
- 11和10浮点没有符号位!这一决定可能是因为它们在大多数用途上精度已经相当差,所以它们在图形API中被设计为只存储颜色;在这里使用符号位将是一个额外的浪费。
- 16位“半”浮点与11和10位的浮点都有相同的指数!这是一个非常有趣的选择,但它保证它们可以表示 /-类似的值范围。指数5保证值可以从到65500和65000(取决于他们的尾数),这对于HDR照明来说甚至也是相当大的(除非使用无偏差的、绝对的曝光值或做精度增加,这一招我会在之后介绍)。指数可以是负的,所以我们可以去用类似的(一个以上)更小的值。
- 尾数最痛苦。差别是相当疯狂的——在最糟糕的情况下,23比5位!我们正在降低18位精度。这是非常不幸的信息,因为它意味着,相对来讲,在相似范围(类似指数)的数字之间,我们正在丢失许多精确度。
同时,由于11 11 10浮点格式不同的比特深度,问题是来自蓝色通道和其他通道的不同尾数位深度——会产生各种变色和色调变化——类似于经常在BC1块压缩中出现的情况(有565个端点的位深度),但不是绿色/紫色,而是黄色/蓝色。我会在后面给一个例子。显然,这一决定是有道理的——11 11 10格式,非常适合一个单一的双字节值,而且感官上,人类视觉对于蓝色通道最不敏感。
因此,正如我们所看到的,我们在将32位浮点转换为16或11/10位的过程中,正在丢失大量的信息。此外,在指数和尾数之间的信息丢失是不成比例的——在每个小浮点情况下,我们都会在尾数中丢失更多信息。这可能会导致一些量化和连带误差。
在分析量化之前,有一件事要提一下——IEEE标准定义了几种不同的舍入模式(例如,到最接近的、到零的、到正无穷的和到负无穷的)。我不认为它们基于GPU(至少在标准、跨供应商的API上)有任何可配置的方式,本篇文章的后半段会忽略这种复杂性,假设用的是简单的四舍五入。
小浮尾数精度——实例
我希望前面的部分,加上看一些数字的比特深度可以清楚地展示:因为很小的尾数问题,损失较小格式的浮点数的数值精度。
首先,一些数值例子。让我们取3个简单的8位整数值,并将它们表示为0到1范围的浮点——颜色的通用操作。
N[252/255,
8]
0.98823529
N[253/255,
8]
0.99215686
N[254/255,
8]
0.99607843
让我们试着用浮点数来表示它们。利用浮点值的知识,知道尾数总是始于一,我们需要将它们乘上2,指数就是2-1。
乘法后我们得到:
BaseForm[N[2*252/255,
8], 2]
1.1111100111111001111110100
BaseForm[N[2*253/255,
8], 2]
1.1111101111111011111111000
BaseForm[N[2*254/255,
8], 2]
1.1111110111111101111111100
我加粗了前5位,为什么?想想10位半浮点只有5位尾数!因此10位半浮点(R11 G11 b10f的蓝色通道)甚至不能准确表示三个几乎最后8位的颜色值!同时,你可以看到下一位确实不同——因此这三个数字会在11F产生两种不同的值,产生白色值错误的颜色。
小浮点尾数精度–可视化
好的,所以我们知道小浮点甚至不能准确反映简单的8位亮度!但是这到底有多糟糕?我创建了一些Mathematica(数学软件)可视化(见链接页面顶部)——首先,最坏的情况下,B10F,因此舍弃了18位的尾数。
情况看起来很好(甚至更好——考虑到浮点是如何编码的就不意外了!)接近于零,但误差开始增多,与线性8位值量化误差相比几乎是其4倍!
这是很不公平的比较,——我们不使用8位线性颜色而使用sRGB,是由于对黑暗与明亮的感知灵敏度(“伽马”),所以不要在意那些明亮的区域,决定将更多信息编码到较暗的部分。这就是3种编码方法的比较:
好,还有点事。看起来10位浮点精度对于值达到线性0.125的值要好点,但是之后就不好了。误差最大值对于10位浮点来讲几乎是1,大了两倍,这样不好……这会在光滑梯度上带来可见带(visible band)。
轻松一下,额外的可视化,相关的误差(被原始值分开):
如所期望的那样,浮点值量化相关误差是有界限的,而且在对应于下一个指数的范围中有最大值(如果我们这里不考虑比最小规范化浮点表示还小),但随着我们接近0,8位线性或者sRGB相关误差会增加。浮点相关误差也在“带(band)”中展现了,对应于下一个指数,误差在两个相邻带之间变大2倍。
我们会继续研究怎样改进,但是首先——再说一点关于第二个问题的事情。
小浮点不均匀尾数长度问题
因为R11G11B10浮点尾数的比特长度分布不均匀,它们数字转换将会不同。厉害到什么程度?与浮点数一样,绝对误差取决于范围:
这种不同的量化在实际中意味着什么?这意味着信号会出现变色/错误的饱和。让我们看看一个简单的从0.5到0.6的渐变。
如果你有很好的显示器/查看条件,就会发现这种渐变是非常糟糕的。现在想象一下,和你一起工作的导演喜欢强烈反差,喜欢过度饱和的像电影一样的渐变:
这个看起来很难用……我们来看看如何改进它。在这篇文章中是通过改变信号的动态范围,在下一篇博客中是通过抖动。
重新调节没有用
一个普遍的误解是,把一个浮点数乘上大数,编码再在解码之后分开就可以了。这是不行的,比如说,让我们看看用16左乘时的量化误差:
零差!为什么?让我们考虑在浮点表示除以16中意味着什么。嗯,尾数是不会改变的!唯一的事情是,我们将从指数减去4。所以由于尾数量化产生的相对误差都是一样的。你可以试着在1/2到2之间乘以一个数字,我们会看到范围变化中的差异,但是它只会把误差转移到更多的白色或更多的黑暗部分:
错误带只向左或向右滑动。
应用一些γ法改善误差
让我们来看看一个不同的方法——这种方法将利用如下事实:即(如果图像是预曝光的!)在大多数精度(为了达到约束的相对精度)被定位的情况下,我们可能不关心非常小的值。
我在之前的博客中提到过关于动态范围精度问题的常用解决方法——利用一定的信号幂次拉伸动态范围(更小或更大)。为了以整数存储更高精度的图像暗区,我们希望采用较低的编码幂次,例如著名的伽玛1/2.2。然而,在这种情况下,我们想做……相反的事情!采用更大的幂次——要想知道为什么这么做,可以看看我们原来引入sRGB变量的比较:
我们把固定的界限内不断振荡的蓝线重新调节成增长的蓝线。这里10位浮点数存在相反的问题——我们有一个渐进增长太快的函数——我们要去掉它。
想一想,这是个很有趣的问题。它与浮动精度分布方式有关——它是非线性的对数分布,能很好地处理较大的动态范围;此外,指数型信号曲线几乎是线性表示的!因此,要从我们低位深度的浮点表示中获得最多信息,我们希望在编码之前尽可能地增加动态范围。比如说我们可以这样做:把信号开方或采取更大的幂次。我使用的三个初始浮点,实际上需要相当大的指数,3是给定值:
BaseForm[N[2*(252/255)*(252/255)*(252/255),
8], 2]
1.1110111000100100001001000
BaseForm[N[2*(253/255)*(253/255)*(253/255),
8], 2]
1.1111010000001100000101000
BaseForm[N[2*(254/255)*(254/255)*(254/255),
8], 2]
1.1111101000000000000001000
注意它们是不同的(尽管前两个将以同样的方式四舍五入)。
让我们看看应用γ3的绝对误差(注:本图假设反规范处理正确,详见下文):
我们的误差看起来比8位sRGB误差小——这可能已经是相当有用的存储库了。我们以前的带状梯度也看起来更好,它的高对比度的版本也更好了(虽然不完美——回忆那种重做伽马的对比):
之前:
之后:
对比前:
对比后:
但天下没有免费的午餐!
首先,有ALU的成本。当我们每3个通道做一次这个操作时,ALU会变得相当重要!x*x*x是两个全速率操作,但如pow(x,1 / 3)是log2 EXP2 乘,所以每个颜色通道2个四分速率 1个全速率= 9个FR指令!廉价的变量只有开方,sqrt(x)是一个四分速率指令,与4个FR指令等效。
其次,这个数据现在显然不是可过滤的/可混合的……在这个空间上混合会带来过亮的问题。这是一个重大问题(如果你需要硬件混合,或者用双线性挖掘进行重采样),但也许不是(如果你可以手动/在计算着色器中这样做)。
第三,这个额外的精度是通过牺牲动态范围来实现的。通过使用的伽马,它的绝对值等于用伽马除指数的绝对值。例如,对于γ3,我们最大的可表示值大约是pow(65000,1 / 3)~ = 40!只有40!HDR够你用吗?如果预先曝光的场景可能够用,但最热的点将被剪辑……开方的变量看起来更好,大约250 。
潜在的小数问题
注意:这一节在Tom Forsyth和Peter Pike Sloan修正后略有改写。我的假设是悲观的(denorm刷新到零),但显然,GPU必须正确处理好,如DirectX中的GPU。多谢你注意到这件事!
另一个问题可能是在不同的部分——最小可表示的数。相同的指数除法绝对值是用于最小可表示数的!因此,应用γ3后,最小可表示的数将为0.03125,大约是8 / 255,如果我们没有denorm或denorm刷新到零,这将导致裁剪!没有处理denorm,放大的误差实际走势图如下:
图如下:
你可以通过预曝光尝试修复它,如4:
但这张图不仅不完美,而且从最高范围内数据又开始不清晰了。(最热的可表示值)不是已经限制的40,你只能得到10!即使在HDR电视上显示信号,这也是不够的……
因此,如果不正确处理denorm,我宁愿推荐坚持γ2,预曝光值为4,接受略高的量化误差:
幸运的是,我得到了纠正——denorm没有不正确处理,我们可以假设,denorm会被处理——所以如果需要的话可以使用那些较大的指数——只是需要考虑我们在上面/较高部分牺牲了多少动态范围。
在完成这一部分之前,有一个有趣的边注:你曾经考虑过在16位浮点操作时标准化浮点精度有多低吗?半浮点有相同的指数位深度,所以,如果你对15位浮点应用对比的操作,你可能会很快进入denorm范围!这在理论上可能导致裁剪。
未经检验的思想–使用YCoCg色彩空间?
一些有趣的(?)想法是可以尝试使用一些不同的像YCoCg这样的颜色空间,或用相似的代替RGB。在(有符号的)YCoCg的色度更小=CG成分的量级更小=更高的精度。这将有助于降低颜色通道之间的关联,当颜色不太饱和时(和当这些变化更明显时)避免丑陋的色度变化。
不幸的是,R11G11B10没有符号位可用——我们需要在“某处”存储两个额外的符号位(不同的表面?尾数最低位/指数最高位?)。
总结——到底用不用R11G11B10 还是不用?
R11G11B10和小的11和10位的花车有许多局限性,但也是极其引人注目的存储格式。与RGBA16F相比,它们将记忆存储和带宽的要求减半,通过一些数值技巧提供大多数彩色编码方案可以接受的精度之后,能够存储高动态范围的信号。对于非临界信号(环境缓冲区、许多后效应缓冲区),我经常使用它们,但我认为如果不需要alpha混合或过滤,也可以对常规的颜色缓冲区进行使用,并且可以稍微修改输入数据。
更新:我从Volga Aksoy和Tom Forsyth得到一些消息,Oculus SDK现在支持并建议按这种格式输出,所以它绝对实用。因为带有HMD的黑暗/完美的观看条件,人类的感知是黑暗和R11G11B10F中更敏感,在较低范围内比8位 sRGB效果好。
在下一篇文章中,我将展示如何抖动浮点,并获得更好的结果,几乎没有感知带(这么做换取噪音)。
奖励——与10位sRGB比较
作为一个小的奖励,与10位sRGB编码简单比较一下(没有硬件支持,但一些视频库支持它允许更精确的颜色配置文件/曲线转换)。两图显示误差在0-1之间,全在0-0.1偏暗范围内。
我们可以看到,10位sRGB更优于大多数的范围,但在非常低/暗的值中,10位浮点是同等的,甚至更优秀一点。。
参考文献
https://www.opengl.org/wiki/Small_Float_Formats
http://steve.hollasch.net/cgindex/coding/ieeefloat.html Steve Hollasch, “IEEE标准754浮点数”
http://community.wolfram.com/groups/-/m/t/274061 Mathematica 帮助文档——把浮点数表示法转换到任何科学记数法
https://msdn.microsoft.com/en-us/library/windows/desktop/cc308050(v=vs.85).aspx#alpha_11_bit Direct3D 10个浮点规则
【版权声明】
原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权;