搜索Unity

XR图形:最佳实践和解决方法

上次更新时间:2019年1月

本页内容简介:有关优化着色器以及如何最有效地使用后处理栈的许多技巧。如果您已经熟悉Unity中的编程且正在或将要开发XR(VR和/或AR)内容,则本文值得一读。

目前,Unity支持XR的四种渲染方法:多通道、单通道、最近发布的单通道实例化和Android单通道(类似于单通道实例化)。

多通道
  • 要求通过场景图两次:对于代表眼睛的两个纹理中的每一个,需要单独渲染对象/场景。
  • 不在纹理之间共享GPU工作,所以渲染路径效率最低;但默认情况下在大多数设备上有效,所以需要的更高很少。
  • 共享剔除和阴影生成渲染的部分。
单通道
  • 通过这种方法,可将两个纹理打包到一个较大的纹理中(称为双宽纹理)。
  • 对于要绘制的第一个对象,视口设置到左侧,绘制对象;然后,视口切换到右侧,再次绘制对象。
  • 仅通过场景图,所以在CPU上快的多。但是需要大量额外的GPU状态更改才能完成。

unity xr图形

单通道立体渲染的一个简单示例

单通道实例化
  • 2017.2及更高版本中可用。
  • 表示眼睛的两个纹理包含在一个纹理阵列中(同一纹理阵列的两个切片)。
  • 设置视口后,它会自动应用到两个切片。当发出绘制调用时,它会发出正常水平两倍的示例绘制。根据示例,Unity决定要渲染到的一个切片。
  • GPU工作量相同,但绘制调用和CPU工作量更少,所以性能明显更高。
  • 可用于:
    • Windows 10,具有D3D11和最新图形驱动程序。
    • Hololens。
    • PS4。
  • 如果目标设备不支持扩展,将恢复到多通道。
  • DrawProceduralIndirect(...)要求手动更改。

unity xr图形

单通道实例化示例

使用单通道实例化时将以下宏添加到着色器

如果您选择单通道实例化,我们强烈建议将以下宏添加到顶点和片元作色器。添加方法很简单,并且会更新Unity的所有内置着色器,以便对其提供支持。

对于顶点着色器:

unity xr图形

不包含宏的着色器代码

unity xr图形

添加了第一个宏的相同代码...

unity xr图形

第二个宏...

unity xr图形

第三个...

unity xr图形

添加了第四个宏。

如果您突然需要在片元着色器中使用unity_StereoEyeIndex,则需要添加以下宏:

unity xr图形

使用RenderScaleRenderViewportScale之间的一些区别

假设您希望将一个纹理的大小从1调整为0.5。如果您使用RenderScale进行调整,它会检测原始纹理,并创建原始纹理四分之大小的新纹理,这样,每个维度的比例就是0.5。

当游戏正在运行时,动态执行此操作的成本很高,这就是为什么RenderViewportScale是更高效的选择。它将视口设置为同一纹理的更小部分并进行渲染,如下所示:

unity xr图形

这样做的效率高得多,但是也存在一些问题,因为您只使用了纹理的一部分(参阅下面的部分中有关RenderViewportScale的更多提示)。

所以,RenderScale实际上会删除原来的纹理,然后创建新纹理,而RenderViewportScale则会修改视口。要注意的一些其他区别包括:

  • XRSettings.eyeTextureResolutionScale适用于实际纹理大小,而XRSettings.renderViewportScale适用于用于渲染的视口。
  • 使用延迟渲染时不支持XRSettings.renderViewportScale
  • 缩放适用于两个维度,所以值0.5将导致图像变成原始大小的25%。
XR中的后期处理栈:挑战与解决方案

现已推出优秀的后期处理栈的最新版本。在XR内容中使用堆栈有一定的挑战性;下一部分讲述了这些内容以及您现在可以使用的主要解决方案。

挑战

要使用许多最新的后期处理效果,您需要渲染到基于源目标大小和属性的中间渲染目标:

  • 对于双宽情形,渲染纹理至少要达到单眼部分宽度的两倍。
  • 对于单通道实例化和类似的Android单通道,我们需要创建一个纹理阵列,每只眼睛一个切片。

解决方案

  • 为了减少生成正确屏幕空间纹理格式的猜测工作和时间,Unity发布了XRSettings.eyeTextureDesc(在2017.2及更高版本中)。
  • 这会返回来自引擎XRTexture管理器的RenderTextureDescriptor,表示它已配置为匹配引擎管理的纹理。
  • 如果可用,您还可以从源纹理查询RenderTextureDescriptor。如果您使用的是旧版MonoBehaviour.OnRenderImage基础架构,例如,您有源纹理且可以从中提取出描述符。
  • 通过使用eyeTextureDesc,您可以使用提供的RenderTextureDescriptor替换所有RenderTexture分配,这比手动生成参数效率更高。
  • RenderTextureDescriptor是一个更简单的API;它与底层API的作用相符。如果您使用显式参数,Unity会将其打包到RenderTextureDescriptor中,并将其传递到核心引擎中(传递到RenderTexture.GetTemporaryCommandBuffer.GetTemporaryRT中)。所以,您是在脚本端对其进行管理,而不是让中间层为您执行所有管理。

挑战

在XR中,物理渲染纹理大小与逻辑屏幕/眼睛大小之间有一个区别。

解决方案

  • 对于中间渲染纹理分配,您会希望使用eyeTextureDesc,因为这会分配物理纹理。
  • 如果您有基于屏幕大小的脚本或着色器逻辑(例如,如果您在着色器中使用屏幕大小参数,或构建纹理金字塔),您希望让其基于逻辑大小。为此,您可以使用XRSettings.eyeTextureHeightXRSettings.eyeTextureWidth
  • 这些代表“每只眼睛”纹理大小,需要这些大小才能知道您运行的屏幕的大小。
  • 请注意,对于双宽情形,eyeTextureWidth并不是正好等于eyeTextureDesc的宽度的一半。这是因为为了分配纹理的目的,描述符会将宽度增加一倍,然后略加填充,以确保适合执行MIP贴图。

以下是在发布eyeTextureWidth之前,您可能使用过的用于确定您希望的屏幕宽度的代码示例:

unity xr图形

现在,您可以直接从eyeTexturewidth获取屏幕宽度,并将其用于屏幕比例等,请查看下面的脚本示例:

unity xr图形

挑战

如何确保立体的纹理坐标正确,并从输出采样以修正眼睛?

如果您从双宽纹理采样,则需要确保纹理坐标从刚好一半纹理采样(每一半对应一只眼睛)。

解决方案

每只眼睛都需要纹理坐标校正器,为此,Unity提供了两种解决方案:

1. TRANSFORM_TEX宏

  • This is a macro that works with _ST properties in ShaderLab. It’s most commonly used with MainTex_ST.
  • _ST属性在单通道双宽模式中的每只眼睛之前更新,从而将纹理坐标转换成眼睛的正确范围。它还会自动处理RenderViewportScale。

unity xr图形

  • Unity will populate _ST shader properties if you’re using Graphics.Blit or CommandBuffer.Blit:
    • 始终填充MainTex_ST
    • 如果纹理是XR纹理(VRTextureUsage!=None),将填写其他_ST属性。
  • 该宏不能用于自定义blit操作程序,如最新的后期处理栈中的BlitFullScreenTriangle

2. unity_StereoScaleOffsetUnityStereoTransformScreenSpaceTex/TransformStereoScreenSpaceTex helper函数

unity_StereoScaleOffset是float4的数组:

  • UnityStereoTransformScreenSpaceTex
  • TransformStereoScreenSpaceTex

它在单通道常量代码块(UnityStereoGlobals)中声明。它使用Unity_StereoEyeIndex索引到其中。

对于双宽情形,unity_StereoEyeIndex绑定在一个常量缓冲区中,每个绘制让正确的眼睛更新。所以,左眼的值将放在一个常量缓冲区中,且左眼将绘制:右眼的值将放在常量缓冲区中,接着右眼绘制。着色器实例将知道它使用的所有绘制的情况,它可以查看常量缓冲区并找到unity_StereoEyeIndex的正确值。

unity xr图形

当您确认已正确填充unity_StereoEyeIndex后,我们就可以相信它已从unity_StereoScaleOffset中选出正确的元素,因此可以放心使用。

unity xr图形

使用unity_StereoScaleOffset有几个弊端:

  • 仅可用于单通道,所以您需要使用helper方法。
  • 直接使用它会引起多通道和单视场,并且会导致着色器编译错误。此外,它不会考虑RenderViewportScale。

如何确保正确声明纹理阵列并从正确的切片采样?

解决方案

使用UNITY_DECLARE_SCREENSPACE_TEXTURE。在着色器语言中,纹理阵列不同于“普通”纹理。单通道实例化和Android单通道使用纹理阵列,所以,我们需要确保正确声明纹理。

unity xr图形

与双宽纹理相似,我们需要从正确的眼睛部分采样;这些眼睛部分位于纹理阵列的切片中。我们可以从unity_StereoEyeIndex以及使用宏UNITY_SAMPLE_SCREENSPACE_TEXTURE获取正确的切片。

unity xr图形

管理RenderViewportScale:几个要点

  • unity_StereoScaleOffset不提供支持。
  • 大部分后期处理效果使用CommandBuffer.Blit/Graphics.Blit,这意味着它们可以使用_MainTex_ST(和另一个_ST方法)来支持RenderViewportScale。
  • 但是,利用Unity后期处理栈的v.2,您不能使用这些方法。为了解决此问题,您需要自己提供支持,但这非常简单,因为堆栈的v.2使用它自己的着色器基础架构版本,这意味着它很容易重写。下面是您要执行的操作:

    在着色器端:

    • 创建一个新常量:在xrLib.hlsl中声明"float rvsGlobal"
    • 修改TransformStereoScreenSpaceTex以利用rvsGlobal

    在脚本端:

    • 从脚本将RenderViewportScale值绑定为全局属性
    • 使用Shader.SetGlobalFloat

    unity xr图形

立体固定

如果在着色器中执行邻域屏幕空间采样,您会希望确保不要在单通道双宽中,或者在有效的RenderViewportScale区域外对错误的眼睛采样。该区域就是插入UnityStereoClamp的位置:它会直接将坐标采样固定到纹理的正确部分。

unity xr图形

虽然需要进行手动检查才能找到邻域采样,但使用方法非常简单。

只要您需要从插值生成的纹理坐标偏移,可能都需要使用Unity.StereoClamp()

  • 由于固定,请注意从同一坐标采样的效果。
  • 不要在顶点着色器中使用。
继续之前的另外几点提示:
  • 务必思考您使用的后期处理效果是否对XR有效。
    • 大多数时间效应会增加模糊和延迟
    • 头盔中的物理相机内置模拟景深功能,可以模拟人眼,但这很可能会造成恶心。
  • 如果您使用多通道并保留历史纹理,则每只眼睛需要一组历史纹理。
    • 单一历史纹理设置可能/将会造成在眼睛之间共享历史纹理!
    • 自动使用单通道
  • 如果您遇到问题,请尝试不同的立体渲染模式。
    • 我们可以根据瑕疵的性质了解可能是哪里出错。
更多资源

您喜欢本文吗?请告诉我们!

喜欢。继续发送 还行。有待改进
明白了

我们使用Cookie来确保在我们的网站上为您提供最佳体验。有关更多信息,请访问我们的Cookie政策页面