- Home /
Is there an easy way to get on-screen render size (bounds)?
I need to quickly find the on-screen bounding box of a given object or mesh. Do I have to iterate convert all the points to world space and compute my own?
Answer by karl_ · May 07, 2012 at 03:25 PM
If anyone is interested in Matt's solution, here is my implementation.
public Rect BoundsToScreenRect(Bounds bounds)
{
// Get mesh origin and farthest extent (this works best with simple convex meshes)
Vector3 origin = Camera.main.WorldToScreenPoint(new Vector3(bounds.min.x, bounds.max.y, 0f));
Vector3 extent = Camera.main.WorldToScreenPoint(new Vector3(bounds.max.x, bounds.min.y, 0f));
// Create rect in screen space and return - does not account for camera perspective
return new Rect(origin.x, Screen.height - origin.y, extent.x - origin.x, origin.y - extent.y);
}
Edit: Here's a better implementation that accounts for rotation and scale:
public static Rect GUIRectWithObject(GameObject go)
{
Vector3 cen = go.renderer.bounds.center;
Vector3 ext = go.renderer.bounds.extents;
Vector2[] extentPoints = new Vector2[8]
{
HandleUtility.WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y-ext.y, cen.z-ext.z)),
HandleUtility.WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y-ext.y, cen.z-ext.z)),
HandleUtility.WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y-ext.y, cen.z+ext.z)),
HandleUtility.WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y-ext.y, cen.z+ext.z)),
HandleUtility.WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y+ext.y, cen.z-ext.z)),
HandleUtility.WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y+ext.y, cen.z-ext.z)),
HandleUtility.WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y+ext.y, cen.z+ext.z)),
HandleUtility.WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y+ext.y, cen.z+ext.z))
};
Vector2 min = extentPoints[0];
Vector2 max = extentPoints[0];
foreach(Vector2 v in extentPoints)
{
min = Vector2.Min(min, v);
max = Vector2.Max(max, v);
}
return new Rect(min.x, min.y, max.x-min.x, max.y-min.y);
}
$$anonymous$$any thanks, Have pinched the fragment entire just to save me rebuilding this particular wheel
...annnnd as an extension method :
using UnityEngine;
using System.Collections;
public static class BoundsExtensions
{
public static Rect ToScreenSpace(this Bounds bounds, Camera camera)
{
var origin = camera.WorldToScreenPoint(new Vector3(bounds.$$anonymous$$.x, bounds.$$anonymous$$.y, 0.0f));
var extents = camera.WorldToScreenPoint(new Vector3(bounds.max.x, bounds.$$anonymous$$.y, 0.0f));
return new Rect(origin.x, Screen.height - origin.y, extents.x - origin.x, origin.y - extents.y);
}
}
You have a typo. Should be:
var origin = camera.WorldToScreenPoint(new Vector3(bounds.$$anonymous$$.x, bounds.max.y, 0.0f));
actually it returns 0,0,0
z should be:
bounds.center.z;
ins$$anonymous$$d of z being 0.0f
karl_'s updated answer references a UnityEditor namespace class. The method can be decompiled to:
public static Vector2 WorldToGUIPoint(Vector3 world)
{
world = Handles.matrix.$$anonymous$$ultiplyPoint(world);
Camera camera = Camera.current;
if (!camera)
{
return new Vector2(world.x, world.y);
}
Vector2 screenPoint = camera.WorldToScreenPoint(world);
screenPoint.y = (float)Screen.height - screenPoint.y;
return GUIClip.Clip(screenPoint);
}
Answer by Bampf · Mar 01, 2011 at 12:55 AM
You could get the bounding volume using Renderer.bounds, then convert those coordinates to screen coordinates. The min and max of the screen X & Y coordinates will be a bounding box.
However, this is only an approximation. Because it is axis-aligned, AND because the camera angle will distort the shape further. You might end up with a box that's bigger than necessary depending on the shape. It will work better for compact, convex objects.
Right, I thought of that, and passed on it for those reasons. What I'm going for is the old 'trombone' shot used by Steven Spielberg and the like, where the 'target object' stays the same size on screen, but the camera moves way back while zoo$$anonymous$$g in at the same time (or vice versa). That approximation would mess a bit with the actual extent of the rendered object. I guess I should try it, but I think for my purposes it will be too distorted.
Actually, this effect was invented by Alfred Hitchcock in Vertigo.
Answer by Luloak2 · May 23, 2016 at 08:10 PM
sgoodrow's answer is not 100% correct in Unity 5 anymore (GUIClip is missing), updated (and combined) the code:
public static Rect GUIRectWithObject(GameObject go)
{
Vector3 cen = go.GetComponent<Renderer>().bounds.center;
Vector3 ext = go.GetComponent<Renderer>().bounds.extents;
Vector2[] extentPoints = new Vector2[8]
{
WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y-ext.y, cen.z-ext.z)),
WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y-ext.y, cen.z-ext.z)),
WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y-ext.y, cen.z+ext.z)),
WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y-ext.y, cen.z+ext.z)),
WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y+ext.y, cen.z-ext.z)),
WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y+ext.y, cen.z-ext.z)),
WorldToGUIPoint(new Vector3(cen.x-ext.x, cen.y+ext.y, cen.z+ext.z)),
WorldToGUIPoint(new Vector3(cen.x+ext.x, cen.y+ext.y, cen.z+ext.z))
};
Vector2 min = extentPoints[0];
Vector2 max = extentPoints[0];
foreach (Vector2 v in extentPoints)
{
min = Vector2.Min(min, v);
max = Vector2.Max(max, v);
}
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
}
public static Vector2 WorldToGUIPoint(Vector3 world)
{
Vector2 screenPoint = Camera.main.WorldToScreenPoint(world);
screenPoint.y = (float) Screen.height - screenPoint.y;
return screenPoint;
}
Answer by pointcache · Jul 15, 2017 at 04:58 PM
An optimized runtime version of the above with zero allocations and garbage. Will work with UGUI if you have anchor and pivot at 0,0 (lower left corner is the start of coordinates). Swap collider with renderer if you need.
public static Rect GetScreenRect(this Collider collider) {
Vector3 cen = collider.bounds.center;
Vector3 ext = collider.bounds.extents;
Camera cam = Camera.main;
float screenheight = Screen.height;
Vector2 min = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z - ext.z));
Vector2 max = min;
//0
Vector2 point = min;
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
//1
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z - ext.z));
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
//2
point = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z + ext.z));
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
//3
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z + ext.z));
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
//4
point = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z - ext.z));
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
//5
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z - ext.z));
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
//6
point = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z + ext.z));
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
//7
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z + ext.z));
min = new Vector2(min.x >= point.x ? point.x : min.x, min.y >= point.y ? point.y : min.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
}
`+1 but god, man, use a function with a 'ref vector' argument ...So many lines of nearly identical code
Well, it's an oprimised version. A method call can actually slow down the performance quite a bit if you need this literally million of times a frame. For example i also did inline some code i've written and it ran about 8 times faster. The script i posted in this answer is the "slow" version as it uses several helper methods (Clamp, FloorToInt, CeilToInt, Repeat, Color.Lerp). In the inlined version I've linked from my answer i removed all method calls. The code becomes a bit longer but runs way faster.
I would argue that this kind of optimization should be left for compiler to do ..or taken out into a c++ dll? This is definitely not as expensive as reflection, so such a tiny optimization would most likely not matter. Though maybe if it were in some super-deep recursion, it would
Have a look at the refactored version:
using System.Runtime.CompilerServices;
public static Rect GetScreenRect(this Collider collider) {
Vector3 cen = collider.bounds.center;
Vector3 ext = collider.bounds.extents;
Camera cam = Camera.main;
float screenheight = Screen.height;
Vector2 $$anonymous$$ = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z - ext.z));
Vector2 max = $$anonymous$$;
//0
Vector2 point = $$anonymous$$;
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
//1
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z - ext.z));
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
//2
point = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z + ext.z));
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
//3
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z + ext.z));
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
//4
point = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z - ext.z));
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
//5
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z - ext.z));
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
//6
point = cam.WorldToScreenPoint(new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z + ext.z));
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
//7
point = cam.WorldToScreenPoint(new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z + ext.z));
get_$$anonymous$$$$anonymous$$ax(point, ref $$anonymous$$, ref max);
return new Rect($$anonymous$$.x, $$anonymous$$.y, max.x - $$anonymous$$.x, max.y - $$anonymous$$.y);
}
[$$anonymous$$ethodImpl($$anonymous$$ethodImplOptions.AggressiveInlining)]
static void get_$$anonymous$$$$anonymous$$ax(Vector2 point, ref Vector2 $$anonymous$$, ref Vector2 max) {
$$anonymous$$ = new Vector2($$anonymous$$.x >= point.x ? point.x : $$anonymous$$.x, $$anonymous$$.y >= point.y ? point.y : $$anonymous$$.y);
max = new Vector2(max.x <= point.x ? point.x : max.x, max.y <= point.y ? point.y : max.y);
}
Refing a vector which is a struct causes boxing which creates garbage.
I think value-types will never create garbage in any scenario, only grow stack. They will be insta-cleaned up once used, but I might need confirmation
https://stackoverflow.com/a/36527617
That's the beauty of structs, but also their curse, since they must be fixed size, and therefore can't be inherited from (unlike classes)
I think boxing does not occur, as behind scenes, nothing will wrap struct into 'object', it simply does a c++ style & reference on it, pointing to its memory adress
also, thanks for the code :)
Answer by jokigenki · Feb 10, 2021 at 08:26 AM
If anyone wants a Canvas based version (based on Luloak2's answer):
/// <summary>
/// Returns a rectangle on this canvas that fully encloses the given GameObject.
/// </summary>
/// <param name="canvas">The canvas on which to base the rectangle</param>
/// <param name="go">The game object to enclose</param>
/// <returns>A Rect that encloses the GameObject</returns>
public static Rect GetRect([NotNull] this UnityEngine.Canvas canvas, [NotNull] GameObject go)
{
var canvasRT = canvas.transform as RectTransform;
var camera = canvas.worldCamera;
if (camera == null) camera = Camera.main;
var renderer = go.GetComponent<Renderer>();
if (camera == null || canvasRT == null || renderer == null) return Rect.zero;
var bounds = renderer.bounds;
var cen = bounds.center;
var ext = bounds.extents;
var extMin = cen - ext;
var extMax = cen + ext;
var extentPoints = new[]
{
new Vector3(extMax.x, extMin.y, extMin.z),
new Vector3(extMin.x, extMin.y, extMax.z),
new Vector3(extMax.x, extMin.y, extMax.z),
new Vector3(extMin.x, extMax.y, extMin.z),
new Vector3(extMax.x, extMax.y, extMin.z),
new Vector3(extMin.x, extMax.y, extMax.z),
extMax
};
var min = camera.WorldToScreenPoint(extMin);
var max = min;
foreach (var v3 in extentPoints)
{
var v = camera.WorldToScreenPoint(v3);
min = Vector2.Min(min, v);
max = Vector2.Max(max, v);
}
var sizeDelta = canvasRT.sizeDelta / 2f;
return new Rect(min.x - sizeDelta.x, min.y - sizeDelta.y, max.x - min.x, max.y - min.y);
}
Your answer
Follow this Question
Related Questions
Bounds Finding Box 1 Answer
Determine if two meshes overlap, without using bounds or trigger? 0 Answers
Stop the player at center of cube mesh 2 Answers
Box/mesh collider 0 Answers
Getting Screen Bounds 1 Answer