Hi I’m trying to make a 3D object fill my screen by changing the camera’s FOV to fit the objects bounds.
I’ve got this working well when looking directly at a plane, but for a 3D object at an angle the visible width and height is larger due to the perspective angle.
Is there a way to calculate the faux 2d bounds of a 3d object as seen through a perspective camera?
using System;
using System.Collections.Generic;
using UnityEngine;
public class ScreenSpaceSizeTest : MonoBehaviour
{
public GameObject target;
[Range(0, 1f)]
public float RendererOrMeshBounds;
public Rect GetScreenSpaceRectFromRendererBounds(GameObject go, Camera cam)
{
if (go == null || cam == null)
{
return Rect.zero;
}
var renderers = go.GetComponentsInChildren<Renderer>();
var bounds = renderers[0].bounds;
for (var i = 1; i < renderers.Length; i++)
{
bounds.Encapsulate(renderers[i].bounds);
}
var ext = bounds.extents;
var screenPositions = new List<Vector2>();
for (var x = -1; x < 2; x+=2)
{
for (var y = -1; y < 2; y+=2)
{
for (var z = -1; z < 2; z+=2)
{
var vect = bounds.center+new Vector3(ext.x*x, ext.y*y, ext.z*z);
var wts = cam.WorldToScreenPoint(vect);
wts.y = Screen.height - wts.y;
screenPositions.Add(wts);
}
}
}
var rect = new Rect(screenPositions[0].x, screenPositions[0].y, 1, 1);
Action<Vector2> FindMinMax = vec =>
{
if (vec.x < rect.xMin) rect.xMin = vec.x;
if (vec.x > rect.xMax) rect.xMax = vec.x;
if (vec.y < rect.yMin) rect.yMin = vec.y;
if (vec.y > rect.yMax) rect.yMax = vec.y;
};
screenPositions.ForEach(FindMinMax);
return rect;
}
public Rect GetScreenSpaceRectFromMeshBounds(GameObject go, Camera cam)
{
if (go == null || cam == null)
{
return Rect.zero;
}
var mFilters = new List<MeshFilter>(go.GetComponentsInChildren<MeshFilter>());
mFilters.RemoveAll(filter => filter.sharedMesh == null);
var bounds = mFilters[0].sharedMesh.bounds;
bounds.center = go.transform.TransformPoint(bounds.center);
for (var i = 1; i < mFilters.Count; i++)
{
var b = mFilters[i].sharedMesh.bounds;
b.center = go.transform.TransformPoint(b.center);
bounds.Encapsulate(b);
}
var ext = bounds.extents;
ext.Scale(go.transform.localScale);
var screenPositions = new List<Vector2>();
for (var x = -1; x < 2; x += 2)
{
for (var y = -1; y < 2; y += 2)
{
for (var z = -1; z < 2; z += 2)
{
var vect = bounds.center + new Vector3(ext.x * x, ext.y * y, ext.z * z);
var wts = cam.WorldToScreenPoint(vect);
wts.y = Screen.height - wts.y;
screenPositions.Add(wts);
}
}
}
var rect = new Rect(screenPositions[0].x, screenPositions[0].y, 1, 1);
Action<Vector2> FindMinMax = vec =>
{
if (vec.x < rect.xMin) rect.xMin = vec.x;
if (vec.x > rect.xMax) rect.xMax = vec.x;
if (vec.y < rect.yMin) rect.yMin = vec.y;
if (vec.y > rect.yMax) rect.yMax = vec.y;
};
screenPositions.ForEach(FindMinMax);
return rect;
}
void OnDrawGizmos()
{
var rend = target.GetComponent<Renderer>();
Gizmos.DrawWireCube(rend.bounds.center, rend.bounds.size);
}
void OnGUI()
{
var r = GetScreenSpaceRectFromRendererBounds(target, Camera.main);
var r2 = GetScreenSpaceRectFromMeshBounds(target, Camera.main);
r.xMin = Mathf.Lerp(r.xMin, r2.xMin, RendererOrMeshBounds);
r.xMax = Mathf.Lerp(r.xMax, r2.xMax, RendererOrMeshBounds);
r.yMin = Mathf.Lerp(r.yMin, r2.yMin, RendererOrMeshBounds);
r.yMax = Mathf.Lerp(r.yMax, r2.yMax, RendererOrMeshBounds);
GUI.Box(r, "");
}
}
That should give you an idea.
The most accurate method though, i think, should be rendering your object like a mask to a RenderTexture and from that calculate the min/max values.