理解Unity中的Unity RGBA编码(EncodeFloatRGBA)

时间:2017-01-10 10:00:10

标签: unity3d shader

内置Unity着色器支持将32位RGBA值编码和解码为32位浮点数的技术。这可以通过简单地将每个通道与其之前的通道的最高可能值相乘来完成。由于存储在浮点数中,因此预计会有一些精度损失。

着色器显然有一些优化,我试图理解。

UnityCG.cginc代码中的着色器如下所示:

getHash

所以我的问题:

  1. 为什么G通道乘以255而不是256(2 ^ 8 = 256),B通道乘以65025而不是65536(2 ^ 16 = 65536),A通道16581375而不是16777216(2 ^ 24 = 16777216)。
  2. 点积似乎与分数相乘,因此template <typename T, int (T::*hash)[3]> static HashStructure& getHash(T& t, int index) { return (t.*hash)[index]; } 不会给出兼容的结果。为什么选择这个?

1 个答案:

答案 0 :(得分:3)

从检查开始,Unity代码看起来好像要将0.01.0之间的浮点值(不包括1)转换为0.0之间的4个浮点值。 1.0使得这些值可以通过乘以255转换为0到255之间的整数值。

但是,dang,你对这段代码持怀疑态度是对的。它有许多缺陷(但通常产生的结果足够接近,大部分都可用)。

它们乘以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%的时间失败)。上面的代码在100%的时间内都有效。

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

<强>&LT;咆哮&GT;
总而言之,我发现Unity代码令人不安,因为它试图重新发明我们已有的东西。我们有一个很好的IEEE 754标准,用于将浮点数编码为32位值,RGBA通常至少为32位(Unity代码当然假设它是)。我不确定为什么他们不只是将浮动放入RGBA(如果你愿意,你仍然可以使用中间的float4代码如下所示)。如果您只是将浮点放入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);
}

<强>&LT; /咆哮&GT;