开发实战:使用PCA API访问Meta Quest的摄像头
查看引用/信息源请点击:skarredghost
(映维网Nweon 2025年04月08日)开发者现在已经可以访问Meta Quest头显的摄像头,但具体的操作是怎样的呢。下面这篇来自Skarredghost的博文将逐步解释如何使用Meta SDK的Passthrough Camera Access在Unity 6创建一个项目:
如果你是Unity或Meta SDK的初学者,你可以查看YouTube视频。但如果你对Unity中的XR开发有一定的认知,并且你喜欢文本而非视频,请继续阅读下文。
在我们开始之前……
用于摄像头访问的SDK属于实验性,在接下来的时间里可能会出现非常大的变化。
先决条件
我们先看看摄像头访问的先决条件是什么:
-
必须是Meta Quest 3或Meta Quest 3S。暂时不支持其他头显
-
设备必须至少是v74。
-
必须有最新版本的Unity。Meta建议2022.3.58f1或6000.0.38f1。不过,稍微不同的版本应该依然可行。本教程使用了6000.0.34f1。
请注意,在撰写本文时,所述功能标记为实验性,所以你不能在Horizon Store发布任何实现它的应用功能。按照过往经验,数个月之后就会解禁。
Meta Quest 3摄像头访问的基础
Quest 3的摄像头访问使用经典的Android工具来访问摄像头框架。特别地,它是以Camera2类作为基础。
在Unity中,几乎没有人直接使用Camera2类,因为它需要JNI调用,而这总是非常痛苦。Unity开发者通常使用WebCamTexture类,它能够将摄像头帧捕获到动态Unity纹理中。对于全新的透视API,可以完全遵循这个过程并获得设备左或右前置摄像头帧的WebCamTexture。在底层,WebCamTexture查询Camera2。
一旦有了WebCamTexture,你就可以做任何事情:你可以在屏幕显示纹理,或者抓取帧像素并分析它们(并可能将它们发送给AI算法)。记住,每次你从纹理中抓取像素并将它们移动到你的“CPU”内存进行分析时,你都会引入延迟,因为将数据从GPU移动到CPU是一个有点慢的操作。
Meta的这种方法有一个优势:与Android手机使用的方法一样,谷歌同样表示,Android XR使用了同样的方法。所以,创建跨平台应用将变得相当容易。
这种方法的另一个优势是给予用户控制权:在Android访问摄像头总是需要用户的许可,而这会发生在Quest身上。Meta已经创建了一个特殊的权限,而你必须请求用户提供摄像头帧访问权限。用户必须信任你,否则你将无法获得摄像头帧。
Passthrough Camera API示例
开始使用Passthrough Camera API的建议方法,包括官方建议,都是从Meta提供的官方示例开始。Meta建议从这个名为Unity-PassthroughCameraApiSamples的样本项目开始。
所述示例项目是开始学习摄像头访问的好方法。你可以从现有的代码中学习,或可以修改调整,甚至可以将其中的代码复制粘贴到自己的项目中,以加快开发速度。
(顺便说一句,除了官方的示例,另一个示例库提供了一系列有趣的例子。当然,你应该先看完官方示例后再深入研究。
需要注意的是,在撰写博文的时候,有一个小问题阻碍了这个项目在Unity 6顺利运行。就理论而言,项目应该开箱即用,但实际上并不是。所以下面将分享如何获得、修复和运行这个项目。
获取和构建示例的步骤如下:
-
克隆你喜欢的存储库的示例
-
打开Unity Hub
-
选择Add -> Project From Disk并选择你刚刚克隆项目的文件夹。项目应该显示为Unity 2022打开。点击项目的Unity版本标签,并在即将到来的窗口中选择你的Unity 6版本和Android build平台。这十分必要,因为本教程是关于Unity 6。
-
确认你想要的版本
-
打开项目
-
修复Activity名称:点击编辑器窗口上方的“Meta XR Tools”下拉列表,并从下拉列表中选择Project Setup Tool
-
在下面的项目设置工具窗口中,你应该会看到一个错误,类似于“Always specify single GameActivity…”。单击与其关联的Fix按钮,错误应该会消失
-
如果有其他错误,修复它们
-
关闭项目设置工具窗口
-
修复Android Manifest文件:在菜单中选择“Meta -> Tools -> Update AndroidManifest.xml”
-
如果有弹出窗口要求确认是否覆盖文件或类似,请确认操作
-
修复摄像头纹理的初始化时间。Camera SDK应该在播放WebCamTexture之前给系统更多的初始化时间
-
打开脚本WebCamTextureManager.cs,位置是\Assets\ passthroughcameraapissamples \PassthroughCamera\Scripts\
-
找到OnEnable函数并将开始摄像头初始化的最后几行改成为初始化添加两帧延迟的协程。一开始的代码是这样:
private void OnEnable()
{
PCD.DebugMessage(LogType.Log, $"PCA: {nameof(OnEnable)}() was called");
if (!PassthroughCameraUtils.IsSupported)
{
PCD.DebugMessage(LogType.Log, "PCA: Passthrough Camera functionality is not supported by the current device." +
$" Disabling {nameof(WebCamTextureManager)} object");
enabled = false;
return;
}
m_hasPermission = PassthroughCameraPermissions.HasCameraPermission == true;
if (!m_hasPermission)
{
PCD.DebugMessage(LogType.Error,
$"PCA: Passthrough Camera requires permission(s) {string.Join(" and ", PassthroughCameraPermissions.CameraPermissions)}. Waiting for them to be granted...");
return;
}
PCD.DebugMessage(LogType.Log, "PCA: All permissions have been granted");
_ = StartCoroutine(InitializeWebCamTexture());
}
private void OnDisable()
{
PCD.DebugMessage(LogType.Log, $"PCA: {nameof(OnDisable)}() was called");
StopCoroutine(InitializeWebCamTexture());
if (WebCamTexture != null)
{
WebCamTexture.Stop();
Destroy(WebCamTexture);
WebCamTexture = null;
}
}
你需要改成这样(注意从第19行开始的与OnEnable相关的修改):
private void OnEnable()
{
PCD.DebugMessage(LogType.Log, $"PCA: {nameof(OnEnable)}() was called");
if (!PassthroughCameraUtils.IsSupported)
{
PCD.DebugMessage(LogType.Log, "PCA: Passthrough Camera functionality is not supported by the current device." +
$" Disabling {nameof(WebCamTextureManager)} object");
enabled = false;
return;
}
m_hasPermission = PassthroughCameraPermissions.HasCameraPermission == true;
if (!m_hasPermission)
{
PCD.DebugMessage(LogType.Error,
$"PCA: Passthrough Camera requires permission(s) {string.Join(" and ", PassthroughCameraPermissions.CameraPermissions)}. Waiting for them to be granted...");
return;
}
else
{
_ = StartCoroutine(WaitAndInitialize());
}
}
private IEnumerator WaitAndInitialize()
{
yield return null;
yield return null;
PCD.DebugMessage(LogType.Log, "PCA: All permissions have been granted");
_ = StartCoroutine(InitializeWebCamTexture());
}
private void OnDisable()
{
PCD.DebugMessage(LogType.Log, $"PCA: {nameof(OnDisable)}() was called");
StopCoroutine(InitializeWebCamTexture());
if (WebCamTexture != null)
{
WebCamTexture.Stop();
Destroy(WebCamTexture);
WebCamTexture = null;
}
}
(有一种替代方法,同一文件的第109行,那里已经有一个yield return null,让你在播放WebCamTexture之前等待一帧,添加更多yield return null函数,令系统等待更长时间。那边可以选择这两种方法中的任何一种,两者都有效)
-
保存文件。由于这个修改,当你多次启动应用程序时,它不会崩溃。你只是在初始化中添加了非常短的延迟,没有人会注意到
-
现在你可以
-
将你的Quest 3接到电脑
-
选择File -> Build And Run
-
选择保存Build的位置
-
等待结束
现在你可以戴上头显并进行体验。
修改示例
强烈建议你尝试一下示例,研究不同的场景。然后,按照下面的说明创建自己的新场景。一般来说,请阅读repo的README文件,阅读Meta关于摄像头访问的官方文档,并尝试相关示例。
从零开始一个摄像头访问项目
我喜欢Meta提供的开箱即用示例。但有时候你真的想从零开始一个项目。这时候,你不能从提供的示例项目开始,而是要自己在项目中实现PCA。下面将介绍如何开始:
-
打开Unity Hub
-
创建一个新的Unity项目,选择Unity 6作为版本,并指定项目类型为“Universal 3D Core”(非必须,但通常当你为Quest开发内容时,你总是使用URP)
-
等待Unity创建并打开新项目
-
切换到Android Build平台。立即执行,以避免稍后重新导入大量内容。如果不清楚,你可以选择File -> Build Profiles,然后在弹出的窗口中选择Android选项卡,点击“切换平台”按钮
-
现在项目已经在正确的平台,如果愿意,你可以更改Project Settings,指定正确的名称,并选择Android包名称。这一步完全属于可选步骤
-
(从Windows菜单)打开Unity Package Manager
-
安装MRUK包:点击包管理器左上角的“+”按钮,选择“Install package by name…”,并指定名称为“com.meta.xr.mrutilitykit”。单击“Install”确认。安装应该在几秒钟后开始
-
如果Unity提示重新启动编辑器,重新启动
-
关闭包管理器和任何打开的弹出窗口
-
重新打开项目设置(编辑->项目设置…)
-
在项目设置窗口中,单击左侧“XR插件管理”选项卡,并选择右侧XR Plug-in management按钮
-
当安装了XR Plug-in management后,窗口的右侧将填充。进入与PC相关的选项卡,选择OpenXR插件。如果一个弹出窗口要求你安装什么Meta XR功能或类似,点击“Yes”。
-
如果已经移动到与Project Validation相关的另一个选项卡,请移回XR Plug-in management的前一个选项卡。然后转到Android相关的子选项卡,选择OpenXR插件。
-
现在我们必须添加Meta Quest控制器的交互配置文件。在左侧选择选项卡XR Plug-in management-> OpenXR。你应该看到窗口右侧显示“Enabled Interaction Profiles”。点击旁边的“+”按钮,添加“Meta Quest Touch Plus Controller Profile”。然后对“Meta Quest Touch Pro Controller Profile”执行同样的操作。PC和Android均需重复这一操作。PC不是特别有用,但如果你想在编辑器中进行一定的测试,你可以尝试一番。
-
在左侧选择选项卡XR Plug-in management->项目验证。如果你是有经验的用户,请评估如何处理各种建议条目。对于这个示例,我们简单行事,在PC和Android选项卡都点击Fix All。
-
现在可以关闭“项目设置”窗口
-
你应该在项目中打开SampleScene,然后执行下面的操作:
-
删除场景中的所有内容
-
在菜单中,转到Meta -> Tools -> Building Blocks
-
将Camera Rig、Passthrough和Controller Tracking拖到场景中。这样我们就可以有一个带透视和可见控制器的场景
-
关闭Building Blocks
-
修复Meta XR设置的警告
-
单击编辑器上方的Meta XR Tools下拉列表,并从下拉列表中选择Project Setup Tool
-
在下面的Project Setup Tool窗口中,你应该看到一定的警告和错误
-
最后一个应该讨论场景的特定权限(“当在项目中使用场景时,它需要执行……”)。点击它旁边的三个点,然后选择“忽略”。它应该消失。之所以忽略,是因为我们不希望Meta自动请求场景权限,因为Camera Access SDK将在脚本之一自行执行
-
现在你可以点击“Apply All”来修复所有剩下的问题
-
关闭项目设置工具窗口
-
从示例项目中导入Camera Access的基础设施。注意,我们可以自己编写,但既然Meta提供了现成的工具,为什么要麻烦呢?
-
克隆你喜欢的存储库(如果没有在前一个教程中执行)
-
将示例项目中的\Assets\PassthroughCameraApiSamples\PassthroughCamera\文件夹复制到新项目的Assets文件夹中。如果愿意,你可以从Windows文件管理器拖拽到Unity项目窗口
-
如果有必要,应用与上文描述的WebCamTextureManager.cs文件相关修复(如果Meta修复了错误,或者如果你已经在遵循上面的教程修复,则现在没有必要)
-
完成场景:
-
将Assets/PassthroughCamera/Prefabs/WebCamTextureManagerPrefab.拖到场景中。这个预制件包含WebCamTextureManager脚本,它将管理摄像头访问并提供WebCamTexture和关于摄像头的元数据。请注意,这个预制件中同时有一个权限管理器,它将询问用户使用摄像头和使用场景数据的权限。如果需要,可以使用这个脚本请求其他运行时权限
-
在场景中创建一个新的cube
-
在项目中创建一个新脚本,将其命名为WebcamTextureAssigner,并粘贴以下代码
-
将WebcamTextureAssigner脚本分配给cube
using PassthroughCameraSamples;
using System.Collections;
using UnityEngine;
///
/// Assigns the
/// to the
///
[RequireComponent(typeof(Renderer))]
public class WebcamTextureAssigner : MonoBehaviour
{
///
/// Start coroutine
///
///
IEnumerator Start()
{
//references that will be filled by the coroutine
WebCamTextureManager webCamTextureManager = null;
WebCamTexture webCamTexture = null;
//wait until the WebCamTextureManager is found and is ready to provide a texture
do
{
yield return null;
//if the WebCamTextureManager is not found yet, find it
if (webCamTextureManager == null)
{
webCamTextureManager = FindFirstObjectByType
}
//else, if we have it, try to get the texture of the camera of the headset
else
{
webCamTexture = webCamTextureManager.WebCamTexture;
}
} while (webCamTexture == null);
//here we have the texture. Assign it to the main texture of the main material of the renderer
GetComponent
}
}
-
我们现在必须修复Android Manifest请求的所有必需权限:
-
在菜单中选择“Meta -> Tools -> Create store-compatible AndroidManifest.xml”。这将用需要所有正确权限的Manifest替换当前的Manifest
-
如果要求确认更换Manifest,请确认
-
现在打开manifest文件,它位于Assets/Plugins/Android/AndroidManifest.xml
-
在文件末尾有各种标签的地方,添加:
-
这将确保提示用户是否允许访问摄像头
-
保存场景,保存项目
-
通过USB接到PC
-
在设备运行(File -> Build And Run)
-
如果提示Input Handling设置为“both”,选择Yes继续(在本示例中,我们不关心,但在Production环境中,你应该使用新的输入系统)
-
享受camera-textured cube。你会看到cube的颜色有点暗淡,这是因为我们从场景中删除了照明。我们使用默认的照明材质。将材质改为unlit应该可以解决这个问题
从WebcamTexture获取数据
上面介绍了如何使用Meta提供的预制工具获得WebcamTexture,而你同时应该学习如何获得实际的像素数据。要做到这一点,你可以使用一些众所周知的方法。一个是使用GetPixels,另一个是使用AsynGPUReadback。第一个是阻塞调用,但以一种简单的方式直接提供想要的所有像素数据;第二个不会,但使用起来有点复杂(它在调用中为你提供数据)。你可以看看相关的文档并决定你想要使用哪一个:
请记住,将数据从GPU(纹理所在)移动到CPU(可以读取像素数据的地方)是一个非常缓慢和成本昂贵的操作。所以只在必要的时候执行。