[Unity 6] Texture2D(TextureFormat.R8) is sampled incorrectly in shaders
closed
TonyEric
After the VRChat client migrated to Unity 6, Texture2D(TextureFormat.R8) no longer behaves correctly when sampled in shaders.
I verified the following:
Texture2D(TextureFormat.R8) is created successfully.
The texture accepts exactly 1 byte per pixel.
Using SetPixels(), I compared writing the same red value both with and without different green and blue values. The shader output remained identical, confirming that the green and blue channels are ignored during storage, as expected for TextureFormat.R8.
Apply() succeeds without errors.
Load(), Sample(), and tex2D() all produce identical incorrect results.
This indicates that the texture data is stored correctly, but is decoded incorrectly when sampled in a shader.
In addition to the red channel becoming non-linear, the sampled green and blue channels are always returned as 1.0, even though TextureFormat.R8 stores only a single red channel.
The sampled red values remain monotonic (their order is preserved), but they are no longer linear. Low values are compressed into a much smaller range, making TextureFormat.R8 unusable as a byte-accurate data texture.
This behavior did not occur before the VRChat client migrated to Unity 6.
First image: old version
Second image: Unity 6 Beta
Photo Viewer
View photos in a modal
Log In
_
_tau_
updated the status to
closed
_
_tau_
updated the status to
needs more information
This post was marked as
tracked
_
_tau_
updated the status to
needs more information
Can you provide a world (ideally minimal) that reproduces this issue? That way we can verify side-by-side against live.
TonyEric
_tau_
using UdonSharp;
using UnityEngine;
namespace VRC.BugReport
{
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class Texture2DR8BugReport : UdonSharpBehaviour
{
[SerializeField] private Material material;
void Start()
{
var textureR8 = new Texture2D(8, 8, TextureFormat.R8, false);
textureR8.filterMode = FilterMode.Point;
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
textureR8.SetPixel(i, j, new Color32((byte)(i + j * 8), 0, 0, 1));
}
}
textureR8.Apply();
material.mainTexture = textureR8;
}
}
}
_
_tau_
TonyEric: Turns out this is due to a bug fix. Your script works in Unity 2022, but is technically incorrect. You construct the Texture2D with R8 in sRGB space, because the default value for the "linear" parameter is "false".
If you use
new Texture2D(8, 8, TextureFormat.R8, false, true);
instead, it should work.In more detail, Unity 2022.3 always maps TextureFormat.R8 to DX11's
kFormatR8_UNorm
. In Unity 6, it (more correctly) maps to kFormatR8_SRGB
. The tricky bit there is that the latter isn't natively supported on many platforms, so it falls back to kFormatR8G8B8A8_SRGB
, which expands the channels engine-side leading to your observed behaviour.Genuine question: Do you think it is enough to document this behaviour change, or are you aware of a widely used prefab or creation that would break from this change? A shim could be put into place, but may lead to desyncs between observed editor vs client behaviour later on, so we're hesitant.
TonyEric
_tau_Thanks, this change helped. I don't think that anyone else uses it in the worlds in such a specific way except me. Don't worry, it doesn't seem like a problem or a bug.
_
_tau_
TonyEric: Noted, I'll close this issue then. Thanks for the reply!