Bagikan melalui


Tutorial: Memanipulasi model

Dalam tutorial ini, Anda mempelajari cara:

  • Menambahkan batas visual dan manipulasi di sekitar model yang dirender dari jarak jauh
  • Memindahkan, memutar, dan menskalakan
  • Raycast dengan kueri spasial
  • Menambahkan animasi sederhana untuk objek yang dirender dari jarak jauh

Prasyarat

Kueri batas objek jarak jauh dan berlaku untuk batas lokal

Untuk berinteraksi dengan objek jarak jauh, kita memerlukan representasi lokal untuk berinteraksi terlebih dahulu. Batas objek berguna untuk manipulasi cepat objek jarak jauh. Batas jarak jauh dapat dikueri dari ARR, menggunakan Entitas lokal sebagai referensi. Batas-batas dikueri setelah model dimuat ke dalam sesi jarak jauh.

Batas model didefinisikan oleh kotak yang berisi seluruh model - sama seperti BoxCollider Unity, yang memiliki pusat dan ukuran yang didefinisikan untuk sumbu x, y, z. Bahkan, kita akan menggunakan BoxCollider Unity untuk mewakili batas-batas model jarak jauh.

  1. Buat skrip baru di direktori yang sama dengan RemoteRenderedModel dan beri nama RemoteBounds.

  2. Mengganti isi skrip dengan kode berikut:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System;
    using UnityEngine;
    
    [RequireComponent(typeof(BaseRemoteRenderedModel))]
    public class RemoteBounds : BaseRemoteBounds
    {
        //Remote bounds works with a specific remotely rendered model
        private BaseRemoteRenderedModel targetModel = null;
    
        private RemoteBoundsState currentBoundsState = RemoteBoundsState.NotReady;
    
        public override RemoteBoundsState CurrentBoundsState
        {
            get => currentBoundsState;
            protected set
            {
                if (currentBoundsState != value)
                {
                    currentBoundsState = value;
                    BoundsStateChange?.Invoke(value);
                }
            }
        }
    
        public override event Action<RemoteBoundsState> BoundsStateChange;
    
        public void Awake()
        {
            BoundsStateChange += HandleUnityEvents;
            targetModel = GetComponent<BaseRemoteRenderedModel>();
    
            targetModel.ModelStateChange += TargetModel_OnModelStateChange;
            TargetModel_OnModelStateChange(targetModel.CurrentModelState);
        }
    
        private void TargetModel_OnModelStateChange(ModelState state)
        {
            switch (state)
            {
                case ModelState.Loaded:
                    QueryBounds();
                    break;
                default:
                    BoundsBoxCollider.enabled = false;
                    CurrentBoundsState = RemoteBoundsState.NotReady;
                    break;
            }
        }
    
        // Create an async query using the model entity
        async private void QueryBounds()
        {
            //Implement me
        }
    }
    

    Catatan

    Jika Anda melihat kesalahan di Visual Studio yang mengklaim Fitur 'X' tidak tersedia di C# 6. Silakan gunakan bahasa versi 7.0 atau lebih baru, kesalahan ini dapat diabaikan dengan aman. Hal ini berkaitan dengan Unity's Solution dan Project generation.

    Skrip ini harus ditambahkan ke GameObject yang sama dengan skrip yang mengimplementasikan BaseRemoteRenderedModel. Dalam hal ini, itu berarti RemoteRenderedModel. Mirip dengan skrip sebelumnya, kode awal ini menangani semua perubahan status, peristiwa, dan data yang terkait dengan batas jarak jauh.

    Hanya ada satu metode yang tersisa untuk diterapkan: QueryBounds. QueryBounds mengambil batas secara asinkron, mengambil hasil kueri dan menerapkannya ke BoxCollider lokal.

    Metode QueryBounds mudah: kirim kueri ke sesi penyajian jarak jauh dan tunggu hasilnya.

  3. Ganti metode QueryBounds dengan metode lengkap berikut ini:

    // Create a query using the model entity
    async private void QueryBounds()
    {
        var remoteBounds = targetModel.ModelEntity.QueryLocalBoundsAsync();
        CurrentBoundsState = RemoteBoundsState.Updating;
        await remoteBounds;
    
        if (remoteBounds.IsCompleted)
        {
            var newBounds = remoteBounds.Result.toUnity();
            BoundsBoxCollider.center = newBounds.center;
            BoundsBoxCollider.size = newBounds.size;
            BoundsBoxCollider.enabled = true;
            CurrentBoundsState = RemoteBoundsState.Ready;
        }
        else
        {
            CurrentBoundsState = RemoteBoundsState.Error;
        }
    }
    

    Kami memeriksa hasil kueri untuk melihat apakah hasilnya berhasil. Jika ya, konversi dan terapkan batas yang dikembalikan dalam format yang dapat diterima BoxCollider.

Sekarang, ketika skrip RemoteBounds ditambahkan ke objek game yang sama dengan RemoteRenderedModel, BoxCollider ditambahkan jika diperlukan dan ketika model mencapai statusnya Loaded , batas akan secara otomatis dikueri dan diterapkan ke BoxCollider.

  1. Menggunakan TestModel GameObject yang dibuat sebelumnya, tambahkan komponen RemoteBounds.

  2. Konfirmasi penambahan skrip.

    Tambah komponen RemoteBounds

  3. Jalankan aplikasi lagi. Tak lama setelah model dimuat, Anda akan melihat batas untuk objek jarak jauh. Anda akan melihat sesuatu seperti nilai di bawah ini:

    Cuplikan layar yang memperlihatkan contoh batas objek jarak jauh.

Sekarang, kita memiliki BoxCollider lokal yang dikonfigurasi dengan batas akurat pada objek Unity. Batas memungkinkan visualisasi dan interaksi menggunakan strategi yang sama yang akan kami gunakan untuk objek yang dirender secara lokal. Misalnya, skrip yang mengubah Transformasi, fisika, dan lainnya.

Memindahkan, memutar, dan menskalakan

Memindahkan, memutar, dan menskalakan objek yang dirender dari jarak jauh bekerja sama dengan objek Unity lainnya. RemoteRenderingCoordinator, dalam metode LateUpdatenya, memanggil Update di sesi yang saat ini aktif. Bagian dari apa yang dilakukan Update adalah menyinkronkan entitas model lokal berubah dengan rekan-rekan jarak jauh mereka. Untuk memindahkan, memutar, atau menskalakan model yang dirender dari jarak jauh, Anda hanya perlu memindahkan, memutar, atau menskalakan transformasi GameObject yang mewakili model jarak jauh. Di sini, kita akan memodifikasi transformasi GameObject induk yang memiliki skrip RemoteRenderedModel yang melekat padanya.

Tutorial ini menggunakan MRTK untuk interaksi objek. Sebagian besar implementasi khusus MRTK untuk memindahkan, memutar, dan menskalakan objek berada di luar cakupan tutorial ini. Ada pengontrol tampilan model yang telah dikonfigurasi sebelumnya di dalam AppMenu, di menu Alat Model.

  1. Pastikan TestModel GameObject yang dibuat sebelumnya ada di adegan.
  2. Pastikan prefab AppMenu ada di adegan.
  3. Tekan tombol Putar Unity untuk memutar adegan dan membuka menu Alat Model di dalam AppMenu. Lihat pengontrol

AppMenu memiliki submenu Model Tools yang mengimplementasikan kontroler tampilan untuk mengikat dengan model. Ketika GameObject berisi komponen RemoteBounds, pengontrol tampilan akan menambahkan komponen BoundingBox, yang merupakan komponen MRTK yang merender kotak batas di sekitar objek dengan BoxCollider. ObjectManipulator, yang bertanggung jawab atas interaksi tangan. Skrip gabungan ini akan memungkinkan kita untuk memindahkan, memutar, dan menskalakan model yang dirender dari jarak jauh.

  1. Pindahkan mouse Anda ke panel Game dan klik di dalamnya untuk memberinya fokus.

  2. Menggunakan simulasi tangan MRTK, tekan, dan tahan tombol Shift kiri.

  3. Kemudikan, tangan yang disimulasikan sehingga sinar tangan menunjuk ke model uji.

    Sinar tangan runcing

  4. Tahan klik kiri dan seret model untuk memindahkannya.

Anda akan melihat gerakan konten yang dirender dari jarak jauh bersama dengan kotak batas. Anda mungkin melihat beberapa keterlambatan atau jeda antara kotak batas dan konten jarak jauh. Penundaan ini akan tergantung pada latensi dan bandwidth internet Anda.

Transmisi sinar dan kueri spasial model jarak jauh

Collider kotak di sekitar model cocok untuk berinteraksi dengan seluruh model, tetapi tidak cukup rinci untuk berinteraksi dengan bagian individual model. Untuk mengatasinya, kita dapat menggunakan transmisi sinar jarak jauh. Transmisi sinar jarak jauh adalah API yang disediakan oleh Azure Remote Rendering untuk mentransmisikan sinar ke adegan jarak jauh dan mengembalikan hasil hit secara lokal. Teknik ini dapat digunakan untuk memilih entitas anak dari model besar atau mendapatkan informasi hasil hit seperti posisi, permukaan normal, dan jarak.

Model pengujian memiliki sejumlah subentitas yang dapat dikueri dan dipilih. Untuk saat ini, pemilihan akan mengeluarkan nama Entitas yang dipilih ke Konsol Unity. Periksa bab Materi, pencahayaan, dan efek untuk menyoroti Entitas yang dipilih.

Pertama, mari kita buat pembungkus statis di sekitar kueri transmisi sinar jarak jauh. Skrip ini akan menerima posisi dan arah di ruang Unity, mengonversinya ke jenis data yang diterima oleh transmisi sinar jarak jauh, dan mengembalikan hasilnya. Skrip akan menggunakan API RayCastQueryAsync.

  1. Buat skrip baru yang disebut RemoteRayCaster dan ganti isiya dengan kode berikut:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    /// <summary>
    /// Wraps the Azure Remote Rendering RayCast queries to easily send requests using Unity data types
    /// </summary>
    public class RemoteRayCaster
    {
        public static double maxDistance = 30.0;
    
        public static async Task<RayCastHit[]> RemoteRayCast(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            if(RemoteRenderingCoordinator.instance.CurrentCoordinatorState == RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected)
            {
                var rayCast = new RayCast(origin.toRemotePos(), dir.toRemoteDir(), maxDistance, hitPolicy);
                var result = await RemoteRenderingCoordinator.CurrentSession.Connection.RayCastQueryAsync(rayCast);
                return result.Hits;
            }
            else
            {
                return new RayCastHit[0];
            }
        }
    
        public static async Task<Entity[]> RemoteRayCastEntities(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            var hits = await RemoteRayCast(origin, dir, hitPolicy);
            return hits.Select(hit => hit.HitEntity).Where(entity => entity != null).ToArray();
        }
    }
    

    Catatan

    Unity memiliki kelas bernama RaycastHit, dan Azure Remote Rendering memiliki kelas bernama RayCastHit. Huruf besar C adalah perbedaan penting untuk menghindari kesalahan kompilasi.

    RemoteRayCaster menyediakan titik akses umum untuk transmisi sinar jarak jauh ke sesi saat ini. Agar lebih spesifik, kami selanjutnya akan menerapkan penangan pointer MRTK. Skrip akan mengimplementasikan antarmuka IMixedRealityPointerHandler, yang akan memberi tahu MRTK bahwa kami ingin skrip ini mendengarkan acara Mixed Reality Pointer.

  2. Buat skrip baru bernama RemoteRayCastPointerHandler dan ganti kode dengan kode berikut:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.MixedReality.Toolkit.Input;
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    public class RemoteRayCastPointerHandler : BaseRemoteRayCastPointerHandler, IMixedRealityPointerHandler
    {
        public UnityRemoteEntityEvent OnRemoteEntityClicked = new UnityRemoteEntityEvent();
    
        public override event Action<Entity> RemoteEntityClicked;
    
        public void Awake()
        {
            // Forward events to Unity events
            RemoteEntityClicked += (entity) => OnRemoteEntityClicked?.Invoke(entity);
        }
    
        public async void OnPointerClicked(MixedRealityPointerEventData eventData)
        {
            if (RemoteEntityClicked != null) //Ensure someone is listening before we do the work
            {
                var firstHit = await PointerDataToRemoteRayCast(eventData.Pointer);
                if (firstHit.success)
                    RemoteEntityClicked.Invoke(firstHit.hit.HitEntity);
            }
        }
    
        public void OnPointerDown(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerDragged(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerUp(MixedRealityPointerEventData eventData) { }
    
        private async Task<(bool success, RayCastHit hit)> PointerDataToRemoteRayCast(IMixedRealityPointer pointer, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            RayCastHit hit;
            var result = pointer.Result;
            if (result != null)
            {
                var endPoint = result.Details.Point;
                var direction = pointer.Rays[pointer.Result.RayStepIndex].Direction;
                Debug.DrawRay(endPoint, direction, Color.green, 0);
                hit = (await RemoteRayCaster.RemoteRayCast(endPoint, direction, hitPolicy)).FirstOrDefault();
            }
            else
            {
                hit = new RayCastHit();
            }
            return (hit.HitEntity != null, hit);
        }
    }
    

Metode OnPointerClickedRemoteRayCastPointerHandler dipanggil oleh MRTK ketika Pointer 'diklik' pada collider, seperti collider kotak kami. Setelah itu, PointerDataToRemoteRayCast dipanggil untuk mengonversi hasil pointer menjadi titik dan arah. Titik dan arah tersebut kemudian digunakan untuk mentransmisikan sinar jarak jauh di sesi jarak jauh.

Batas diperbarui

Mengirim permintaan untuk transmisi sinar saat diklik adalah strategi yang efisien untuk mengkueri objek jarak jauh. Namun, itu bukan pengalaman pengguna yang ideal karena kursor bertabrakan dengan collider kotak, bukan model itu sendiri.

Anda juga dapat membuat pointer MRTK baru yang lebih sering mentransmisikan sinarnya di sesi jarak jauh. Meskipun ini adalah pendekatan yang lebih kompleks, pengalaman pengguna akan lebih baik. Strategi ini tidak dibahas dalam tutorial ini, tetapi contoh pendekatan ini dapat dilihat di Aplikasi Showcase, yang ada di repositori sampel ARR.

Ketika transmisi sinar berhasil diselesaikan di RemoteRayCastPointerHandler, Entity hit dipancarkan dari peristiwa Unity OnRemoteEntityClicked. Untuk menanggapi peristiwa tersebut, kami akan membuat skrip pembantu yang menerima Entity dan melakukan tindakan di atasnya. Mari kita mulai dengan mendapatkan skrip untuk mencetak nama Entity ke log debug.

  1. Buat skrip baru bernama RemoteEntityHelper dan ganti isinya dengan yang berikut ini:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using UnityEngine;
    
    public class RemoteEntityHelper : MonoBehaviour
    {
        public void EntityToDebugLog(Entity entity)
        {
            Debug.Log(entity.Name);
        }
    }
    
  2. Pada TestModel GameObject yang dibuat sebelumnya, tambahkan komponen RemoteRayCastPointerHandler dan komponen RemoteEntityHelper.

  3. Tetapkan metode EntityToDebugLog ke peristiwa OnRemoteEntityClicked. Ketika jenis output peristiwa dan jenis input metode cocok, kita dapat menggunakan hookup peristiwa dinamis Unity, yang akan secara otomatis meneruskan nilai peristiwa ke dalam metode.

    1. Buat bidang panggilan balik baru Tambahkan panggilan balik
    2. Seret komponen Pembantu Entitas Jarak Jauh ke bidang Objek, untuk mereferensikan objek GameObject Assign induk
    3. EntityToDebugLog Menetapkan sebagai panggilan balik Tetapkan panggilan balik
  4. Tekan putar di Editor Unity untuk memulai adegan, sambungkan ke sesi jarak jauh, dan muat model pengujian.

  5. Menggunakan simulasi tangan MRTK, tekan dan tahan tombol Shift kiri.

  6. Kemudikan, tangan yang disimulasikan sehingga sinar tangan menunjuk ke model uji.

  7. Klik panjang untuk mensimulasikan ketukan udara, mengeksekusi peristiwa OnPointerClicked.

  8. Amati Konsol Unity untuk pesan log dengan nama entitas anak yang dipilih. Misalnya: Contoh entitas anak

Menyinkronkan grafik objek jarak jauh ke dalam hierarki Unity

Hingga saat ini, kami hanya melihat satu GameObject lokal yang mewakili seluruh model. Ini berfungsi dengan baik untuk penyajian dan manipulasi seluruh model. Namun, jika kita ingin menerapkan efek atau memanipulasi subentitas tertentu, kita harus membuat GameObjects lokal untuk mewakili entitas tersebut. Pertama, kita dapat mengeksplorasi secara manual dalam model pengujian.

  1. Mulai adegan dan muat model pengujian.
  2. Perluas anak-anak dari TestModel GameObject dalam hierarki Unity dan pilih TestModel_Entity GameObject.
  3. Di Inspektur, klik tombol Perlihatkan Anak. Perlihatkan anak-anak
  4. Terus perluas anak-anak dalam hierarki dan klik Perlihatkan Anak hingga muncul daftar besar anak. Semua anak

Daftar puluhan entitas sekarang akan mengisi hierarki. Memilih salah satu entitas akan menampilkan komponen Transform dan RemoteEntitySyncObject dalam Inspektur. Secara default, setiap entitas tidak secara otomatis disinkronkan setiap bingkai, sehingga perubahan lokal pada Transform tidak disinkronkan ke server. Anda dapat memeriksa Sinkronkan Setiap Bingkai, lalu memindahkan, menskalakan, atau memutar transformasi dalam tampilan Adegan, Anda tidak akan melihat model yang dirender dalam tampilan adegan, menonton tampilan Game untuk melihat posisi model dan rotasi yang diperbarui secara visual.

Proses yang sama dapat dilakukan secara terprogram dan merupakan langkah awal dalam memodifikasi entitas jarak jauh tertentu.

  1. Ubah skrip RemoteEntityHelper agar berisi metode berikut:

    public void MakeSyncedGameObject(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var sync = entityGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    }
    
  2. Tambahkan panggilan balik tambahan ke OnRemoteEntityClicked peristiwa RemoteRayCastPointerHandler, atur ke MakeSyncedGameObject . Panggilan balik tambahan

  3. Menggunakan simulasi tangan MRTK, tekan dan tahan tombol Shift kiri.

  4. Kemudikan, tangan yang disimulasikan sehingga sinar tangan menunjuk ke model uji.

  5. Klik panjang untuk mensimulasikan ketukan udara, mengeksekusi peristiwa OnPointerClicked.

  6. Centang dan perluas Hierarki untuk melihat objek anak baru, mewakili entitas yang diklik. Representasi GameObject

  7. Setelah pengujian, hapus panggilan balik untuk MakeSyncedGameObject karena kami akan memasukkan ini sebagai bagian dari efek lain nanti.

Catatan

Menyinkronkan setiap bingkai hanya diperlukan saat Anda perlu menyinkronkan data transformasi. Ada beberapa overhead untuk menyinkronkan transformasi, sehingga harus digunakan dengan hemat.

Membuat instans lokal dan membuatnya secara otomatis disinkronkan adalah langkah pertama dalam memanipulasi subentitas. Teknik yang sama yang kami gunakan untuk memanipulasi model secara keseluruhan dapat digunakan pada subentitas juga. Misalnya, setelah membuat instans lokal yang disinkronkan dari entitas, Anda dapat meminta batasnya dan menambahkan penangan manipulasi agar dapat dipindahkan oleh sinar tangan pengguna.

Langkah berikutnya

Anda sekarang dapat memanipulasi dan berinteraksi dengan model yang dirender dari jarak jauh! Dalam tutorial berikutnya, kita akan membahas memodifikasi bahan, mengubah pencahayaan, dan menerapkan efek pada model yang dirender dari jarak jauh.