Unity VR中基于触摸位置的RenderTexture射线投射精度问题
问题分析与修复评估
一、原始代码核心问题
- 触摸点计算偏差:用
other.ClosestPointOnBounds(transform.position)获取的是控制器碰撞体到面板位置的最近点,并非控制器在面板平面上的精确投影,导致触摸位置初始就有偏移。 - 角度修正逻辑失效:仅通过
Vector3.Angle计算相机与面板的夹角,无法区分旋转方向(比如左右翻转),180度时的对称偏移就是因为角度无法判断相机是向左还是向右翻转,手动修正X轴的逻辑完全不可靠。 - 硬编码面板尺寸:直接写死面板尺寸值,一旦面板缩放或修改尺寸,归一化计算直接失效,适配性为零。
二、修复代码的有效改进
- 改用RectTransform处理坐标:通过
rectTransform.InverseTransformPoint将世界坐标转为UI本地坐标,自动适配面板的尺寸、缩放和锚点,彻底解决硬编码尺寸的问题。 - 触摸点投影更精准:用
Plane.ClosestPointOnPlane获取控制器在面板平面上的精确投影,替代原始的碰撞体边界最近点计算,避免了碰撞体形状带来的误差。 - 适配UVRect映射:考虑了RawImage的
uvRect属性,若RenderTexture与面板的UV映射有偏移,这部分逻辑能修正纹理坐标到正确的视口位置。
三、修复代码仍存在的问题
- 初始化逻辑性能浪费:
InitializeCameraAndPanel在Update中每帧调用,遍历所有PhotonView查找相机和面板,不仅性能开销大,还可能频繁覆盖目标引用,导致状态不稳定。 - 触摸点未实时更新:仅在
OnTriggerEnter记录一次碰撞点,控制器在面板上移动时不会更新触摸位置,始终使用第一次接触的点,无法跟随控制器移动。 - Y轴坐标未翻转:Unity UI坐标以左上为原点,而相机视口坐标以左下为原点,修复代码未处理这个差异,会导致Y轴触摸位置上下颠倒。
- 未处理相机全方向旋转:若其他玩家相机存在俯仰角度等非Z轴旋转,当前逻辑无法适配,射线仍会出现偏移。
四、修正建议
1. 优化初始化逻辑
将初始化从Update移除,改为在启动和玩家状态变化时执行:
void Start() { InitializeCameraAndPanel(); PhotonNetwork.AddCallbackTarget(this); } public override void OnPlayerEnteredRoom(Player newPlayer) { InitializeCameraAndPanel(); } public override void OnPlayerLeftRoom(Player otherPlayer) { InitializeCameraAndPanel(); } void OnDestroy() { PhotonNetwork.RemoveCallbackTarget(this); }
2. 实时更新触摸点
在Update中判断控制器处于触发状态时,实时计算投影点:
void Update() { bool gripHeld = OVRInput.Get(OVRInput.Button.PrimaryHandTrigger, OVRInput.Controller.RTouch); bool triggerNotPressed = !OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger, OVRInput.Controller.RTouch); bool isTouching = colliderPoint != null; if (isTouching) { var rightHand = GameObject.FindGameObjectWithTag("rightHand"); if (rightHand != null) { var plane = new Plane(transform.forward, transform.position); colliderPoint = plane.ClosestPointOnPlane(rightHand.transform.position); } } if (gripHeld && triggerNotPressed && isTouching) { InteractWithRenderTexture(); } }
3. 修正Y轴坐标翻转
转换坐标时处理UI与视口的原点差异:
private void InteractWithRenderTexture() { if (colliderPoint == null || displayGameObject == null || displayRenderCamera == null) return; Vector3 worldSpaceHitPoint = colliderPoint.Value; Vector2 localHitPoint = displayGameObject.rectTransform.InverseTransformPoint(worldSpaceHitPoint); var rect = displayGameObject.rectTransform.rect; // 转换为0-1范围,翻转Y轴适配视口原点 Vector2 textureCoord = new Vector2( (localHitPoint.x - rect.xMin) / rect.width, 1f - (localHitPoint.y - rect.yMin) / rect.height ); // 应用UVRect的偏移与缩放 textureCoord.x = displayGameObject.uvRect.x + textureCoord.x * displayGameObject.uvRect.width; textureCoord.y = displayGameObject.uvRect.y + textureCoord.y * displayGameObject.uvRect.height; Ray ray = displayRenderCamera.ViewportPointToRay(textureCoord); // 调试与射线检测逻辑保持不变 }
4. 适配相机全方向旋转
用矩阵变换替代角度修正,支持任意相机旋转:
private Vector2 GetCameraViewportPoint(Vector3 worldHitPoint) { // 将世界触摸点转为相机本地坐标 Vector3 cameraLocalPoint = displayRenderCamera.transform.InverseTransformPoint(worldHitPoint); // 通过投影矩阵计算视口坐标 Vector4 viewportPoint = displayRenderCamera.projectionMatrix * new Vector4(cameraLocalPoint.x, cameraLocalPoint.y, -cameraLocalPoint.z, 1f); viewportPoint /= viewportPoint.w; return new Vector2((viewportPoint.x + 1f) / 2f, (viewportPoint.y + 1f) / 2f); }
在InteractWithRenderTexture中调用此方法替代原纹理坐标计算,即可适配相机任意旋转角度。
内容的提问来源于stack exchange,提问作者Yumekawa-chan
相关产品推荐
相关产品推荐

