VRTK解决多Canvas射线穿透问题(WorldSpace)

很久之前写过一篇关于 VRTK 多 Canvas 画布重叠手柄射线穿透问题的文章,当时才疏学浅,没有找到解决方案。最近虽然不干 VR 了,但偶然研究了下,找到了比较完美的解决方案。
2023 年补充
你的需求真的非 VRTK 不可么?
都 2023 年了,别再抱着 VRTK 不放了,你现在既有现成的 XRI 套件(Unity 官方维护),也有第三方的 MRTK、Oculus Interaction SDK 等套件使用。VRTK 开发时还是 VR 初级阶段,开发者很多边界条件都没考虑。
我就要用 VRTK
如果非得用 VRTK,建议复写自己的 InputModule 及 Raycaster,从源头上把 UI 排序整对了。针对一些运行时创建遮罩层的 UGUI 组件(eg:Dropdown),建议看看 UGUI C# 源码,看看人家到底干了啥,再参照 Unity 官方的 Raycaster 与 InputModule,想想自己复写时怎么处理排序。
你到底行不行
本文内容只是为了解决多画布穿透问题,不涉及其他问题(例如某些组件工作不正常)。
当然,你也可以找我定制一套完全修复版的 VRTK UI 交互脚本,让你的各种 UI 组件都可以正常工作,但那不是本文的内容。
上文已经提到你可以自己去复写 InputModule 及 Raycaster,这没什么难的。我给你提示了方向,如果你去研究下,会发现自己实现一套 UI 交互只需要改动一点点代码而已,根本不复杂。
懒得写或者不会写,也可以找我定制一套。
起因
起因请看这篇文章: VRTK重叠Canvas上UIPointer射线穿透的问题(Unity VR)
解决方法
以下 1、2 两点必须同时修改,非二选一。
步骤1:修改 VRTK_UIGraphicRaycaster
在VRTK_UIGraphicRaycaster
脚本第 59 行,找到如下一行:
//梓喵出没
eventData.pointerCurrentRaycast = nearestRaycast.Value;
将本行注释掉:
//梓喵出没
//eventData.pointerCurrentRaycast = nearestRaycast.Value;
注释的目的是让射线投射从本地零点开始,这一行就是造成“近在咫尺的 UI 选不到,射线穿透到背后很远的 UI 上”的原因。
关于Raycast
方法如何被调用的问题,有兴趣的可以翻 UGUI 关于 GraphicRaycaster 与 EventSystem 的源码。
步骤2:修改 VRTK_VRInputModule 自定义排序
修改 1 中内容后,使用EventSystem.RaycastAll
即可以获取到射线路径上所有的 UI 元素,如果不修改 1 中内容,则会导致获取不全。
由于 EventSystem.RaycastAll 返回结果的排序与我们预期有差别,因此需要手动排序。在VRTK_VRInputModule
脚本的第 56 行,即 eventSystem.RaycastAll
的后面,追加如下内容:
raycasts.Sort((res1, res2) =>{
if (Mathf.Abs(res1.distance - res2.distance) < 0.001)
{
return res2.depth.CompareTo(res1.depth);
}
return res1.distance.CompareTo(res2.distance);
});
if (raycasts.Count > 0)
{
pointer.pointerEventData.pointerCurrentRaycast = raycasts[0];
}
距离差小于 0.001 后按 depth 排序的目的是为了避免距离计算中的精度问题。当 A B两个元素重叠时,有可能上一帧计算的距离值 A 比 B 小,在这一帧距离值就变成了 B 比 A 小(float),如果顺序乱跳,后面强行设置pointerCurrentRaycast
时就可能出现悬浮点在 A B 间不停切换的现象。
引用资料
该修复方法来自于 GitHub finartist。
- [代码]【GitHub】finartist issues/1846