Search Unity

Float texture using EncodeToEXR save to .exr not the same value

Discussion in 'Scripting' started by flyer19, Sep 13, 2019.

  1. flyer19

    flyer19

    Joined:
    Aug 26, 2016
    Posts:
    126
  2. flyer19

    flyer19

    Joined:
    Aug 26, 2016
    Posts:
    126
  3. doctorpangloss

    doctorpangloss

    Joined:
    Feb 20, 2013
    Posts:
    270
    That's definitely a bug :)

    As a workaround, encode signed floats to unsigned by using the 2nd to last highest order bit as the sign bit. In other words:

    Actual value that does not store:

    100011 (-3)

    Value that stores:
    000011 (3)

    Your New Value:
    010011 (3 + 16 = 19)
    --^ use this bit as sign bit
     
    flyer19 likes this.
  4. flyer19

    flyer19

    Joined:
    Aug 26, 2016
    Posts:
    126
    waiting for unity team fixed this.
     
  5. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,073
    Actually I have had problem with unity EXR encoding, was struggling with it for a few days scratching my head what I'm doing wrong.
    In the end I have used MiniExr script for saving exr files and it worked like a charm for me.
     
  6. stefan562

    stefan562

    Joined:
    Feb 21, 2022
    Posts:
    1
    Can you give me a link or code I cant find it and it would really help me
     
  7. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,073
    I could not find the link. So here is the code.

    Code (CSharp):
    1. // MiniEXR 2013 by Aras Pranckevicius / Unity Technologies.
    2. //
    3. // C# conversion by Ilya Suzdalnitski.
    4. //
    5. // Writes OpenEXR RGB files out of half-precision RGBA or RGB data.
    6. //
    7.  
    8. using UnityEngine;
    9.  
    10. namespace MiniEXR
    11. {
    12.     //Based on source-forge project: http://sourceforge.net/projects/csharp-half/
    13.     internal static class HalfHelper
    14.     {
    15.         private static uint[] mantissaTable = GenerateMantissaTable();
    16.         private static uint[] exponentTable = GenerateExponentTable();
    17.         private static ushort[] offsetTable = GenerateOffsetTable();
    18.         private static ushort[] baseTable = GenerateBaseTable();
    19.         private static sbyte[] shiftTable = GenerateShiftTable();
    20.  
    21.         // Transforms the subnormal representation to a normalized one.
    22.         private static uint ConvertMantissa(int i) {
    23.             uint m = (uint)(i << 13); // Zero pad mantissa bits
    24.             uint e = 0; // Zero exponent
    25.  
    26.             // While not normalized
    27.             while ((m & 0x00800000) == 0) {
    28.                 e -= 0x00800000; // Decrement exponent (1<<23)
    29.                 m <<= 1; // Shift mantissa              
    30.             }
    31.             m &= unchecked((uint)~0x00800000); // Clear leading 1 bit
    32.             e += 0x38800000; // Adjust bias ((127-14)<<23)
    33.             return m | e; // Return combined number
    34.         }
    35.  
    36.         private static uint[] GenerateMantissaTable() {
    37.             uint[] mantissaTable = new uint[2048];
    38.             mantissaTable[0] = 0;
    39.             for (int i = 1; i < 1024; i++) {
    40.                 mantissaTable[i] = ConvertMantissa(i);
    41.             }
    42.             for (int i = 1024; i < 2048; i++) {
    43.                 mantissaTable[i] = (uint)(0x38000000 + ((i - 1024) << 13));
    44.             }
    45.  
    46.             return mantissaTable;
    47.         }
    48.         private static uint[] GenerateExponentTable() {
    49.             uint[] exponentTable = new uint[64];
    50.             exponentTable[0] = 0;
    51.             for (int i = 1; i < 31; i++) {
    52.                 exponentTable[i] = (uint)(i << 23);
    53.             }
    54.             exponentTable[31] = 0x47800000;
    55.             exponentTable[32] = 0x80000000;
    56.             for (int i = 33; i < 63; i++) {
    57.                 exponentTable[i] = (uint)(0x80000000 + ((i - 32) << 23));
    58.             }
    59.             exponentTable[63] = 0xc7800000;
    60.  
    61.             return exponentTable;
    62.         }
    63.         private static ushort[] GenerateOffsetTable() {
    64.             ushort[] offsetTable = new ushort[64];
    65.             offsetTable[0] = 0;
    66.             for (int i = 1; i < 32; i++) {
    67.                 offsetTable[i] = 1024;
    68.             }
    69.             offsetTable[32] = 0;
    70.             for (int i = 33; i < 64; i++) {
    71.                 offsetTable[i] = 1024;
    72.             }
    73.  
    74.             return offsetTable;
    75.         }
    76.         private static ushort[] GenerateBaseTable() {
    77.             ushort[] baseTable = new ushort[512];
    78.             for (int i = 0; i < 256; ++i) {
    79.                 sbyte e = (sbyte)(127 - i);
    80.                 if (e > 24) { // Very small numbers map to zero
    81.                     baseTable[i | 0x000] = 0x0000;
    82.                     baseTable[i | 0x100] = 0x8000;
    83.                 } else if (e > 14) { // Small numbers map to denorms
    84.                     baseTable[i | 0x000] = (ushort)(0x0400 >> (18 + e));
    85.                     baseTable[i | 0x100] = (ushort)((0x0400 >> (18 + e)) | 0x8000);
    86.                 } else if (e >= -15) { // Normal numbers just lose precision
    87.                     baseTable[i | 0x000] = (ushort)((15 - e) << 10);
    88.                     baseTable[i | 0x100] = (ushort)(((15 - e) << 10) | 0x8000);
    89.                 } else if (e > -128) { // Large numbers map to Infinity
    90.                     baseTable[i | 0x000] = 0x7c00;
    91.                     baseTable[i | 0x100] = 0xfc00;
    92.                 } else { // Infinity and NaN's stay Infinity and NaN's
    93.                     baseTable[i | 0x000] = 0x7c00;
    94.                     baseTable[i | 0x100] = 0xfc00;
    95.                 }
    96.             }
    97.  
    98.             return baseTable;
    99.         }
    100.         private static sbyte[] GenerateShiftTable() {
    101.             sbyte[] shiftTable = new sbyte[512];
    102.             for (int i = 0; i < 256; ++i) {
    103.                 sbyte e = (sbyte)(127 - i);
    104.                 if (e > 24) { // Very small numbers map to zero
    105.                     shiftTable[i | 0x000] = 24;
    106.                     shiftTable[i | 0x100] = 24;
    107.                 } else if (e > 14) { // Small numbers map to denorms
    108.                     shiftTable[i | 0x000] = (sbyte)(e - 1);
    109.                     shiftTable[i | 0x100] = (sbyte)(e - 1);
    110.                 } else if (e >= -15) { // Normal numbers just lose precision
    111.                     shiftTable[i | 0x000] = 13;
    112.                     shiftTable[i | 0x100] = 13;
    113.                 } else if (e > -128) { // Large numbers map to Infinity
    114.                     shiftTable[i | 0x000] = 24;
    115.                     shiftTable[i | 0x100] = 24;
    116.                 } else { // Infinity and NaN's stay Infinity and NaN's
    117.                     shiftTable[i | 0x000] = 13;
    118.                     shiftTable[i | 0x100] = 13;
    119.                 }
    120.             }
    121.  
    122.             return shiftTable;
    123.         }
    124.  
    125.         public static float HalfToSingle(ushort half) {
    126.             uint result = mantissaTable[offsetTable[half >> 10] + (half & 0x3ff)] + exponentTable[half >> 10];
    127.  
    128.             return System.BitConverter.ToSingle(System.BitConverter.GetBytes(result), 0);
    129.  
    130.             //return *((float*)&result);
    131.         }
    132.         public static ushort SingleToHalf(float single) {
    133.             //uint value = *((uint*)&single);
    134.  
    135.             uint value = System.BitConverter.ToUInt32(System.BitConverter.GetBytes(single), 0);
    136.  
    137.             ushort result = (ushort)(baseTable[(value >> 23) & 0x1ff] + ((value & 0x007fffff) >> shiftTable[value >> 23]));
    138.             return result;
    139.         }
    140.     }
    141.  
    142.     public static class MiniEXR
    143.     {
    144.  
    145.         // Writes EXR into a memory buffer.
    146.         // Input:
    147.         //   - (width) x (height) image,
    148.         //   - channels=4: 8 bytes per pixel (R,G,B,A order, 16 bit float per channel; alpha ignored), or
    149.         //   - channels=3: 6 bytes per pixel (R,G,B order, 16 bit float per channel).
    150.         // Returns memory buffer with .EXR contents and buffer size in outSize. free() the buffer when done with it.
    151.  
    152.         public static void MiniEXRWrite(string _filePath, uint _width, uint _height, uint _channels, float[] _rgbaArray) {
    153.             System.IO.File.WriteAllBytes(_filePath, MiniEXRWrite(_width, _height, _channels, _rgbaArray));
    154.         }
    155.  
    156.         public static void MiniEXRWrite(string _filePath, uint _width, uint _height, Color[] _colorArray) {
    157.             System.IO.File.WriteAllBytes(_filePath, MiniEXRWrite(_width, _height, _colorArray));
    158.         }
    159.  
    160.         public static byte[] MiniEXRWrite(uint _width, uint _height, Color[] _colorArray) {
    161.             float[] rgbaArray = new float[_colorArray.Length * 3];
    162.  
    163.             for (int i = 0; i < _colorArray.Length; i++) {
    164.                 rgbaArray[i * 3] = _colorArray[i].r;
    165.                 rgbaArray[i * 3 + 1] = _colorArray[i].g;
    166.                 rgbaArray[i * 3 + 2] = _colorArray[i].b;
    167.             }
    168.  
    169.             return MiniEXRWrite(_width, _height, 3, rgbaArray);
    170.         }
    171.  
    172.         public static byte[] MiniEXRWrite(uint _width, uint _height, uint _channels, float[] _rgbaArray) {
    173.             //const void* rgba16f
    174.             uint ww = _width - 1;
    175.             uint hh = _height - 1;
    176.             byte[] kHeader = {
    177.                 0x76, 0x2f, 0x31, 0x01, // magic
    178.                 2, 0, 0, 0, // version, scanline
    179.                 // channels
    180.                 (byte)'c',(byte)'h',(byte)'a',(byte)'n',(byte)'n',(byte)'e',(byte)'l',(byte)'s',0,
    181.                 (byte)'c',(byte)'h',(byte)'l',(byte)'i',(byte)'s',(byte)'t',0,
    182.                 55,0,0,0,
    183.                 (byte)'B',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // R, half
    184.                 (byte)'G',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // G, half
    185.                 (byte)'R',0, 1,0,0,0, 0, 0,0,0,1,0,0,0,1,0,0,0, // B, half
    186.                 0,
    187.                 // compression
    188.                 (byte)'c',(byte)'o',(byte)'m',(byte)'p',(byte)'r',(byte)'e',(byte)'s',(byte)'s',(byte)'i',(byte)'o',(byte)'n',0,
    189.                 (byte)'c',(byte)'o',(byte)'m',(byte)'p',(byte)'r',(byte)'e',(byte)'s',(byte)'s',(byte)'i',(byte)'o',(byte)'n',0,
    190.                 1,0,0,0,
    191.                 0, // no compression
    192.                 // dataWindow
    193.                 (byte)'d',(byte)'a',(byte)'t',(byte)'a',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',0,
    194.                 (byte)'b',(byte)'o',(byte)'x',(byte)'2',(byte)'i',0,
    195.                 16,0,0,0,
    196.                 0,0,0,0,0,0,0,0,
    197.                 (byte)(ww&0xFF), (byte)((ww>>8)&0xFF), (byte)((ww>>16)&0xFF), (byte)((ww>>24)&0xFF),
    198.                 (byte)(hh&0xFF), (byte)((hh>>8)&0xFF), (byte)((hh>>16)&0xFF), (byte)((hh>>24)&0xFF),
    199.                 // displayWindow
    200.                 (byte)'d',(byte)'i',(byte)'s',(byte)'p',(byte)'l',(byte)'a',(byte)'y',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',0,
    201.                 (byte)'b',(byte)'o',(byte)'x',(byte)'2',(byte)'i',0,
    202.                 16,0,0,0,
    203.                 0,0,0,0,0,0,0,0,
    204.                 (byte)(ww&0xFF), (byte)((ww>>8)&0xFF), (byte)((ww>>16)&0xFF), (byte)((ww>>24)&0xFF),
    205.                 (byte)(hh&0xFF), (byte)((hh>>8)&0xFF), (byte)((hh>>16)&0xFF), (byte)((hh>>24)&0xFF),
    206.                 // lineOrder
    207.                 (byte)'l',(byte)'i',(byte)'n',(byte)'e',(byte)'O',(byte)'r',(byte)'d',(byte)'e',(byte)'r',0,
    208.                 (byte)'l',(byte)'i',(byte)'n',(byte)'e',(byte)'O',(byte)'r',(byte)'d',(byte)'e',(byte)'r',0,
    209.                 1,0,0,0,
    210.                 0, // increasing Y
    211.                 // pixelAspectRatio
    212.                 (byte)'p',(byte)'i',(byte)'x',(byte)'e',(byte)'l',(byte)'A',(byte)'s',(byte)'p',(byte)'e',(byte)'c',(byte)'t',(byte)'R',(byte)'a',(byte)'t',(byte)'i',(byte)'o',0,
    213.                 (byte)'f',(byte)'l',(byte)'o',(byte)'a',(byte)'t',0,
    214.                 4,0,0,0,
    215.                 0,0,0x80,0x3f, // 1.0f
    216.                 // screenWindowCenter
    217.                 (byte)'s',(byte)'c',(byte)'r',(byte)'e',(byte)'e',(byte)'n',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',(byte)'C',(byte)'e',(byte)'n',(byte)'t',(byte)'e',(byte)'r',0,
    218.                 (byte)'v',(byte)'2',(byte)'f',0,
    219.                 8,0,0,0,
    220.                 0,0,0,0, 0,0,0,0,
    221.                 // screenWindowWidth
    222.                 (byte)'s',(byte)'c',(byte)'r',(byte)'e',(byte)'e',(byte)'n',(byte)'W',(byte)'i',(byte)'n',(byte)'d',(byte)'o',(byte)'w',(byte)'W',(byte)'i',(byte)'d',(byte)'t',(byte)'h',0,
    223.                 (byte)'f',(byte)'l',(byte)'o',(byte)'a',(byte)'t',0,
    224.                 4,0,0,0,
    225.                 0,0,0x80,0x3f, // 1.0f
    226.                 // end of header
    227.                 0,
    228.             };
    229.  
    230.             uint kHeaderSize = (uint)kHeader.Length;
    231.  
    232.             uint kScanlineTableSize = 8 * _height;
    233.             uint pixelRowSize = _width * 3 * 2;
    234.             uint fullRowSize = pixelRowSize + 8;
    235.  
    236.             uint bufSize = kHeaderSize + kScanlineTableSize + _height * fullRowSize;
    237.  
    238.             byte[] buf = new byte[bufSize];
    239.  
    240.             // copy in header
    241.  
    242.             int bufI = 0;
    243.  
    244.             for (int i = 0; i < kHeaderSize; i++) {
    245.                 buf[bufI] = kHeader[i];
    246.  
    247.                 bufI++;
    248.             }
    249.  
    250.             // line offset table
    251.             uint ofs = kHeaderSize + kScanlineTableSize;
    252.             for (int y = 0; y < _height; ++y) {
    253.                 buf[bufI++] = (byte)(ofs & 0xFF);
    254.                 buf[bufI++] = (byte)((ofs >> 8) & 0xFF);
    255.                 buf[bufI++] = (byte)((ofs >> 16) & 0xFF);
    256.                 buf[bufI++] = (byte)((ofs >> 24) & 0xFF);
    257.                 buf[bufI++] = 0;
    258.                 buf[bufI++] = 0;
    259.                 buf[bufI++] = 0;
    260.                 buf[bufI++] = 0;
    261.  
    262.                 ofs += fullRowSize;
    263.             }
    264.  
    265.             //Convert float to half float
    266.             ushort[] srcHalf = new ushort[_rgbaArray.Length];
    267.  
    268.             for (int i = 0; i < _rgbaArray.Length; i++) {
    269.                 //Gamma encode before converting
    270.                 _rgbaArray[i] = Mathf.Pow(_rgbaArray[i], 2.2f);
    271.                 srcHalf[i] = HalfHelper.SingleToHalf(_rgbaArray[i]);
    272.             }
    273.  
    274.             uint srcDataI = 0;
    275.  
    276.             for (int y = 0; y < _height; ++y) {
    277.                 // coordinate
    278.                 buf[bufI++] = (byte)(y & 0xFF);
    279.                 buf[bufI++] = (byte)((y >> 8) & 0xFF);
    280.                 buf[bufI++] = (byte)((y >> 16) & 0xFF);
    281.                 buf[bufI++] = (byte)((y >> 24) & 0xFF);
    282.                 // data size
    283.                 buf[bufI++] = (byte)(pixelRowSize & 0xFF);
    284.                 buf[bufI++] = (byte)((pixelRowSize >> 8) & 0xFF);
    285.                 buf[bufI++] = (byte)((pixelRowSize >> 16) & 0xFF);
    286.                 buf[bufI++] = (byte)((pixelRowSize >> 24) & 0xFF);
    287.                 // B, G, R
    288.                 //memcpy (ptr, src, width*6);    //Copy first line - 6 bits, 2 bits per channel
    289.  
    290.  
    291.                 //First copy a line of B
    292.                 uint tempSrcI = srcDataI;
    293.                 for (int x = 0; x < _width; ++x) {
    294.                     //Blue
    295.                     byte[] halfBytes = System.BitConverter.GetBytes(srcHalf[tempSrcI + 2]);
    296.                     buf[bufI++] = halfBytes[0];
    297.                     buf[bufI++] = halfBytes[1];
    298.  
    299.                     tempSrcI += _channels;
    300.                 }
    301.  
    302.                 //Then copy a line of G
    303.                 tempSrcI = srcDataI;
    304.                 for (int x = 0; x < _width; ++x) {
    305.                     //Blue
    306.                     byte[] halfBytes = System.BitConverter.GetBytes(srcHalf[tempSrcI + 1]);
    307.                     buf[bufI++] = halfBytes[0];
    308.                     buf[bufI++] = halfBytes[1];
    309.  
    310.                     tempSrcI += _channels;
    311.                 }
    312.  
    313.                 //Finally copy a line of R
    314.                 tempSrcI = srcDataI;
    315.                 for (int x = 0; x < _width; ++x) {
    316.                     //Blue
    317.                     byte[] halfBytes = System.BitConverter.GetBytes(srcHalf[tempSrcI]);
    318.                     buf[bufI++] = halfBytes[0];
    319.                     buf[bufI++] = halfBytes[1];
    320.  
    321.                     tempSrcI += _channels;
    322.                 }
    323.  
    324.                 srcDataI += _width * _channels;
    325.             }
    326.  
    327.             return buf;
    328.         }
    329.     }
    330. }