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
相关产品推荐
相关产品推荐

