Render textures, assuming you’re using a float or half format, can have negative values written to and read from. Make sure your render texture is one of those formats, and that the Texture2D you’re copying the values to with ReadPixels is the matching format (For example RenderTextureFormat.ARGBHalf and TextureFormat.RGBAHalf).
Here’s a very simple test script I wrote up to confirm this all works. This tests to make sure Color values aren’t being clamped when set, SetPixels() on a floating point texture don’t get clamped, writing to a render texture with a shader doesn’t clamp the values, ReadPixels() from a floating point render texture to a floating point texture doesn’t clamp the values, and writing out an EXR file doesn’t clamp the values.
using UnityEngine;
using System.Collections;
using System.IO;
public class NegativeEXRTest : MonoBehaviour
{
public int resolutionX = 16;
public int resolutionY = 16;
public TextureFormat tex2DFormat = TextureFormat.RGBAHalf;
public RenderTextureFormat rtFormat = RenderTextureFormat.ARGBHalf;
public Texture2D.EXRFlags exrFlags = Texture2D.EXRFlags.None;
[ContextMenu("Do Test")]
public void DoTest()
{
// Create an array of colors that has RGB values from -1.0 to 1.0
Color[] colors = new Color[resolutionX * resolutionY];
int numPixels = resolutionX * resolutionY;
for (int i=0; i<numPixels; i++)
{
float a = ((float)i / (float)(numPixels - 1)) * 2f - 1f;
colors[i] = new Color(a, a, a, 1f);
}
// Create Texture2D and set pixels colors
var tex = new Texture2D(resolutionX, resolutionY, tex2DFormat, false, true);
tex.SetPixels(colors, 0);
tex.Apply();
// Create RenderTexture
RenderTexture rt = new RenderTexture(resolutionX, resolutionY, 0, rtFormat, RenderTextureReadWrite.Linear);
rt.Create();
// Copy Texture2D to RenderTexture
// It would be faster to use CopyTexture, but Blit() works by rendering the source texture into the destination
// render texture with a simple unlit shader.
Graphics.Blit(tex, rt);
// Read RenderTexture contents into a new Texture2D using ReadPixels
var texReadback = new Texture2D(resolutionX, resolutionY, tex2DFormat, false, true);
Graphics.SetRenderTarget(rt);
texReadback.ReadPixels(new Rect(0, 0, resolutionX, resolutionY), 0, 0, false);
Graphics.SetRenderTarget(null);
texReadback.Apply();
// Save out EXR file to project's root folder (outside of assets)
byte[] bytes = texReadback.EncodeToEXR(exrFlags);
File.WriteAllBytes(Application.dataPath + "/../Negative EXR Test.exr", bytes);
Debug.Log("Saved texture to: " + Application.dataPath + "/../Negative EXR Test.exr");
// Destroy texture objects
Object.DestroyImmediate(tex);
Object.DestroyImmediate(texReadback);
Object.DestroyImmediate(rt);
}
}
Add this to a game object and right click on the component, then select the Do Test option. It’ll save out an exr file with a range of RGB values from -1.0 to 1.0. I confirmed this works with 2018.1.0f2. You can play around with the format and exr compression settings to see how that breaks things if you want. However setting the Exr Flags = Compress PIZ option crashes the editor, so don’t do that.