You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity VR中基于触摸位置的RenderTexture射线投射精度问题

问题分析与修复评估

一、原始代码核心问题

  • 触摸点计算偏差:用other.ClosestPointOnBounds(transform.position)获取的是控制器碰撞体到面板位置的最近点,并非控制器在面板平面上的精确投影,导致触摸位置初始就有偏移。
  • 角度修正逻辑失效:仅通过Vector3.Angle计算相机与面板的夹角,无法区分旋转方向(比如左右翻转),180度时的对称偏移就是因为角度无法判断相机是向左还是向右翻转,手动修正X轴的逻辑完全不可靠。
  • 硬编码面板尺寸:直接写死面板尺寸值,一旦面板缩放或修改尺寸,归一化计算直接失效,适配性为零。

二、修复代码的有效改进

  • 改用RectTransform处理坐标:通过rectTransform.InverseTransformPoint将世界坐标转为UI本地坐标,自动适配面板的尺寸、缩放和锚点,彻底解决硬编码尺寸的问题。
  • 触摸点投影更精准:用Plane.ClosestPointOnPlane获取控制器在面板平面上的精确投影,替代原始的碰撞体边界最近点计算,避免了碰撞体形状带来的误差。
  • 适配UVRect映射:考虑了RawImage的uvRect属性,若RenderTexture与面板的UV映射有偏移,这部分逻辑能修正纹理坐标到正确的视口位置。

三、修复代码仍存在的问题

  1. 初始化逻辑性能浪费:InitializeCameraAndPanel在Update中每帧调用,遍历所有PhotonView查找相机和面板,不仅性能开销大,还可能频繁覆盖目标引用,导致状态不稳定。
  2. 触摸点未实时更新:仅在OnTriggerEnter记录一次碰撞点,控制器在面板上移动时不会更新触摸位置,始终使用第一次接触的点,无法跟随控制器移动。
  3. Y轴坐标未翻转:Unity UI坐标以左上为原点,而相机视口坐标以左下为原点,修复代码未处理这个差异,会导致Y轴触摸位置上下颠倒。
  4. 未处理相机全方向旋转:若其他玩家相机存在俯仰角度等非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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 16:35:55