Featured image of post Unity AR + GPS, tracking di immagini in Realtà Aumentata basato sulla posizione

Unity AR + GPS, tracking di immagini in Realtà Aumentata basato sulla posizione

In base alla posizione dello Smartphone, il comportamento dinamico di un'applicazione Unity per il tracciamento delle immagini...

In questo articolo vedremo brevemente come creare un’applicazione Unity per Android in Realtà Aumentata, utilizzando ARFoundation e ARCore, in particolare come posizionare degli oggetti appartenenti al mondo virtuale nel mondo reale e con l’ausilio del GPS personalizzeremo il tracking delle immagini in base alla posizione.

La prima parte di questo progetto proviene dall’articolo https://www.francescogarofalo.it/post/imagetrackingunity/.


Introduzione

Per creare questa applicazione avremo bisogno:

Il primo passo è quello di creare un progetto per l’Image Tracking, puoi seguire questo articolo https://www.francescogarofalo.it/post/imagetrackingunity/ oppure scaricare da github il seguente progetto Github T1 - Tracking Image AR e lavorare a partire da quest’ultimo.

Se sei già confidente sull’utilizzo del GPS in Unity puoi saltare il prossimo capitolo e passare direttamente al capitolo “Creazione applicazione AR GPS”.

Image Tracking Unity

Per comprendere al meglio come creare questo progetto è fortemente consigliato leggere l’articolo https://www.francescogarofalo.it/post/imagetrackingunity/ o guardare il video https://www.youtube.com/watch?v=_gBBfdSZ-H8.

In alternativa ecco una veloce infarinatura:

AR Foundation consente di lavorare con più piattaforme in realtà aumentata, questo pacchetto infatti fornisce un’interfaccia per gli sviluppatori Unity da utilizzare, ma da sola non implementa alcuna funzionalità AR.

Per utilizzare AR Foundation è necessario conoscere il dispositivo di destinazione e scaricare dei pacchetti separati in base alla piattaforma di destinazione supportata da Unity; nel nostro caso utilizzeremo ARCore XR Plug-in Android.

Il GPS in un dispositivo mobile?

GPS sta per Global Positioning System. È una tecnologia sviluppata dalla Marina degli Stati Uniti e attualmente di proprietà del governo degli Stati Uniti e supervisionata dalla sua Air Force.

Cos’è il GPS?

Il GPS è un sistema di radionavigazione. Utilizza le onde radio tra satelliti e il ricevitore all’interno del device (smartphone, telefono, dispositivo IoT, ecc.) per fornire informazioni sulla posizione e sull’ora a qualsiasi software/applicazione che deve utilizzarlo. Non è necessario inviare alcun dato effettivo nello spazio affinché il GPS funzioni; devi solo essere in grado di ricevere dati da quattro o più dei satelliti in orbita dedicati alla geolocalizzazione.

Il GPS è preciso, però è lento e consuma molta energia da entrambi i lati.

Ogni satellite ha il proprio orologio atomico interno e invia un segnale codificato nel tempo su una frequenza specifica. Il chip del ricevitore determina quali satelliti sono visibili e senza ostacoli, quindi inizia a raccogliere dati dai satelliti con i segnali più forti. I dati GPS sono lenti, e questo è in base alla progettazione quindi ci vorrà circa un minuto per ottenere la geolocalizzazione molto precisa.

Il ricevitore GPS del telefono utilizza i dati di questi segnali per triangolare dove ti trovi e a che ora è. Si noti la parola triangolazione e la menzione sopra che sono necessari quattro satelliti per il funzionamento del GPS. Il quarto segnale viene utilizzato per determinare l’altitudine in modo da poter ottenere i dati di geolocalizzazione su una mappa con solo tre segnali.

I ricevitori GPS consumano molta energia e richiedono una visione libera di più satelliti per funzionare. Gli ostacoli possono includere edifici alti, e ciò significa che i luoghi in cui vive la maggior parte di noi possono (e lo fanno) avere difficoltà a ottenere i dati di cui ha bisogno tutto il tempo. È qui che entra in gioco AGPS (Assisted Global Positioning System).

Integrazione con Unity - LocationService

Unity ha una classe LocationService per l’utilizzo della posizione GPS.

Dalla documentazione ufficiale Unity (un po’ scarna di informazioni) abbiamo i seguenti parametri:

  1. Il parametro desiredAccuracyInMeters indica la precesione che desideriamo, dal servizio GPS, definita in metri. Il livello di precisione è dato dalle ultime coordinate GPS fornite dal dispositivo all’applicazione. Valori alti (come 500) non richiedono al dispositivo di utilizzare il GPS, quindi si ottiene una posizione approssimativa questo permette al dispositivo di risparmiare batteria, invece valori come 5-10 utilizzano il GPS del device al costo energivori più alti. Il valore di default di questo parametro è 10 metri.
  2. Il parametro updateDistanceInMeters indica la distanza minima, in metri, di uno spostamento, del dispositivo prima che Unity aggiorni Input.location. Valori alti come 500 producono meno aggiornamenti e richiedono meno risorse da elaborare. Il valore di default di questo parametro è 10 metri.

Su Android, l’utilizzo della metodo LocationService.Start in uno script aggiunge automaticamente il privilegio ACCESS_FINE_LOCATION al manifest Android. Se si utilizzano valori di precisione bassa come 500 o superiori, è possibile selezionare la posizione a bassa precisione nel Player Settings e aggiungere il privilegio ACCESS_COARSE_LOCATION.

Un esempio di script C# per la gestione della posizione in Unity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using UnityEngine;
using System.Collections;

    public class TestLocationService : MonoBehaviour
    {
        IEnumerator Start()
        {
            // Verifica se l'utente ha abilitato il servizio di localizzazione.
            if (!Input.location.isEnabledByUser)
                yield break;

            // Avvia il Location Service
            Input.location.Start();

            // Attende finché l'inizializzazione del servizio di localizzazione viene eseguita
            int maxWait = 20;
            while (Input.location.status == LocationServiceStatus.Initializing && maxWait > 0)
            {
                yield return new WaitForSeconds(1);
                maxWait--;
            }

            // Se il servizio non viene inizializzato entro 20 secondi, l'utilizzo del servizio di localizzazione viene annullato.
            if (maxWait < 1)
            {
                print("Timed out");
                yield break;
            }

            // Se la connessione fallisce, l'utilizzo del servizio di localizzazione viene annullato.
            if (Input.location.status == LocationServiceStatus.Failed)
            {
                print("Unable to determine device location");
                yield break;
            }
            else
            {
                // Se la connessione è riuscita, recupera la posizione corrente del dispositivo e la visualizza nella Console.
                print("Location: " + Input.location.lastData.latitude + " " + Input.location.lastData.longitude + " " + Input.location.lastData.altitude + " " + Input.location.lastData.horizontalAccuracy + " " + Input.location.lastData.timestamp);
            }

            // Arresta il servizio di localizzazione se non è necessario chiedere continuativamente la posizione.
            Input.location.Stop();
        }
    }

Unity AR e GPS

Get Started

Creiamo un progetto Unity 3D, e iniziamo la configurazione per ARCore e i servizi di localizzazione:

  1. Dal Package Maanger importiamo ARCore XR Plugin e ARFoundation:

Package AR Foudation, ARCore

  1. A questo punto possiamo configurare il progetto android ed installare il XR Plug-in Management ed abilitare ARCore. Quindi click su File -> Build Settings -> Android e cliccate su Switch Platform; una volta terminato click su Player Settings… -> XR Plug-in Management -> Initialize XR on Startup e assicuratevi che ARCore sia selezionato.

Unity Settings XR Plug-in Management

  1. Configurare il Player: Dal menu Project Settings click su Player -> Rimuovete la spunta su Auto Graphics API e rimuovete Vulkan dalle API; infine dalla scheda Minimum API Level settate Android 9.0 ‘Pie’ (API level 28).

Unity Settings Player

  1. Configurare l’accesso alla location: Dal Player spuntare Low Accuracy Location

Unity Settings Location

N.B. Su Apple iOS le operazioni da eseguire sono diverse.

Dopo aver ultimato la configurazione possiamo lavorare sul progetto partendo prima dall’Image Tracking e poi del GPS con Image Tracking.

GPS Script

Dobbiamo creare uno script per la gestione del GPS del nostro device.

Procediamo quindi alla creazione dello script GPSLocation.cs basandoci sullo script definito dalla documentazione Unity.

Questo script si occuperà sia della gestione del GPS, sia dell’attivazione del tracciamento in base alla posizione infatti all’interno del metodo activateImageTracking dovremmo inserire le coordinate geografiche dove attivare il tracciamento.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.Android;
using UnityEngine.UI;

public class GPSLocation : MonoBehaviour
{
    [SerializeField]
    public TextMeshProUGUI statusGPS;

    [SerializeField]
    public TextMeshProUGUI latitudeValue;

    [SerializeField]
    public TextMeshProUGUI logitudeValue;

    public GameObject trackingARScript;

    float latitude;
    float longitude;
    void Start()
    {
        StartCoroutine(startGPS());
    }
    IEnumerator startGPS()
    {
        // check if user has location service enabled
        if (!UnityEngine.Input.location.isEnabledByUser) {
            // TODO Failure
            Debug.LogFormat("Location not enabled");
            statusGPS.text = "Location not enabled";
            yield break;
        }


        // start service
        Input.location.Start();
        statusGPS.text = "Start Service";

        // wait until service inizialized 
        int maxWait = 20;
        while (Input.location.status == LocationServiceStatus.Initializing && maxWait > 0)
        {
            yield return new WaitForSeconds(1);
            maxWait--;
            statusGPS.text = "Wait for posizion";

        }

        // service not init in 20 seconds
        if (maxWait < 1)
        {
            print("GPSLocation: Timed out");
            statusGPS.text = "Timed Out";
            yield break;
        }

        // connection failed
        if (Input.location.status == LocationServiceStatus.Failed)
        {
            print("Unable to determine device location");
            statusGPS.text = "Unable to determine device location...";
            yield break;
        }
        else
        {
            // access granted
            print("Location: " + Input.location.lastData.latitude + " " + Input.location.lastData.longitude + " " + Input.location.lastData.altitude + " " + Input.location.lastData.horizontalAccuracy + " " + Input.location.lastData.timestamp);
            // invoke gps every second
            InvokeRepeating("UpdateGPS", 0.5f, 1f);
            InvokeRepeating("activateImageTracking", 0.5f, 5f);
            statusGPS.text = "Running";
        }

        // Stop service GPSLocation
        Input.location.Stop();
    }
    private void UpdateGPS()
    {
            //access granted to GPS
            //statusGPS.text = "Running";
            latitudeValue.text = Input.location.lastData.latitude.ToString();
            logitudeValue.text = Input.location.lastData.longitude.ToString();
    }

    public static float getLongitude()
    {
        float longitude = Input.location.lastData.longitude;
        return longitude;
    }

    public static float getLatitude()
    {
        float latitude = Input.location.lastData.latitude;
        return latitude;
    }

    public void activateImageTracking()
    {
        if (getLongitude() > 12.61000 && getLongitude() < 12.79000
    && getLatitude() > 41.85054 && getLatitude() < 41.87054)
        {
            statusGPS.text = "Tracking Enabled";
            GameObject.Find("AR Session Origin").GetComponent<ARTrackedImageGPS>().enabled = true;
        }
        else
        {
            statusGPS.text = "Tracking Disabled";
            GameObject.Find("AR Session Origin").GetComponent<ARTrackedImageGPS>().enabled = false;
        }
    }

}

Ci sono degli elementi che chiamano uno script ARTrackedImageGPS.cs che creeremo nel prossimo paragrafo.

Image Tracking

Dalla schermata principale di Unity il primo passo è rimuovere la Main Camera standard. Nel pannello Hierarchy click con il tasto destro del mouse -> XR -> AR Session, e XR -> AR Session Origin.

Unity Hierarchy with AR Session

In questo caso dobbiamo creare uno script che si occupi sia del tracciamento che del posizionamento dell’oggetto sull’immagine da tracciare.

Procediamo quindi alla creazione dello script ARTrackedImageGPS.cs che si occuperà della parte AR.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
using System;
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

public class ARTrackedImageGPS : MonoBehaviour
{
    [SerializeField]
    private TextMeshProUGUI debugLog;

    [SerializeField]
    private GameObject placedObjectPosition;
    
    [SerializeField]
    private Vector3 scaleFactor = new Vector3(0.1f, 0.1f, 0.1f);

    [SerializeField]
    private XRReferenceImageLibrary runtimeImageLibrary;

    private ARTrackedImageManager trackImageManager;

    void Start()
    {
        trackImageManager = gameObject.AddComponent<ARTrackedImageManager>();
        trackImageManager.referenceLibrary = trackImageManager.CreateRuntimeLibrary(runtimeImageLibrary);
        trackImageManager.requestedMaxNumberOfMovingImages = 3;

        trackImageManager.enabled = true;
        trackImageManager.trackedImagePrefab = placedObjectPosition;
        debugLog.text += "Creating Runtime Mutable Image Library\n";
        trackImageManager.trackedImagesChanged += OnTrackedImagesChanged;
        ShowTrackerInfo();
    }


public void ShowTrackerInfo()
    {
        var runtimeReferenceImageLibrary = trackImageManager.referenceLibrary as MutableRuntimeReferenceImageLibrary;
    }

    void OnDisable()
    {
        trackImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
    }

    public IEnumerator AddImageJob(Texture2D texture2D)
    {
        yield return null;

        debugLog.text = string.Empty;
        debugLog.text += "Adding image\n";

        var firstGuid = new SerializableGuid(0, 0);
        var secondGuid = new SerializableGuid(0, 0);
        XRReferenceImage newImage = new XRReferenceImage(firstGuid, secondGuid, new Vector2(0.1f, 0.1f), Guid.NewGuid().ToString(), texture2D);
            try
            {
                MutableRuntimeReferenceImageLibrary mutableRuntimeReferenceImageLibrary = trackImageManager.referenceLibrary as MutableRuntimeReferenceImageLibrary;
                var jobHandle = mutableRuntimeReferenceImageLibrary.ScheduleAddImageWithValidationJob(texture2D, Guid.NewGuid().ToString(), 0.1f);
            }
            catch (Exception e)
            {
                if (texture2D == null)
                {
                    debugLog.text += "texture2D is null";
                }
                debugLog.text += $"Error: {e.ToString()}";
            }
    }

    void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
    {

            foreach (ARTrackedImage trackedImage in eventArgs.added)
            {
                // Display the name of the tracked image in the canvas
                trackedImage.transform.Rotate(Vector3.up, 180);

            }

            foreach (ARTrackedImage trackedImage in eventArgs.updated)
            {
                // Display the name of the tracked image in the canvas
                trackedImage.transform.Rotate(Vector3.up, 180);

            }
    }

}

Integration

Possiamo usare gli elementi di testo della UI per ottenere informazioni di debug durante l’esecuzione dell’applicazione sul dispositivo Android. Per fare questo dobbiamo creare i seguenti elementi nella Hierarchy di Unity come da immagine sottostante:

Unity UI

Ora integriamo gli script all’interno dell’oggetto AR Session Origin e la UI per avere un’interfaccia da cui leggere le informazioni relative al GPS e al tracciamento localizzato, come da immagine sottostante:

Unity AR Session Integration

A questo punto siamo pronti per provare la nostra applicazione.

Fonti

Grazie ☺

Condividi:
Create with Hugo, Theme Stack;
Sito creato e gestito da Francesco Garofalo;