文章目录
- 📕教程说明
- 📕动态开启和关闭透视
- ⭐方法一:OVRManager.instance.isInsightPassthroughEnabled
- ⭐方法二:OVRPassthroughLayer 脚本中的 hidden 变量
- 📕透视风格 Passthrough Styling
- ⭐Inspector 面板控制
- ⭐代码控制
- 📕局部透视
- ⭐透视材质
- ⭐设置 OVRManager.eyeFovPremultipliedAlphaModeEnabled 为 false
- ⭐表面投射透视 Surface-projected Passthrough
- 🔍步骤一:OVRPassthroughLayer 脚本中的 Projection Surface 参数
- 🔍步骤二:OVRPassthroughLayer 脚本中的 AddSurfaceGeometry 方法
- ⭐多个透视图层
- 🔍OVRPassthroughLayer 上的 Composition Depth 参数
📕教程说明
前期需要的一体机开发的环境配置可以参考这篇教程:Unity VR 开发教程: Meta Quest 一体机开发 (一) 环境配置
电脑操作系统:Windows
使用的 VR 设备:Meta Quest 2
使用的 Unity 版本:2021.3.5 LTS (这里推荐使用 2021 及以上的 LTS 版本)
Oculus Integration 版本:v54 (目前 v54 以上也适用)
官方文档:https://developer.oculus.com/documentation/unity/unity-passthrough/
注:本篇教程可能具有时效性,因为 Oculus 的 SDK 更新迭代得比较快,如果大家使用的 SDK 版本比我的新,在不方便查看官方文档的情况下也可以先试试本篇教程的配置步骤,如果发现教程过时,欢迎大家进行反馈,我也会及时进行更改说明,一切以官方文档为主。
上一篇教程,我们学习了 Quest Passthrough 透视功能的环境配置,实现了在透视的现实场景下看到虚拟的物体。(教程链接:Unity Meta Quest MR 开发教程:(一)混合现实 MR 透视 Passthrough 环境配置,强烈推荐上一篇教程看完了,对设置透视场景的步骤熟悉了之后再来看这篇教程)
实际上,我们还可以自定义透视,比如设置透视的风格、颜色,设置局部透视(比如一个虚拟的世界下有一小块地方是透视的现实场景)等。所以这篇教程,我们来学习一下如何自定义透视功能,相当于对透视功能的介绍进行补充。
当我们导入 Oculus Integration 后,在下图所示的路径下可以看到官方提供的几个透视 Demo,它们基本上涵盖了 Quest 透视的一些用法,大家可以自己体验一下。我会选取几个重点进行讲解。
📕动态开启和关闭透视
我们可以在程序运行过程中通过代码动态地控制透视的开启和关闭。
⭐方法一:OVRManager.instance.isInsightPassthroughEnabled
我们可以用以下代码控制透视的开启和关闭。
OVRManager.instance.isInsightPassthroughEnabled = true;
OVRManager.instance.isInsightPassthroughEnabled = false;
这个相当于控制场景中 OVRCameraRig 物体上的 OVR Manager 脚本中的 Enable Passthrough 选项。
⭐方法二:OVRPassthroughLayer 脚本中的 hidden 变量
我们可以在 OVRCameraRig 物体上添加 OVRPassthroughLayer 脚本:
OVRPassthroughLayer 控制着透视图层。因为透视场景也相当于一个图层 Layer,所以我们可以用以下代码控制透视图层的开启和关闭,当透视的图层关闭了,我们就看不到现实场景了。
public OVRPassthroughLayer passthroughLayer;
//在passthroughLayer 变量被赋值后,用以下代码
passthroughLayer.hidden = true;
passthroughLayer.hidden = false;
OVRPassthroughLayer 类下的 hidden 变量是个 bool 类型,当 hidden 为 true 时,透视图层被隐藏,我们在头显里就看不到现实场景了。
注:使用这种方法时要确保 OVRManager 脚本中的 Enable Passthrough 是勾选上的。而使用方法一时 Enable Passthrough 一开始可以不用勾选。因为这个 Enable Passthrough 勾选上了会在程序启动时就初始化透视。如果没有初始化,OVRPassthroughLayer 的功能也是无效的,而只有 OVRManager.instance.isInsightPassthroughEnabled 能够控制 Enable Passthrough 的开启和关闭。
📕透视风格 Passthrough Styling
⭐Inspector 面板控制
我们可以设置 OVRPassthroughLayer 脚本上 Style 的参数来自定义透视的风格:
Opacity:透视的透明度
Edge Rendering:勾选之后,在透视场景中,现实物体的边缘的颜色会高亮显示,而 Edge Color 是设置边缘的颜色。效果如下图所示(图片来自 Meta 官方文档,可以看到物体的边缘有绿色的高亮轮廓):
Color Control:可以对透视图像呈现的色彩效果进行更细致的控制,它能够将设备采集到的色彩图像作为输入,通过 Color Mapping 技术转换成新的色彩图像进行输出。点击 Color Control 按钮会出现以下几个选项:
根据官方文档的说法,它们的作用如下:
None: Display passthrough images unchanged.
Color Adjustment: Adjust the image’s brightness, contrast, and saturation. Saturation adjustment only has an effect on devices that support color passthrough.
Grayscale: Adjust brightness and contrast and apply a posterization effect to grayscale passthrough images. Color passthrough images are converted to grayscale first if this option is chosen.
Grayscale To Color: Colorize grayscale passthrough images, adjust brightness and contrast, and apply a posterization effect. Color passthrough images are converted to grayscale first if this option is chosen.
Color LUT: Apply a color look-up table (LUT), which maps each RGB input color into an arbitrary RGB(A) in the passthrough image stream.
Blended Color LUTs: Apply the blend between two color LUTs to the passthrough image stream. This option can be used to smoothly transition between two color LUTs.
这里介绍一下前三种,它们使用起来比较简单方便。后两种的专业性更强一点,使用起来略微复杂,感兴趣的可以查看官方文档:https://developer.oculus.com/documentation/unity/unity-customize-passthrough-color-mapping/
Color Adjustment
选择后 Inspector 面板如下:
其中,Contrast 表示对比度,Brightness 表示亮度,Saturation 表示饱和度。
Grayscale
其中,Posterize 表示色调分离度。大家可以自己调整一下 Poseterize 的数值,感受透视效果的不同。
Grayscale To Color
相比 Grayscale 模式多了个 Colorize 参数。在上一篇教程中,我们有介绍过可以改变眼部相机的 Background 颜色来改变透视的颜色。这个 Colorize 也能调整透视的颜色,它是一个 Unity 中的 Gradient 类型,能够调整色彩的渐变,我们可以点击 Colorize,在弹出的 Gradient Editor 面板中对颜色进行调整。
⭐代码控制
首先需要一个 OVRPassthroughLayer 类的实例:
public OVRPassthroughLayer passthroughLayer;
确保这个实例不为空后,我们就可以用代码对透视风格进行控制了,从结果上而言和在 Inpsector 面板中调整参数是一样的。
调整透明度
passthroughLayer.textureOpacity = 0.5f;
开启 Edge Rendering
passthroughLayer.edgeRenderingEnabled = true;
改变 Edge Rendering 颜色
passthroughLayer.edgeColor = Color.green;
改变对比度
passthroughLayer.colorMapEditorContrast = 0.5f;
改变亮度
passthroughLayer.colorMapEditorBrightness = 0.5f;
改变色彩分离度
passthroughLayer.colorMapEditorPosterize = 0.5f;
改变饱和度
passthroughLayer.colorMapEditorSaturation = 0.5f;
改变透视色彩
先定义一个 Gradient 类的变量
public Gradient colorGradient;
然后我们可以在 Inspector 面板中编辑颜色:
编辑完后写下如下代码:
passthroughLayer.colorMapEditorGradient = colorGradient;
此外,我们还可以用如下方法对透视风格进行设置:
📕局部透视
局部透视的效果如下图所示:
也就是在虚拟场景中有一小块区域是透视场景。
⭐透视材质
Oculus Integration 中有一个透视的 Shader,我们可以新建一个材质 Material,然后修改它的 Shader 为 SelectivePassthrough:
另外,我们还需要增大材质中的 Render Queue 的数值,默认是 2000。但是如果是按默认的值,我们无法看到透视材质。Render Queue 越大,越后渲染。建议是设置到 2500 以上,我这边设置成了 3000。确保透视材质比场景中的其他虚拟物体后渲染,这样我们才能看到透视材质叠加在虚拟场景上。这时候,我们的透视材质就创建成功了,把透视材质赋给一个物体,这个物体的表面会变成透视的区域。
为了演示,我创建了一个有天空盒的场景,导入 OVRCameraRig 预制体,然后在眼部相机前放了一个方块,将刚刚创建的透视材质赋给方块,并且像前一篇教程那样设置好 OVRCameraRig 上的 OVRManager 和 OVRPassthroughLayer 脚本参数。
OVRManger:
OVRPassthroughLayer:
相机:
⭐设置 OVRManager.eyeFovPremultipliedAlphaModeEnabled 为 false
如果这个时候尝试去运行程序,你可能会发现透视材质看上去颜色比较淡,太过透明,不是很清晰。我们需要在代码中将 OVRManager.eyeFovPremultipliedAlphaModeEnabled 设为 false,Oculus Integration 中已经为我们提供了相关的脚本。我们可以在 OVRCameraRig 物体上添加 EnableUnpremultipliedAlpha 脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnableUnpremultipliedAlpha : MonoBehaviour
{
void Start()
{
// Since the alpha values for Selective Passthrough are written to the framebuffers after the color pass, we
// need to ensure that the color values get multiplied by the alpha value during compositing. By default, this is
// not the case, as framebuffers typically contain premultiplied color values. This step is only needed when
// Selective Passthrough is non-binary (i.e. alpha values are neither 0 nor 1), and it doesn't work if the
// framebuffer contains semi-transparent pixels even without Selective Passthrough, as those will have
// premultiplied colors.
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_ANDROID
OVRManager.eyeFovPremultipliedAlphaModeEnabled = false;
#endif
}
}
设置 eyeFovPremultipliedAlphaModeEnabled 为 false 的原理可以参考代码的英文注释,因为本人对这方面知识的学习不是很深,所以这里就不展开了,也欢迎了解原理的小伙伴解释一下。
没有设为 fasle 的运行结果,可以看到材质比较透明,效果不是很好:
设为 false 的运行效果:
另外如果要确保虚拟物体和透视区域的前后遮挡关系,需要将 OVRManager.eyeFovPremultipliedAlphaModeEnabled 设为 false,否则因为透视区域的视觉效果太过透明,导致无法看到前后遮挡的情况。
我们分析一下设置了透视材质后的视觉效果。可以看到方块的表面是透视的区域。因为 OVRPassthroughLayer 中设置了 Underlay,透视图层会位于虚拟图层之下,所以如果不加透视材质,整个虚拟场景会覆盖在透视场景上面。而加了透视材质后,相当于方块的表面变“透明”了,我们就能直接看到位于虚拟场景之后的透视场景。
用简易的图来解释,原本透视图层和虚拟图层的位置如下,透视图层在后,虚拟图层在前。
添加了透视材质后,相当于在虚拟图层上开了一个洞,这时候就能透过洞看到后面的透视图层:
⭐表面投射透视 Surface-projected Passthrough
这个功能相当于把透视的部分限制在某个区域。之前的透视材质只是让某个物体的表面呈现出位于虚拟图层后方的透视图层,这时候我们的透视图层依然包含了整个现实场景,相当于设备将整个现实环境进行重建,只不过刚好加了透视材质的区域变“透明”了,才能看到“藏”在虚拟图层后的现实场景。而有的时候,我们想把透视图层限制在一个小的范围,这时候就要用上表面投射透视 (Surface-projected Passthrough),它可以将透视场景投射到某些物体的表面上,这时候透视图层只有在物体的表面上是可见的,其余的部分的透视图层是不可见的。
(官方文档:https://developer.oculus.com/documentation/unity/unity-customize-passthrough-surface-projected-passthrough/)
透明材质和表面投射透视的区别可以用下面这张图来描述:
假设透视图层都位于虚拟图层的下方,那么在右侧的表面投射透视中,透视图层实际上是藏在虚拟图层之下,被虚拟图层所覆盖,所以我用虚线来表示。这种情况下,透视图层被限定到了一个比虚拟图层更小的范围内。
🔍步骤一:OVRPassthroughLayer 脚本中的 Projection Surface 参数
如果你想要透视图层为整个现实环境,你需要将 OVRPassthroughLayer 脚本中的 Projection Surface 参数设为 Reconstructed。而如果你想要用上表面投射透视,将透视图层限定在指定的一个范围内,你需要将 Projection Surface 参数设为 User Defined。
🔍步骤二:OVRPassthroughLayer 脚本中的 AddSurfaceGeometry 方法
在设置了 Projection Surface 为 User Defined 之后,我们需要指定什么物体的表面能够投射透视场景。这时候就要用上 OVRPassthroughLayer 脚本中的 AddSurfaceGeometry 方法,我们可以新建一个脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PassthroughUserDefine : MonoBehaviour
{
public OVRPassthroughLayer passthroughLayer;
public bool isTransformUpdate;
private MeshRenderer meshRenderer;
void Start()
{
Init();
}
private void Init()
{
if (passthroughLayer == null)
{
passthroughLayer = GameObject.Find("OVRCameraRig")?.GetComponent<OVRPassthroughLayer>();
}
passthroughLayer.AddSurfaceGeometry(gameObject, isTransformUpdate);
meshRenderer = GetComponent<MeshRenderer>();
meshRenderer.enabled = false;
}
}
这个 AddSurfaceGeometry 需要传入两个参数,一个是表面需要被透视投射游戏物体,另一个是个 bool 值。
注:
1)这个方法只有在设置了 Projection Surface 为 User Defined 之后才能生效。
2)第二个 bool 参数表示限定的透视区域是否会跟随物体的移动而移动。我们知道透视的场景会被投射到物体的表面上,如果为 false,透视区域会一直处于物体初始化的位置,后续如果物体移动了,透视区域的位置不会改变;如果为 true,透视区域会在每帧进行渲染,可以随着物体的移动而移动,会确保透视场景一直投射在物体的表面上。
3)我在脚本中还关闭了物体的 MeshRenderer,这是因为我们不希望看到虚拟物体原本的材质。如果处于 Underlay 模式并且物体的 MeshRenderer 是激活的,即使透视场景被投射到了物体表面上,还是会因为透视图层位于虚拟图层之上,导致物体原本的材质覆盖了透视的场景。
脚本创建完毕后,我们需要把它挂载到表面需要被投射的物体上。我这里重新创建了一个大方块,给它添加刚刚创建的脚本:
这个大方块的表面作为透视可见的区域,然后我在这个方块面前放了个小的方块,作为虚拟物体。
为了更加明显地看出表面投射透视的特征,我又加了两个拥有透视材质的球,并且让它们跟随手柄运动。
首先在下图所示的这两个物体上创建球作为子物体,这时候球就会跟随手柄运动:
然后把透视材质赋给球:
这样,我们的手柄周围就会跟随着一个拥有透视材质的球。这个时候,我们运行程序看下效果:
因为此时 OVRPassthroughLayer 处于 Underlay 模式,所以透视图层位于虚拟图层之下,一开始我们只能看到虚拟场景。而跟随手柄的球具有透视材质,我们就能够通过透视材质看到位于虚拟图层下方的透视场景。然后我们发现只有设置了表面投射透视的大方块表面上可以看到透视图层,而大方块区域以外之只能透过透视材质看到黑色的场景,因为其余的透视图层是不可见的,我们看不到其余部分的透视场景。
如果你把 Projection Surface 改成 Reconstructed,透视图层的范围就不会被限定,我们始终能透过球看到透视场景。
你也可以改变脚本的 isTransformUpdate 参数,然后在运行过程中移动大方块,就能够看出 true 和 false 的区别,如果设为 true,透视区域会跟随大方块移动。
此外,具有透视材质的球与红色的虚拟方块还有前后遮挡的关系。当透视球放在虚拟方块前的时候,虚拟方块会被球遮挡;当透视球放在虚拟方块后的时候,球会被虚拟方块遮挡。
不过前提是透视材质的 ZTest 设为了 LessEqual。具体原理和 Shader 渲染有关,感兴趣的小伙伴可以深入研究。
⭐多个透视图层
多个透视图层基本和表面投射透视一起使用。我们可以在虚拟场景中限定多个透视可见的区域,每一个透视区域相当于一个透视图层,每一个透视图层由一个 OVRPassthroughLayer 脚本来控制。沿用演示表面投射透视的场景,我在场景中用一个横着的方块来表示一个新的透视可见区域,那么之后我们就要在这个新的大方块上添加表面投射透视:
然后创建一个空物体,命名为 Second Passthrough,给它添加 OVRPassthroughLayer 脚本:
现在场景中有两个物体添加了 OVRPassthroughLayer 脚本,一个是 OVRCameraRig,一个是 Second Passthrough。
Second Passthrough 物体上的 OVRPassthroughLayer 就专门管理我们新增的大方块。因为我们想要在新增的大方块上添加表面投射透视,所以我们要把这个 OVRPassthroughLayer 脚本的 Projection Surface 改为 User Defined。Placement 参数的话按自己的需求设置,我这里为了方便演示,把它设为 Overlay,并且把 OVRCameraRig 中的 OVRPassthroughLayer 也设为了 Overlay,这样透视图层位于虚拟图层之上,我们能直观地看到两个透视图层。
🔍OVRPassthroughLayer 上的 Composition Depth 参数
Composition Depth 表示图层的深度,数值越小,越后渲染,也就是渲染在越前面。
Composition Depth 一般用于多个透视图层上,比如有一个深度为 0 的物体和一个深度为 -1 的物体,会先渲染深度为 0 的物体,再渲染深度为 -1 的物体,也就是深度为 -1 的物体会被渲染在深度为 0 的物体前面。当这个物体放在一起时,深度为 -1 的物体会遮挡深度为 0 的物体。
根据摆放在场景中的两个白色大方块的前后关系,我们希望摆放在前面的大方块后渲染,也就是视野向前看的时候,横着的大方块能够遮挡住竖着的大方块的下半部分,如下图所示:
原来的 OVRCameraRig 物体上的 OVRPassthroughLayer 管理着竖着的大方块,因为这个方块上的 PassthroughUserDefine 脚本引用了 OVRCameraRig 上的 OVRPassthroughLayer,然后调用了 这个 OVRPassthroughLayer 类的 AddSurfaceGeometry 方法,才能给竖着的大方块添加表面投射透视。
类似的,我们也要给横着的大方块添加 PassthroughUserDefine 脚本,然后引用 Second Passtrhough 物体上的 OVRPassthroughLayer 脚本,如下图所示:
最后就是处于两个 OVRPassthroughLayer 的 Composition Depth。既然原先 OVRCameraRig 物体上的 Composition Depth 为 0,为了让横着的大方块能够遮挡竖着的大方块,我们要让 Second Passthrough 物体上的 Composition Depth 小于 OVRCameraRig 物体上的 Composition Depth。因此,我将 Second Passthrough 物体上的 Composition Depth 设为 -1。
现在我们可以运行程序看看效果:
可以看到横着的大方块把竖着的大方块的下面一部分给遮挡了,这就是 Composition Depth 在多个透视图层中的作用。
如果你让 Second Passthrough 物体上的 Composition Depth 大于 OVRCameraRig 物体上的 Composition Depth,你仍然能够看到竖着的大方块的下半部分,因为这个时候竖着的大方块会渲染在横着的大方块的前面。