HoloLens1开发(三):Trilib插件动态加载模型-Part2

前言

最近有新成员加入本团队,为了方便其开发HoloLens1 / HoloLens2,将不定时更新HoloLens相关开发相关内容。

软件需求:

HoloLens 1:VS2017 + Unity2017;HoloLens 2:VS2019 + Unity2019;

1.安装VS2017 / VS2019,HoloLens 1安装Win10 SDK 17134或者17763,HoloLens2要求至少18362;相关安装与配置请参考博文.

2.Unity2017 / Unity2019,安装UWP平台;

注:如果使用的软件为VS2019和Unity2019来开发HoloLens1,可参考HoloLens2的开发过程,修改MRTK的配置文件为HoloLens1即可!可参考博文1,博文2.


上一节介绍了HoloLens+Trilib插件综合开发-Part1,简单介绍了Trilib插件及示例工程Scene1,本节将开始Trilib插件在HoloLens端使用基础介绍。

一、主要代码说明

首先拷贝Trilib插件的示例工程Scene1到自己新建的文件夹下,自定义重命名,eg:HoloTrilib.

1.场景说明

打开场景HoloTrilib后看到左侧面板上如下图所示
HoloLens1开发(三):Trilib插件动态加载模型-Part2_第1张图片
主要功能脚本都在ForegroundCanvas下挂载:
1)AssetLoader下挂载AssetDownloader和AssetLoaderWindow组件。通过查看脚本内容可以看出,AssetDownloader组件为消息处理脚本;AssetLoaderWindow组件与AssetLoader下的界面UI绑定,主要为界面按钮得响应;

2)FileLoader下的FileOpenDialog为文件选取脚本,主要作用为调用系统的FileBrowser,显示文件目录提供给用户选取相应的fbx文件;

3)ErrorDialog如名字一般,其主要为错误反馈组件;

4)URIDialog没有用到,根据名字看是网络下载模型窗口;

5)LoadingTime显示加载模型用时;

我们在HoloLens上开发主要用到本地加载,因此着重于AssetLoader和FileLoader两个组件即可。

2.代码说明

1.AssetDownloader不需要修改,后期直接挂在我们的场景中即可

2.AssetLoaderWindow中最重要的部分如下:

private void LoadLocalAssetButtonClick()
{
    var fileOpenDialog = FileOpenDialog.Instance;
    fileOpenDialog.Title = "Please select a File";
    fileOpenDialog.Filter = AssetLoaderBase.GetSupportedFileExtensions() + "*.zip;";

#if !UNITY_EDITOR && UNITY_WINRT && (NET_4_6 || NETFX_CORE || NET_STANDARD_2_0) && !ENABLE_IL2CPP && !ENABLE_MONO
    fileOpenDialog.ShowFileOpenDialog(delegate (byte[] fileBytes, string filename) 
    {
        LoadInternal(filename, fileBytes);
#else
    fileOpenDialog.ShowFileOpenDialog(delegate (string filename)
    {
        LoadInternal(filename);
#endif
    }
    );

}

private void LoadInternal(string filename, byte[] fileBytes = null)
{
    _loadingTimer.Reset();
    _loadingTimer.Start();
    PreLoadSetup();
    var assetLoaderOptions = GetAssetLoaderOptions();
    if (!Async)
    {
        using (var assetLoader = new AssetLoader())
        {
            assetLoader.OnMetadataProcessed += AssetLoader_OnMetadataProcessed;
            try
            {
#if !UNITY_EDITOR && UNITY_WINRT && (NET_4_6 || NETFX_CORE || NET_STANDARD_2_0) && !ENABLE_IL2CPP && !ENABLE_MONO
            var extension = FileUtils.GetFileExtension(filename);
            _rootGameObject = assetLoader.LoadFromMemoryWithTextures(fileBytes, extension, assetLoaderOptions, _rootGameObject);
#else
                if (fileBytes != null && fileBytes.Length > 0)
                {
                    var extension = FileUtils.GetFileExtension(filename);
                    _rootGameObject = assetLoader.LoadFromMemoryWithTextures(fileBytes, extension, assetLoaderOptions, _rootGameObject);
                }
                else if (!string.IsNullOrEmpty(filename))
                {
                    //获取obj
                    _rootGameObject = assetLoader.LoadFromFileWithTextures(filename, assetLoaderOptions);
                }
                else
                {
                    throw new System.Exception("File not selected");
                }
#endif
            }
            catch (Exception exception)
            {
                ErrorDialog.Instance.ShowDialog(exception.ToString());
            }
        }
        if (_rootGameObject != null)
        {
            PostLoadSetup();
            ShowLoadingTime();
        }
        else
        {
            HideLoadingTime();
        }
    }
    else
    {
        using (var assetLoader = new AssetLoaderAsync())
        {
            assetLoader.OnMetadataProcessed += AssetLoader_OnMetadataProcessed;
            try
            {
                if (fileBytes != null && fileBytes.Length > 0)
                {
                    var extension = FileUtils.GetFileExtension(filename);
                    assetLoader.LoadFromMemoryWithTextures(fileBytes, extension, assetLoaderOptions, null, delegate (GameObject loadedGameObject)
                    {
                        _rootGameObject = loadedGameObject;
                        if (_rootGameObject != null)
                        {
                            PostLoadSetup();
                            ShowLoadingTime();
                        }
                        else
                        {
                            HideLoadingTime();
                        }
                    });
                }
                else if (!string.IsNullOrEmpty(filename))
                {
                    assetLoader.LoadFromFileWithTextures(filename, assetLoaderOptions, null, delegate (GameObject loadedGameObject)
                    {
                        _rootGameObject = loadedGameObject;
                        if (_rootGameObject != null)
                        {
                            PostLoadSetup();
                            ShowLoadingTime();
                        }
                        else
                        {
                            HideLoadingTime();
                        }
                    });
                }
                else
                {
                    throw new Exception("File not selected");
                }
            }
            catch (Exception exception)
            {
                HideLoadingTime();
                ErrorDialog.Instance.ShowDialog(exception.ToString());
            }
        }
    }
}

这两个函数是底层实例化模型的接口函数,即用户在UI界面通过选择要加载的数模后,通过文件地址以及相关参数,Unity根据这两个函数实例化数模。

3.FileOpenDialog为文件选择脚本,我们将在HoloLens端开发使用时具体修改。

读者会注意到在LoadLocalAssetButtonClick函数中包含“#if…#else…#endif” 的结构,这是因为HoloLens所在的UWP平台和Unity所在的Unity在函数接口上的不一致,因此需要根据平台的不同,使用不同的接口用法。

3.HoloLens开发场景准备

经过以上介绍,我们在进行HoloLens+Trilib时,只需要保留需要的本地加载组件,同时还需要引入HoloLens交互组件,因此进行如下准备工作。

1.场景中,BackgroundCanvas删除,新建Canvas命名为“LoadCanvas”,取出ForegroundCanvas下的FileLoader组件,将其设为LoadCanvas的子物体,同时,把ForegroundCanvas剩余部分,如下所示
HoloLens1开发(三):Trilib插件动态加载模型-Part2_第2张图片
在LoadCanvas下新建UI-Button,并调整LoadCanvas的属性(Transform),将其调整到相机的可视范围内。这里为了后期上传HoloLens不需要再调整Canvas的比例,建议直接将LoadCanvas比例缩小,并保证相机可视。
HoloLens1开发(三):Trilib插件动态加载模型-Part2_第3张图片
2.为了方便HoloLens交互的舒适感,这里使用HoloToolKit的交互组件,因此需要导入HoloToolKit组件,如果苏读者有自己的交互设计,可忽略此步。
HoloLens1开发(三):Trilib插件动态加载模型-Part2_第4张图片
至此,准备工作完成,可以正式开始HoloLens+Trilib开发。

二、HoloLens+Trilib开发

1.场景及脚本修改

1.新建空物体,命名为Manager,挂载AssetDownloader脚本
HoloLens1开发(三):Trilib插件动态加载模型-Part2_第5张图片
2.Copy AssetLoaderWindow脚本至Scripts文件夹下,重命名,如myFBXLib,打开脚本修改脚本名,保持与文件名一致。
HoloLens1开发(三):Trilib插件动态加载模型-Part2_第6张图片
同时删除脚本中所有与UI有关的声明,以及所有URI加载数模的响应函数,最终如下

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
#if NETFX_CORE  //UWP下编译
using Windows.Storage;
#endif
using Debug = UnityEngine.Debug;

namespace TriLib
{
    namespace Samples
    {
        [RequireComponent(typeof(AssetDownloader))]
        public class myFBXLib : MonoBehaviour
        {
            public static AssetLoaderWindow Instance { get; private set; }

            public bool Async;

            private GameObject _rootGameObject;

            /// 
            /// Handles "Load local asset button" click event and tries to load an asset at chosen path.
            /// 
            public void LoadLocalAssetButtonClick()
            {
                var fileOpenDialog = FileOpenDialog.Instance;
                fileOpenDialog.Title = "Please select a File";
                fileOpenDialog.Filter = AssetLoaderBase.GetSupportedFileExtensions() + "*.zip;";

#if !UNITY_EDITOR && UNITY_WINRT && (NET_4_6 || NETFX_CORE || NET_STANDARD_2_0) && !ENABLE_IL2CPP && !ENABLE_MONO
                fileOpenDialog.ShowFileOpenDialog(delegate (byte[] fileBytes, string filename) 
                {
                    LoadInternal(filename, fileBytes);
#else
                fileOpenDialog.ShowFileOpenDialog(delegate (string filename)
                {
                    LoadInternal(filename);
#endif
                }
                );
            }

            /// 
            /// Loads a model from the given filename or given file bytes.
            /// 
            /// Model filename.
            /// Model file bytes.
            private void LoadInternal(string filename, byte[] fileBytes = null)
            {
                PreLoadSetup();
                var assetLoaderOptions = GetAssetLoaderOptions();
                if (!Async)
                {
                    using (var assetLoader = new AssetLoader())
                    {
                        assetLoader.OnMetadataProcessed += AssetLoader_OnMetadataProcessed;
                        try
                        {
#if !UNITY_EDITOR && UNITY_WINRT && (NET_4_6 || NETFX_CORE || NET_STANDARD_2_0) && !ENABLE_IL2CPP && !ENABLE_MONO
                        var extension = FileUtils.GetFileExtension(filename);
                        _rootGameObject = assetLoader.LoadFromMemoryWithTextures(fileBytes, extension, assetLoaderOptions, _rootGameObject);
#else
                            if (fileBytes != null && fileBytes.Length > 0)
                            {
                                var extension = FileUtils.GetFileExtension(filename);
                                _rootGameObject = assetLoader.LoadFromMemoryWithTextures(fileBytes, extension, assetLoaderOptions, _rootGameObject);
                            }
                            else if (!string.IsNullOrEmpty(filename))
                            {
                                //获取obj
                                _rootGameObject = assetLoader.LoadFromFileWithTextures(filename, assetLoaderOptions);
                            }
                            else
                            {
                                throw new System.Exception("File not selected");
                            }
#endif
                        }
                        catch (Exception exception)
                        {
                            ErrorDialog.Instance.ShowDialog(exception.ToString());
                        }
                    }
                    if (_rootGameObject != null)
                    {
                        PostLoadSetup();
                    }
                }
                else
                {
                    using (var assetLoader = new AssetLoaderAsync())
                    {
                        assetLoader.OnMetadataProcessed += AssetLoader_OnMetadataProcessed;
                        try
                        {
                            if (fileBytes != null && fileBytes.Length > 0)
                            {
                                var extension = FileUtils.GetFileExtension(filename);
                                assetLoader.LoadFromMemoryWithTextures(fileBytes, extension, assetLoaderOptions, null, delegate (GameObject loadedGameObject)
                                {
                                    _rootGameObject = loadedGameObject;
                                    if (_rootGameObject != null)
                                    {
                                        PostLoadSetup();
                                    }
                                });
                            }
                            else if (!string.IsNullOrEmpty(filename))
                            {
                                assetLoader.LoadFromFileWithTextures(filename, assetLoaderOptions, null, delegate (GameObject loadedGameObject)
                                {
                                    _rootGameObject = loadedGameObject;
                                    if (_rootGameObject != null)
                                    {
                                        PostLoadSetup();
                                    }
                                });
                            }
                            else
                            {
                                throw new Exception("File not selected");
                            }
                        }
                        catch (Exception exception)
                        {
                            ErrorDialog.Instance.ShowDialog(exception.ToString());
                        }
                    }
                }
            }

            /// 
            /// Event assigned to FBX metadata loading. Editor debug purposes only.
            /// 
            /// Type of loaded metadata
            /// Index of loaded metadata
            /// Key of loaded metadata
            /// Value of loaded metadata
            private void AssetLoader_OnMetadataProcessed(AssimpMetadataType metadataType, uint metadataIndex, string metadataKey, object metadataValue)
            {
                Debug.Log("Found metadata of type [" + metadataType + "] at index [" + metadataIndex + "] and key [" + metadataKey + "] with value [" + metadataValue + "]");
            }

            /// 
            /// Gets the asset loader options.
            /// 
            /// The asset loader options.
            private AssetLoaderOptions GetAssetLoaderOptions()
            {
                var assetLoaderOptions = AssetLoaderOptions.CreateInstance();
                assetLoaderOptions.DontLoadCameras = false;
                assetLoaderOptions.DontLoadLights = false;
                assetLoaderOptions.AddAssetUnloader = true;
                return assetLoaderOptions;
            }

            /// 
            /// Pre Load setup.
            /// 
            private void PreLoadSetup()
            {
                if (_rootGameObject != null)
                {
                    Destroy(_rootGameObject);
                    _rootGameObject = null;
                }
            }

            /// 
            /// Post load setup.
            /// 
            private void PostLoadSetup()
            {
                _rootGameObject.transform.eulerAngles = new Vector3(0, 0, 0);
                _rootGameObject.transform.position = new Vector3(0, 0, (float)0.5);
                _rootGameObject.transform.localScale = new Vector3((float)0.008, (float)0.008, (float)0.008);
            }
        }
    }
}

2.上传设备

1.把myFBXLib脚本挂在Manager上.

2.选中之前加入的Button,将其响应函数设定为myFBXLib中的LoadLocalAssetButtonClick.

3.先在PC上尝试一下效果,运行如下

4.为了在HoloLens上运行,需要加上HoloToolKit的交互组件,搜索并打开示例场景——InteractiveButtonComponents,复制其中的如下三个组件至我们的场景中,同时把Main Camera取消勾选,并调整Canvas到合适位置.
HoloLens1开发(三):Trilib插件动态加载模型-Part2_第7张图片
5.其他相关上传HoloLens的设置如常即可,导出部署,将数模放在HoloLens的3D Object目录下,运行效果如下

需要注意的是,PostLoadSetup函数中localScale的大小需要根据模型的大小自己设定,否则会出现加载后的模型过大/过小的问题,影响效果。

根据以上设定,如此便实现了Trilib插件在HoloLens上动态加载数模的功能。

3.后续内容

细心的读者会发现,当前的工程实现还存在一些问题,例如:

1.每次点击UI后,HoloLens主线程暂停,造成假死状态,且需要持续一段时间后才能正常打开FileBrowser,且每点一次按钮会有相同的情况,影响操作流畅感;

2.已加载模型后,再次点击按钮发现新的模型被加载,已加载模型消失;

3.对于已经加载的模型能否对其进行后续操作;

4.每次默认打开HoloLens的3D Object文件夹,能否选择其他文件夹内的数模;

5.单个按钮的UI过于单调,如何在复杂的UI场景中使用Trilib插件;

以上的问题的答案是肯定的,均可以通过修改相关脚本实现以上功能,笔者将在后续推出教程。


总结

以上为HoloLens+Trilib插件综合开发的Part2,介绍了Trilib插件在HoloLens中的简单使用,但该工程相对简单,还不能满足我们在正常工程中的使用,笔者将在后续继续更新相关内容。欢迎批评指正!

风和日暖,令人愿意永远活下去 .HDarker

你可能感兴趣的:(HoloLens开发,Unity模型相关,AR/VR)