Search Unity

How to get UI Image screen bounds in a shader?

Discussion in 'Shaders' started by sarahnorthway, Feb 7, 2021.

  1. sarahnorthway

    sarahnorthway

    Joined:
    Jul 16, 2015
    Posts:
    78
    I need the bottom-left, width and height of a square Image in camera screen-space coordinates (0,0 at bottom-left of display window, 1,1 at top-right). It needs to work with 9-slice images and atlases.

    Currently I pass it in as a Material property:

    Code (CSharp):
    1. Vector3[] worldCorners = new Vector3[4];
    2.         image.rectTransform.GetWorldCorners(worldCorners);
    3.         for (int i = 0; i < worldCorners.Length; i++) {
    4.             Vector3 screenPoint = camera.WorldToScreenPoint(worldCorners[i]);
    5.             screenPoint.x = screenPoint.x / Screen.width;
    6.             screenPoint.y = screenPoint.y / Screen.height;
    7.             worldCorners[i] = screenPoint;
    8.         }
    9.         Vector3 min = worldCorners[0];
    10.         Vector3 max = worldCorners[2];
    11.         Vector4 bounds = new Vector4(min.x, min.y, max.x - min.x, max.y - min.y);
    12.         image.material.SetVector("_Rect", bounds);
    This requires me to Instantiate a copy of the Material for each Image that uses it.

    It's also completely broken in Scene view.

    There must be a better way! Can I access this info (The Image's RectTransform.rect plus required matrix transforms) from inside the shader?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Yep.

    Yep.

    Nope.

    Eh... not really, no.

    You can fix the issue for the scene view by setting the value on the material during
    onPreRender
    or
    OnWillRenderObject
    .
    https://docs.unity3d.com/ScriptReference/Camera-onPreRender.html
    https://docs.unity3d.com/ScriptReference/Camera.OnWillRenderObject.html
    Both of those give you access to the camera that will be used to render, which you should be using instead of the main camera. This includes the scene view camera. I'd suggest using
    OnWillRenderObject
    , but I honestly don't know if UI renderer components get that called or not, hence why I listed the other option as well.

    To avoid instantiating the material every time, you could write a script that adds the data you need to the vertex data.
    https://docs.unity3d.com/2018.3/Documentation/ScriptReference/UI.BaseMeshEffect.html
    This is probably more efficient to render, but may not be noticeably faster.
     
    sarahnorthway likes this.
  3. sarahnorthway

    sarahnorthway

    Joined:
    Jul 16, 2015
    Posts:
    78
    Looks like OnWillRenderObject only runs on Renderers, not UI CanvasRenderers. Camera.OnPreRender did the trick though!

    Piggybacking shader vars in the uv data also seems like a better system than instantiating a bunch of Materials, and means I'd still have a hope of batching draw calls together. This is a good general workaround for the lack of MaterialPropertyBlocks on UI, thanks!
     
    Last edited: Mar 8, 2021
    bgolus likes this.