CSS `WebXR Device API` 姿态数据驱动 `CSS 3D Transforms` 实现 AR UI

各位朋友,大家好!欢迎来到今天的“WebXR与CSS 3D奇妙夜”!今天咱们要聊点儿酷炫的,那就是用WebXR Device API获取到的姿态数据,来驱动CSS 3D Transforms,最终实现AR UI!听起来是不是有点儿绕?别怕,我会用最通俗易懂的方式,带你一步步踏入这个充满魔力的领域。

先来个开场白:想象一下,你戴着AR眼镜,眼前浮现出一个漂浮的按钮,你伸手一点,它就乖乖地响应了。是不是很像科幻电影里的场景?现在,咱们就要用Web技术,把这个幻想变成现实!

第一幕:WebXR Device API —— 姿态数据的宝库

WebXR Device API是咱们通往AR/VR世界的钥匙。它提供了访问XR硬件(比如AR眼镜、VR头显)的能力,让我们能够获取设备的位置、方向等信息。简单来说,它就是个“线人”,时刻汇报着设备的“行踪”。

1.1 获取XR会话 (XR Session)

首先,我们需要请求一个XR会话。这就像跟设备说:“嘿,哥们儿,我想跟你玩一会儿,能授权一下吗?”

navigator.xr.isSessionSupported('immersive-ar')
  .then((supported) => {
    if (supported) {
      navigator.xr.requestSession('immersive-ar', {
        requiredFeatures: ['local-floor', 'hit-test'] // 重点:需要hit-test!
      }).then(onSessionStarted).catch(onSessionError);
    } else {
      console.log("AR is not supported");
    }
  });

let xrSession = null;

function onSessionStarted(session) {
  xrSession = session;
  console.log("XR Session started!");

  // 设置会话结束时的处理函数
  xrSession.addEventListener('end', onSessionEnded);

  // 获取 WebGL 上下文
  const canvas = document.createElement('canvas');
  document.body.appendChild(canvas);
  gl = canvas.getContext('webgl', {xrCompatible: true});

  // 设置 WebGL 渲染状态
  xrSession.updateRenderState({ baseLayer: new XRWebGLLayer(xrSession, gl) });

  // 创建引用空间
  xrSession.requestReferenceSpace('local').then((refSpace) => {
    xrRefSpace = refSpace;
    xrSession.requestAnimationFrame(render); // 开始渲染循环
  });
}

function onSessionEnded(event) {
    xrSession = null;
    console.log("XR Session ended!");
}

function onSessionError(error) {
    console.error("XR Session error:", error);
}

这段代码做了几件事:

  • navigator.xr.isSessionSupported('immersive-ar'): 检查浏览器是否支持AR模式。
  • navigator.xr.requestSession('immersive-ar', {requiredFeatures: ['local-floor', 'hit-test']}): 请求一个AR会话。注意 requiredFeatures,这里我们请求了 local-floorhit-test 这两个重要的特性。 local-floor用于定义一个本地坐标系,而hit-test则是实现AR交互的关键!
  • xrSession.requestReferenceSpace('local'): 请求一个参考空间。local表示本地坐标系,相对于设备原点。
  • xrSession.requestAnimationFrame(render): 开始渲染循环,render函数会在每一帧被调用。

1.2 获取姿态数据 (Pose Data)

在渲染循环中,我们就可以从XR会话中获取到设备的姿态数据了。

let gl = null;
let xrRefSpace = null;

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

  xrSession.requestAnimationFrame(render);

  const pose = frame.getViewerPose(xrRefSpace);

  if (pose) {
    const glLayer = xrSession.renderState.baseLayer;
    gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // 获取视图信息
    const view = pose.views[0];

    // 获取投影矩阵和视图矩阵
    const projectionMatrix = view.projectionMatrix;
    const viewMatrix = view.transform.inverse.matrix;

    // 在这里进行WebGL渲染,使用投影矩阵和视图矩阵

    // 获取姿态数据
    const position = pose.transform.position;
    const orientation = pose.transform.orientation;

    // 将姿态数据传递给CSS 3D元素
    updateCSS3DObject(position, orientation);
  }
}

这段代码的关键在于frame.getViewerPose(xrRefSpace),它返回一个XRViewerPose对象,包含了设备的位置(position)和方向(orientation)。

  • pose.transform.position: 设备在参考空间中的位置,是一个DOMPointReadOnly对象,包含xyz坐标。
  • pose.transform.orientation: 设备在参考空间中的方向,是一个DOMQuaternionReadOnly对象,包含xyzw四个分量,表示一个四元数。

第二幕:CSS 3D Transforms —— 舞台搭建

有了姿态数据,接下来就要用CSS 3D Transforms来搭建我们的AR UI舞台了。CSS 3D Transforms允许我们在三维空间中对HTML元素进行变换,包括平移、旋转、缩放等。

2.1 创建3D场景

首先,我们需要创建一个包含3D元素的HTML结构。

<div id="ar-container">
  <div id="ar-object">
    <button>Click Me!</button>
  </div>
</div>

然后,我们需要设置一些CSS样式,让这个结构呈现出3D效果。

#ar-container {
  position: fixed; /* 或者 absolute,取决于你的需求 */
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  perspective: 800px; /* 模拟相机距离 */
  transform-style: preserve-3d; /* 关键:开启3D空间 */
  pointer-events: none; /* 防止遮挡底层元素 */
}

#ar-object {
  position: absolute;
  width: 200px;
  height: 100px;
  background-color: rgba(255, 0, 0, 0.5);
  color: white;
  text-align: center;
  line-height: 100px;
  pointer-events: auto; /* 允许点击 */
}
  • perspective: 模拟相机距离,影响3D效果的强烈程度。
  • transform-style: preserve-3d: 关键属性!开启3D空间,让子元素也参与3D变换。
  • pointer-events: none: 防止#ar-container遮挡底层的HTML元素,影响交互。
  • pointer-events: auto: 允许#ar-object接收点击事件。

2.2 驱动3D变换

现在,我们可以编写JavaScript代码,将WebXR获取到的姿态数据应用到CSS 3D元素上。

const arContainer = document.getElementById('ar-container');
const arObject = document.getElementById('ar-object');

function updateCSS3DObject(position, orientation) {
  // 将位置转换为字符串
  const translateX = position.x.toFixed(3);
  const translateY = position.y.toFixed(3);
  const translateZ = position.z.toFixed(3);

  // 将四元数转换为旋转矩阵
  const quaternion = new THREE.Quaternion(orientation.x, orientation.y, orientation.z, orientation.w);
  const rotationMatrix = new THREE.Matrix4();
  rotationMatrix.makeRotationFromQuaternion(quaternion);

  // 从旋转矩阵中提取欧拉角(弧度)
  const euler = new THREE.Euler();
  euler.setFromRotationMatrix(rotationMatrix);

  // 将弧度转换为角度
  const rotateX = THREE.MathUtils.radToDeg(euler.x).toFixed(3);
  const rotateY = THREE.MathUtils.radToDeg(euler.y).toFixed(3);
  const rotateZ = THREE.MathUtils.radToDeg(euler.z).toFixed(3);

  // 构建CSS transform字符串
  const transform = `translate3d(${translateX}m, ${translateY}m, ${translateZ}m) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`;

  // 应用transform
  arObject.style.transform = transform;
}

这段代码做了以下几件事:

  1. 引入了Three.js: 因为四元数到欧拉角的转换需要用到矩阵运算,这里我们引入了Three.js库来简化计算。当然,你也可以自己实现矩阵运算,但是会比较麻烦。Three.js是一个强大的3D图形库,可以帮助我们处理各种3D相关的计算。需要在HTML中引入Three.js库:

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
  2. 将位置转换为字符串: 将position.xposition.yposition.z转换为字符串,并添加单位m(米)。

  3. 将四元数转换为旋转矩阵: 使用Three.js的THREE.QuaternionTHREE.Matrix4将四元数转换为旋转矩阵。

  4. 从旋转矩阵中提取欧拉角: 使用Three.js的THREE.Euler从旋转矩阵中提取欧拉角。

  5. 将弧度转换为角度: 使用Three.js的THREE.MathUtils.radToDeg将欧拉角的弧度值转换为角度值。

  6. 构建CSS transform字符串: 将平移和旋转的字符串拼接起来,形成完整的CSS transform属性值。

  7. 应用transform: 将transform属性应用到#ar-object元素上。

第三幕:Hit Test —— 交互的灵魂

仅仅让UI元素跟着设备动还不够,我们还需要让UI元素能够与真实世界交互。这就需要用到Hit Test API了。Hit Test API允许我们检测从设备发出的射线与真实世界的相交情况,从而确定UI元素应该放置在哪里。

3.1 发起Hit Test

let hitTestSource = null;
let xrHitPose = null; // 保存hit test的结果

xrSession.requestReferenceSpace('viewer').then((viewerSpace) => {
    xrSession.requestHitTestSource({ space: viewerSpace }).then((source) => {
        hitTestSource = source;
    });
});

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

    if (hitTestSource) {
        const hitTestResults = frame.getHitTestResults(hitTestSource);

        if (hitTestResults.length > 0) {
            const hit = hitTestResults[0];
            const hitPose = hit.getPose(xrRefSpace);

            if (hitPose) {
              xrHitPose = hitPose;
              // 更新UI位置
              updateCSS3DObject(hitPose.transform.position, hitPose.transform.orientation);
            }

        }
    }
}

这段代码做了以下几件事:

  • xrSession.requestHitTestSource({ space: viewerSpace }): 请求一个Hit Test源。viewerSpace表示观察者视角下的参考空间。
  • frame.getHitTestResults(hitTestSource): 获取Hit Test结果。如果射线与真实世界相交,会返回一个包含相交信息的数组。
  • hit.getPose(xrRefSpace): 从Hit Test结果中获取相交点的位置和方向。
  • updateCSS3DObject(hitPose.transform.position, hitPose.transform.orientation): 将相交点的位置和方向应用到CSS 3D元素上,使其放置在真实世界中。

3.2 根据Hit Test 结果调整UI

有了Hit Test的结果,我们就可以将UI元素放置在真实世界的表面上。例如,我们可以将一个按钮放置在桌面上,或者墙壁上。

现在,arObject将会跟随Hit Test的结果移动,看起来就像是真实地放置在了AR环境中。

第四幕:优化与进阶

有了基本的功能,我们还可以对代码进行优化和进阶,使其更加完善和强大。

4.1 性能优化

  • 减少DOM操作: 频繁的DOM操作会影响性能,尽量减少对arObject.style.transform的赋值次数。
  • 使用requestAnimationFrame: 确保渲染循环在每一帧都执行,避免卡顿。
  • 使用Web Workers: 将复杂的计算放在Web Workers中执行,避免阻塞主线程。

4.2 交互增强

  • 手势识别: 使用手势识别库,例如Hammer.js,实现更自然的交互方式。
  • 物理引擎: 使用物理引擎,例如Cannon.js,模拟真实的物理效果。
  • 遮挡处理: 使用WebGL渲染遮挡,使UI元素能够被真实世界的物体遮挡。

4.3 错误处理

  • 检查WebXR支持: 在使用WebXR API之前,先检查浏览器是否支持WebXR。
  • 处理会话错误: 监听session.onend事件,处理会话结束的情况。
  • 处理Hit Test错误: 处理Hit Test失败的情况,例如没有找到相交点。

总结

今天我们一起探索了如何使用WebXR Device API获取姿态数据,并用CSS 3D Transforms来构建AR UI。希望这次“WebXR与CSS 3D奇妙夜”能给你带来启发,让你在AR的世界里自由驰骋!

下面是一个简单的表格,总结了我们今天讲到的关键技术点:

技术 描述 关键代码/属性
WebXR Device API 用于访问XR硬件,获取设备的位置和方向信息。 navigator.xr.requestSession('immersive-ar', {requiredFeatures: ['local-floor', 'hit-test']})

发表回复

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