【问题标题】:Understanding Unitys RGBA encoding in float (EncodeFloatRGBA)了解浮点中的 Unitys RGBA 编码 (EncodeFloatRGBA)
【发布时间】:2018-01-07 23:17:00
【问题描述】:

内置的 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)。
  2. 点积似乎与分数相乘,因此f = R + 255 * G + 65025 * B + 16581375 * A 不会给出兼容的结果。为什么选择这个?

【问题讨论】:

  • 据我了解 255 * 255 = 65025,它可能会为您提供第一个问题的线索,但我不明白第二个问题:D
  • 对,但是每个通道有 256 种颜色——不是 255 种。:)
  • 正如我所说,我看到它乘以 255 而不是 256。 :P
  • 好的,stanlo's answer here 应该可以帮助您。我猜这是因为您可以解释的 8 位最大数字是 255,因此它们的每个通道的范围从 0 到 255(总共 256 个阴影)。甚至 unity 的颜色选择器工具的范围都是 [0, 255]。
  • 但在底部的同一个线程中,您可以在公式中看到使用 256 的计算,这可以正常工作,完全搞砸了我的观点:D

标签: unity3d shader


【解决方案1】:

从检查来看,Unity 代码看起来想要将介于 0.01.0(不包括 1)之间的浮点值转换为介于 0.01.0 之间的 4 个浮点值,这样那些值可以通过乘以 255 转换为 0 到 255 的整数值。

但是,该死,你对这段代码持怀疑态度是正确的。它有很多缺陷(但通常会产生足够接近的结果,几乎可以使用)。

他们乘以 255 而不是 256 的原因是因为他们错误地认为通过将值保持为浮点数可以获得合理的结果(并计划稍后将浮点数转换为 0-255 值的整数,就像其他人一样在 cmets 中提到过)。但是,然后他们使用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。


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

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);
}

【讨论】:

  • 不将浮点数直接放入 RGBA 纹理的 32 位的一个原因是插值纹理访问会破坏您的结果。通过将四个字节视为 base-256 数字,纹理的线性插值也可以正确插值表示的浮点数。
  • 你使用的那些类型真的存在于 hlsl 或 cg 中吗?如果不是,那么你的咆哮有点毫无意义,不是吗。对不起,死灵术。尽管现在读到这里的人应该知道。
猜你喜欢
  • 1970-01-01
  • 2023-03-11
  • 2017-02-23
  • 1970-01-01
  • 2011-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多