问题
前段一个同事在使用Unity开发时遇到一个奇怪的问题,使用左键点击发射射线的方式选择物体,总是选不准,尤其是小的物体,鼠标点击到物体上,有时能选上,有时选不上,偶尔点击到物体旁边反而能选上,于是他让我帮看看咋回事。我第一个想法是也许代码写的有问题吧,但我仔细检查了同事写的代码,规规矩矩,完全没问题。
就类似下面这种:
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 500))
{
Debug.Log(hit.transform.name);
}
}
我一时也是有点儿发懵,但是当我检查他的摄像机设置时,发现他把摄像机的近剪切平面距离设置的很小,是0.01,同事说是为了避免距离近的物体看不到,只能设置到最小,本来想设置为0,但是Unity不允许,最小就只能是0.01了。好吧,反正我也找不到问题所在,咱不妨把这个值先设置为默认值0.3,排除是这个引发问题的可能吧。结果不可思议的事情发生了,再次运行,马上就能够精确点击了。
为啥(一)
问题是解决了,但是为什么呢?
先看看这行代码:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
很多时候,这几乎就是Unity的标准写法,一直以来,我都没有深入的研究过,我一直以为这个ray是从摄像机的位置发射出来,也就是ray.origin这个值应该和摄像机的位置是重叠的,但是,当我把ray.origin打印出来(代码如下)的时候,我发现,这个这值位于摄像机的近剪切平面上,并不和摄像机位置重叠!!!
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.Log(ray.origin);
但是,这和近剪切距离的值有什么关系呢?
因为我们会发现,近剪切平面的长宽和近剪切距离是等比关系,如果近剪切距离小,那么意味着近剪切平面的长宽也变得很小,对于
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
这样的代码来说,射线的 ray.origin这个值的位置取决于鼠标在屏幕上的位置映射到近剪切平面的结果,而近剪切平面的长宽越大,越容易获得一个精确的值(这个也实在是没办法的事情,Unity的位置精度是float嘛,而这种精度也不单单是Unity如此,大部分游戏引擎都如此,据说使用Double精度的位置对显卡渲染速度影响很大,同时动力学计算也有问题,当然了,听说而已,没亲身研究过)。
所以我们可以认为,如果近剪切距离是0.3的话,其精度是近剪切距离0.01的30倍,嗯,好像应该可以这么认为吧。
为啥(二)
但是为什么射线的起始点不是和摄像机位置重叠,偏偏要放在近剪切平面上呢?仔细想想,好像也很好理解,摄像机只会渲染其视锥体内的物体,不会渲染摄像机所在位置和近剪切平面之间的物体,如果射线的出发点和摄像机重叠,那么就可以点击到摄像机所在位置和近剪切平面之间的物体,而这个物体用户根本看不到!Unity当然不应该让这种事情发生,于是把射线的位置放在了近剪切平面上就成了解决这个问题的绝佳方法!!
为啥啊
到此为止,我以为一切都在掌握之中,于是我认为,远剪切平面之外的物体也点击不了,不管你在Physics.Raycast方法中把射线发射距离设置为多远!
然后我把Physics.Raycast方法中的maxDistance设置为2000,因为摄像机默认的远剪切距离为1000,如果我把一个物体放置在远剪切平面之外,那么一定是即渲染不了,也点击不到。然而经过测试,发现并非如此,远剪切平面之外的物体只要在射线发射的maxDistance距离之内,就能够被点击到,哈哈哈哈,为啥啊......