How to convert from world space to canvas space?

I have a screen space canvas and I simply want to move a “target” over an object in world space. My world space coordinate is obtained with a raycast and I do a debug draw to make sure it is correct. The problem is none of the following code is giving me the right result.

void Update ()
{
    if( currentSelection==null) return;
      
    // Translate world position to UI coordinates
    RectTransform rt = (RectTransform) this.transform;
    Vector3 pos =  ( (RectTransform)rt.parent ).InverseTransformPoint( currentSelection.position );
    rt.position = pos; // nether rt or rt.parent works!
}

I solved this problem by doing the following:

//this is your object that you want to have the UI element hovering over
GameObject WorldObject;

//this is the ui element
RectTransform UI_Element;

//first you need the RectTransform component of your canvas
RectTransform CanvasRect=Canvas.GetComponent<RectTransform>();

//then you calculate the position of the UI element
//0,0 for the canvas is at the center of the screen, whereas WorldToViewPortPoint treats the lower left corner as 0,0. Because of this, you need to subtract the height / width of the canvas * 0.5 to get the correct position.

Vector2 ViewportPosition=Cam.WorldToViewportPoint(WorldObject.transform.position);
Vector2 WorldObject_ScreenPosition=new Vector2(
((ViewportPosition.x*CanvasRect.sizeDelta.x)-(CanvasRect.sizeDelta.x*0.5f)),
((ViewportPosition.y*CanvasRect.sizeDelta.y)-(CanvasRect.sizeDelta.y*0.5f)));

//now you can set the position of the ui element
UI_Element.anchoredPosition=WorldObject_ScreenPosition;

This issue also has another solution:

Vector2 pos = gameObject.transform.position;  // get the game object position
Vector2 viewportPoint = Camera.main.WorldToViewportPoint(pos);  //convert game object position to VievportPoint

// set MIN and MAX Anchor values(positions) to the same position (ViewportPoint)
rectTransform.anchorMin = viewportPoint;  
rectTransform.anchorMax = viewportPoint;

This is the cleanest way I can find of making a UI element follow a world object’s 3D position.

public RectTransform canvasRectT;
public RectTransform healthBar;
public Transform objectToFollow;

void Update()
{
    Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(Camera.main, objectToFollow.position);

    healthBar.anchoredPosition = screenPoint - canvasRectT.sizeDelta / 2f;
}

Reading Sylos’s answer above helped.

I wrote a static function which requires canvas rectransform, camera and vector position.
That way you can work with multiple canvas and cameras.
Works like a charm.

private Vector2 WorldToCanvasPosition(RectTransform canvas, Camera camera, Vector3 position) {
        //Vector position (percentage from 0 to 1) considering camera size.
        //For example (0,0) is lower left, middle is (0.5,0.5)
        Vector2 temp = camera.WorldToViewportPoint(position);

        //Calculate position considering our percentage, using our canvas size
        //So if canvas size is (1100,500), and percentage is (0.5,0.5), current value will be (550,250)
        temp.x *= canvas.sizeDelta.x;
        temp.y *= canvas.sizeDelta.y;

        //The result is ready, but, this result is correct if canvas recttransform pivot is 0,0 - left lower corner.
        //But in reality its middle (0.5,0.5) by default, so we remove the amount considering cavnas rectransform pivot.
        //We could multiply with constant 0.5, but we will actually read the value, so if custom rect transform is passed(with custom pivot) , 
        //returned value will still be correct.

        temp.x -= canvas.sizeDelta.x * canvas.pivot.x;
        temp.y -= canvas.sizeDelta.y * canvas.pivot.y;

        return temp;
    }

I extended Sylos’s slightly by making it an extension method to UnityEngine.Canvas itself, giving the user the option to use a non-main camera if they’d like:

public static class CanvasExtensions
{
    public static Vector2 WorldToCanvas(this Canvas canvas,
                                        Vector3 world_position,
                                        Camera camera = null)
    {
        if (camera == null)
        {
            camera = Camera.main;
        }

        var viewport_position = camera.WorldToViewportPoint(world_position);
        var canvas_rect = canvas.GetComponent<RectTransform>();

        return new Vector2((viewport_position.x * canvas_rect.sizeDelta.x) - (canvas_rect.sizeDelta.x * 0.5f),
                           (viewport_position.y * canvas_rect.sizeDelta.y) - (canvas_rect.sizeDelta.y * 0.5f));
    }
}

It can be called like so:

// _ui_canvas being the Canvas, _world_point being a point in the world

var rect_transform = _ui_element.GetComponent<RectTransform>();

rect_transform.anchoredPosition = _ui_canvas.WorldToCanvas(_world_point);

You just have to include the namespace the CanvasExtensions class resides in

Since this page is the second top result from my search, I will post my solution. This is probably something that didn’t work back in 2015, but fortunately Unity made it easier. This method eliminates the need to deal with confusing RectTransform math and properties.

GameObject healthBarGO; // a UI GameObject that is a child of the Canvas Transform
GameObject enemyGO; // for instance, maybe you want to draw a health bar over an enemy

Vector3 screenPosition = Camera.main.WorldToScreenPoint (enemyGO.transform.position); // pass the world position

healthBarGO.transform.position = screenPosition; // set the UI Transform's position as it will accordingly adjust the RectTransform values

This will draw the UI element exactly at the position of the enemy’s Transform, but you can offset the position before passing it to the WorldToScreenPoint method if you want the health bar to be drawn above or below the enemy’s Transform (or anywhere else, of course).

For others who might find this old post looking for the right way to do this, this works for all different canvas modes and anchors, and seems like it’s at least one of the ways Unity intended it to work:

Vector2 WorldToCanvasPosition(Canvas canvas, RectTransform canvasRect, Camera camera, Vector3 position)
{
    Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(camera, position);
    Vector2 result;
    RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPoint, canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : camera, out result);

    return canvas.transform.TransformPoint(result);
}

(the separate canvasRect parameter is there for performance issues, especially when you are doing this every frame, e.g. for healthbars)

I hadn’t the time to play around with the beta yet. So i can’t say much about the new UI system, but the canvas and the RectTransform positions are pure screenspace / canvas coordinates and have no relation to “worldspace”. Worldspace coordinates are actually mapped to screenspace / viewspace coordinates when rendered by a camera. If you want to know the screen / view space coordinate of a worldspace object, you have to use Camera.WorldToScreenPoint or Camera.WorldToViewportPoint of the camera that renders that object.

If you have the position in screenspace you might want to use the RectTransform hierarchy to get it as a relative position within a certain area.

I figured out that set position property of an new UI element on Awake() not have any effect. Probably because the layout system will set anchoredPosition on next frame, cleaning the position setted on Awake(). I figured out also that setting position property on Start() works nice.

RectTransform.GetLocalCorners(Vetor3 array) will fill out an array with 4 elements. This gives you in world space how big the canvas is.

Here is my version using the built-in ScreenPointToLocalPointInRectangle method. The code assumes that you set your Canvas to Screen Space Overlay and that your target UI Element is anchored to the bottom left of the Canvas. If the latter is not the case you have to modify this part: 0.5f * CanvasRect.sizeDelta + …

public class Tracker : MonoBehaviour {

	public GameObject TrackingPoint;
	
	private RectTransform ButtonRect = null;
	private RectTransform CanvasRect = null;
	
	private Vector3 worldPos;
	private Vector2 screenPos;
	private Vector2 canvasPos;
	
	void Awake () 
	{	
		ButtonRect = GetComponent<RectTransform> ();
		CanvasRect = GetComponentInParent<Canvas>().GetComponent<RectTransform>();
	}
	
	void Update () 
	{
		worldPos = TrackingPoint.transform.position;
		screenPos = RectTransformUtility.WorldToScreenPoint (Camera.main, worldPos);
		
		if (RectTransformUtility.ScreenPointToLocalPointInRectangle (CanvasRect, screenPos, null, out canvasPos)) 
		{
			ButtonRect.anchoredPosition = 0.5f * CanvasRect.sizeDelta + canvasPos;
		}
	}
}

Hope this helps.

Sean

Well I faced some problem when I trying to place a UI in front of some world space object, too. My original code works fun but when canvas scale it broke. After a while I realized that the scaleFactor in CanvasScaler is ALWAYS 1…
So I finally got my solution :

    var sp = RectTransformUtility.WorldToScreenPoint(gameCamera, m_tracking*.transform.position);// gameCamera.WorldToViewportPoint();*
  •  	var rect = elementsRoot.rect;*
    

_ var cp = new Vector2(sp.x / Screen.width * rect.width,sp.y / Screen.height * rect.height);_

//cp would be the Vector2 that you want to place your UI element.
elementsRoot in the code is simply a RectTransform

So I usually use my canvas’s Render Mode set as World Space and a child of the camera. That way I can have 3D objects as part of my UI.
Its probably no the most performant way [giggles]

-First I find the height that fits best for my canvas as it will be a constant in this case its 310

-then I add a boxCollider to my canvas

-then

	public void SetCanvasSize(){
		float w = Screen.width;
		float h = Screen.height;
		float ratio = h/w  ;
		if (ratio != 0) {
			canvasrect.sizeDelta = new Vector2 (310 / ratio, 310);
			boxCollider.size = new Vector3 (310 / ratio, 310, .1f);
		}
	}

-then I Raycast from my Camera position to the “World Object” I want and set the UI position to the hit point.

This seems to work well with different aspectRatio’s and stuff.

Everyone seems to have added their two cents to this one, but somehow I still couldn’t get it working, so I’ll include my (hacky) solution below. This solution takes the Canvas’s scaleFactor into account, and it works with all anchors as long as anchorMin and anchorMax are the same.

   public static Vector2 WorldToScreenWithScale(Canvas myCanvas, Camera camera, Vector2 targetPosition)
    {
        Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(camera, targetPosition);
        screenPoint = screenPoint / myCanvas.scaleFactor; //apply scaling
        return screenPoint;
    }

    public static void PlaceRectAtWorldPosition(Canvas myCanvas, Camera camera, Vector2 targetPosition, RectTransform myRect)
    {
        targetPosition = WorldToScreenWithScale(myCanvas, camera, targetPosition);

        //get half of screen width and height, taking scaling into account
        RectTransform canvasRect = myCanvas.GetComponent<RectTransform>();
        int screenWidthFromCenter = (int)(Screen.width / myCanvas.scaleFactor) / 2;
        int screenHeightFromCenter = (int)(Screen.height / myCanvas.scaleFactor) / 2;

        //"convert" this value to bottom left of parent (i.e. pretend that anchor = 0,0)
        Vector2 bottomLeftOfParent = new Vector2((-myRect.anchorMax.x * 2) * screenWidthFromCenter, (-myRect.anchorMax.y * 2) * screenHeightFromCenter);

        //move the "converted" value to targetPosition
        myRect.anchoredPosition = bottomLeftOfParent + targetPosition;
    }

public Transform target;
public RectTransform followUI;
void Update()
{
Vector2 screensize = new Vector2(Screen.width* followUI.anchorMax.x0.5f, Screen.height followUI.anchorMax.y * 0.5f);
Vector2 sizerect = Vector2.Scale(followUI.rect.size, followUI.pivot);
followUI.anchoredPosition = RectTransformUtility.WorldToScreenPoint(Camera.main, target.transform.position)- screensize + sizerect;
}

Duplicate From @Sylos

    public static Vector2 WorldSpaceToCanvas 
    (
        RectTransform canvasRect,
        Camera camera,
        Vector3 worldPos  
    )
    {
        Vector2 viewportPosition= camera.WorldToViewportPoint(worldPos);
        Vector2 canvasPos = new Vector2
        (
            (
                (viewportPosition.x * canvasRect.sizeDelta.x) - (canvasRect.sizeDelta.x*0.5f) 
            ),
            (
                (viewportPosition.y* canvasRect.sizeDelta.y)-(canvasRect.sizeDelta.y*0.5f)
            )
        );

        return canvasPos;
    }

A little modification to the @YoungDeveloper version due to the fact that canvas could be nested and it’s RectTransform could be not a full-screen rect. And using a rect.size instead of sizeDelta, coz sizeDelta for a full-screen rect of the root canvas is usually (0, 0).

private Vector2 WorldToCanvasPosition(Canvas canvas, Camera worldCamera, Vector3 worldPosition) {
    //Vector position (percentage from 0 to 1) considering camera size.
    //For example (0,0) is lower left, middle is (0.5,0.5)
    Vector2 viewportPoint = worldCamera.WorldToViewportPoint(worldPosition);

    var rootCanvasTransform = (canvas.isRootCanvas ? canvas.transform : canvas.rootCanvas.transform) as RectTransform;
    var rootCanvasSize = rootCanvasTransform!.rect.size;
    //Calculate position considering our percentage, using our canvas size
    //So if canvas size is (1100,500), and percentage is (0.5,0.5), current value will be (550,250)
    var rootCoord = (viewportPoint - rootCanvasTransform.pivot) * rootCanvasSize;
    if (canvas.isRootCanvas)
        return rootCoord;

    var rootToWorldPos = rootCanvasTransform.TransformPoint(rootCoord);
    return canvas.transform.InverseTransformPoint(rootToWorldPos);
}