JS `AR/VR` `WebXR Device API` `Pose Tracking` 与 `Hit Testing`

哈喽大家好,欢迎来到今天的WebXR小课堂!今天咱们要聊聊WebXR里边最酷炫的两大技能:Pose Tracking和Hit Testing。有了它们,你的网页瞬间就能变成AR/VR的入口,让用户直接在浏览器里跟虚拟世界互动。准备好了吗?咱们这就开始!

第一部分:Pose Tracking(姿态追踪)—— 掌握你的头和手!

Pose Tracking,顾名思义,就是追踪用户头部和手部的姿态。这“姿态”可不是指你今天心情好不好,而是指它们在三维空间里的位置(position)和旋转(orientation)。有了这些信息,我们才能把虚拟物体放到正确的地方,让用户感觉真实。

1. WebXR Pose Tracking的基本概念

  • XRFrame: 每一帧画面,都包含着关于当前XR环境的信息,包括设备姿态。
  • XRViewerPose: 代表了用户视点的姿态。通常,它对应于用户的头部位置和朝向。
  • XRInputSource: 代表用户的输入设备,比如VR手柄。
  • XRInputSource.gripSpace: 手柄的握持位置,通常用来放置虚拟物体。
  • XRInputSource.targetRaySpace: 从手柄发出的射线,可以用来进行交互。

2. 如何获取Pose信息?

首先,我们需要一个XR会话(XRSession)。有了会话,才能获取XRFrame,然后才能获取Pose信息。

let xrSession = null;
let xrRefSpace = null;
let xrViewerPose = null;
let inputSources = []; // 存储所有的输入源(手柄)

async function initXR() {
  // 检查浏览器是否支持WebXR
  if (navigator.xr) {
    // 请求WebXR会话
    try {
      xrSession = await navigator.xr.requestSession('immersive-vr', {
        requiredFeatures: ['local-floor', 'hand-tracking'] // 注意这里!手部追踪需要声明
      });

      // 设置会话的渲染循环
      xrSession.updateRenderState({ baseLayer: new XRWebGLLayer(xrSession, gl) });

      // 获取参考空间
      xrRefSpace = await xrSession.requestReferenceSpace('local-floor');

      // 监听会话结束事件
      xrSession.addEventListener('end', () => {
        xrSession = null;
      });

      xrSession.addEventListener('inputsourceschange', (event) => {
        inputSources = event.session.inputSources;
      });

      // 开始渲染循环
      xrSession.requestAnimationFrame(render);

    } catch (error) {
      console.error('WebXR 初始化失败:', error);
    }
  } else {
    alert('你的浏览器不支持WebXR!');
  }
}

function render(time, frame) {
  if (!frame) return;

  xrSession.requestAnimationFrame(render);

  const pose = frame.getViewerPose(xrRefSpace);
  if (pose) {
    xrViewerPose = pose; // 保存当前的viewer pose
    // 获取视点位置和旋转
    const position = pose.transform.position;
    const orientation = pose.transform.orientation;

    // 在这里使用位置和旋转信息来更新你的场景

  }

  // 处理手柄输入
  for (const inputSource of inputSources) {
    if (inputSource.hand) { // 确保支持手部追踪
      // 获取手部姿态
      const handPose = frame.getPose(inputSource.hand.get('wrist'), xrRefSpace);
      if (handPose) {
        //使用 handPose.transform.position 和 handPose.transform.orientation
        //更新手部模型的位置
      }
    }

    //处理手柄按键事件
    if(inputSource.gamepad){
      const gamepad = inputSource.gamepad;
      gamepad.buttons.forEach((button, index) => {
        if (button.pressed) {
          // 处理按钮按下事件
          console.log(`Button ${index} pressed`);
        }
      });
    }
  }

  // 渲染场景 (这里省略了 Three.js 或其他渲染库的代码)
  renderScene();

}

// 调用初始化函数
initXR();

代码解释:

  • navigator.xr.requestSession('immersive-vr', {requiredFeatures: ['local-floor', 'hand-tracking']}):请求一个沉浸式VR会话,并且声明我们需要local-floor (地面参考系) 和 hand-tracking (手部追踪) 这两个特性。注意,手部追踪是需要特别声明的!
  • frame.getViewerPose(xrRefSpace):获取当前帧的视点姿态。xrRefSpace 是一个参考空间,决定了姿态的坐标系。
  • pose.transform.positionpose.transform.orientation:分别包含了视点的位置和旋转信息。
  • inputSource.hand.get('wrist'): 获取手腕关节的位置,这个位置通常被认为是手部的位置。
  • 在手柄输入处理部分,我们遍历所有的 inputSources,检查它们是否支持手部追踪,然后获取手部姿态,并处理手柄按键事件。

3. Pose Tracking的注意事项

  • 参考空间的选择: local, local-floor, bounded-floor, unbounded,不同的参考空间有不同的特性,选择合适的参考空间很重要。local-floor通常是AR应用的理想选择,因为它提供了一个稳定的地面参考。
  • 坐标系: WebXR使用的是右手坐标系。
  • 性能: Pose Tracking是一个计算密集型的任务,优化你的代码以保证流畅的体验。

第二部分:Hit Testing(碰撞检测)—— 指哪打哪!

Hit Testing,也叫光线投射(Raycasting),就是从你的手柄或者眼睛(视点)发射一条虚拟的射线,然后检测这条射线是否与场景中的物体相交。这个技术是实现AR/VR交互的基础,比如用手柄点击按钮,或者把虚拟物体放置在真实世界的表面上。

1. WebXR Hit Testing的基本概念

  • XRRay: 代表一条射线,由原点(origin)和方向(direction)定义。
  • XRHitTestSource: 一个用于执行Hit Testing的对象。
  • XRHitTestResult: Hit Testing的结果,包含了射线与物体相交的位置和姿态。

2. 如何进行Hit Testing?

let xrHitTestSource = null;
let hitTestResults = [];

async function initHitTesting() {
  try {
    xrHitTestSource = await xrSession.requestHitTestSource({ space: xrRefSpace, profile: 'generic-touchscreen' });
  } catch (error) {
    console.error('Hit Testing 初始化失败:', error);
  }
}

function render(time, frame) {
  // ... (之前的Pose Tracking代码)

  if (xrHitTestSource) {
    // 获取Hit Test结果
    hitTestResults = frame.getHitTestResults(xrHitTestSource);

    // 处理Hit Test结果
    if (hitTestResults.length > 0) {
      const hit = hitTestResults[0];
      const hitPose = hit.getPose(xrRefSpace);

      if (hitPose) {
        // 在射线相交的位置放置一个虚拟物体
        const position = hitPose.transform.position;
        const orientation = hitPose.transform.orientation;

        // 使用位置和旋转信息来更新你的虚拟物体
        updateVirtualObject(position, orientation);
      }
    }
  }

  // 渲染场景
  renderScene();
}

// 在初始化XR会话后,初始化Hit Testing
initXR().then(() => {
  initHitTesting();
});

代码解释:

  • xrSession.requestHitTestSource({ space: xrRefSpace, profile: 'generic-touchscreen' }):创建一个Hit Test Source。space 指定了参考空间,profile 指定了输入设备的类型(这里使用 generic-touchscreen 只是为了示例,实际应用中应该根据输入设备选择合适的profile,比如手柄的 profile)。
  • frame.getHitTestResults(xrHitTestSource):执行Hit Testing,返回一个包含所有相交结果的数组。
  • hit.getPose(xrRefSpace):获取相交点的姿态。
  • updateVirtualObject(position, orientation):这是一个假设的函数,用于更新虚拟物体的位置和旋转。

3. 高级Hit Testing技巧

  • 指定Hit Testing的范围: 你可以通过设置 offsetRay 来调整射线的起始位置和方向,从而实现更精确的Hit Testing。
  • 使用XRRay进行自定义Hit Testing: 你可以自己创建一个XRRay对象,并使用XRHitTestSource.requestHitTest() 方法进行Hit Testing。这允许你更灵活地控制射线的参数。
  • 过滤Hit Test结果: 你可以根据自己的需求,过滤掉不感兴趣的Hit Test结果。例如,你可以只关心与特定类型的物体相交的结果。

第三部分:Pose Tracking + Hit Testing = 无限可能!

现在,我们已经掌握了Pose Tracking和Hit Testing这两大技能。把它们结合起来,就能创造出各种各样的AR/VR体验。

1. 实例:用手柄放置虚拟物体

let virtualObject = null; // 虚拟物体

function render(time, frame) {
  // ... (之前的代码)

  // 处理手柄输入
  for (const inputSource of inputSources) {
    // 只有当手柄的 squeeze button 按下时,才进行 hit test
    if (inputSource.gamepad && inputSource.gamepad.buttons[0].pressed) { // 假设 squeeze button 是第一个按钮

      // 从手柄发出射线
      const targetRayPose = frame.getPose(inputSource.targetRaySpace, xrRefSpace);
      if (targetRayPose) {
        // 创建 XRRay
        const rayOrigin = targetRayPose.transform.position;
        const rayDirection = {x:0, y:0, z:-1}; // 默认方向,需要根据手柄的旋转调整

        const transform = new XRRigidTransform(rayOrigin, targetRayPose.transform.orientation);
        const ray = new XRRay(transform.position, transform.orientation);

        xrSession.requestHitTest(ray.origin, ray.direction, xrRefSpace).then(results => {
          if(results.length > 0){
            const hit = results[0];
            const hitPose = hit.getPose(xrRefSpace);

             if (hitPose) {
                // 在射线相交的位置放置一个虚拟物体
                const position = hitPose.transform.position;
                const orientation = hitPose.transform.orientation;

                // 如果虚拟物体不存在,则创建
                if (!virtualObject) {
                  virtualObject = createVirtualObject(); // 创建虚拟物体的函数,需要你自己实现
                  scene.add(virtualObject); // 将虚拟物体添加到场景中
                }

                // 更新虚拟物体的位置和旋转
                virtualObject.position.copy(position);
                virtualObject.quaternion.copy(orientation);
             }
          }
        })

      }

    }
  }

  // 渲染场景
  renderScene();
}

代码解释:

  • 我们监听手柄的 squeeze button (挤压按钮) 是否被按下。
  • 如果按钮被按下,我们就从手柄发出一条射线。
  • 使用xrSession.requestHitTest方法进行Hit Testing。
  • 如果Hit Testing有结果,我们就创建一个虚拟物体,并将其放置在射线相交的位置。

2. 更多创意

  • 绘制: 用手柄在空中绘制线条,或者在真实世界的表面上涂鸦。
  • 交互: 用手柄点击虚拟按钮,或者拖动虚拟物体。
  • 游戏: 创造各种各样的AR/VR游戏,比如射击游戏,解谜游戏,等等。

第四部分:WebXR开发中的一些小技巧和注意事项

技巧/注意事项 描述
性能优化 WebXR应用对性能要求很高。尽量减少draw call,使用LOD(Level of Detail)技术,优化你的shader,等等。
兼容性 WebXR还在快速发展中,不同的浏览器和设备对WebXR的支持程度可能不同。使用polyfill来提高兼容性。
用户体验 保证用户在使用AR/VR应用时的舒适度。避免快速的移动和旋转,提供清晰的视觉反馈,等等。
权限请求 WebXR需要访问用户的摄像头和传感器,需要向用户请求权限。提供清晰的解释,说明为什么你的应用需要这些权限。
手部追踪的鲁棒性 手部追踪在复杂环境下可能会出现误差。使用平滑算法来减少抖动,或者使用更高级的算法来提高追踪的准确性。
环境光估计 WebXR提供了环境光估计的功能,可以帮助你更好地模拟真实世界的光照效果。使用XRLightEstimate接口来获取环境光信息。
调试工具 使用WebXR的调试工具来帮助你诊断问题。Chrome的WebXR Device Emulator可以模拟WebXR设备,方便你在桌面电脑上进行开发和调试。
辅助功能 考虑到不同用户的需求,提供辅助功能,比如字幕,语音控制,等等。

总结:

Pose Tracking和Hit Testing是WebXR开发的两大核心技术。掌握它们,你就能创造出各种各样令人惊艳的AR/VR体验。希望今天的课程对你有所帮助!记住,实践是最好的老师,多多尝试,你一定能成为WebXR开发的高手!

好啦,今天的WebXR小课堂就到这里,希望大家有所收获。如果有什么问题,欢迎随时提问!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注