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

Unity AR GPS项目问题:3D胶囊未正确显示在指定GPS位置

AR GPS项目定位偏移与方向偏差问题
  • 项目目标:在Unity AR中让胶囊预制体显示在办公桌范围内的指定GPS坐标,兼容Android和iOS,不使用付费地图服务,后续计划添加碰撞体与近距离交互功能。
  • 当前问题:胶囊可正常显示、尺寸随距离适配,但未出现在指定GPS位置(始终靠近用户),且罗盘方向偏差约90度。
  • 场景层级:AR Session、Directional Light、UI、XR Origin (AR Rig)、GPS Manager。
  • 已做操作:添加Debug日志确认流程正常,修改胶囊缩放为(4,80,4)规避海拔问题。

挂载的GPS Manager脚本

using System.Collections;
using UnityEngine;
using TMPro;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

public class GPSLocationManager : MonoBehaviour
{
    public GameObject capsulePrefab; // Assigned in the Inspector
    
    private GameObject xrOrigin; // Used as the AR Session Origin
    private ARAnchorManager anchorManager; // AR Anchor Manager for anchoring objects in AR space - unsure if working

    // Reference GPS location dynamically set to the user's current location
    private Vector2 referenceGPSPos;
    private bool isReferencePointSet = false;

    // List of colors is working
    private Color[] capsuleColors = new Color[]
    {
        Color.red, Color.blue, Color.green, Color.yellow, Color.magenta
    };

    void Awake() 
    {
        xrOrigin = GameObject.Find("XR Origin (AR Rig)"); 
        if (xrOrigin == null)
        {
            Debug.LogError("XR Origin (AR Rig) is not found in the scene.");
            return;
        }
        
        anchorManager = xrOrigin.GetComponent<ARAnchorManager>();
        if (anchorManager == null) 
        {
            Debug.LogError("ARAnchorManager component is not found on the XR Origin (AR Rig).");
        }
    }

    void Start()
    {
        StartCoroutine(StartLocationService());
    }

    private IEnumerator StartLocationService()
    {
        if (!Input.location.isEnabledByUser)
        {
            Debug.Log("[GPSLocationManager] User has not enabled GPS");
            yield break;
        }

        Input.location.Start(5f, 5f); 
        Debug.Log("[GPSLocationManager] Starting location service...");

        while (Input.location.status == LocationServiceStatus.Initializing)
        {
            yield return new WaitForSeconds(1);
        }

        if (Input.location.status == LocationServiceStatus.Failed)
        {
            Debug.Log("[GPSLocationManager] Unable to determine device location");
            yield break;
        }
        else if (!isReferencePointSet && Input.location.status == LocationServiceStatus.Running)
        {
            SetReferenceLocation();
            Debug.Log("[GPSLocationManager] Location service started successfully");
            PlaceCapsuleBasedOnGPS();
            Input.location.Stop();
            Debug.Log("[GPSLocationManager] Stopping location service...");
        }
    }

    void SetReferenceLocation()
    {
        referenceGPSPos = new Vector2(Input.location.lastData.latitude, Input.location.lastData.longitude);
        isReferencePointSet = true;
    }

    void PlaceCapsuleBasedOnGPS()
    {
        float[,] targetLocations = new float[,] {
            {-33.XXXXXXf, 151.XXXXXXf}, 
            {-33.XXXXXXf, 151.XXXXXXf}, 
            {-33.XXXXXXf, 151.XXXXXXf}, 
        };

        Debug.Log("[GPSLocationManager] Placing capsules based on GPS locations...");

        for (int i = 0; i < targetLocations.GetLength(0); i++)
        {
            float lat = targetLocations[i, 0];
            float lon = targetLocations[i, 1];
            if(IsCloseEnough(Input.location.lastData.latitude, Input.location.lastData.longitude, lat, lon))
            {
                PlaceCapsuleAtGPSLocation($"Capsule {i+1}", lat, lon);
            }
            else
            {
                Debug.Log($"[GPSLocationManager] Skipped placing Capsule {i+1} due to distance.");
            }
        }
    }

    void PlaceCapsuleAtGPSLocation(string capsuleName, float lat, float lon)
    {
        Vector3 position = GPSToUnitySpace(lat, lon);
        Pose pose = new Pose(position, Quaternion.identity);
        ARAnchor anchor = anchorManager.AddAnchor(pose);

        if (anchor == null)
        {
            Debug.LogWarning("Failed to create anchor.");
            return;
        }

        GameObject capsule = Instantiate(capsulePrefab, position, Quaternion.identity, anchor.transform);
        capsule.name = capsuleName;
        Renderer renderer = capsule.GetComponent<Renderer>();
        if (renderer != null)
        {
            renderer.material.color = capsuleColors[Random.Range(0, capsuleColors.Length)];
        }

        var textMesh = capsule.GetComponentInChildren<TextMeshProUGUI>();
        if (textMesh != null)
        {
            textMesh.text = capsuleName;
        }
    }

    public Vector3 GPSToUnitySpace(float lat, float lon)
    {
        float latDistance = (lat - referenceGPSPos.x) * 111319.9f; // Meters per degree latitude
        float lonDistance = (lon - referenceGPSPos.y) * Mathf.Cos(Mathf.Deg2Rad * lat) * 111319.9f; // Meters per degree longitude
        Vector3 positionOffset = new Vector3(lonDistance, 0, latDistance);
        return xrOrigin.transform.position + positionOffset; // Now correctly using xrOrigin
    }

    bool IsCloseEnough(float currentLat, float currentLon, float targetLat, float targetLon)
    {
        float distanceLat = Mathf.Abs(currentLat - targetLat);
        float distanceLon = Mathf.Abs(currentLon - targetLon);
        float latDistanceMeters = distanceLat * 111319.9f;
        float lonDistanceMeters = distanceLon * Mathf.Cos(Mathf.Deg2Rad * currentLat) * 111319.9f;
        float distanceMeters = Mathf.Sqrt(latDistanceMeters * latDistanceMeters + lonDistanceMeters * lonDistanceMeters);
        
        // Removed incorrect reference to 'i' here, no loop index needed.
        Debug.Log($"[GPSLocationManager] Distance to target: {distanceMeters} meters");

        return distanceMeters <= 2000.0f; // Use an appropriate distance threshold
    }
}

问题排查与修复方案

1. 坐标轴映射错误(方向偏差核心原因)

Unity采用右手坐标系(X=右,Z=前),但当前代码未结合设备罗盘航向,直接将GPS经度差映射到X轴、纬度差映射到Z轴,导致方向错位90度。同时GPS的南北/东西方向需要和Unity空间正确对应。

修改坐标转换逻辑,加入设备航向修正:

// 新增字段存储设备真实航向
private float deviceTrueHeading;

// 在StartLocationService中初始化罗盘
private IEnumerator StartLocationService()
{
    // ... 原有GPS初始化代码 ...
    
    // 启用罗盘并等待初始化
    if (!Input.compass.enabled)
    {
        Input.compass.enabled = true;
        while (!Input.compass.enabled)
        {
            yield return new WaitForSeconds(0.5f);
        }
    }
    
    // ... 原有代码 ...
}

// 重写GPS转Unity空间方法
public Vector3 GPSToUnitySpace(float lat, float lon)
{
    // 计算相对GPS偏移量
    float latDelta = lat - referenceGPSPos.x;
    float lonDelta = lon - referenceGPSPos.y;
    
    // 转换为米级距离:纬度每度≈111319.9米,经度每度需乘以cos(纬度弧度)修正
    float northDistance = latDelta * 111319.9f;
    float eastDistance = lonDelta * Mathf.Cos(Mathf.Deg2Rad * referenceGPSPos.x) * 111319.9f;
    
    // 获取设备真实航向(0=正北,顺时针递增),转换为Unity Y轴旋转
    deviceTrueHeading = Input.compass.trueHeading;
    Quaternion headingRotation = Quaternion.Euler(0, -deviceTrueHeading, 0);
    
    // 将GPS的东/北方向转换为Unity的本地空间方向,再叠加设备航向
    Vector3 localOffset = headingRotation * new Vector3(eastDistance, 0, northDistance);
    
    return xrOrigin.transform.position + localOffset;
}

2. GPS单次采样精度问题

室内GPS精度有限(飘移数米),当前代码仅采样一次就停止服务,导致基准位置误差过大。改为多采样平均,并持续更新GPS:

// 新增平滑采样相关字段
private Vector2 smoothedGPSPos;
private int gpsSampleCount = 0;
private const int sampleThreshold = 5; // 取5次采样平均值

// 修改Update方法实现持续采样
void Update()
{
    if (Input.location.status == LocationServiceStatus.Running && gpsSampleCount < sampleThreshold)
    {
        smoothedGPSPos += new Vector2(Input.location.lastData.latitude, Input.location.lastData.longitude);
        gpsSampleCount++;
        
        if(gpsSampleCount == sampleThreshold)
        {
            smoothedGPSPos /= sampleThreshold;
            referenceGPSPos = smoothedGPSPos;
            isReferencePointSet = true;
            PlaceCapsuleBasedOnGPS();
            // 室内建议持续运行GPS以修正位置
            // Input.location.Stop();
        }
    }
}

// 修改StartLocationService移除单次采样后停止的逻辑
private IEnumerator StartLocationService()
{
    // ... 原有初始化代码 ...
    
    else if (Input.location.status == LocationServiceStatus.Running)
    {
        Debug.Log("[GPSLocationManager] Location service started successfully");
        // 采样逻辑交给Update处理,不再立即停止服务
    }
}

3. ARAnchor使用逻辑优化

直接用世界位置创建Anchor会导致AR空间跟踪偏移,改为基于XR Origin的局部位置创建Anchor:

void PlaceCapsuleAtGPSLocation(string capsuleName, float lat, float lon)
{
    Vector3 worldPosition = GPSToUnitySpace(lat, lon);
    // 转换为XR Origin的局部位置
    Vector3 localPosition = xrOrigin.transform.InverseTransformPoint(worldPosition);
    Pose localPose = new Pose(localPosition, Quaternion.identity);
    
    ARAnchor anchor = anchorManager.AddAnchor(localPose);
    if (anchor == null)
    {
        Debug.LogWarning("Failed to create anchor.");
        return;
    }

    GameObject capsule = Instantiate(capsulePrefab, localPosition, Quaternion.identity, anchor.transform);
    capsule.name = capsuleName;
    
    // ... 原有颜色、文本设置代码 ...
}

4. 调试辅助

添加UI文本显示实时航向与GPS坐标,方便验证:

// 在Inspector中赋值UI文本组件
public TextMeshProUGUI debugInfoText;

void Update()
{
    if(debugInfoText != null && isReferencePointSet)
    {
        debugInfoText.text = $"真实航向: {deviceTrueHeading:F1}°\n基准GPS: {referenceGPSPos.x:F6}, {referenceGPSPos.y:F6}";
    }
}

内容的提问来源于stack exchange,提问作者walk slowly

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:59:50