内置的Unity着色器支持将32位RGBA值编码和解码为32位浮点数的技术。这可以通过简单地将每个通道与之前通道的最高可能值相乘来完成。由于它存储在浮点数中,因此预期会损失一些精度。
着色器显然有一些优化去为它,我试图了解。
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 );
}
所以我的问题是:
1.为什么G通道乘以255而不是256(2^8=256),B通道乘以65025而不是65536(2^16=65536),A通道乘以16581375而不是16777216(2^24=16777216)。
1.点积似乎是分数的乘积,所以f = R + 255 * G + 65025 * B + 16581375 * A
不会给予兼容的结果。为什么选择这个选项?
2条答案
按热度按时间2uluyalo1#
通过检查,Unity代码似乎想要将
0.0
和1.0
之间的浮点值(不包括1)转换为0.0
和1.0
之间的4个浮点值,以便通过乘以255将这些值转换为0到255之间的整数值。但是,该死,你对这段代码持怀疑态度是真的正确的。它有很多缺陷(但通常产生的结果足够接近,大部分都是可用的)。
它们乘以255而不是256的原因是,它们错误地认为,通过将值保持为浮点数可以得到合理的结果(并计划稍后将浮点数转换为0 - 255取值的整数,就像其他人在注解中提到的那样)。但是,然后他们使用那个
frac()
调用。你需要把看起来像这样的浮点代码识别为有 * 坏代码气味 * TM。正确的代码如下所示:
以及
Unity代码在给定随机输入的情况下,大约23%的时间无法准确完成往返(如果不使用额外的处理,如在乘以255后舍入编码值,则大约90%的时间会失败)。
请注意,32位浮点型只有23位精度,因此32位RGBA值将有前导或尾随0位。当起始位为0时,您需要使用尾随位的情况可能很少,因此您可能会简化代码,根本不使用
ew
值,而是编码为RGB而不是RGBA。**
总而言之,我发现Unity代码令人不安,因为它试图重新发明我们已经拥有的东西。我们有一个很好的IEEE 754标准,用于将浮点数编码为32位值,RGBA通常至少为32位(Unity代码当然假设它是)。我不确定他们为什么不直接将float放入RGBA(如果你愿意,你仍然可以使用一个中间float4,就像下面的代码那样)。如果你只是把float放入RGBA中,您不必担心23位精度,也不限于
0.0
和1.0
之间的值,甚至可以对无穷大和NaN
进行编码。代码如下所示:以及
**
nkoocmlb2#
1.着色器的输出是[0..1]中的浮点数,当存储在RGBA8缓冲区中时,GPU会将其转换为[0..255]中的U8。这就是 *255而不是 *256的来源。使用256是不正确的。
enc -= enc.yzww * kEncodeBit;
这条线看起来很奇怪,但实际上是有意义的:这是为了修整较低位并避免舍入。1.点积实际上正确地重建了原始值。