unity3d 了解Unitys RGBA浮点编码(EncodeFloatRGBA)

rseugnpd  于 2023-01-13  发布在  其他
关注(0)|答案(2)|浏览(357)

内置的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不会给予兼容的结果。为什么选择这个选项?

2uluyalo

2uluyalo1#

通过检查,Unity代码似乎想要将0.01.0之间的浮点值(不包括1)转换为0.01.0之间的4个浮点值,以便通过乘以255将这些值转换为0到255之间的整数值。
但是,该死,你对这段代码持怀疑态度是真的正确的。它有很多缺陷(但通常产生的结果足够接近,大部分都是可用的)。
它们乘以255而不是256的原因是,它们错误地认为,通过将值保持为浮点数可以得到合理的结果(并计划稍后将浮点数转换为0 - 255取值的整数,就像其他人在注解中提到的那样)。但是,然后他们使用那个frac()调用。你需要把看起来像这样的浮点代码识别为有 * 坏代码气味 * TM。
正确的代码如下所示:

inline float4 EncodeFloatRGBA(float v)
{
    var vi = (uint)(v * (256.0f * 256.0f * 256.0f * 256.0f));
    var ex = (int)(vi / (256 * 256 * 256) % 256);
    var ey = (int)((vi / (256 * 256)) % 256);
    var ez = (int)((vi / (256)) % 256);
    var ew = (int)(vi % 256);
    var e = float4(ex / 255.0f, ey / 255.0f, ez / 255.0f, ew / 255.0f);
    return e;
}

以及

inline float DecodeFloatRGBA(float4 enc) 
{
    var ex = (uint)(enc.x * 255);
    var ey = (uint)(enc.y * 255);
    var ez = (uint)(enc.z * 255);
    var ew = (uint)(enc.w * 255);
    var v = (ex << 24) + (ey << 16) + (ez << 8) + ew;
    return v / (256.0f * 256.0f * 256.0f * 256.0f);
}

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.01.0之间的值,甚至可以对无穷大和NaN进行编码。代码如下所示:

inline float4 EncodeFloatRGBA(float v)
{
    byte[] eb = BitConverter.GetBytes(v);
    if (BitConverter.IsLittleEndian)
    {
        return float4(eb[3] / 255.0f, eb[2] / 255.0f, eb[1] / 255.0f, eb[0] / 255.0f);
    }

    return float4(eb[0] / 255.0f, eb[1] / 255.0f, eb[2] / 255.0f, eb[3] / 255.0f);
}

以及

inline float DecodeFloatRGBA(float4 enc) 
{
    var eb = BitConverter.IsLittleEndian ?
        new[] { (byte)(enc.w * 255), (byte)(enc.z * 255),
                (byte)(enc.y * 255), (byte)(enc.x * 255) } :
        new[] { (byte)(enc.x * 255), (byte)(enc.y * 255),
                (byte)(enc.z * 255), (byte)(enc.w * 255) };
    return BitConverter.ToSingle(eb, 0);
}

**

nkoocmlb

nkoocmlb2#

1.着色器的输出是[0..1]中的浮点数,当存储在RGBA8缓冲区中时,GPU会将其转换为[0..255]中的U8。这就是 *255而不是 *256的来源。使用256是不正确的。
enc -= enc.yzww * kEncodeBit;这条线看起来很奇怪,但实际上是有意义的:这是为了修整较低位并避免舍入。
1.点积实际上正确地重建了原始值。

相关问题