如何在2DUI上生成3D模型的包围盒
前言
在实际开发中遇到了这个问题,需要判断一个3D模型在专用相机下的显示范围,将其展示在UI上。在这里记录一下实现步骤和代码。
实现方法
- 从需要渲染的对象上获取Renderer组件(对于复杂的3D模型,一般得到的是
MeshRenderer
) - 根据Renderer组件的bounds.center,bounds.extents计算出世界坐标的八个点
- 使用拍摄渲染对象的Camera调用接口
UnityEngine.Camera.WorldToScreenPoint
,获取八个点的屏幕坐标 - 遍历八个点,取出x方向与y方向的最大最小值,返回一个
Vector4(minX, minY, maxX, maxY)
,这个坐标即是3D模型在2DUI上的显示包围盒(bounds) - 最后在UI层调整包围矩形的位置和大小即可
代码示例
// Static Toolkit Code
/// <summary>
/// calculate all worldCorners with bounds, and translate into screen point(minX, minY, maxX, maxY)
/// </summary>
/// <param name="target"></param>
/// <param name="camera"></param>
/// <param name="zTest">if ture, x and y will be set to 0 if z less or equal than 0 if screen point</param>
/// <returns></returns>
public static Vector4 GetScreenCorners(GameObject target, UnityEngine.Camera camera, bool zTest)
{
var renderer = target.GetComponentInChildren<Renderer>();
if (renderer == null)
{
return Vector4.zero;
}
var bounds = renderer.bounds;
var center = bounds.center;
var extents = bounds.extents;
var worldCorners = new[]
{
new Vector3(center.x - extents.x, center.y - extents.y, center.z - extents.z),
new Vector3(center.x - extents.x, center.y - extents.y, center.z + extents.z),
new Vector3(center.x - extents.x, center.y + extents.y, center.z - extents.z),
new Vector3(center.x - extents.x, center.y + extents.y, center.z + extents.z),
new Vector3(center.x + extents.x, center.y - extents.y, center.z - extents.z),
new Vector3(center.x + extents.x, center.y - extents.y, center.z + extents.z),
new Vector3(center.x + extents.x, center.y + extents.y, center.z - extents.z),
new Vector3(center.x + extents.x, center.y + extents.y, center.z + extents.z),
};
var screenCornerArray = new Vector3[8];
for (int i = 0; i < 8; i++)
{
screenCornerArray[i] = camera.WorldToScreenPoint(worldCorners[i]);
if (zTest && screenCornerArray[i].z <= 0)
{
screenCornerArray[i].x = 0;
screenCornerArray[i].y = 0;
}
}
var minX = float.MaxValue;
var minY = float.MaxValue;
var maxX = float.MinValue;
var maxY = float.MinValue;
for (int i = 0; i < screenCornerArray.Length; i++)
{
var corner = screenCornerArray[i];
if (corner.x < minX)
{
minX = corner.x;
}
if (corner.x > maxX)
{
maxX = corner.x;
}
if (corner.y < minY)
{
minY = corner.y;
}
if (corner.y > maxY)
{
maxY = corner.y;
}
}
return new Vector4(minX, minY, maxX, maxY);
}
// UI Controller Code: Size adjustment
private void OnTargetObjectScreenBoundsUpdate(object[] param)
{
if (param.Length != 2)
{
return;
}
var targetName = (string) param[0];
var vec4 = (Vector4) param[1];
// Debug.LogFormat("[ScreenCornerController.OnTargetObjectScreenBoundsUpdate] {0}", vec4);
var centerX = (vec4[2] + vec4[0]) / 2;
var centerY = (vec4[3] + vec4[1]) / 2;
var width = vec4[2] - vec4[0];
var height = vec4[3] - vec4[1];
// selectorImage<UnityEngine.UI.Image>
selectorImage.rectTransform.anchoredPosition = new Vector2(centerX, centerY);
selectorImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
selectorImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
// selectorText<TMPro.TextMeshProUGUI>
selectorText.text = $"{targetName}\nminX:{vec4[0]} minY:{vec4[1]}\nmaxX:{vec4[2]} maxY:{vec4[3]}";
selectorImage.gameObject.SetActive(width > 0.001f && height > 0.001f);
}