增强现实( AR )与透视

增强现实的支持方式因硬件不同而有所差异。

诸如 Magic Leap、TiltFive 等设备通过使用“透视显示屏”显示渲染结果,使得用户能同时看到虚拟与现实世界。

而另一方面, Quest、HTC Elite 和 Lynx R1 等设备通过一种称为视频透视(Video Passthrough)的技术实现这一功能:摄像头记录现实世界的画面作为渲染背景,在上面显示渲染结果。

备注

透视功能在不同平台上的实现方式也不尽相同。

In Godot 4.3 we have implemented a unified approach that is explained on this help page so you don't need to worry about these differences, the XRInterface implementation is now responsible for applying the correct platform-dependent method [1].

对于 Meta Quest 和 HTC Elite 等头戴设备,你需要使用 OpenXR 供应商插件 v3.0.0 或更高版本来启用视频透视。

为保证向后兼容,旧的透视 API 仍然可用,但推荐按照以下新说明进行操作。

环境混合模式

我们通过设置环境混合模式来配置 VR 或 AR 功能。该模式决定了(现实世界)环境与虚拟世界的融合方式。

混合模式一览

混合模式

描述

XR_ENV_BLEND_MODE_OPAQUE

渲染的图像将是不透明的,我们看不到现实世界,只能看到 VR 世界。视频透视功能同样会被关闭。

XR_ENV_BLEND_MODE_ADDITIVE

渲染的图像将以半透明的形式被添加到现实世界中。该模式通常用于无法遮挡现实世界的透视设备。如果同时在使用视频透视功能,透视会被启用。

XR_ENV_BLEND_MODE_ALPHA_BLEND

渲染的图像将与现实世界进行 alpha 混合。在支持此功能的透视设备上,alpha 值将控制光学元件的透明度。在视频透视设备上,alpha 混合将应用于视频图像,并在适用时启用透视。

你可以通过 XRInterface 实例的 environment_blend_mode 属性来设置应用程序的环境混合模式。

同时,可以使用同一实例上的 get_supported_environment_blend_modes 属性来查询硬件支持的混合模式。

配置背景

当环境混合模式为 XR_ENV_BLEND_MODE_ALPHA_BLEND 时,必须将 Viewporttransparent_bg 属性设置为 true。而使用 XR_ENV_BLEND_MODE_ADDITIVE 混合模式时,应将背景颜色设置为黑色。

这两种解决方案都会导致背景渲染不再提供环境照明。因此,建议相应地调整环境设置,确保有足够的环境光来照亮场景。

备注

部分增强现实 SDK 确实会提供环境光信息——甚至是完整的辐射图——以允许在虚拟对象中模拟真实世界的反射。不过,目前 Godot XR 的核心尚不支持此功能,但可以通过插件来实现。

OpenXR 特定功能

在 OpenXR 选项卡中,你可以配置想要使用的默认混合模式。如果该模式可用,Godot 将在启动时选择此混合模式。如果不可用,Godot 将默认选择 XR 运行时提供的第一个支持的混合模式。

../../_images/openxr_default_blend_mode.webp

For passthrough devices OpenXR requires additional settings to be configured. These settings are platform-dependent and provided through the OpenXR vendors plugin.

举个例子,以下是 Meta Quest 所需的设置:

../../_images/openxr_export_passthrough.webp

Passthrough 设置定义了是否支持或者需要启动视频透视功能。

The Boundary Mode allows you to define whether the guardian is needed, disabling this fully requires passthrough to be enabled at all times.

融会贯通

综合以上内容,我们可以使用以下代码作为基础:

@onready var viewport : Viewport = get_viewport()
@onready var environment : Environment = $WorldEnvironment.environment

func switch_to_ar() -> bool:
    var xr_interface: XRInterface = XRServer.primary_interface
    if xr_interface:
        var modes = xr_interface.get_supported_environment_blend_modes()
        if XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND in modes:
            xr_interface.environment_blend_mode = XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND
            viewport.transparent_bg = true
        elif XRInterface.XR_ENV_BLEND_MODE_ADDITIVE in modes:
            xr_interface.environment_blend_mode = XRInterface.XR_ENV_BLEND_MODE_ADDITIVE
            viewport.transparent_bg = false
    else:
        return false

    environment.background_mode = Environment.BG_COLOR
    environment.background_color = Color(0.0, 0.0, 0.0, 0.0)
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
    return true

func switch_to_vr() -> bool:
    var xr_interface: XRInterface = XRServer.primary_interface
    if xr_interface:
        var modes = xr_interface.get_supported_environment_blend_modes()
        if XRInterface.XR_ENV_BLEND_MODE_OPAQUE in modes:
            xr_interface.environment_blend_mode = XRInterface.XR_ENV_BLEND_MODE_OPAQUE
        else:
            return false

    viewport.transparent_bg = false
    environment.background_mode = Environment.BG_SKY
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_BG
    return true

阴影转不透明模式

阴影转不透明( Shadow to opacity )是 Godot 空间着色器的一种渲染模式,在 Godot 3 中专门为 AR 引入。这种特殊的渲染模式会根据表面阴影决定其透明程度:当表面越多地处于阴影中,表面就变得越不透明。而表面完全被照亮时,表面会变得完全透明,从而显示出真实世界。

但是,表面实际上是在不透明状态下渲染的。使用该模式会导致两个后果:

  • 由于深度缓冲区和颜色缓冲区都被写入,即使表面完全透明,我们仍会遮挡其后方的几何体。

  • 由于我们使表面在阴影中变得不透明,因此我们可以让虚拟物体在现实世界物体上投射阴影 [2]

“阴影到不透明”效果在用户桌面上的应用。

“阴影到不透明”效果在用户桌面上的应用。

这使得以下应用场景成为可能:

  • 你可以在现实世界的桌子周围渲染一个盒子网格,这样即使有虚拟物体放在桌子下面,桌子也能保持可见。虚拟物体会被正确地遮挡。当将虚拟物体放在现实世界的桌子上时,会在桌子上投下阴影。

  • 在使用手部追踪功能渲染手部网格时,你可以使用这种渲染模式的着色器,以确保你的手能正确地遮挡虚拟物体。

以下着色器代码可以作为实现该功能的良好基础:

shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back, shadow_to_opacity;

void fragment() {
    ALBEDO = vec3(0.0, 0.0, 0.0);
}