简述:简单记录一下 如何将 一个 (0-1)float 的值转换到RGBA,和如何从 RGBA 解码出一个(0-1)的float值,这个主要用在需要存储单个高精度的数到纹理时,如果只存放在单个通道那么其只能是八位存储,精度肯定不足,而如果使用32位存储,就相当于把四个通道拼接起来,这样精度就可大幅提升,注意这张纹理不可以压缩,压缩完解出来的数就无法正常还原了
Unity 中的函数
函数来自于 unity2021.3.11f1
UnityCG.cginc中的原函数代码如下:
// Encoding/decoding [0..1) floats into 8 bit/channel RGBA. Note that 1.0 will not be encoded properly.
inline float4 EncodeFloatRGBA( float v )
{
float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 16581375.0);
float kEncodeBit = 1.0/255.0;
float4 enc = kEncodeMul * v;
enc = frac (enc);
enc -= enc.yzww * kEncodeBit;
return enc;
}
inline float DecodeFloatRGBA( float4 enc )
{
float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/16581375.0);
return dot( enc, kDecodeDot );
}
关于 float
float 是一个32位浮点数
double 是64位浮点数
何为位
我们都知道计算机用的是 010101...... 的二进制,我们拿 8 位无符号整数来举例子
- 255 二进制表示就是 11111111
- 254 二进制表示就是 11111110
- 10 二进制表示就是 00001010
可以看到,我们可以用 8 个 0 和 1 来存储 255 以内的整数。
以上我们用的是无符号,当使用有符号时,那么就会从32位中拿出一位来表示正负数,一般取最左边的一位
同理 32 位整数 就是用 32 个 0 和 1 存储的整数,32 位浮点数更为复杂,他还需要处理小数点,
单位的表示与进制
一个位我们记做 bit
一个字节有8位 我们记做 Byte 存储的最小单元,即:计算机最小也要存 8 位
所以我们也可以说一个 float值 占 4 个 Byte
再往下就是我们熟悉的存储单位进制了
1KB = 1024 Byte
1MB = 1024 KB
1GB = 1024 MB
1TB = 1024 GB
float 的二进制存储方式
参考自浮点数的表示方法
在计算机中一个任意二进制数 N 可以写成: N=2^e.M
其中
- M 称为浮点数的尾数,是一个纯小数
- e 是比例因子的指数,称为浮点数的指数,是一个整数
- 比例因子的基数2对二进记数制的机器是一个常数

- S 是浮点数的符号位,占1位,安排在最高位,S=0表示正数,S=1表示负数
- M 是尾数,放在低位部分,占用23位,小数点位置放在尾数域最左(最高)有效位的右边
- E 是阶码,占用8位,阶符采用隐含方式,,即采用移码方法来表示正负指数,移码方法对两个指数大小的比较和对阶操作都比较方便,因为阶码域值大者其指数值也大。采用这种方式时,将浮点数的指数真值e变成阶码E时,应将指数e加上一个固定的偏移值127(01111111),即 E=e+127
在IEEE754标准中,一个规格化的32位浮点数x的真值表示为
x = (-1) ^s X(1.M)X 2^(E-127)
e = E - 127
乘除法与位移的关系
本人不是专业搞数学的,可能描述不严谨,但是整体的原理意思是这样的
一个非 0 数乘以 则就是向左移 n 位,除以
就是向右移动 n 位
以 int32 的 1 为例子:
1 的二进制表示为 00000000 00000000 00000000 00000001 为了书写方便,前面的 24 个 0 我们接下来就不写了,但是要知道他是存在的
可以写为
二进制为 00000001<<1 = 00000010
可以写为
二进制为 00000001<<3 = 00001000
我们换个数 11 其二进制(同样没写前面的24个0) 00001011
可以写为
二进制为 0000 1011<<3 = 0101 1000
可以写为
二进制为 0101 100 >>3 = 0000 1011
Unity 中的编码与解码函数
inline float4 EncodeFloatRGBA( float v )
{
float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 16581375.0);
float kEncodeBit = 1.0/255.0;
float4 enc = kEncodeMul * v;
enc = frac (enc);
enc -= enc.yzww * kEncodeBit;
return enc;
}
inline float DecodeFloatRGBA( float4 enc )
{
float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/16581375.0);
return dot( enc, kDecodeDot );
}
适用于0-1的范围内的值的编码
我们通过观察一个 float 的编码过程来看其计算原理
编码过程
-
EncodeFloatRGBA( 0.523)
-
float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 160581375.0); // 声明了一个常数我们来分析一下
//给定的数如下 255 的二进制表示 为 1111 1111 65025 的二进制表示为 1111 1110 0000 0001 16581375 的二进制表示为 1111 1101 0000 0010 1111 1111 //从二进制找不到规律,但是发现他们和255 有关系 65025 = 255 X 255 = 255 ^2 16581375 = 65025 X 255 =255^3 //单个通道的颜色值范围就是 0 - 255 //所以得到的推理结果为 将对应的 (0-1)的数放到 255 倍 255^2倍 255^3倍
-
float kEncodeBit = 1.0/255.0; //后续会有除以255.0操作,这里提前做一个除法,在后续中使用乘法乘以 kEncodeBit 相当于除以了 255,一个计算优化
-
float4 enc = kEncodeMul * v; //我们把值带入看一下计算过程
//将具体值代入 float4 enc = float4(1.0, 255.0, 65025.0, 160581375.0) * 0.523; enc =float4(0.523 , 133.365 , 34008.075 , 8672059.125);
-
enc = frac (enc); //取小数部分,这一步和下一步可能不好理解
enc=frac(float4(0.523 , 133.365 , 34008.075 , 8672059.125)); enc=float4(0.523 , 0.365 , 0.075 ,0.125) //R 通道没有变化,我们从G通道开始看 //133.365 是将 0.523放大了255倍,做 frac 之后我们相当于移除掉了数据中超过1的部分,保存小数部分 ,整数部分应该存在R通道,当然目前R通道保存的还是元数据 //34008.075 是将 0.523放大了255^2 倍,也可以理解为是在 G 通道截取小数之后,再放大 255 倍,然后再移除超过1的部分,然后将剩余小数部分存在 B 通道, //同理 A 通道 是在 B通道截取之后取将剩余小数放大255倍,然后做移除和截取 //到此我们会发现,R通道的保存的小数是最全的,G 通道放大 255 之后损失了部分大于一的部分,而损失的这一部分是可以通过 R 通道的值乘以 255 取整得到的,同理,B 通道损失的整数部分可以通过 G 通道乘以 255 得到,以此类推 //但是目前这个四个通道无法通过简单的缩小相加得到原来的值,因为 R 的小数部分和G是有重合的,G 的小数部分 和 B 是有重合的,所以有了下一步
-
enc -= enc.yzww * kEncodeBit; // 这一步是为了移除每个通道和其后面的通道的重合部分
//为了统一表达 我们令 enc.yzww 等价于 enc.gbaa enc.gbaa*kEncodeBit;//其实是将当前通道缩放回小数状态 //g*kEncodeBit;得到的是 g 保存的小数在 r 通道中的大小,因为 G 是将 r 放大 255 倍之后获取的小数,所以 将 G 缩小 255 倍,就是这部分小数在 r 中的实际大小 , //依次类推,每一个通道除以255(也就是乘以kEncodeBit)都是为了得到当前数据在前一通道的实际小数值 enc -= enc.gbaa*kEncodeBit 展开写为 enc.rgba=enc.rgba-enc.gbaa*kEncodeBit;//实际上就是从前一个通道要保存的数据 移除后一面通道的数据,这样后期解码的时候才可以简单缩放相加得到原始的值 //带入数据 enc.gbaa*kEncodeBit的结果为float4(0.00143,0.00029,0.00049,0.00049)//小数数很长没有全写上,便于表示完整数据,我们使用分数形式来写 enc.rgba=float4(0.523 , 0.365 , 0.075 ,0.125) - float4(0.365/255 ,0.075/255 ,0.125/255 ,0.125/255) enc.rgba=float4(0.523-0.365/255 , 0.365-0.075/255 , 0.075-0.125/255 , 0.125-0.125/255) //这里有一个点,就是 A 通道的数据减了一次自己缩小了255的值,这里正常来讲A 应该是不做操作,它应该包含最后的所有小数部分。在解码的时候我们可以观察到 其确实有损失,但是非常小,就0.523来说大概在小数点后12位会有一点大的偏差,官方可能是为了性能考虑,没有单独再处理 A 分量的问题
解码操作
-
DecodeFloatRGBA(float4(0.523-0.365/255 , 0.365-0.075/255 , 0.075-0.125/255 , 0.125-0.125/255) )
-
float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/16581375.0);//声明一个缩放常数,用来将每个通道的数值缩放回其正常大小值
-
return dot( enc, kDecodeDot ); //做点积操作,然后返回最终结果,这里的最终结果就是解码结果
//先看一下dot的计算方式 float4 A ,B //AB是两个四维变量 dot(A,B)=A.x*B.x+A.y*B.y+A.z*B.z+A.w*B.w //带入数值 计算 dot( enc, kDecodeDot ) dot( enc, kDecodeDot )=(0.523-0.365/255)+(0.365-0.075/255)/255+( 0.075-0.125/255 )/65025+(0.125-0.125/255)/16581375 =0.523-0.365/255+0.365/255-0.075/255/255+0.075/65025-0.125/255/65025+0.125/16581375-0.125/255/16581375 =0.523-0.365/255+0.365/255-0.075/65025+0.075/65025-0.125/16581375+0.125/16581375-0.125/255/16581375 =0.523-0.125/255/16581375 =0.52299999997043694636715154509083//这里有损失 很小的一个损失
图解原理
下方图片来自 https://blog.csdn.net/manipu1a/article/details/121914335
原图作者使用的是2幂次来做的解释,个人感觉和255并没有完全对上,对于这一点还是感觉使用移除正数保留小数部分的说法更准确,如有更好的解释,欢迎留言






上图作者是处理了W分量的
网友评论