(2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序

本文探讨了从零开始创建UWP应用的过程,详细解析了应用启动流程、窗口消息循环的建立及使用Composition API进行界面显示和简单交互。通过实践,揭示了UWP程序启动机制的奥秘。
原文: (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序

版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:http://blog.youkuaiyun.com/wpwalter/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系(walter.lv@qq.com)。 https://blog.youkuaiyun.com/WPwalter/article/details/81230332

每次使用 Visual Studio 的模板创建一个 UWP 程序,我们会在项目中发现大量的项目文件、配置、应用启动流程代码和界面代码。然而这些文件在 UWP 程序中到底是如何工作起来的?

我从零开始创建了一个 UWP 程序,用于探索这些文件的用途,了解 UWP 程序的启动流程。


本文分为两个部分:

本文将从 Main 函数开始,一步步跑起来一个应用程序,显示一个窗口,并在窗口中显示一些内容。重点在了解在 UWP 中运行应用程序,并显示窗口。

启动应用

在上一篇文章中的末尾,我们成功启动了程序并进入了 Main 函数的断点,但实际上运行会报错。我们能看见一个窗口显示出来,随后提示进程已启动,但应用尚未运行。

The Walterlv.Demo.ZeroUwp.exe process started, but the activation request failed with error ‘The app didn’t start’.

是的,我们只有一个什么都没做的 Main 函数,进程当然能够成功启动;但我们需要能够启动应用。那么 UWP 的应用是什么呢?是 CoreApplication。

所以我们使用 CoreApplication 类型执行 Run 静态方法。

CoreApplication.Run

此方法要求传入一个 IFrameworkViewSource。事实上 UWP 已经有一个 IFrameworkViewSource 的实现了,是 FrameworkViewSource。不过,我希望自己写一个,了解其原理。

所以,就用 ReSharper 生成了 IFrameworkViewSource 的一个实现:

using Windows.ApplicationModel.Core;

namespace Walterlv.Demo.ZeroUwp
{
    internal sealed class WalterlvViewSource : IFrameworkViewSource
    {
        public IFrameworkView CreateView() => new WalterlvFrameworkView();
    }
}

IFrameworkViewSource 接口中只有一个方法 CreateView,返回一个新的 IFrameworkView 的实例。

只是写一个 NotImplementedException 的异常,当然是跑不起来的,得返回一个真的 IFrameworkView 的实例。UWP 自带的实现为 FrameworkView,那么我也自己实现一个。

这次需要实现的方法会多一些:

using Windows.ApplicationModel.Core;
using Windows.UI.Core;

namespace Walterlv.Demo.ZeroUwp
{
    internal sealed class WalterlvFrameworkView : IFrameworkView
    {
        public void Initialize(CoreApplicationView applicationView) => throw new System.NotImplementedException();
        public void SetWindow(CoreWindow window) => throw new System.NotImplementedException();
        public void Load(string entryPoint) => throw new System.NotImplementedException();
        public void Run() => throw new System.NotImplementedException();
        public void Uninitialize() => throw new System.NotImplementedException();
    }
}

因此,我们需要理解这些方法的执行时机以及含义才能正确实现这些方法。庆幸的是,这些方法的含义都能在官方文档中找到(其实就是平时看到的注释):

为了方便查看,我将其整理到这些方法上作为注释。

顺便的,下面这些方法刚好是按照应用生命周期的顺序被调用,也就是 Initialize->SetWindow->Load->Run->Uninitialize

/// <summary>
/// 当应用启动时将执行此方法。进行必要的初始化。
/// </summary>
public void Initialize(CoreApplicationView applicationView) { }

/// <summary>
/// 每次应用需要显示一个窗口的时候,此方法就会被调用。用于为当前应用程序显示一个新的窗口视图。
/// </summary>
public void SetWindow(CoreWindow window) { }

/// <summary>
/// 会在 <see cref="Run"/> 方法执行之前执行。如果需要使用外部资源,那么这时需要将其加载或激活。
/// </summary>
public void Load(string entryPoint) { }

/// <summary>
/// 当此方法调用时,需要让应用内的视图(View)显示出来。
/// </summary>
public void Run() { }

/// <summary>
/// 当应用退出时将执行此方法。如果应用启动期间使用到了外部资源,需要在此时进行释放。
/// </summary>
public void Uninitialize() { }

在此接口的所有方法留空地实现完以后,我们的 UWP 应用终于能跑起来了。当按下 F5 调试之后,不会再提示错误,而是依次执行这五个方法后,正常退出应用。

启动窗口

注意到以上所有方法都留空之后,应用程序很快就退出了。这与我们开发传统 Win32 应用时的效果是一致的 —— 是的,我们缺一个消息循环。我们需要一个不断处理的消息循环用来阻断主线程的退出,同时又能够不断响应消息。而这样的方法需要写到 Run() 方法里面。

UWP 中开启一个消息循环是非常容易的,不过我们需要一个 CoreDispatcher 对象。在我们目前的接口实现中,CoreDispatcher 对象可以从 CoreWindow 中获取到。所以我们需要在 SetWindow 方法中拿到 CoreWindow 的实例,然后在 Run 中使用它开启窗口消息循环。

public void SetWindow(CoreWindow window)
{
    _window = window;
}

public void Run()
{
    _window.Activate();
    _window.Dispatcher.ProcessEvents(CoreProcessEventsOption.ProcessUntilQuit);
}

private CoreWindow _window;

开启消息循环
▲ 开启了消息循环之后,应用不会直接退出了

在窗口中显示点东西

我们使用 CompositionAPI 可以在窗口中创建 Visual 并显示出来。

public void SetWindow(CoreWindow window)
{
    _window = window;

    var compositor = new Compositor();
    var root = compositor.CreateContainerVisual();
    var compositionTarget = compositor.CreateTargetForCurrentView();
    compositionTarget.Root = root;

    var child = compositor.CreateSpriteVisual();
    child.Size = new Vector2(100f, 100f);
    child.Brush = compositor.CreateColorBrush(Color.FromArgb(0xFF, 0x00, 0x80, 0xFF));
    root.Children.InsertAtTop(child);
}

窗口中新增的 Visual

在窗口中做一些交互

CoreWindow 除了为我们提供了消息循环之外,也可以提供交互。监听 PointerMoved 事件,我们可以做一些简单的交互。

下面我用 Git 标准差异比较的方式添加了交互的代码 PointerMoved

  public void SetWindow(CoreWindow window)
  {
      _window = window;
+     _window.PointerMoved += OnPointerMoved;

      var compositor = new Compositor();
-     var root = compositor.CreateContainerVisual();
+     _root = compositor.CreateContainerVisual();
      var compositionTarget = compositor.CreateTargetForCurrentView();
-     compositionTarget.Root = _root;
+     compositionTarget.Root = _root;

      var child = compositor.CreateSpriteVisual();
      child.Size = new Vector2(100f, 100f);
      child.Brush = compositor.CreateColorBrush(Color.FromArgb(0xFF, 0x00, 0x80, 0xFF));
-     root.Children.InsertAtTop(child);
+     _root.Children.InsertAtTop(child);
  }

+ private void OnPointerMoved(CoreWindow sender, PointerEventArgs args)
+ {
+     var visual = _root.Children.First();
+     var position = args.CurrentPoint.Position;
+     visual.Offset = new Vector3((float) (position.X - 50f), (float) (position.Y - 50f), 0f);
+ }

  private CoreWindow _window;
+ private ContainerVisual _root;

能够完成一些简单的交互。

窗口内的交互

总结

在本文中,我们了解到 UWP 的应用程序启动中也一样需要有窗口消息循环。不过 UWP 中创建消息循环还是非常简单的。

我们使用 CompositionAPI 进行了一些界面显示和简单的交互。了解到即便是如此复杂的 UWP 程序,其启动流程也没有那么复杂。

不过,如果你阅读了前面一篇 (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序,会发现复杂的部分都在项目文件和系统的部分。

<think>好的,我现在需要帮助用户了解如何在HoloLens上从零开始创建一个工程并渲染三维高斯场景。用户之前已经询问过大致步骤,现在他们希望得到一个更详细的逐步指南,从创建工程开始,一直到设置和部署。我需要确保我的回答详细且易于跟随,适合可能有Unity和HoloLens开发经验的用户,但可能对具体步骤不太熟悉。 首先,用户的需求是从创建工程开始,逐步设置工程。之前的回复已经涵盖了关键步骤,但可能不够详细。我需要拆解每个步骤,确保用户能够按部就班地进行操作。可能需要包括安装必要的软件、配置Unity项目、导入MRTK、设置场景、编写脚本、优化性能等。 用户可能对HoloLens开发不太熟悉,所以需要解释清楚每个步骤的目的和操作方法。例如,如何安装Unity的正确版本,如何通过Package Manager导入MRTK,如何配置相机和场景设置,如何编写生成高斯点云的脚本,以及如何配置着色器和优化性能。此外,部署到HoloLens设备的具体步骤也需要详细说明,包括构建设置和远程调试。 我还需要考虑用户可能遇到的常见问题,比如性能优化、空间映射、交互功能等,并在步骤中提前给出解决方案或提示。例如,在性能优化部分,可能需要提到GPU实例化和LOD技术,以及如何在Unity中实现这些。 另外,用户可能希望有一个完整的示例项目结构,包括脚本和着色器代码,所以需要提供具体的代码示例,并解释每部分代码的作用。同时,确保代码的准确性和兼容性,比如使用C#脚本和Unity的Shader语言,并适配HoloLens的硬件限制。 在组织内容时,应该分步骤进行,每个步骤都有明确的标题和子步骤,使用户能够逐步跟随。可能还需要使用列表、代码块和强调部分来突出重点。同时,语言要简洁明了,避免技术术语过于复杂,或者在必要时进行解释。 最后,需要验证所有步骤的正确性,确保用户按照指南操作能够成功创建工程并运行示例。可能需要参考微软官方文档和Unity的最佳实践,以确保信息的准确性。如果有不确定的部分,应该进行标注或建议用户查阅更多资源。</think>以下是从零开始创建 HoloLens 项目并渲染三维高斯场景的详细分步指南: --- ### **第一步:开发环境搭建** #### **1. 安装必要软件** 1. **Unity Hub** - 从 [Unity 官网](https://unity.com/) 下载并安装 Unity Hub。 - 在 Unity Hub 中安装 **Unity 2021.3 LTS**(兼容 HoloLens 2 的最新稳定版本)。 2. **Visual Studio 2022** - 安装时勾选以下组件: - **使用 C++ 的桌面开发** - **通用 Windows 平台开发 (UWP)** - **Windows 10 SDK (10.0.19041.0 或更高版本)** 3. **Windows SDK** - 确保安装 **Windows 10 SDK (10.0.19041.0)**(与 HoloLens 2 兼容)。 --- ### **第二步:创建 Unity 项目** 1. **新建项目** - 在 Unity Hub 中选择 **3D Core 模板**,命名为 `HoloLensGaussianScene`。 - 设置项目路径(建议路径不含中文或空格)。 2. **切换构建平台** - 点击菜单栏 **File > Build Settings**。 - 选择 **Universal Windows Platform**,点击 **Switch Platform**。 - 确认 **Architecture** 为 **ARM64**,**Target Device** 为 **HoloLens**。 --- ### **第三步:导入 MRTK 混合现实工具包** 1. **通过 Package Manager 安装 MRTK** - 点击菜单栏 **Window > Package Manager**。 - 点击左上角 **+** 选择 **Add package from Git URL**,输入: ``` https://github.com/microsoft/MixedRealityToolkit-Unity.git?path=/Assets/MRTK ``` - 等待下载完成。 2. **配置 MRTK 项目设置** - 点击菜单栏 **Mixed Reality > Toolkit > Utilities > Configure Unity Project**。 - 勾选所有选项(如 Spatial Awareness、Input System 等),点击 **Apply**。 3. **添加 MRTK 到场景** - 在 Hierarchy 窗口右键,选择 **Mixed Reality Toolkit > Add to Scene and Configure**。 --- ### **第四步:设置场景基础** #### **1. 配置主相机** - 删除默认的 **Main Camera**。 - 在 MRTK 预制体中找到 **MRTK XR Rig**,拖入场景中。 #### **2. 创建空物体作为高斯场景容器** - 右键 Hierarchy 窗口,创建空物体 **GaussianScene**。 - 添加脚本 `GaussianPointGenerator.cs`(后续步骤编写)。 --- ### **第五步:生成高斯点云数据** #### **1. 创建点预制体** - 右键 Project 窗口,选择 **Create > Prefab**,命名为 `GaussianPointPrefab`。 - 拖入一个 **Sphere** 到场景中,调整缩放为 `(0.1, 0.1, 0.1)`,拖回 Project 窗口保存为预制体。 #### **2. 编写点云生成脚本** - 在 Project 窗口右键 **Create > C# Script**,命名为 `GaussianPointGenerator`。 - 双击打开并替换为以下代码: ```csharp using UnityEngine; public class GaussianPointGenerator : MonoBehaviour { public int numPoints = 1000; // 点数量 public float radius = 2.0f; // 分布半径 public GameObject pointPrefab; // 预制体 void Start() { for (int i = 0; i < numPoints; i++) { // 生成三维高斯分布坐标 Vector3 position = new Vector3( GaussianRandom(0, radius), GaussianRandom(0, radius), GaussianRandom(0, radius) ); GameObject point = Instantiate(pointPrefab, position, Quaternion.identity); point.transform.parent = transform; // 挂载到父物体 } } // 高斯随机数生成(Box-Muller 变换) private float GaussianRandom(float mean, float stdDev) { float u1 = 1.0f - Random.value; float u2 = 1.0f - Random.value; float randStdNormal = Mathf.Sqrt(-2.0f * Mathf.Log(u1)) * Mathf.Sin(2.0f * Mathf.PI * u2); return mean + stdDev * randStdNormal; } } ``` #### **3. 绑定脚本** - 将 `GaussianPointGenerator` 脚本拖到 **GaussianScene** 物体上。 - 在 Inspector 窗口中将 `pointPrefab` 字段赋值为之前创建的 `GaussianPointPrefab`。 --- ### **第六步:编写透明高斯着色器** #### **1. 创建自定义着色器** - 右键 Project 窗口,选择 **Create > Shader > Unlit Shader**,命名为 `GaussianPointShader`。 - 双击打开并替换为以下代码: ```glsl Shader "Custom/GaussianPoint" { Properties { _MainColor ("Color", Color) = (1,1,1,1) _FadeDistance ("Fade Distance", Range(0.1, 5)) = 1.0 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing // 启用 GPU Instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 pos : SV_POSITION; float size : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _MainColor) UNITY_DEFINE_INSTANCED_PROP(float, _FadeDistance) UNITY_INSTANCING_BUFFER_END(Props) v2f vert (appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_TRANSFER_INSTANCE_ID(v, o); // Billboard 效果:始终面向相机 float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; float3 viewDir = normalize(_WorldSpaceCameraPos - worldPos); float3 up = UNITY_MATRIX_V[1].xyz; float3 right = normalize(cross(viewDir, up)); // 根据距离动态调整大小 float distance = length(_WorldSpaceCameraPos - worldPos); float scale = distance * UNITY_ACCESS_INSTANCED_PROP(Props, _FadeDistance); right *= scale; up *= scale; // 计算最终顶点位置 float3 finalPos = worldPos + v.vertex.x * right + v.vertex.y * up; o.pos = mul(UNITY_MATRIX_VP, float4(finalPos, 1.0)); o.size = scale; return o; } fixed4 frag (v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); // 圆形透明度衰减 float alpha = exp(-i.size * i.size * 2.0); fixed4 col = UNITY_ACCESS_INSTANCED_PROP(Props, _MainColor); col.a *= alpha; return col; } ENDCG } } } ``` #### **2. 应用材质到预制体** - 右键 Project 窗口,创建 **Material**,命名为 `GaussianMaterial`。 - 将 Shader 设置为 `Custom/GaussianPoint`,调整颜色和 `Fade Distance`。 - 将材质赋给 `GaussianPointPrefab` 预制体。 --- ### **第七步:性能优化** #### **1. 启用 GPU Instancing** - 在 `GaussianMaterial` 的 Inspector 窗口中勾选 **Enable GPU Instancing**。 #### **2. 添加 LOD Group** - 修改 `GaussianPointGenerator.cs`,在生成点时动态添加 LOD: ```csharp // 在 Instantiate 之后添加: LODGroup lod = point.AddComponent<LODGroup>(); lod.SetLODs(new LOD[] { new LOD(0.5f, new Renderer[]{ point.GetComponent<Renderer>() }), new LOD(0.1f, new Renderer[0]) // 远处时消失 }); ``` #### **3. 空间分区加载** - 创建 `SpatialPartitionManager.cs` 脚本,根据用户位置动态加载/卸载点云区块。 --- ### **第八步:部署到 HoloLens** #### **1. 构建设置** - 点击 **File > Build Settings**,确保: - **Target Device**: HoloLens - **Architecture**: ARM64 - **Build Type**: D3D Project - 勾选 **Unity C# Projects** #### **2. 生成 Visual Studio 工程** - 点击 **Build**,选择输出文件夹(如 `Build/UWP`)。 - 等待 Unity 完成导出。 #### **3. 使用 Visual Studio 部署** - 打开生成的 `.sln` 文件。 - 选择 **Release** 配置和 **ARM64** 平台。 - 右键项目选择 **Deploy**(需通过 Wi-Fi 或 USB 连接 HoloLens)。 --- ### **第九步:调试与交互** #### **1. 手势交互(MRTK)** - 在 MRTK 配置文件中启用 **Hand Tracking**。 - 添加以下代码实现手势缩放: ```csharp // 在 GaussianScene 物体上添加脚本 public class ScaleHandler : MonoBehaviour { void Update() { if (HandJointUtils.TryGetJointPose(TrackedHandJoint.Palm, Handedness.Right, out MixedRealityPose pose)) { float pinchStrength = HandPoseUtils.CalculatePinchStrength(Handedness.Right); transform.localScale = Vector3.one * (1 + pinchStrength * 2); } } } ``` #### **2. 空间映射** - 在 MRTK 配置中启用 **Spatial Awareness System**。 - 设置 `SpatialAwarenessMeshLevelOfDetail` 为 **Low** 以减少性能消耗。 --- ### **最终效果验证** 1. 佩戴 HoloLens 启动应用。 2. 通过手势(空中点击)调整点云缩放。 3. 观察高斯点云在真实环境中的稳定渲染(透明球体按高斯分布衰减)。 --- ### **常见问题解决** - **问题 1:点云闪烁或穿透现实物体** **解决**:在材质中设置 `ZWrite Off`,并调整渲染队列为 `"Queue"="Transparent+100"`。 - **问题 2:帧率低于 30 FPS** **解决**: 1. 减少 `numPoints` 至 500 以下。 2. 在着色器中移除复杂的 Billboard 计算。 3. 使用 MRTK 的性能分析工具(**Mixed Reality Toolkit > Diagnostics > Profiler**)。 --- 通过以上步骤,你可以完成从零到 HoloLens 设备上渲染三维高斯场景的全流程。如需更高级功能(如动态更新高斯参数),可进一步集成 Compute Shader 或 Azure 空间锚点服务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值