[SOLVED] Texture2D.SetPixels unexpected behavior on iOS

Hi!

I’m creating a game where a texture is drawn based on where the player touches/clicks on the screen. Most of the code is taken from this tutorial by CodeMonkey:

I have made a first prototype of this game using the built-in renderer and assets found on the Asset Store. This version builds correctly on iOS and the texture is updated the same way on iOS and in the editor.

However, after refactoring the project with custom models (using the same code and shader for drawing and updating the texture) the behavior changed on iOS and it seems to only be able to draw on the top-left or bottom-left corner of the texture.

I already tried different things (URP/no URP, rewriting the shader in shader graph, different settings to import the mesh) but nothing seems to fix it. I think it might have to do with the UVs of my models which are voluntarily mirrored on the X axis. Nothing else is “wrong” with the models as far as I know, eg correct face orientations, transforms are applied, modifiers are applied, etc. I’m thinking Metal handles UVs differently, but it didn’t seem to be a problem in the previous version (I’m also coding on a Mac with a Silicon version of Unity, so I suppose the Graphics API is the same ?).

Here’s the piece of code that handles writing the texture:

// Start function

_mask = new Texture2D(512, 512);

_renderer = GetComponent<Renderer>();
_renderer.material.SetTexture("_Mask", _mask);

for (int y = 0; y < _mask.height; y++)
{
    for (int x = 0; x < _mask.width; x++)
    {
        _mask.SetPixel(x, y, Color.white);
    }
}
_mask.Apply();

// ...

public void CleanPixels(Vector2Int coords) {
    Vector2Int offset = new Vector2Int(coords.x - 32, coords.y - 32);
  
    for (int x = 0; x < 64; x++) {
        for (int y = 0; y < 64; y++) {
            Color pixelDirt = _brush.GetPixel(x, y);
            Color pixelMask = _mask.GetPixel(offset.x + x, offset.y + y);
          
            float value = Mathf.Clamp(pixelDirt.r * pixelMask.r, 0, pixelMask.r);

            _mask.SetPixel(offset.x + x, offset.y + y, new Color(value, value, value));
        }
    }
  
    _mask.Apply();

}

Couldn’t find any solution on the Internet so any help welcome!

Thanks :slight_smile:

I guess you’re raycasting against the mesh to determine where to “clean”, correct?

If so, make sure your meshes are readable (in the mesh import settings, read/write must be enabled) and MeshColliders not convex. Otherwise RaycastHits will not contain proper texcoord hit info outside the editor, instead they will always report (0,0).

The reasons for this are simple:

  • If the mesh is not readable, the CPU cannot know the UV coordinates of the hit point since they’re not available to the CPU, only the GPU.
  • If the MeshCollider is convex, there’s no simple way to map the surface of its convex representation to the surface of the actual (possible concave) mesh, so there’s no way to know the UV coordinates at the hit point.

Hi arkano,

You solved it, thank you so much!!! For anyone stumbling upon this, the textures you want to work with need to have Read/Write enabled too.

I’m wondering why it worked in the Unity Editor though, maybe Unity automatically turns the option on in Play mode ?

Glad it is working now! :slight_smile:

When uploading mesh data to the GPU, the editor probably keeps the CPU copy around in for some editor-specific stuff (for instance creating collider geometry? don’t know for sure), but in the build it gets rid of it to save memory unless you’ve explicitly asked to keep it around.

Would be nice indeed to get the same behavior in-editor though, as it would save some headaches.

1 Like