上一节 【祥哥带你玩HoloLens开发】了解 DirectX 全息应用开发(一) C# DirectX全息应用的框架 介绍了 C# DirectX全息应用模板的应用启动管理的框架,这一节我们就真正进入Hololens全息应用开发的正题,介绍基于SharpDX的DirectX 全息应用的主程序。
我们还是接着上一节【祥哥带你玩HoloLens开发】了解 DirectX 全息应用开发(一) C# DirectX全息应用的框架中创建的名为 HolographicAppDemo 的 Holographic DirectX 11 App (Universal Windows) C#项目来讲,HolographicAppDemoMain.cs中的 HolographicAppDemoMain 类就是本节的话题基于SharpDX的DirectX 全息应用的主程序,接下来一一解读吧。
1.项目目录介绍
上一章我们主要讲了app启动、生命周期、window事件管理,对 HolographicAppDemo 项目目录没有详细介绍,我们在讲主程序之前先来把项目目录结构再来介绍下
│ project.json
│ Program.cs 项目main入口程序
│ AppViewSource.cs 实现了IFrameworkViewSource接口的AppViewSource
│ AppView.cs 管理应用视图启动、生命周期及窗口事件
│ HolographicAppDemoMain.cs **HolographicAppDemoMain** 全息应用的主程序(本节的主角)
│ FxCompile.cs
│ HolographicAppDemo.csproj
│ HolographicAppDemo_TemporaryKey.pfx
│ ms.fxcompile.targets
│ Package.appxmanifest
│
├─Assets UWP的静态资源目录
│ LockScreenLogo.scale-200.png
│ SplashScreen.scale-200.png
│ Square150x150Logo.scale-200.png
│ Square44x44Logo.scale-200.png
│ Square44x44Logo.targetsize-24_altform-unplated.png
│ StoreLogo.png
│ Wide310x150Logo.scale-200.png
│
├─Common 全息应用的通用工具目录
│ CameraResources.cs 如果要实现3D效果的关键,这个管理场景中Camera的CameraResources类非常重要,如何利用它实现VR、360效果后面会专门介绍
│ DeviceResources.cs 这个上一章节的AppView里已经涉及到了
│ DirectXHelper.cs
│ Disposer.cs
│ InteropStatics.cs
│ StepTimer.cs 这个是控制动画画面切换的定时器,每秒60帧就是在这里控制的。
│
├─Content 模板程序里渲染的旋转立方体的程序资源目录
│ │ ShaderStructures.cs
│ │ SpatialInputHandler.cs 这个控制手势输入
│ │ SpinningCubeRenderer.cs 这个就是旋转的立方体渲染
│ │
│ └─Shaders 着色器程序
│ GeometryShader.hlsl
│ PixelShader.hlsl
│ VertexShader.hlsl
│ VPRTVertexShader.hlsl
│
└─Properties
AssemblyInfo.cs
Default.rd.xml
全息应用就是用 HolographicAppDemoMain 来控制整个全息场景的更新渲染的实现,以及手势输入管理、DirectX 设备及其事件管理、场景中摄像机管理、位置跟踪管理,下面通过代码注释的方式代码一一介绍
//
// 把这句注释了的话那么这个场景里的示例全息立方体就不会显示了
// 另外要真正开发自己的全息项目的话 Content 目录也应该清理了
#define DRAW_SAMPLE_CONTENT
using System;
using System.Diagnostics;
using Windows.Graphics.Holographic;
using Windows.Perception.Spatial;
using Windows.UI.Input.Spatial;
using HolographicAppDemo.Common;
using System.Threading.Tasks;
using Windows.Foundation;
using System.Collections.Generic;
#if DRAW_SAMPLE_CONTENT
using HolographicAppDemo.Content; //using了示例立方体的命名空间
#endif
namespace HolographicAppDemo
{
///
/// 使用 Direct3D 更新、渲染和呈现全息内容.
///
internal class HolographicAppDemoMain : IDisposable
{
#if DRAW_SAMPLE_CONTENT
// 渲染一个宽20cm宽的彩色的全息立方体.此例内容用于演示 world-locked 渲染.
private SpinningCubeRenderer spinningCubeRenderer; // 要渲染的立方体
private SpatialInputHandler spatialInputHandler; // 空间输入句柄,也就是手势控制
#endif
// 缓存设备资源的引用.
private DeviceResources deviceResources;
// 循环渲染的定时器
private StepTimer timer = new StepTimer();
// 用户周围的全息空间
HolographicSpace holographicSpace;
// SpatialLocator 是附加的主 camera
SpatialLocator locator;
// 附加在全息摄像机上的帧引用
SpatialStationaryFrameOfReference referenceFrame;
///
/// 加载完应用程序的时候加载和初始化应用程序资源
///
///
public HolographicAppDemoMain(DeviceResources deviceResources)
{
this.deviceResources = deviceResources;
// 注册Direct3D设备失去和恢复的通知
this.deviceResources.DeviceLost += this.OnDeviceLost;
this.deviceResources.DeviceRestored += this.OnDeviceRestored;
}
public void SetHolographicSpace(HolographicSpace holographicSpace)
{
this.holographicSpace = holographicSpace;
//
// TODO: 在这里添加代码初始化你的全息内容
//
#if DRAW_SAMPLE_CONTENT
// 初始化全息立方体示例
spinningCubeRenderer = new SpinningCubeRenderer(deviceResources);
spatialInputHandler = new SpatialInputHandler();
#endif
// 使用默认的 SpatialLocator 跟踪设备运动.
locator = SpatialLocator.GetDefault();
// 响应位置跟踪状态的变化。
locator.LocatabilityChanged += this.OnLocatabilityChanged;
// 注册全息场景的 CameraAdded 事件
holographicSpace.CameraAdded += this.OnCameraAdded;
// 注册全息场景的 CameraRemoved 事件
holographicSpace.CameraRemoved += this.OnCameraRemoved;
// 渲染 world-locked 全息影像最简单的方法是在app启动时创建一个固定的参考帧。
// 这大致类似于以app启动时的设备位置创建一个“世界”坐标系。
referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation();
// 关于空间跟踪API:
// * 固定的参考帧别设计来提供相对于整体空间的一个最佳位置。在参考帧内的个别位置被允许轻微漂移,因为
// 设备能感知学习到更多的环境情况。
// * 全息影像个体精确定位是必须要的,SpatialAncho 是用来在真实世界中的位置固定全息物体的,例如用户
// 的一个兴趣点。锚定的位置是不漂移,但可以纠正;锚将会用校正过的位置开始下一帧后发生的事情。
}
public void Dispose()
{
#if DRAW_SAMPLE_CONTENT
// 销毁处置 spinningCubeRenderer
if (spinningCubeRenderer != null)
{
spinningCubeRenderer.Dispose();
spinningCubeRenderer = null;
}
#endif
}
///
/// 此方法实现了在每一帧里更新应用程序状态一次
///
public HolographicFrame Update()
{
// 在定时更新之前,有一些需要为全息渲染做的事情。
// 首先,我们先获取当前帧的的相关信息。
// HolographicFrame 有app 需要为了更新和渲染当前帧的信息。
// app 每一个新的帧都是从调用 CreateNextFrame 开始的。
HolographicFrame holographicFrame = holographicSpace.CreateNextFrame();
// 当这帧呈现时获取一个全息摄像机的预测位置。
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction;
// 后台缓冲区从帧到帧改变。验证每一个缓冲区,并且重新创建所需要的视图资源和深度缓冲区。
deviceResources.EnsureCameraResources(holographicFrame, prediction);
// 下一步,我们从引用当前帧关联附加的帧获取一个坐标系。
// 然后,当渲染这个样本内容时,用这个坐标系创建立体视图向量
SpatialCoordinateSystem currentCoordinateSystem = referenceFrame.CoordinateSystem;
#if DRAW_SAMPLE_CONTENT
// 检查上一帧以来的输入状态,比如做了一个Tap点击手势
SpatialInteractionSourceState pointerState = spatialInputHandler.CheckForInput();
if (null != pointerState)
{
// 当一个Tap的手势被检测到时,示例全息图会重新定位在用户前方2米处。
spinningCubeRenderer.PositionHologram(
pointerState.TryGetPointerPose(currentCoordinateSystem)
);
}
#endif
timer.Tick(() =>
{
// TODO: 更新场景对象.
//
// 基于时间的更新放在这里.默认情况下此代码每帧执行一次,
// 但是如果你用一个固定时间步长改变 StepTimer ,此代码运行多次才能到当前步骤。
#if DRAW_SAMPLE_CONTENT
spinningCubeRenderer.Update(timer);
#endif
});
// 我们通过使用我们的内容的位置相关信息设置焦点来完成帧的更新.
foreach (var cameraPose in prediction.CameraPoses)
{
#if DRAW_SAMPLE_CONTENT
// HolographicCameraRenderingParameters class 提供了访问设置图像稳定的参数。
HolographicCameraRenderingParameters renderingParameters = holographicFrame.GetRenderingParameters(cameraPose);
// SetFocusPoint 在你的场景中告诉系统一个特定的点优先处理图像稳定。每个全息相机的焦点是独立设置的。
// 你应该设置用户正在查看的内容附近作为焦点.
// 在这个例子里,我们把焦点放在了全息示例的的中心,因为这是用户唯一可以聚焦的全息物体。
// 你也可以设置相对速度和所面对的内容;样例全息影像在一个固定点,所以我们只需要表明它的位置。
renderingParameters.SetFocusPoint(
currentCoordinateSystem,
spinningCubeRenderer.Position
);
#endif
}
// 全息帧将用于获取最新的视图和投影向量并且呈现给交换链 swap chain。
return holographicFrame;
}
///
/// 依照当前应用和空间位置状态,为每一个全息显示渲染当前帧。如果帧至少渲染一个显示,则返回 True
///
public bool Render(ref HolographicFrame holographicFrame)
{
// 不要试图在第一次更新之前渲染任何东西
if (timer.FrameCount == 0)
{
return false;
}
// TODO: 在这里添加欲通过渲染代码
//
// 照顾到任务任务,这不特定于一个单独的全息相机。这包括任何不需要最终视图或投影向量,比如烘焙贴图。
// 到目前为止帧预测有效提高图像稳定性并且允许更多的全息影像的精准位置。
holographicFrame.UpdateCurrentPrediction();
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction;
// 锁定全息相机的资源集,然后在这帧里绘制给每个相机。
return deviceResources.UseHolographicCameraResources(
(Dictionary cameraResourceDictionary) =>
{
bool atLeastOneCameraRendered = false;
foreach (var cameraPose in prediction.CameraPoses)
{
// 这作为 HolographicCamera 的基于设备的资源。
CameraResources cameraResources = cameraResourceDictionary[cameraPose.HolographicCamera.Id];
// 获取设备 context.
var context = deviceResources.D3DDeviceContext;
var renderTargetView = cameraResources.BackBufferRenderTargetView;
var depthStencilView = cameraResources.DepthStencilView;
// 为当前全息相机设置渲染目标
context.OutputMerger.SetRenderTargets(depthStencilView, renderTargetView);
// 清除后台缓冲区和深度模板视图
SharpDX.Mathematics.Interop.RawColor4 transparent = new SharpDX.Mathematics.Interop.RawColor4(0.0f, 0.0f, 0.0f, 0.0f);
context.ClearRenderTargetView(renderTargetView, transparent);
context.ClearDepthStencilView(
depthStencilView,
SharpDX.Direct3D11.DepthStencilClearFlags.Depth | SharpDX.Direct3D11.DepthStencilClearFlags.Stencil,
1.0f,
0);
//
// TODO:用你自己的内容替换示例内容
//
// 关于全息内容的注释:
// * 对于绘制,请记住,相对于同一分辨率的非立体渲染目标,你可能在一个渲染目标中填充两倍像素。
// 避免不必要的或重复写入相同像素,只用绘制用户可以看到的全息图。
// *
//
// * 黑色像素在设备中将是呈现透明的,但你应该人人使用alpha混合绘制到半透明的全息图上.
// 在结束的时候你也必须清除掉屏幕上的透明。
// 每一个全息相机的视图和投影向量将在每一个帧都会改变。这个功能通过表明的 cameraPose 为全息相机
// 刷新固定缓冲区的数据。
cameraResources.UpdateViewProjectionBuffer(deviceResources, cameraPose, referenceFrame.CoordinateSystem);
// 为这个相机的图形管道附加视图/投影固定缓冲区。
bool cameraActive = cameraResources.AttachViewProjectionBuffer(deviceResources);
#if DRAW_SAMPLE_CONTENT
// 只有当前位置跟踪有效时才渲染空间锁定内容。
if (cameraActive)
{
// 渲染绘制示例全息图像
spinningCubeRenderer.Render();
}
#endif
atLeastOneCameraRendered = true;
}
return atLeastOneCameraRendered;
});
}
public void SaveAppState()
{
//
// TODO: 这里插入保存app状态的代码
// 当应用程序即将暂停时调用此方法。
//
// 例如,在 SpatialAnchorStore 存储信息。
//
}
public void LoadAppState()
{
//
// TODO: 这里插入加载app状态数据的代码。
// 当应用程序恢复时调用此方法。
//
// For example, 在 SpatialAnchorStore 加载信息.
//
}
///
/// 通知Renderer,设备资源需要释放。
///
public void OnDeviceLost(Object sender, EventArgs e)
{
#if DRAW_SAMPLE_CONTENT
spinningCubeRenderer.ReleaseDeviceDependentResources();
#endif
}
///
/// 通知 renderers 设备资源重现.
///
public void OnDeviceRestored(Object sender, EventArgs e)
{
#if DRAW_SAMPLE_CONTENT
spinningCubeRenderer.CreateDeviceDependentResourcesAsync();
#endif
}
void OnLocatabilityChanged(SpatialLocator sender, Object args)
{
switch (sender.Locatability)
{
case SpatialLocatability.Unavailable:
// 全息影像不能渲染.
{
String message = "Warning! Positional tracking is " + sender.Locatability + ".";
Debug.WriteLine(message);
}
break;
// In the following three cases, it is still possible to place holograms using a
// SpatialLocatorAttachedFrameOfReference.
case SpatialLocatability.PositionalTrackingActivating:
// 系统正在准备使用位置跟踪
case SpatialLocatability.OrientationOnly:
// 位置跟踪未被激活.
case SpatialLocatability.PositionalTrackingInhibited:
// 位置跟踪暂时被抑制。为了恢复位置跟踪,可能需要的操作
break;
case SpatialLocatability.PositionalTrackingActive:
// 位置跟踪式激活的。 World-locked 内容可以渲染.
break;
}
}
public void OnCameraAdded(
HolographicSpace sender,
HolographicSpaceCameraAddedEventArgs args
)
{
Deferral deferral = args.GetDeferral();
HolographicCamera holographicCamera = args.Camera;
Task task1 = new Task(() =>
{
//
// TODO: 为新相机分配资源并加载特定于该摄像机的任何内容。请注意,渲染目标的尺寸(以像素为单位)
// 的holographiccamera对象的属性,可以用来创建离屏渲染的holographiccamera分辨率匹配目标。
// 为全息摄像机创建基于设备的资源,并将其添加到用于更新和渲染的摄像机列表中。注意:
// * 因为这个功能可能会在任何时间,等待的 AddHolographicCamera 方法可以在加入新的相机上的全息摄像头资源集合的锁。
// 在每秒60帧里这个等待不是很长时间
// * 后续更新将从这台相机的 CameraPose 的 RenderingParameters 带后台缓冲区和使用它来创建这个相机的
// ID3D11RenderTargetView 可以将内容的 HolographicCamera 渲染。
deviceResources.AddHolographicCamera(holographicCamera);
// 全息框架的预测不包括任何信息关于这个相机直到延缓完成
deferral.Complete();
});
task1.Start();
}
public void OnCameraRemoved(
HolographicSpace sender,
HolographicSpaceCameraRemovedEventArgs args
)
{
Task task2 = new Task(() =>
{
//
// TODO: 异步卸载或停用内容资源(而不是后台缓冲资源),仅针对已删除的相机。
//
});
task2.Start();
// 在启用此回调返回之前,请确保所有对后台缓冲区的引用已释放。
// 因为这个功能可能会在任何时间,在 RemoveHolographicCamera
// 功能等到它可以释放资源之前,这台相机在全息照相机资源集合的锁。
// 在每秒60帧里这个等待不是很长时间
deviceResources.RemoveHolographicCamera(args.Camera);
}
}
}