Indice
ARKit Portal => Unity Vuforia ARKit Portal!
Ebbene si! Il video qui sopra e il tutorial unity che stai iniziando a leggere sono la mia soluzione definitiva all’oramai famoso interdimensional portal, sviluppato per ARKit, in cui entri in un mondo virtuale e che in tanti stanno cercando di riprodurre utilizzando Unity e Vuforia. Ora che ha il supporto per ARKit e ARcore potrai avere il portale interdimensionale sia per iOS e sia per android.
Il principale beneficio di usare Vuforia, oramai incluso in Unity engine, è quello di poter avere il portale non solo per iOS anche per Android, non mi sembra poco; oramai il binomio Unity Vuforia sta diventando una garanzia in ambito AR.
Se ancora non lo conosci, scoprirai cosa è Vuforia Ground Plane e come può aiutarti a realizzare il portale.
Bene, come sempre mettiti comodo e leggi questo tutorial unity3d che ho scritto per te.
Da dove partire?
Vuforia mette a disposizione una feature chiamata Ground Plane; questa ti permette di posizionare un oggetto su una superficie piana, nel nostro caso un bel pavimento oppure un marciapiede; utilizzare infatti questa feature all’aperto rende molto più realistico il passaggio fisico tra il mondo reale e quello virtuale compiuto attraversando il portale.
Quindi con GroundPlane puoi posizionare il portale dove preferisci e realizzare la stessa identica funzionalità che hai visto nel video al link suddetto. Questo è il primo step da soddisfare.
Per questo progetto di esempio ho utilizzato Unity 2018.2.19 e Vuforia 7.26; crea un progetto, abilita SOLO Vuforia in XR Settings, rimuovi il game object Camera di default, aggiungi il medesimo che trovi nel menu Vuforia e assicurati che abbia il tag impostato a MainCamera.
Ora osserva il componente Camera incluso nel game object ARCamera e imposta Clipping Planes a Near = 0.01 e Far = 500, Depth = -1.
Aggiungi poi un componente Sphere Collider e un RigidBody; questi due ti servono per far intercettare la camera da un altro script, che troverai nel seguito dell’articolo, quando ti avvicinerai al portale.
Le impostazioni fatte finora devono corrispondere a quelle che vedi in Fig.1:

Ground Plane è il segreto del successo di Unity Vuforia
Ti serve ora un game object che ti permetta di gestire Ground Plane per fargli capire che deve far comparire il tuo mondo virtuale, portale compreso, nel punto che gli indicherai con il tap del dito sul display dello smartphone.
Crea quindi un game object, chiamalo PortalPlaneManager e associagli lo script seguente:
using System.Collections.Generic; using UnityEngine; using Vuforia; public class PortalPlaneManager : MonoBehaviour { public enum PlaneMode { GROUND } public PlaneFinderBehaviour m_PlaneFinder; public GameObject m_PlaneAugmentation; public static bool GroundPlaneHitReceived, PortalIsPlaced; public static PlaneMode planeMode = PlaneMode.GROUND; public static bool AnchorExists { get { return anchorExists; } private set { anchorExists = value; } } StateManager m_StateManager; SmartTerrain m_SmartTerrain; PositionalDeviceTracker m_PositionalDeviceTracker; ContentPositioningBehaviour m_ContentPositioningBehaviour; AnchorBehaviour m_PlaneAnchor; int AutomaticHitTestFrameCount; int m_AnchorCounter; static bool anchorExists; void Start() { VuforiaARController.Instance.RegisterVuforiaStartedCallback(OnVuforiaStarted); VuforiaARController.Instance.RegisterOnPauseCallback(OnVuforiaPaused); DeviceTrackerARController.Instance.RegisterTrackerStartedCallback(OnTrackerStarted); DeviceTrackerARController.Instance.RegisterDevicePoseStatusChangedCallback(OnDevicePoseStatusChanged); m_PlaneFinder.HitTestMode = HitTestMode.AUTOMATIC; m_PlaneAnchor = m_PlaneAugmentation.GetComponentInParent<AnchorBehaviour>(); UtilityHelper.EnableRendererColliderCanvas(m_PlaneAugmentation, false); } void Update() { if (!VuforiaRuntimeUtilities.IsPlayMode() && !AnchorExists) { AnchorExists = DoAnchorsExist(); } GroundPlaneHitReceived = (AutomaticHitTestFrameCount == Time.frameCount); SetSurfaceIndicatorVisible( GroundPlaneHitReceived && (planeMode == PlaneMode.GROUND )); } void OnDestroy() { VuforiaARController.Instance.UnregisterVuforiaStartedCallback(OnVuforiaStarted); VuforiaARController.Instance.UnregisterOnPauseCallback(OnVuforiaPaused); DeviceTrackerARController.Instance.UnregisterTrackerStartedCallback(OnTrackerStarted); DeviceTrackerARController.Instance.UnregisterDevicePoseStatusChangedCallback(OnDevicePoseStatusChanged); } public void HandleAutomaticHitTest(HitTestResult result) { AutomaticHitTestFrameCount = Time.frameCount; } public void HandleInteractiveHitTest(HitTestResult result) { if (result == null) { return; } m_ContentPositioningBehaviour = m_PlaneFinder.GetComponent<ContentPositioningBehaviour>(); m_ContentPositioningBehaviour.DuplicateStage = false; m_ContentPositioningBehaviour.AnchorStage = m_PlaneAnchor; m_ContentPositioningBehaviour.PositionContentAtPlaneAnchor(result); UtilityHelper.EnableRendererColliderCanvas(m_PlaneAugmentation, true); m_PlaneAugmentation.transform.localPosition = Vector3.zero; UtilityHelper.RotateTowardCamera(m_PlaneAugmentation); PortalIsPlaced = true; } public void SetGroundMode(bool active) { if (active) { planeMode = PlaneMode.GROUND; m_PlaneFinder.enabled = true; } } public void ResetScene() { m_PlaneAugmentation.transform.position = Vector3.zero; m_PlaneAugmentation.transform.localEulerAngles = Vector3.zero; UtilityHelper.EnableRendererColliderCanvas(m_PlaneAugmentation, false); DeleteAnchors(); PortalIsPlaced = false; } public void ResetTrackers() { m_SmartTerrain = TrackerManager.Instance.GetTracker<SmartTerrain>(); m_PositionalDeviceTracker = TrackerManager.Instance.GetTracker<PositionalDeviceTracker>(); m_SmartTerrain.Stop(); m_PositionalDeviceTracker.Reset(); m_SmartTerrain.Start(); } void DeleteAnchors() { m_PlaneAnchor.UnConfigureAnchor(); AnchorExists = DoAnchorsExist(); } void SetSurfaceIndicatorVisible(bool isVisible) { Renderer[] renderers = m_PlaneFinder.PlaneIndicator.GetComponentsInChildren<Renderer>(true); Canvas[] canvas = m_PlaneFinder.PlaneIndicator.GetComponentsInChildren<Canvas>(true); foreach (Canvas c in canvas) c.enabled = isVisible; foreach (Renderer r in renderers) r.enabled = isVisible; } public static void RotateTowardCamera(GameObject augmentation) { if (Vuforia.VuforiaManager.Instance.ARCameraTransform != null) { var lookAtPosition = Vuforia.VuforiaManager.Instance.ARCameraTransform.position - augmentation.transform.position; lookAtPosition.y = 0; var rotation = Quaternion.LookRotation(lookAtPosition); augmentation.transform.rotation = rotation; } } public static void EnableRendererColliderCanvas(GameObject gameObject, bool enable) { var rendererComponents = gameObject.GetComponentsInChildren<Renderer>(true); var colliderComponents = gameObject.GetComponentsInChildren<Collider>(true); var canvasComponents = gameObject.GetComponentsInChildren<Canvas>(true); foreach (var component in rendererComponents) component.enabled = enable; foreach (var component in colliderComponents) component.enabled = enable; foreach (var component in canvasComponents) component.enabled = enable; } bool DoAnchorsExist() { if (m_StateManager != null) { IEnumerable<TrackableBehaviour> trackableBehaviours = m_StateManager.GetActiveTrackableBehaviours(); foreach (TrackableBehaviour behaviour in trackableBehaviours) { if (behaviour is AnchorBehaviour) { return true; } } } return false; } void OnVuforiaStarted() { m_StateManager = TrackerManager.Instance.GetStateManager(); m_PositionalDeviceTracker = TrackerManager.Instance.GetTracker<PositionalDeviceTracker>(); m_SmartTerrain = TrackerManager.Instance.GetTracker<SmartTerrain>(); if (m_PositionalDeviceTracker != null && m_SmartTerrain != null) { if (!m_PositionalDeviceTracker.IsActive) m_PositionalDeviceTracker.Start(); if (m_PositionalDeviceTracker.IsActive && !m_SmartTerrain.IsActive) m_SmartTerrain.Start(); } } void OnVuforiaPaused(bool paused) { if (paused) ResetScene(); } void OnTrackerStarted() { m_PositionalDeviceTracker = TrackerManager.Instance.GetTracker<PositionalDeviceTracker>(); m_SmartTerrain = TrackerManager.Instance.GetTracker<SmartTerrain>(); if (m_PositionalDeviceTracker != null) { if (!m_PositionalDeviceTracker.IsActive) m_PositionalDeviceTracker.Start(); } } }
il suddetto è la modifica dello script PlaneManager contenuto nel Ground Plane sample di Vuforia, scaricabile dall’Asset Store di Unity; ho effettuato su di esso una riduzione del codice lasciando soltanto ciò che mi è servito per questo tutorial. In pratica ho eliminato la gestione del MidAir e del Placement che non sono necessari in questo progetto.
Il Plane Finder
Dopo che hai associato questo script al game object , e aggiunto quest’ultimo nel progetto allo stesso livello di ARCamera, devi inserire, come figlio, il Plane Finder che troverai nel menu Game Object | Vuforia | Ground Plane. Questo componente contiene la logica per individuare una superficie piana.
Nella seguente figura sono indicate le impostazioni da effettuare su Plane Finder:

Ti faccio notare che il CustomPlaneIndicator referenziato in Fig,2 è un game object contenente una texture che rappresenta un indicatore che appare quando Vuforia intercetta la superficie piana, puoi decidere di inserirlo oppure no, nel secondo caso dovrai modificare il metodo SetSurfaceIndicatorVisible() nello script precedente per non causare una exception.
Mentre il Plane Stage da referenziare in Content Positioning Behaviour, indicato sempre in Fig.2, è il prossimo componente che devi inserire nel progetto; crea, a tal proposito, un game object di tipo Ground Plane Stage che trovi nel menu GameObject | Vuforia | Ground Plane, aggiungilo allo stesso livello di ARCamera e di GroundPlaneManager e rimuovi in esso il componente Default Trackable Event Handler poichè non necessario per questo progetto.
Aggiunta del portale e del mondo virtuale in Unity Vuforia ARKit Portal
Il Ground Plane Stage è il “luogo” nel quale puoi inserire tutto ciò che deve apparire in augmented reality quando Vuforia ha riconosciuto la superficie piana che hai inquadrato e dopo che hai fatto tap col dito sul display; come puoi vedere nel tuo progetto in Unity, questo particolare game object fornito da Vuforia rappresenta un reticolato verde sul quale posizionare un modello 3D.
Quindi ora crea un game object figlio di Ground Plane Stage, chiamalo Augmentation e assicurati che abbia tutte le coordinate a zero e Scale a 1.
Ora sei giunto al momento nel quale devi inserire in Augmentation il tuo mondo 3D e il portale. Se non hai un environment tuo puoi sempre procurartene uno già pronto tra gli asset free o a pagamento che ti offre l’Asset Store di Unity; il portale invece non è altro che un modello 3D formato dagli stipiti di una porta, anche questo puoi reperirlo facilmente.
Perchè non ti fornisco questi asset? Semplicemente perchè non sono oggetto fondamentale di questo articolo.
Non sono gli asset che ti permettono di realizzare l’effetto dell’ interdimensional ARKit Portal che hai visto in passato, bensì è il codice di uno script e il fatto che il tutto è “immerso” nel Ground Plane di Vuforia che ti permette la realizzazione. Quindi preferisco concentrarmi su questo.
Bene, supponendo che ti sia procurato l’environment e il modello 3d di una porta completa di stipiti e che il tutto tu lo abbia aggiunto come child del game object Augmentation, la situazione dovrebbe apparire come in Fig.3:

Ora ti illustro il nocciolo della questione ma prima osserva la figura seguente:

Se possiedi il codice sorgente di uno dei progetti del portale sviluppati con unity ARKit avrai al suo interno quasi tutta la parte al di sotto di Augmentation.
Se la possiedi ti basta estrarla da quel progetto e inserirla in questo e il tutorial per te termina qui, se invece non hai nessun progetto ARKit allora prosegui la lettura di Unity Vuforia ARKit Portal.
Lo script Portal
Proseguiamo il tutorial Unity, ciò che occorre realizzare ora è il portale e il meccanismo che permette la transizione tra realtà e realtà aumentata.
Crea un game object chiamato Portal e aggiungi i componenti che vedi in Fig.4; ovviamente eccetto lo script Portal che aggiungerai dopo aver copiato il codice seguente:
using UnityEngine; using System.Collections; public class Portal : MonoBehaviour { [Header("Cameras")] public Camera mainCam; public Camera portalCam; void Update () { Vector3 cameraOffset = mainCam.transform.position - transform.position; portalCam.transform.position = transform.position + cameraOffset; portalCam.transform.rotation = Quaternion.LookRotation(mainCam.transform.forward, Vector3.up); } void OnTriggerEnter (Collider other) { if (other.CompareTag ("MainCamera")) { mainCam.cullingMask ^= 1 << LayerMask.NameToLayer("ARWorld"); } } }
Questo è quindi lo script che permette la transizione; devi passargli in input due game object Camera, per ora hai solo ARCamera, tra un pò di righe avrai anche la seconda camera che ti spiegherò a breve.
Un passaggio fondamentale da fare è la creazione di due Layer: Portal e ARWorld.
Il primo devi impostarlo sul game object Portal e tutti i suoi child; il secondo invece impostalo su tutti i game object che compongono l’environment 3d che aggiungerai alla fine del tutorial unity vuforia.
Il Portal shader
Ora devi creare un game object che rappresenta l’interno del portale.
Mi spiego meglio: quando sei davanti alla porta vedrai l’interno del mondo virtuale e se ti sposti a destra o a sinistra prima di varcare la soglia vedrai il mondo virtuale che si sposta esattamente come quando guardi da una finestra; la stessa esatta sensazione di movimento.
Questo effetto lo si ricrea posizionando una mesh di tipo Plane che ricopre l’interno della porta e applicando sulla mesh un materiale con uno shader che si trova in varie versioni googlando su internet, te ne propongo una:
Shader "Custom/PortalShader" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Pass { CGPROGRAM #pragma target 3.0 #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex; struct v2f { float4 pos : SV_POSITION; float4 pos_frag : TEXCOORD0; }; v2f vert(appdata_base v) { v2f o; float4 clipSpacePosition = UnityObjectToClipPos(v.vertex); o.pos = clipSpacePosition; o.pos_frag = clipSpacePosition; return o; } half4 frag(v2f i) : SV_Target { float2 uv = i.pos_frag.xy / i.pos_frag.w; uv = (uv + float2(1.0, 1.0)) * 0.5; return tex2D(_MainTex, uv); } ENDCG } } }
crea uno shader in Unity e incolla il codice qui sopra; poi crea un materiale, associagli lo shader e inserisci il materiale nel game object che andrai a creare come in Fig.5:

Il materiale appena creato richiede, come puoi notare, una texture; utilizzando una RenderTexture, ossia una speciale texture che permette di renderizzare su una superficie di qualsiasi tipo una immagine ma in real-time (classico esempio sono le render texture usate per gli effetti di rifrazione e riflessione di luci su acqua) si ottiene l’effetto di vedere l’ambiente virtuale proiettato sulla texture in tempo reale.
Quindi devi creare un oggetto di tipo render texture e configurarlo come in Fig.6:
Ora devi creare un game object chiamato MaskFace, anch’esso ricopre l’interno della porta; ha una mesh di tipo Plane e un materiale con il seguente shader;
Shader "Custom/DepthMask" { SubShader { Tags {"Queue" = "Geometry-1" } ColorMask 0 ZWrite On Pass {} } }
Ecco in Fig.7 come devi posizionare il MaskFace e quali componenti devi includere in esso:
Il PortalCam
E’ giunto il momento di creare il game object PortalCam. Esso contiene un componente Camera che permette la visualizzazione, mentre sei posizionato davanti al portale ma ancora non sei entrato al suo interno, dell’ambiente 3D virtuale.
La struttura di PortalCam deve essere come mostrato in Fig.8; nel target texture andrai ad inserire la Render Texture di cui ti ho parlato in precedenza.
Bene, manca poco, se hai seguito tutto quello che ho scritto in questo tutorial unity dovresti avere la situazione seguente:
Door ovviamente è il modello 3d della porta, che in questo caso è composta da 3 parti; una cosa fondamentale che ti rammento è che partendo da Portal ogni child, compreso Door, devi metterlo sotto il layer Portal, come ti ho già spiegato un pò di righe più su nel tutorial; altrimenti lo script non è in grado di capire quale è il tuo portale.
Conclusioni
Sei arrivato praticamente al termine di questo tutorial unity, l’ultima cosa che ti resta da fare è la più semplice e divertente, devi creare l’ambiente virtuale nel quale passeggiare quando entrerai in quello che ora puoi chiamare interdimensional Vuforia ARkit portal.
Lo farai creando un game object come child di Augmentation e al suo interno metterai l’intero environment ricordandoti di inserire il tutto nel layer ARWorld.
Qui lascio spazio alla tua abilità se sei un creativo oltre che uno sviluppatore, altrimenti puoi sempre procurarti un environment, free o a pagamento, io ti consiglio questo, bellissimo, seguendo questo link ma ricordati di mettere il portale in punto interno all’environment, in tal modo ,quando avvierai il progetto sullo smartphone e posizionerai il portale facendo tap sul display nel punto desiderato (sempre guardando ad un pavimento), apparirà intorno a te l’ambiente virtuale e potrai uscire e rientrare dal portale.unity arkit
Grazie per l’attenzione, spero ti sia divertito a fare questo tutorial per unity insieme a me, ci leggiamo al prossimo ma prima ti suggerisco questo articolo su la realtà aumentata e questo tutorial.
Se invece ti interessa Unity AR Foundation ti consiglio di iniziare vedendo questo videotutorial: Come installare e configurare AR Foundation per iOS e Android.
🔥2.1 K volte è stato letto questo articolo
2 Comments
Yahya
16 Maggio 2020 - 19:23Hello, thanks for the tutorial however I’ve some problem when try to follow it (maybe because of language barrier?)
Could you provide the Unity project files so I can understand it better? Thanks
Fabio Carucci
17 Maggio 2020 - 16:44Hello Yahya,
Thanks for the comment. Unfortunately, due to a malware I lost the Unity project about this tutorial. But if you want you can tell me the problem or send me your unity project and I will debug it!
Bye
-Fabio