unity通常会有将一个模型展示在界面上用于旋转缩放观察的功能,通常是使用相机看向模型,然后使用RawImage显示相机看到的画面。
但很多时候展示的模型尺寸与中心点是差别较大的,就需要自适应的修改模型的位置使其能够完整的显示在屏幕中心位置,下面就是相关代码
首先将物体的子物体进行网格合并
/// <summary>
/// 计算模型全部展示位置
/// </summary>
/// <param name="root"></param>
/// <returns></returns>
private Vector3 CalculateAllShowPos(GameObject root)
{
if (root.transform.childCount > 0)
{
//若物体是由一堆子物体拼接而成, 可以先将他们的Mesh合并
MeshFilter[] meshFilters = root.transform.GetComponentsInChildren<MeshFilter>();
CombineInstance[] combineInstances = new CombineInstance[meshFilters.Length];
for (int k = 0; k < meshFilters.Length; k++)
{
combineInstances[k].mesh = meshFilters[k].sharedMesh;
combineInstances[k].transform = meshFilters[k].transform.localToWorldMatrix;
}
Mesh newMesh = new Mesh();
//若合并的Mesh面数太多,需要将indexFormat 改为UInt32,默认为UInt16
newMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
newMesh.CombineMeshes(combineInstances);
root.AddComponent<MeshFilter>().sharedMesh = newMesh;
float xx = root.GetComponent<MeshFilter>().sharedMesh.bounds.size.x;
float yy = root.GetComponent<MeshFilter>().sharedMesh.bounds.size.y;
float zz = root.GetComponent<MeshFilter>().sharedMesh.bounds.size.z;
float sizeF = 1;
if (xx >= yy)
{
sizeF = xx;
}
else
{
sizeF = yy;
}
if (sizeF >= zz)
{
}
else
{
sizeF = zz;
}
//返回模型需要向后移动的距离
return new Vector3(0, 0, 1.1f * sizeF);
}
return Vector3.zero;
}
然后计算物体的几何中心点
/// <summary>
/// 计算模型的几何中心点(局部坐标系下)
/// </summary>
/// <param name="tran"></param>
/// <returns></returns>
private Vector3 CalculateModelCenter(Transform tran)
{
//计算每个子物体包围盒的中心,平均值得到物体的中心
Vector3 center = Vector3.zero;
Renderer[] renders = tran.GetComponentsInChildren<Renderer>();
foreach (Renderer child in renders)
{
center += child.bounds.center;
}
center /= renders.Length;
//返回相对位置
return center - tran.position;
}
最后就是展示模型时调用上述方法即可
/// <summary>
/// 显示三维模型
/// </summary>
public void ShowModel(GameObject model)
{
if (model == null)
{
return;
}
//ModelPoint为相机看向的点,是一个空物体,将展示的物体设置为其子物体
model.transform.SetParent(ModelPoint, false);
Vector3 backPos = CalculateAllShowPos(model);
Vector3 pos = CalculateModelCenter(model.transform);
model.transform.localPosition = -pos;
model.transform.localRotation = Quaternion.identity;
ModelPoint.position += backPos;
}
需要注意的是创建RenderTexture时需要设置宽高与展示模型的RawImage的宽高一致,否则展示画面会有拉伸感
private void Start()
{
RenderTexture render = new RenderTexture((int)rect.rect.width, (int)rect.rect.height, 16, RenderTextureFormat.ARGB32);
ModelCamera.targetTexture = render;
ModelImage.texture = render;
}
最后是简单的鼠标控制相机拖拽,旋转,缩放观察模型,若需要射线监测点击物体则需要注意射线发射的位置为鼠标位置减去RawImage左下角位置,具体代码如下
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
/// <summary>
/// 控制模型移动,旋转,缩放
/// </summary>
public class ModelControl : MonoBehaviour,IPointerEnterHandler,IPointerExitHandler,IPointerClickHandler,IDragHandler,IEndDragHandler,IBeginDragHandler
{
/// <summary>
/// 模型相机
/// </summary>
public Camera ModelCamera;
[HideInInspector]
/// <summary>
/// 模型父节点
/// </summary>
public Transform ModelPoint;
public RectTransform RawImageRect;
/// <summary>
/// 是否鼠标进入操作区域
/// </summary>
private bool isMouseEnter = false;
private bool isDragging = false;
RaycastHit hit;
public UnityAction<Vector3, Vector3> OnCalibrationPointClick;
private void Update()
{
if (!isMouseEnter)
return;
float scrollWheel = Input.GetAxis("Mouse ScrollWheel");
if(scrollWheel != 0)
{
//相机前后移动实现缩放
ModelCamera.transform.position += ModelCamera.transform.forward * scrollWheel * 10f;
}
}
public void OnPointerEnter(PointerEventData eventData)
{
isMouseEnter = true;
}
public void OnPointerExit(PointerEventData eventData)
{
isMouseEnter = false;
}
public void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Right || isDragging)
return;
//右键点击,发射线
Vector3 mousePos = Input.mousePosition;
Vector2 localPoint = Vector2.zero;
//将鼠标点转到RawImage内相对坐标,使用时需要注意Canvas的RenderMode要为Overlay,RawImage的锚点需要在左下角,Rendertexture的尺寸要合RawImage一致
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(RawImageRect, mousePos, null, out localPoint))
{
localPoint = new Vector2(localPoint.x, localPoint.y);
Ray ray = ModelCamera.ScreenPointToRay(localPoint);
if (Physics.Raycast(ray, out hit, 300))
{
OnCalibrationPointClick?.Invoke(hit.point, -ray.direction.normalized);
}
}
}
public void OnDrag(PointerEventData eventData)
{
if(eventData.button == PointerEventData.InputButton.Left)
{
float x = Input.GetAxis("Mouse X");
float y = Input.GetAxis("Mouse Y");
//相机上下左右移动实现拖拽移动
ModelCamera.transform.position -= ModelCamera.transform.right * x * 0.2f;
ModelCamera.transform.position -= ModelCamera.transform.up * y * 0.2f;
}
else if(eventData.button == PointerEventData.InputButton.Middle)
{
float x = Input.GetAxis("Mouse X");
float y = Input.GetAxis("Mouse Y");
float xnew = Mathf.Abs(x);
float ynew = Mathf.Abs(y);
if (xnew > ynew)
{
ModelCamera.transform.RotateAround(ModelPoint.position, Vector3.up, x * 5f);
}
else
ModelCamera.transform.RotateAround(ModelPoint.position, ModelCamera.transform.right, -y * 5f);
}
}
public void OnEndDrag(PointerEventData eventData)
{
isDragging = false;
}
public void OnBeginDrag(PointerEventData eventData)
{
isDragging = true;
}
}