了解float中的Unitys RGBA编码(EncodeFloatRGBA)

Ted*_*sen 5 shader unity-game-engine

内置的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 );
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:

  1. 为什么G通道乘以255而不是256(2 ^ 8 = 256),B通道乘以65025而不是65536(2 ^ 16 = 65536)和A通道16581375而不是16777216(2 ^ 24 = 16777216) )。
  2. 点积似乎与分数相乘,因此f = R + 255 * G + 65025 * B + 16581375 * A不会给出兼容的结果。为什么选择这个?

mhe*_*man 5

从检查,统一代码看起来像它要转换是之间浮点值0.01.0(不包括1),它们之间成4浮点值0.01.0使得这些值可以通过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;
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

在给定随机输入的情况下,Unity 代码在大约 23% 的时间内无法准确地执行往返(如果您不使用额外的处理,例如在乘以 255 后对编码值进行舍入,则大约有 90% 的时间会失败)。上面的代码在 100% 的时间里都有效。

请注意,32 位浮点数只有 23 位精度,因此 32 位 RGBA 值将具有前导或尾随 0 位。当您在开始时有 0 时,您关心使用尾随位的情况可能很少,因此您可以简化代码以根本不使用这些ew值并编码为 RGB 而不是 RGBA。

<rant>
总而言之,我发现 Unity 代码令人不安,因为它试图重新发明我们已经拥有的东西。我们有一个很好的 IEEE 754 标准,用于将浮点数编码为 32 位值,而 RGBA 通常至少为 32 位(Unity 代码当然假设它是)。我不知道为什么他们不只是将浮点数放入 RGBA(如果需要,您仍然可以像下面的代码那样使用中间浮点数)。如果你只是把浮到RGBA,你不必了解的精度23位烦恼,你不局限于之间的值0.01.0。您甚至可以对无穷大和NaNs进行编码。该代码如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

</rant>