Unity Meta Quest MR 开发(六):将现实的门窗替换成虚拟的门窗,实现虚拟场景与现实空间的融合【局部透视 | MR 门窗效果】


此教程相关的详细教案,文档,思维导图和工程文件会放入 Spatial XR 社区。这是一个高质量 XR 开发者社区,博主目前在内担任 XR 开发的讲师。该社区提供专人答疑、完整进阶教程、从零到一项目孵化保姆服务(包含产品上架App lab)、投资|融资对接、工程文件下载等服务。

社区链接:
SpatialXR社区:完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子

在这里插入图片描述


📕教程说明

这期教程我将介绍如何实现 MR 门窗效果。我会教大家如何把现实的门窗替换成虚拟的门窗,实现虚拟场景与现实空间的融合,也就是局部透视的效果(如下图所示),那么我们就可以透过虚拟的门窗看到位于现实房间之外的虚拟世界。

在这里插入图片描述

学了本期以及即将推出的几期教程,你将了解大部分 MR 游戏或应用中在现实空间中透视出虚拟场景的实现原理。

在这里插入图片描述

配套的视频链接:
https://www.bilibili.com/video/BV1TK421h7Me

系列教程专栏:https://blog.csdn.net/qq_46044366/category_12118293.html

Meta XR SDK 版本:v63

Unity 版本:2022.3.20f1


📕Shader 实现

我们主要会用一种 Shader 来实现我们想要达到的视觉效果。我们可以直接使用 Meta 的一个开源项目 “The World Beyond” 中写好的一个 Shader。
项目链接:https://github.com/oculus-samples/Unity-TheWorldBeyond/tree/main
Shader链接:https://github.com/oculus-samples/Unity-TheWorldBeyond/blob/main/Assets/MultiToy/Materials/DepthOnly.shader

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * Licensed under the Oculus SDK License Agreement (the "License");
 * you may not use the Oculus SDK except in compliance with the License,
 * which is provided at the time of installation or download, or which
 * otherwise accompanies this software in either electronic or hard copy form.
 *
 * You may obtain a copy of the License at
 *
 * https://developer.oculus.com/licenses/oculussdk/
 *
 * Unless required by applicable law or agreed to in writing, the Oculus SDK
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

Shader "TheWorldBeyond/DepthOnly" {
  Properties {
  }
  SubShader {
    Tags{"RenderType" = "Transparent"} LOD 100

        // First Pass: render outside shell of hand, as depth object
        Pass {
      ColorMask 0 Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

          struct appdata {
        float4 vertex : POSITION;
      };

      struct v2f {
        UNITY_FOG_COORDS(1)
        float4 vertex : SV_POSITION;
      };

      v2f vert(appdata v) {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        UNITY_TRANSFER_FOG(o, o.vertex);
        return o;
      }

      fixed4 frag(v2f i) : SV_Target {
        //clip(mask.r - 0.5);
        return float4(0,0,0,0);
      }
      ENDCG
    }
  }
}

这个 Shader 不会写入颜色信息,只会写入深度信息,所以叫 Depth Only。因此,如果一个物体的材质用上了这个 Depth Only Shader,那么我们看不到这个物体的颜色,但是该物体仍然会参与到深度测试中。根据这个特性,我们可以进一步分析。


📕控制物体之间的渲染顺序和深度

为了达到我们想要实现的效果,我们主要会涉及到三类物体:
1)现实房间外的虚拟场景
2) 现实的墙(具有透视材质的平面)
3)位于墙上的虚拟门窗

我们会给位于墙上的虚拟门窗添加 Depth Only Shader,然后通过控制物体之间的渲染顺序和深度来实现 MR 门窗效果。

深度涉及到深度测试深度测试会基于一个像素的深度值与深度缓冲区中已有的值进行比较,来决定一个像素是否应该被绘制。深度说白了就是物体与相机的距离。一般来说深度测试的条件是小于等于,也就是说如果像素的深度值小于等于深度缓冲区已有的值,那么会通过深度测试,这个像素就会被渲染出来。如果大于,就不会被渲染。这个情况符合我们的现实规律,因为在现实中位于前面的物体能够遮挡住位于后面的物体,如果把我们的眼睛比作相机,我们先看到了位于前面的物体,然后位于后面的物体深度比前面的物体大,那么它被前面的物体挡住了,我们也就看不到位于后面的物体,就是位于后面的物体没有通过深度测试。那么放在计算机的渲染当中也是类似的,先渲染的物体会把它的深度值写入深度缓冲区,然后接下来渲染的物体会与先渲染的物体进行深度比较,假如我们想要让位于前面的物体遮挡住位于后面的物体,那么位于后面的物体深度大,不会通过深度测试,就不会被渲染出来。

渲染顺序可以通过设置 Render Queue 来控制。Render Queue 越大的物体越后渲染

因此,我们以这样设置:
Render Queue:虚拟场景<门窗<墙 (我这里设置为虚拟场景:2000,门窗上的 Depth Only Shader:2998,墙上的 SelectivePassthrough Shader:2999)
深度:门窗<墙<虚拟场景

那么渲染顺序就是先渲染虚拟场景,再渲染门窗,再渲染墙。结合深度,我们可以这样分析:

由于虚拟世界是首先被渲染的,它的深度信息会被写入深度缓冲区。这个阶段没有先前的深度信息可以比较,所以虚拟世界的渲染不会被深度测试阻止。

接下来渲染门窗,由于 Depth Only Shader 的作用,虽然不写入颜色信息,但会更新深度缓冲区。这个更新会覆盖之前虚拟场景在相同像素位置的深度信息,因为门在这些位置上的深度值更小(更靠近相机)。

接下来渲染墙。对于没有被门遮挡的部分,墙会成功通过深度测试并覆盖虚拟场景的像素,因为这部分区域的深度缓冲区记录的是虚拟场景的深度信息,只有与门重叠的这部分的深度信息被替换成了门的深度信息,其他位置记录的还是原先虚拟场景的深度。然而,对于被门遮挡的部分,因为墙的深度值大于门在深度缓冲区中设置的值(即,墙在门之后),那么这部分墙在深度测试中会失败,因此不会在这些像素位置上覆盖之前的内容(即虚拟世界)。因为门本身没有颜色,所以门这块区域就只剩下最早保留的虚拟世界的颜色。

在这里插入图片描述

总结一下,Depth Only Shader 能够影响在它之后渲染的物体,如果在它之后渲染的物体深度更大,那么被它遮住的这部分不会渲染出颜色。


📕将现实的门窗替换成虚拟的门窗

首先需要配置好场景理解的功能,可以参考我之前写的这篇教程:https://blog.csdn.net/qq_46044366/article/details/135930423
需要配置具有透视材质的 Plane Prefab 和 Volume Prefab 用来表示现实中的平面和 3D 物体。

接下来我们需要识别出现实中的门窗,这个可以在 Quest 的空间设置步骤中标出现实中的门窗,然后添加上对应的标签。

在这里插入图片描述

然后我们可以修改 OVRSceneManager 的 Prefab Overrides,我们将标签为门(DOOR_FRAME)和窗户(WINDOW_FRAME)的现实物体替换成虚拟的门窗物体。

在这里插入图片描述

门:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

根物体需要添加 OVRSceneAnchor 脚本,子物体需要有一个 Quad 物体,范围和门口的大小一样,该物体的材质需要是 Depth Only Shader

窗:

在这里插入图片描述
在这里插入图片描述

最终效果:

在这里插入图片描述
在这里插入图片描述

📕解决一些小问题

这时候运行程序,我们可能会遇到几个问题:
1)打开程序的一瞬间会看到完整的虚拟场景,随后现实场景才出现
2)虚拟的地面可能会比现实地面高

第一个问题出现的原因是:场景模型的加载需要一段时间,可能刚打开程序的时候场景模型还没加载好,因此这个时候看不到场景模型中的带有透视材质的房间物体。

第二个问题出现的原因是:假如虚拟场景的地面高度为 0,场景模型中的地面高度不一定是 0,如果比 0 小,那么看上去虚拟地面就会比现实地面高。

因此,解决思路是:等到场景模型加载完毕后,显示虚拟场景,并且调整虚拟场景的高度,让虚拟场景的地面略微低于场景模型中代表了现实地面的场景锚点。

我们可以写个脚本来实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VirtualWorldManager : MonoBehaviour
{
    //虚拟场景的父物体
    public Transform envRoot;
    //让虚拟场景位于场景模型的floor anchor之下多少米
    public float groundDelta = 0.02f;

    private OVRSceneManager sceneManager;

    private void Awake()
    {
        sceneManager = GetComponent<OVRSceneManager>();
        envRoot.gameObject.SetActive(false);
        sceneManager.SceneModelLoadedSuccessfully += InitRoom;
    }

    private void InitRoom()
    {
        List<OVRSceneAnchor> ovrSceneAnchors = new List<OVRSceneAnchor>();
        OVRSceneAnchor.GetSceneAnchors(ovrSceneAnchors);
        for(int i = 0; i < ovrSceneAnchors.Count; i++)
        {
            OVRSceneAnchor sceneAnchor = ovrSceneAnchors[i];
            OVRSemanticClassification classification = sceneAnchor.GetComponent<OVRSemanticClassification>();
            if (classification != null && classification.Contains(OVRSceneManager.Classification.Floor))
            {
                Vector3 envPos = envRoot.transform.position;
                float groundHeight = sceneAnchor.transform.position.y - groundDelta;
                envRoot.transform.position = new Vector3(envPos.x, groundHeight, envPos.z);
            }
        }
        envRoot.gameObject.SetActive(true);
    }
}

将这个脚本添加到挂载了 OVRSceneManager 脚本的物体上:

在这里插入图片描述

EnvRoot 物体为虚拟场景的父物体。

  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在使用Quest开发无缝场景加载时,需要注意以下几点技巧: 1. 避免资源过度消耗:在场景切换过程中,需要加载新的资源,如果过度消耗资源会导致卡顿和延迟,因此需要在设计场景时尽可能减少资源的使用。 2. 合理布局场景:当一个大型场景需要分成多个区域进行加载时,需要考虑不同区域之间的联系,尽量设计成相邻的区域,以提高场景切换的稳定性。 3. 异步加载:使用异步加载可以避免长时间等待资源加载完成,提高场景加载的效率和稳定性。 4. 懒加载:将场景的资源按需加载,只有在实际需要用到的时候才进行加载,这样可以避免不必要的资源浪费,提高场景的加载速度。 5. 预处理场景:在场景运行前,对场景进行预处理和优化,尽量减少在场景运行时的资源消耗和运行开销,以提高场景的流畅性和稳定性。 综上所述,无缝场景加载需要综合考虑资源的使用、场景的设计、异步加载、懒加载和预处理等因素,以实现流畅稳定的场景加载效果。 ### 回答2: 在进行Quest Unity开发的过程中,保持场景流畅稳定的无缝加载是非常重要的。以下是一些技巧,可以帮助你实现这一目标。 首先,你应该正确地管理场景对象。在每个场景中,只加载必要的对象。这样可以减少内存使用量,并且加快场景加载速度。 其次,你可以尝试使用场景剪辑来帮助实现流畅的加载。使用场景剪辑可以使你的场景在加载时只显示其中的部分,而不是全部加载。这样减少了渲染负载,可以提高场景的性能。 第三,你可以采用异步加载来减少场景加载时间。使用异步加载可以在主线程之外加载场景资源,这可以减少在场景加载期间的延迟和画面卡顿。 最后,你还可以优化场景的渲染和材质,从而减少场景的复杂度。这可以使场景更快地渲染,从而加快场景的加载速度。 综上所述,使用正确的管理和加载技巧,以及良好的渲染优化,可以实现流畅稳定的无缝场景加载。这些技巧可以帮助你打造出更好的Quest Unity应用程序,提供更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YY-nb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值