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

内置的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 );
}

所以我的问题:

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

最佳答案 从检查开始,Unity代码看起来好像要将介于0.0和1.0之间的浮点值(不包括1)转换为介于0.0和1.0之间的4个浮点值,以便这些值可以转换为0到255之间的整数值乘以255.

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

它们乘以255而不是256的原因是因为他们错误地认为他们可以通过将值保持为浮点数来获得合理的结果(并且计划在稍后的时间将浮点数转换为0-255个有价值的整数,正如其他人提到的那样)评论).但是,然后他们使用那个frac()调用.您需要识别看起来像具有错误代码smellTM的浮点代码.

正确的代码看起来像这样:

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.0到1.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);
}

< /咆哮>

点赞