一、制作原理
我们希望在一个模型的头顶放置一个UI,但是我们不可能直接挂一个UI在上面;因为我们的UI需要用一个单独的相机而非模型的相机来渲染,所以如果直接在模型的GameObject下直接挂一个子物体,渲染出来的结果肯定不对;
明白这一点后,我们就需要找到如果想要渲染到模型的顶部,我们得到一个UI的世界坐标使其在渲染到屏幕空间后,正好位于模型顶部;设UI的世界坐标Pos1,其计算方式为,首先得到模型顶部的世界坐标pos2,然后使用其场景相机将其转换到屏幕空间,这个点也正好是我们希望Pos1通过UI相机转换到屏幕空间的位置,所以直接反向求解即可;
以下是使用NGUI代码给出的简单算法实现:
void Update()
{
Vector3 pt = Camera.main.WorldToScreenPoint(followTf.position + newOffsetPos);
tf.position = UICamera.FindCameraForLayer(this.gameObject.layer).cachedCamera.ScreenToWorldPoint(new Vector3(pt.x, pt.y, pt.z));
}
二、需求扩展
当相机靠近模型远离模型时,UI都是不会变化的,如果我们希望当模型远离相机时,或者相机的FOV改变的时候,UI也要随着改变,那如何处理,下面给出了一个方案,提供了一个CalibrateUITf()校验函数 ,它会根据相机距离的远近,FOV的大小来调整UI的scale以及UI到模型的距离;
public Vector3 offsetPos;
public Transform followTf;
private Transform tf;
// 默认相机的一系列参数
private float originFOV;
private float originDist2Camera;
// UI的原始大小
private Vector3 originScale;
// Start is called before the first frame update
void Start()
{
tf = this.transform;
originFOV = Camera.main.fieldOfView;
originDist2Camera = followTf.position.z - Camera.main.gameObject.transform.position.z;
originScale = tf.localScale;
}
// Update is called once per frame
void Update()
{
Vector3 newOffsetPos = CalibrateUITf();
Vector3 pt = Camera.main.WorldToScreenPoint(followTf.position + newOffsetPos);
tf.position = UICamera.FindCameraForLayer(this.gameObject.layer).cachedCamera.ScreenToWorldPoint(new Vector3(pt.x, pt.y, pt.z));
}
Vector3 CalibrateUITf(){
float calibrationArg = 1; // 计算校正参数
float currentFOV = Camera.main.fieldOfView;
calibrationArg = currentFOV / originFOV;
float d = followTf.position.z - Camera.main.gameObject.transform.position.z;
calibrationArg *= d / originDist2Camera;
tf.localScale = originScale / calibrationArg;
Vector3 newOffsetPos = offsetPos * calibrationArg;
return newOffsetPos;
}