通常简单的做法
用一个Orthographic相机来做UI相机,Canvas的RenderMode用ScreenSpace-Camera . 这种直角相机照射的3D模型显示有问题,所以需要另外一个Perspective的锥形透视相机来单独照射模型。UI的直角相机设置CullingMask不照射模型。Perspective相机可以用SolidColor模式,背景色的alpha设置为0。
这样的简单做法就可以满足基本的需求了。
上面的做法的问题就是,如果有两个界面重叠都有3D模型,那么就会出现层次渲染问题。
那么进一步优化,给相机加RenderTexture,然后把透视相机拍摄的图像挂载在RawImage里问题就解决了。
创建一个RenderTexture,默认大小是256x256的,如果要清晰点可以设置成1024x1024。
不同的手机分辨率不同的,可能带来RawImage大小不同导致位置不对的问题。
我的CanvasScaler的UIScaleMode设置的是ScaleWithScreenSize.
Reference Resolution x:1080 y:1920
所以我想我只要给UI中心放一张RawImage来呈现Render。能动态计算出RawImage的宽和高就可以了。
我的Canvas的PlaneDistance设置的10,所以相机刚好距离Canvas10米的位置,锥形透视相机在UI直角主相机的子级,本地坐标是0。方便观察和计算。
首先计算出透视相机刚好在UI的视口的等距离的4个点的位置,如图:黄色的框就是计算出的位置。
计算这个网上有很多代码,贴一个上来。
Vector3[] GetCorners(float distance)
{
Vector3[] corners = new Vector3[4];
Transform tx = theCamera.transform;
float halfFOV = (theCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
float aspect = theCamera.aspect;
float height = distance * Mathf.Tan(halfFOV);
float width = height * aspect;
// UpperLeft
corners[0] = tx.position - (tx.right * width);
corners[0] += tx.up * height;
corners[0] += tx.forward * distance;
// UpperRight
corners[1] = tx.position + (tx.right * width);
corners[1] += tx.up * height;
corners[1] += tx.forward * distance;
// LowerLeft
corners[2] = tx.position - (tx.right * width);
corners[2] -= tx.up * height;
corners[2] += tx.forward * distance;
// LowerRight
corners[3] = tx.position + (tx.right * width);
corners[3] -= tx.up * height;
corners[3] += tx.forward * distance;
Debug.Log("0:"+corners[0] + "UpperLeft -> UpperRight dis:" + Vector3.Distance(corners[0], corners[1]));
Debug.Log("1:" + corners[1] + "UpperRight -> LowerRight dis:" + Vector3.Distance(corners[1], corners[3]));
Debug.Log("2:" + corners[2] + "LowerRight -> LowerLeft dis:" + Vector3.Distance(corners[3], corners[2]));
Debug.Log("3:" + corners[3] + "LowerLeft -> UpperLeft dis:" + Vector3.Distance(corners[2], corners[0]));
Debug.DrawLine(corners[0], corners[1], Color.yellow ) ; // UpperLeft -> UpperRight
Debug.DrawLine(corners[1], corners[3], Color.yellow); // UpperRight -> LowerRight
Debug.DrawLine(corners[3], corners[2], Color.yellow); // LowerRight -> LowerLeft
Debug.DrawLine(corners[2], corners[0], Color.yellow); // LowerLeft -> UpperLeft
}
观察输出坐标发现4个边之间距离是相同的,因为我们的相机没有改过ViewportRect相关参数。
所以我们只用取左上角的一个点就可以计算出RawImage的宽度和高度。
下面的代码就很简单就计算出来了。
Vector3 scrPos = theUICamer.WorldToScreenPoint(corners[0]); //计算左上角的点在UI相机中的位置
float w = (Mathf.Abs(scrPos.x) + Screen.width / 2) * (1080f / Screen.width) * 2; //计算在当前分辨率的宽度 1080是CanvasScaler的x
Debug.Log("lt:" + theUICamer.WorldToScreenPoint(corners[0]));
Debug.Log("w:" + w);
if (rawimage != null)
{
rawimage.sizeDelta = new Vector2(w,w);
}
这样我们把rawimage放在UI正中心就可以正确的显示RenderTexture里的内容了。
另外可能还遇到一些问题
如果模型不动,可以用相机的Depth模式。如果动可能有重影,就改成SolidColor。但是对于半透明效果的,锥形透视相机可能不能正常显示,可以加一个2D Sprite放在模型后方,否则alpha Blend不会计算 , 具体怎么做后面有空在研究。如果是纯色还好,如果有底图那么可能要计算2D Sprite的位置和大小.