大家好,我是阿赵
这里来分享一个最近遇到的小问题。

一、发现问题

如果我们想将3D模型放在UI上,一个比较普遍的做法是:
用一个单独的摄像机,把3D模型拍下来,并转成RenderTexture,贴到RawImage上。
那么如果我们想获取3D模型在UI上的位置,该怎么做呢?
一般的步骤是:
1.通过Camera.WorldToScreenPoint把模型的世界坐标转换到相对于摄像机的屏幕坐标
2.通过RectTransformUtility.ScreenPointToWorldPointInRectangle把屏幕坐标转换到相对于指定的UI控件和摄像机的UI坐标
这个过程应该没什么问题,但最近做一个功能的时候,发现怎么算都不对,算出来的UI坐标会因为分辨率的变化而变化。
中途心酸的查Bug过程就不说了,直接说结论。
原来之前我做了一个优化, 设置在摄像机的TargetTexture上的RenderTexture,它的分辨率并不是和屏幕一样大的,而是根据一个范围去等比缩小的,比如根据屏幕分辨率,向下对齐到某个接近的值,比如1024之类,这是为了避免因为屏幕分辨率过大而导致生成的RenderTexture太大,占用内存。
然后,Camera.WorldToScreenPoint这个方法,如果在指定的摄像机没有设置TargetTexture时,它所计算的大小,是屏幕的大小。如果指定了TargetTexture,那么他的计算大小,就是指定的RenderTexture的大小。

二、验证问题

这里我们可以通过一个小例子来验证一下:
首先写了个很简单的方法来获取屏幕坐标:

private void GetScreenPos()
{
	Vector2 pos = cam.WorldToScreenPoint(go.transform.position);
	Debug.Log(pos);
}

然后建立了一个和当前Game视图屏幕分辨率一样的的RenderTexture

unity Texture2DArray 使用 unity target texture_ui

在场景里面随便摆了一个Cube,运行一下,得到的屏幕坐标是:(743.0, 446.5)
接下来把RenderTexture缩小10倍,变成192x108,这次得到的屏幕坐标是:(74.3, 44.6)

三、解决问题

对于这个问题,既然知道了原因,那么要解决也是很简单的,方案有这些:
1、生成的RenderTexture的尺寸和屏幕分辨率一样。
这个方案是最简单的,最容易实现。
但是有个问题,现在的千元手机的屏幕都达到2k屏以上了,如果真的按照屏幕分辨率去生成RenderTexture,那么一张RenderTexture的占用内存可能就要达到几十甚至上百M了。但千元机有可能纯粹只是屏幕分辨率高,其他配置跟不上,一个不好,可能就闪退了。

2、在需要获取屏幕坐标的摄像机身上,复制多一个摄像机出来。
这个摄像机平时不用,只有在需要获取屏幕坐标时,才拿来用一下。
由于这个摄像机本身并没有设置RenderTexture,所以不会在计算上出问题。
多个摄像机的管理需要比较严谨,所以写代码的时候要注意一下,是否真的不用的时候没有开,不然就无端端多了一倍以上的渲染了。

3、在需要获得屏幕坐标时,把TargetTexture置空
这个方法看着有点扯淡,但实现起来却意外的简单。
封一个方法获取屏幕坐标,把摄像机和物体传进去。然后在计算之前,先判断一下摄像机上有没有挂TargetTexture,如果有,那么拿一个临时变量先存储起来,接着清空摄像机的TargetTexture,获取屏幕坐标,然后把存起来的TargetTexture赋值回去。