Overdraw: SpriteRenderer in UI?

Question:
Should i start writing UI-scaling-functions for the SpriteRenderer or will the Unity-UI get an update to support non-quad geometry at some point in the future?

Background:
I want to use big circles as analog-pad indicators. Obviously this causes overdraw because the UI-System creates only quads (i can see the fps drop on the device while those circle images are active).

The solution would be to have a mesh-geometry similar to the SpriteRenderer, that cuts out most transparent areas. You can actually use a SpriteRenderer in the UI but there are some restrictions:

  • SpriteRenderer will not be visible in ScreenSpace-Overlay mode, you need to use ScreenSpace-Camera
  • SpriteRenderer completely ignores the RectTransform except for centering in it, so scaling has to be done by yourself, but that can easily be done with some functions
  • At least one additional Drawcall if i use both normal GUI-Image and SpriteRenderers within the UI - but that is much less of a performance hit than the fillrate

Picture:
left circle: UI-Image, right circle: SpriteRenderer
left side: Shaded Wirframe, right side: Overdraw

Thank you for reading up to this point.

I ended up writing a costum scaling function for my UI-Spriterenderers. Now they look exactly like the previous UI-Images. Scaling function is simple:

public static SpriteRenderer AddScaledUISpriteRenderer(RectTransform rectTrans, int? sortingOrder = null){
        //Adds a child GameObject to the UI-GameObject that copies the sprite of the image
        //Needs to be a child because we need to scale it but still anchor its position to the old Object
        //If we would add a SpriteRenderer to the actual Image-Object and scale it accordingly, children of it will be scaled too.
       
        float pxWidth        = rectTrans.rect.width;            //width  of the scaled UI-Object in pixel
        float pxHeight        = rectTrans.rect.height;        //height of the scaled UI-Object in pixel
       
        Image uiImage        = rectTrans.GetComponent<Image>();
       
        float spriteSizeX    = uiImage.sprite.bounds.size.x;    //width  of the unscaled sprite in pixel
        float spriteSizeY    = uiImage.sprite.bounds.size.y;    //height of the unscaled sprite in pixel
       
        //scale that needs to be applied to the SpriteRender-Gameobject to give it the same screen size as the UI-Image
        float scaleX = pxWidth    /spriteSizeX;
        float scaleY = pxHeight    /spriteSizeY;
       
       
        //create new SpriteGameObject in UI
        GameObject spriteGO        = new GameObject();
        spriteGO.name            = rectTrans.name+"Sprite";                    //give it a name
        spriteGO.layer            = LayerMask.NameToLayer("UI");                //culling layer, if needed
        spriteGO.transform.localScale = new Vector3(scaleX, scaleY, 1F);    //scale the SpriteRenderer
       
        RectTransform sRect        = spriteGO.AddComponent<RectTransform>();    //add a rect Transform to replace the normal Transform
        sRect.SetParent(rectTrans, false);                                    //make the image Object the new parent, so this will move with it       
       
        SpriteRenderer spriteR    = spriteGO.AddComponent<SpriteRenderer>();    //Add a sprite Renderer to the new Object
        spriteR.sprite            = uiImage.sprite;                            //copy the Image's sprite
        spriteR.color            = uiImage.color;                            //copy the Image's coloration
        spriteR.enabled            = uiImage.enabled;                            //copy enabled state
        spriteR.sortingOrder    = sortingOrder ?? 1;                        //needed to be at least 1 if the UI is drawn above a render texture
               
        uiImage.enabled            = false;    //disable the old UI-Image
                       
        return spriteR;
    }

Previous overdraw:


New overdraw:

3 Likes

+1, this is one reason why UI is currently so slow, the overdraw is super heavy, specially on Android devices

I would like to contribute.
As long as we keep the sprite or even a Mesh quad parented to some parent-rect, we can stretch that parent rect and sprite will follow it.

Just make sure to attach the following script to the child sprite (or child mesh quad)

using UnityEngine;

// Attach this script onto a sprite renderer.
// Stretches a sprite to follow corners of parent rect.
// Make sure it's parented to a "dummy" rectTransform and stretch that instead of our tranform.
//
// IgorAherne March 2017
// Marrt  July 2015
// https://forum.unity3d.com/threads/overdraw-spriterenderer-in-ui.339912/#post-3009616
// https://forum.unity3d.com/threads/overdraw-spriterenderer-in-ui.339912/#post-3009616

[ExecuteInEditMode, RequireComponent(typeof(RectTransform))]
public class QuadScalerRelativeToAnchors : MonoBehaviour {


    [SerializeField, HideInInspector] RectTransform _myRectTransform;
    [SerializeField, HideInInspector] RectTransform _parentRectTransf;
    [SerializeField, HideInInspector] SpriteRenderer _mySpriteRenderer;

    void Reset() {
        Start();
    }
 
 
    void Start() {
        _parentRectTransf = transform.parent as RectTransform;
        _mySpriteRenderer = GetComponent(typeof(SpriteRenderer)) as SpriteRenderer;
        _myRectTransform = transform as RectTransform;
    }



    void Update() {

#if UNITY_EDITOR
        //if we are in execute in edit mode, in editor, we might not have some references yet.
        //so establish them if needed:
        if(_mySpriteRenderer == null  ||  _parentRectTransf == null  || _myRectTransform == null) {
            Start();
        }
#endif

        keepScaleRelativeToParent();
    }



    public void keepScaleRelativeToParent() {

        float pxWidth = _parentRectTransf.rect.width;            //width  of the scaled UI-Object in pixel
        float pxHeight = _parentRectTransf.rect.height;        //height of the scaled UI-Object in pixel

        if (float.IsNaN(pxHeight)  ||  float.IsNaN(pxWidth)) {
            //unity hasn't not yet initialized (usually happens during start of the game)
            return;
        }

        float spriteSizeX = 1;
        float spriteSizeY = 1;

        if (_mySpriteRenderer) {
            spriteSizeX = _mySpriteRenderer.sprite.bounds.size.x;  //width  of the unscaled sprite in pixel
            spriteSizeY = _mySpriteRenderer.sprite.bounds.size.y;  //height of the unscaled sprite in pixel
        }

        //scale that needs to be applied to the SpriteRender-Gameobject to give it the same screen size as the UI-Image
        float scaleX = pxWidth / spriteSizeX;
        float scaleY = pxHeight / spriteSizeY;

       
        //create new SpriteGameObject in UI
        this.gameObject.layer = LayerMask.NameToLayer("UI");            //culling layer, if needed
        _myRectTransform.localScale = new Vector3(scaleX, scaleY, 1F);    //scale our SpriteRenderer


        //position self in the middle of parent
        _myRectTransform.anchoredPosition3D = new Vector3(0,0, _myRectTransform.anchoredPosition3D.z);
        _myRectTransform.anchorMin = Vector2.one * 0.5f;
        _myRectTransform.anchorMax = Vector2.one * 0.5f;

        float myLocalScale_x = Mathf.Max(0.0001f, _myRectTransform.localScale.x);
        float myLocalScale_y = Mathf.Max(0.0001f, _myRectTransform.localScale.y);

        _myRectTransform.sizeDelta = new Vector2( Mathf.Max(0.0001f, _parentRectTransf.rect.width / myLocalScale_x),
                                                  Mathf.Max(0.0001f, _parentRectTransf.rect.height / myLocalScale_y) );
    }

}
3 Likes

Since people still like this thread:
This workaround is not needed anymore, unity now supports this natively in UI-Sprites

1 Like

@Marrt Could you elaborate please, through which component and in which Unity Versions UI-Sprites are supported natively now?

Edit: Nevermind:

the bool useSpriteMesh has the feature that we need.
The feature exists in Unity 2018.4 and upwards.
Hint: you have to assign as sprite in order to set the field in the inspector, or you need to switch the inspector to debug mode.

2 Likes

So UI-Sprites are preferred over Sprite Renderers ? (Just for sake of re-usability in both Canvas and GameWorld?)

This code was very useful for me, thank you for sharing.

1 Like