Render Target Precision

So I was experimenting with the render targets and I came across something odd. If I pack a value into say the specular channels of render target 1, I do not get the same value in the deferred shader that reads from the gbuffer.

For example, in the surface shader deferred lighting function, if I put (252.0/255.0) into the specular channels, and read this in the internal deferred shader and print it (ofcourse accounting for precise color space correction), I get back 253/255 as the color. However if inside the deferred shader I simply return (252.0 / 255.0) then I get the exact 252 color. I checked a wide variety of numbers and at the larger values, every other value is skipped. That is to say I can pass 253/251/249/247/245/243 etc, but not the in between values, and even if I try and put decimal values in between 251 and 253 into the gbuffer, I cannot get 252 on the other side even though its 8 bit precision. This however does not occur for darker colors.

Is this something intended? Is it some kind of mechanism to offset quantization error in the darker regions, or am I understanding/doing something wrong? It seems odd that I literally cannot pass the value 252 into the deferred shader when using an 8 bit precision render target.

Any help or insight would be appreciated!

Suppose my guess was correct, it felt a lot like the quantization errors that happen with bright colors due to linear to sRGB color space conversion. Switching to gamma color space to test shows this is indeed the case. Now knowing the cause, I am trying to solve it but unsure how.

How can I disable sRGBWrite for one of the GBuffers? Is that even possible? I know that the Gbuffer2 for normals has sRGBWrite off, but is it possible to modify the setting of one of the GBuffers through script?

It’s a shame you never got an answer to this.

I stumbled upon this while trying to solve the exact same issue, so for anyone else who ends up here:

  • Using RenderDoc to analyse the gBuffers does indeed confirm that if your project is in Linear colourspace then gBuffer1 and gBuffer2 are treated as SRGB render targets. (Tested in 2018.4, not sure if still the case in future versions).
  • There doesn’t appear to be a way to tell unity to use a different format (outside of SRP) but a simple conversion will enable you to put in a specific value on one side (e.g. finalgbuffer function in a surface shader) and have it come out the same on the other side (e.g. custom deferred lighting shader).

Here’s how I did it:

  1. I made these wrappers for Unity’s single channel conversion functions:
{
return float3(GammaToLinearSpaceExact(value.r), GammaToLinearSpaceExact(value.g), GammaToLinearSpaceExact(value.b));
}

inline float3 LinearToGammaSpaceExact(float3 value)
{
return float3(LinearToGammaSpaceExact(value.r), LinearToGammaSpaceExact(value.g), LinearToGammaSpaceExact(value.b));
}```

Note: these 'Exact' conversions are more costly than the approximations (GammaToLinearSpace/LinearToGammaSpace). I have yet to test whether the non-exact versions are sufficient for this usage.

2) Then where you want to store your value in the gbuffer:

```float3 valueToWrite = float3(253.0 / 255.0, 0, 0);
#ifndef UNITY_COLORSPACE_GAMMA
valueToWrite = GammaToLinearSpaceExact(valueToWrite);
#endif
gbuffer1.rgb = valueToWrite;```

3) And where you want to unpack this at the other end:

```float valueRead = gbuffer1.rgb;
#ifndef UNITY_COLORSPACE_GAMMA
valueRead = LinearToGammaSpaceExact(valueRead);
#endif```

4) You should then find that the following conversion will give you 253:

```(uint)(valueRead.r * 255.0f + 0.5);```