Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

RGBA Half textures clamping negative numbers...

Discussion in 'General Graphics' started by bkeesing, Jun 6, 2018.

  1. bkeesing

    bkeesing

    Joined:
    Apr 10, 2017
    Posts:
    15
    I'm using a HDR texture encoded using RGBA Half to store a baked position map. Everything seems to be working fine except where there are negative numbers. It looks as though all negative numbers are being clamped to zero. Here's a pic:

    upload_2018-6-6_9-47-8.png

    You can see that everything within the red line looks fine, but everything outside is being stretched/clamped.
    I've read on the forums that negative numbers should definitely be possible. I have also tried encoding as BC6H but I get the same result. I have tried ticking and unticking "sRGB" in the texture import (which doesn't seem to have any effect). I have head of a mysterious "Bypass sRGB Sampling" option which may solve it, but seems to have been removed in recent releases.

    I am doing all this in Unity 2018.1.

    Does anyone know how to resolve this?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Do I understand correctly that you have an RGBAHalf texture you’re saving to disk and then importing back in? What file format are you saving it to, and are you generating and exporting the file from Unity or with an external application?

    The tiff and psd file formats allow for 16bit unsigned values, that is to say they can’t store negative numbers, though those aren’t formats Unity can write so that’s only useful information if you’re generating the textures in an external application. The OpenEXR format can store negative numbers, but there’s a bug in Unity that it can only import them assuming sRGB color space even if bypass sRGB is checked. It also would not surprise me if it clips negative numbers.

    The BC6H format is also unsigned, so it cannot store negative numbers either. The RGBAHalf texture format can hold negative numbers just fine, but as I mentioned above I’m pretty sure Unity’s texture importer clips negative values even when it shouldn’t.

    Additionally I think there’s a bug with ReadPixels which also clips negative values. So if you’re rendering to an ARGBHalf render texture and want to copy those contents to an RGBAHalf texture you’ll loose the negative values there too.

    The solution is to remap [-1.0, 1.0] ranges to [0.0, 1.0] when you first record the value. If you’re creating these textures in an external application you may need to write your own texture importer using LoadRawTextureData.
     
    captainmashroom likes this.
  3. bkeesing

    bkeesing

    Joined:
    Apr 10, 2017
    Posts:
    15
    Thanks for the reply. I am exporting from a unity script into an EXR file. As far as I can tell, it is exporting correctly (I can open it up in photoshop and fiddling with exposure offset, I can see that negative values exist).
    It seems crazy that there are so many bugs that all prevent you from doing this... seems almost like it was intentional:confused:
    Are there are any bug reports on the issue? I can't find any so I might do up one.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Not for Unity, no. There was some related discussion on the topic for FreeImage, the library Unity uses for importing and exporting files, but the bug was filed in 2015 after the most recent version of that was released, and Unity uses a version of the library from 2008 anyway. That's not to say Unity can't modify their version of the library though.
     
  5. bkeesing

    bkeesing

    Joined:
    Apr 10, 2017
    Posts:
    15
    For anyone else running into this issue, the reply I have received from Unity is that EXR does not support negative values, and it sounds like they have not intention to do so in the near future.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    They're wrong.

    The OpenEXR format conforms to IEEE 754-2008 floating point numbers, which has a sign bit for 16 bit, 32 bit, and 64 bit floating point numbers. So if Unity doesn't support it, it means they or the library they're using hasn't properly implemented reading of the OpenEXR format. However that's not the case, at least with 2018.1 on. I can prove Unity is totally capable of reading and saving EXR floating point images with negative numbers.

    A few days after this post I decided to spend the time to try to get to the bottom of this problem, and to my surprise I cannot reproduce the issues you and others have had with HDR values getting clamped, at least when properly using Color and floating point texture formats.

    In this thread I posted a script that creates a floating point texture with negative values and passes it through a battery of tests, including saving out an EXR at the end.
    https://forum.unity.com/threads/how-to-write-negative-values-to-render-textures.536963/#post-3538664

    The resulting image has negative values stored within, which can be confirmed by looking at the values in an external program that can read floating point EXR files, or even by importing into Unity and setting the compression to None (no compressed texture format supports negative values).


    That doesn't exclude the possibility that there's some bug with how Unity is handling OpenEXR format files, and the file you're saving out from an external program is for some reason not being properly handled by Unity. I was able to open up an EXR saved from Unity in Photoshop using the OpenEXR plugin and resave it as a new file and still have it work. If that's the problem, doing that may possibly be a workaround.
     
    Kalificus likes this.
  7. bkeesing

    bkeesing

    Joined:
    Apr 10, 2017
    Posts:
    15
    Sorry, I think I worded it a bit wrong. Their actual response was this:

    I did export the file from unity and it did have the correct values when I imported it into photoshop (as you have said), but when I import them back into unity, the negative values are clamped.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Try this. This an exr file produced by the script I linked to above, reimported into Unity and using a shader to validate negative values. In 2018.1 the bottom half of the material shows red where the values are negative.
     

    Attached Files:

  9. SoyUnBonus

    SoyUnBonus

    Joined:
    Jan 19, 2015
    Posts:
    42
    I've tried this as I'm trying to use EXR to store some transforms.

    And in 2018.2.3f1, dropping the files in the zip onto a project and creating a cube with the NegativeValueTest material, shows white on top, and black on the lower half (I guess it goes from 1 to -1 so the bottom should be red).

    So it seems there are no negative values there :(
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    I was totally flummoxed by this. In an existing project running 2018.1.0f2 I have it properly handling negative values for exr files.

    Here's what the material looks like in that project.
    upload_2018-9-4_11-4-20.png

    Copying the texture, shader, and material assets from that project to a new project running 2018.1.0f2 or 2018.2.6f1 instead showed what everyone else sees, which is this:
    upload_2018-9-4_11-4-37.png

    It's totally not working!


    So I poked around a little to try to figure out what was different. The new projects had defaulted to use Gamma Color Space. Switching it to use Linear color space, like it should be defaulting to, produced the expected top image with negative values properly being sampled.
     
    AlfieBooth likes this.
  11. SoyUnBonus

    SoyUnBonus

    Joined:
    Jan 19, 2015
    Posts:
    42
    I can't check right now (we're polishing a release build) but we always use linear color space in our games. I ended up saving the Texture2D into a ".asset" file and that worked fine.

    But I totally agree that "linear" should be the default value nowadays.

    Thanks for taking some time to check the issue!
     
  12. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,445
    Unity's EXR saving/loading code is not really workable for data. It will Gamma correct the image, clamp it, etc, so you can't use it to store linear data correctly, for one. I also really wish the format names were more clear, offering prefixes for signed/unsigned, ranges, etc.

    My workaround was to use the new ScriptableAssetImport pipeline to save my data in my own format, and have it deserialize into a Texture2D automatically. This way it looks like any other texture to Unity, but doesn't actually need to use an image format like EXR.
     
  13. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,873
    Independently figured this out myself today.

    TL;DR: Unity's texture importer is still broken in Unity 2019. Appears to still be broken all versions through 2019.7. Create a new project, and BAM! texture importer automatically can't import textures. Great :).

    I've filed a bug, I expect (given they didn't fix it years ago!) it'll come back as "BY DESIGN: we want a broken importer that corrupts incoming files" (do I sound bitter? :) ...because I am!).
     
    AlfieBooth and SoyUnBonus like this.
  14. AlfieBooth

    AlfieBooth

    Joined:
    Dec 2, 2013
    Posts:
    31
    Thank you bgolus and a436t4ataf..

    Switching the color space of the project to linear allowed the accurate storing of vector coordinates into an EXR that could be read back again.. this one was a headache.
     
  15. ferretnt

    ferretnt

    Joined:
    Apr 10, 2012
    Posts:
    405
    This is still, I would argue, broken in 2020.3, in the sense that if you have your editor platform set to Standalone and color space to Linear, then actually interrogating min/max values during AssetImporter.OnPostProcessTexture() will return negative values.

    However, if you switch to WebGL (even with linear color space, webgl 2.0 only) the data will be clamped to a minimum of 0 long before TextureImporter.OnPostProcessTexture, no matter what you force the format to in OnPreProcessTexture.

    I would argue that full, unclamped values should make it as far as the stage of writing texture data, or at least somewhere we can extract original range info before quantizing on any platform.

    That said, the scriptedImporter approach also works.
     
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    I don't think anyone would argue this wasn't still broken, apart from maybe Unity themselves. Or at least argue it's not broken enough for them to consider spending the time to fix. Sadly most of us that care have just found work arounds to the problem at this point. Gamma space rendering is generally considered legacy and I don't think a lot of time is spent thinking about it anymore. WebGL 2.0 in linear space having the issue may in fact be a big enough bug to warrant them fixing it though, as it suggests they're gamma correcting HDR images when they shouldn't be.
     
  17. ferretnt

    ferretnt

    Joined:
    Apr 10, 2012
    Posts:
    405
    OK, I'll file a bug specifically about WebGL 2.0 behaviour in linear colour space not matching standalone in linear colour space.

    (But in the meantime I've written a scriptedimporter anyway.)
     
  18. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,445
    If there was a decent enough image file loader for c# that could be used to read the formats, it would be fairly easy to write a new import pipeline for textures using it with modified extensions. I wish the importer was written this way anyway, as there’s a lot you can do to the raw bits before they get quantize into 8bit to improve quality.
     
  19. Coredumping

    Coredumping

    Joined:
    Dec 17, 2014
    Posts:
    51
    For anyone stumbling on this issue like me, I used the first C# EXR loader I found on github and converted the resulting floats to bytes and uploaded it to an RGBAFloat Texture2D and it worked first try!

    The resulting code ended up looking like this, it's not performant or pretty, but just to give you an idea of the process:

    Code (CSharp):
    1. EXRFile exr;
    2. using (var str = File.OpenRead("Assets\\image.exr"))
    3.     {
    4.     exr = EXRFile.FromStream(str);
    5.     }
    6. using (var str = File.OpenRead("Assets\\image.exr"))
    7.     {
    8.     exr.Parts[0].Open(str);
    9.     }
    10. var floats = exr.Parts[0].GetFloats(ChannelConfiguration.RGB, false, GammaEncoding.Linear);
    11. var data = new byte[sizeof(float) * floats.Length];
    12. for (int i = 0; i < floats.Length; i++)
    13.     {
    14.     var d = BitConverter.GetBytes(floats[i]);
    15.     for (int j = 0; j < sizeof(float); j++)
    16.         {
    17.         data[i * sizeof(float) + j] = d[j];
    18.         }
    19.     }
    20. var t = new Texture2D(exr.Parts[0].DataWindow.Width, exr.Parts[0].DataWindow.Height, TextureFormat.RGBAFloat, false);
    21. t.LoadRawTextureData(data);
     
    SoyUnBonus likes this.