mcpField
T
threejs-impl-xr

threejs-impl-xr

github.com/impertio-studio开发23129 次安装30 周前更新
代码仓库

关于 threejs-impl-xr

这是面向 Three.js 开发者的 WebXR 实现参考,适用于构建浏览器中的 VR 和 AR 场景。内容涵盖会话设置、控制器、手部追踪、命中测试、移动方式和性能要点。

主要功能

  1. 01WebXR 会话配置与参考空间
  2. 02VR 和 AR 场景初始化
  3. 03控制器与手部追踪
  4. 04AR 表面命中测试
  5. 05XR 渲染性能优化

适用场景

  1. 01为 Three.js 场景添加 VR 会话
  2. 02配置 AR 会话并放置虚拟物体
  3. 03实现控制器或手势交互
  4. 04调整 XR 场景的帧率与渲染开销

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/impertio-studio/three.js-claude-skill-package --skill threejs-impl-xr

安装后在 Claude Code 中输入/threejs-impl-xr确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
anti-patterns.md
examples.md
methods.md

详细介绍

threejs-impl-xr

快速参考

WebXRManager 属性
属性类型默认值描述
enabledbooleanfalse启用 XR 渲染
isPresentingboolean只读XR 会话是否处于活动状态
cameraAutoUpdatebooleantrue根据 XR 设备姿态自动更新相机
WebXRManager 方法
方法签名描述
getSession()() => XRSession | null当前 XR 会话
setSessionInit(options)(XRSessionInit) => void进入会话前配置会话功能
setReferenceSpaceType(type)(string) => void设置参考空间类型
getController(index)(number) => Group获取控制器目标射线空间
getControllerGrip(index)(number) => Group获取控制器握持空间
getHand(index)(number) => Group获取手部追踪组
setFoveation(level)(number) => void设置注视点渲染(0.0–1.0)
getFoveation()() => number获取当前注视点渲染级别
getEnvironmentBlendMode()() => string获取混合模式(不透明、叠加、Alpha 混合)
setFramebufferScaleFactor(scale)(number) => void调整 XR 渲染分辨率
会话类型
类型用例
'immersive-vr'完整的 VR 头显体验
'immersive-ar'头显或手机上的 AR 透视
'inline'浏览器窗口中的非沉浸式 XR
参考空间类型
类型原点用例
'viewer'头部位置HUD 元素、视线锁定 UI
'local'初始头部位置坐姿体验
'local-floor'起始时的地面高度站立式 VR,房间规模体验始终优先使用
'bounded-floor'带边界的地面带安全边界的房间规模体验
'unbounded'世界原点大范围 AR 体验
XR 附加组件类
类导入路径用途
VRButtonthree/addons/webxr/VRButton.js创建带功能检测的“进入 VR”按钮
ARButtonthree/addons/webxr/ARButton.js创建带功能检测的“进入 AR”按钮
XRControllerModelFactorythree/addons/webxr/XRControllerModelFactory.js加载适用的控制器 3D 模型
XRHandModelFactorythree/addons/webxr/XRHandModelFactory.js创建手部追踪可视化
XRHandPrimitiveModelthree/addons/webxr/XRHandPrimitiveModel.js简单的几何手部表示
XREstimatedLightthree/addons/webxr/XREstimatedLight.jsAR 环境光照估算
XRPlanesthree/addons/webxr/XRPlanes.jsAR 平面检测可视化
重要警告

绝不要使用 requestAnimationFrame() 进行 XR 渲染——始终使用 renderer.setAnimationLoop()。WebXR API 需要使用自己的帧计时;XR 会话处于活动状态时,requestAnimationFrame 会停止触发。

绝不要在 VR 中应用繁重的后期处理——每种效果都会运行两次(每只眼睛各一次),使 GPU 开销翻倍。丢帧会导致晕动症。

始终以 72fps(Quest)或 90fps(PC VR)为目标——丢帧会导致恶心和不适。更低的目标帧率不可接受。

始终在创建 VRButton/ARButton 之前设置 renderer.xr.enabled = true——按钮会检查此属性以检测功能。

绝不要忘记将控制器添加到场景中——getController() 会返回一个 Group,必须通过 scene.add() 添加,否则它不会渲染或触发事件。


VR 设置

最简 VR 场景
import * as THREE from 'three';
import { VRButton } from 'three/addons/webxr/VRButton.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);

// Enable XR BEFORE creating VRButton
renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType('local-floor');
document.body.appendChild(VRButton.createButton(renderer));

// MUST use setAnimationLoop — NEVER requestAnimationFrame
renderer.setAnimationLoop((time, frame) => {
  renderer.render(scene, camera);
});
AR 设置
import * as THREE from 'three';
import { ARButton } from 'three/addons/webxr/ARButton.js';

const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.xr.enabled = true;

// Configure AR features BEFORE creating ARButton
renderer.xr.setSessionInit({
  requiredFeatures: ['hit-test'],
  optionalFeatures: ['dom-overlay'],
  domOverlay: { root: document.getElementById('overlay') }
});

document.body.appendChild(ARButton.createButton(renderer, {
  requiredFeatures: ['hit-test']
}));

renderer.setAnimationLoop((time, frame) => {
  renderer.render(scene, camera);
});

控制器

控制器空间

Three.js 为每个物理控制器提供了三个不同的空间:

方法空间使用场景
getController(index)目标射线指向方向、激光指示器
getControllerGrip(index)握持空间手握住控制器的位置
getHand(index)手部完整的手部追踪骨架

始终将所有用到的空间添加到场景中。每个方法都会返回一个 THREE.Group。

控制器事件
事件触发条件
selectstart按下主扳机
selectend松开主扳机
select按下并松开主扳机
squeezestart按下握持按钮
squeezeend松开握持按钮
squeeze按下并松开握持按钮
connected检测到控制器(event.data = XRInputSource)
disconnected控制器断开
控制器模型
import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';

const factory = new XRControllerModelFactory();

// Target ray space — for laser pointer / interaction ray
const controller0 = renderer.xr.getController(0);
controller0.addEventListener('selectstart', onSelectStart);
controller0.addEventListener('selectend', onSelectEnd);
scene.add(controller0);

// Grip space — for rendering the controller model
const grip0 = renderer.xr.getControllerGrip(0);
grip0.add(factory.createControllerModel(grip0));
scene.add(grip0);
激光指示器视觉效果
const geometry = new THREE.BufferGeometry().setFromPoints([
  new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(0, 0, -5)
]);
const material = new THREE.LineBasicMaterial({ color: 0xffffff });
const line = new THREE.Line(geometry, material);
controller0.add(line);

手部追踪

import { XRHandModelFactory } from 'three/addons/webxr/XRHandModelFactory.js';

// Request hand-tracking feature
renderer.xr.setSessionInit({
  optionalFeatures: ['hand-tracking']
});

const handFactory = new XRHandModelFactory();

const hand0 = renderer.xr.getHand(0);
hand0.add(handFactory.createHandModel(hand0, 'mesh'));
scene.add(hand0);

const hand1 = renderer.xr.getHand(1);
hand1.add(handFactory.createHandModel(hand1, 'mesh'));
scene.add(hand1);

手部模型配置: 'mesh'(逼真模型)、'spheres'(关节球体)、'boxes'(关节方块)。

手部组会触发以下事件:

  • connected — 检测到手部
  • disconnected — 手部断开
  • pinchstart / pinchend — 拇指与食指捏合手势

AR 命中测试

命中测试可将虚拟对象放置在真实世界的表面上。

let hitTestSource = null;
let hitTestSourceRequested = false;
const reticle = new THREE.Mesh(
  new THREE.RingGeometry(0.15, 0.2, 32).rotateX(-Math.PI / 2),
  new THREE.MeshBasicMaterial()
);
reticle.matrixAutoUpdate = false;
reticle.visible = false;
scene.add(reticle);

renderer.setAnimationLoop((time, frame) => {
  if (frame) {
    const session = renderer.xr.getSession();
    const referenceSpace = renderer.xr.getReferenceSpace();

    if (!hitTestSourceRequested) {
      session.requestReferenceSpace('viewer').then((viewerSpace) => {
        session.requestHitTestSource({ space: viewerSpace }).then((source) => {
          hitTestSource = source;
        });
      });
      hitTestSourceRequested = true;
    }

    if (hitTestSource) {
      const results = frame.getHitTestResults(hitTestSource);
      if (results.length > 0) {
        const pose = results[0].getPose(referenceSpace);
        reticle.visible = true;
        reticle.matrix.fromArray(pose.transform.matrix);
      } else {
        reticle.visible = false;
      }
    }
  }
  renderer.render(scene, camera);
});

传送模式

const tempMatrix = new THREE.Matrix4();
const raycaster = new THREE.Raycaster();
const marker = new THREE.Mesh(
  new THREE.CircleGeometry(0.25, 32).rotateX(-Math.PI / 2),
  new THREE.MeshBasicMaterial({ color: 0x00ff00 })
);
scene.add(marker);

const controller = renderer.xr.getController(0);
controller.addEventListener('selectend', () => {
  tempMatrix.identity().extractRotation(controller.matrixWorld);
  raycaster.ray.origin.setFromMatrixPosition(controller.matrixWorld);
  raycaster.ray.direction.set(0, 0, -1).applyMatrix4(tempMatrix);

  const intersects = raycaster.intersectObject(floor);
  if (intersects.length > 0) {
    const point = intersects[0].point;
    // Move the XR camera rig, NOT the camera directly
    cameraRig.position.set(point.x, 0, point.z);
  }
});
scene.add(controller);

始终移动包含摄像机的摄像机架组,绝不要直接移动摄像机——WebXR API 会控制摄像机相对于其父级的位置。


VR 性能

目标帧率
平台目标 FPS备注
Meta Quest 2/372–120 fps默认 72,可选 90/120
PC VR (SteamVR)90 fps标准目标
PSVR290–120 fps取决于平台
优化技巧
  1. 注视点渲染 — 使用 renderer.xr.setFoveation(1.0) 以获得最高性能。范围为 0.0(无)到 1.0(最高)。
  2. 帧缓冲区缩放 — 在 GPU 受限时,使用 renderer.xr.setFramebufferScaleFactor(0.75) 降低分辨率。
  3. 尽量减少绘制调用 — 对重复对象使用 THREE.InstancedMesh。目标是少于 100 次绘制调用。
  4. 避免后期处理 — 每种效果在立体渲染中都会渲染两次。尽可能移除 bloom、SSAO 和抗锯齿通道。
  5. 使用烘焙光照 — 实时阴影的开销会翻倍。尽可能预先烘焙。
  6. LOD(细节层次) — 使用 THREE.LOD 降低远处对象的多边形数量。
  7. 纹理压缩 — 使用 KTX2/Basis 纹理以减少 GPU 内存占用。
摄像机架模式

VR 移动始终使用摄像机架组:

const cameraRig = new THREE.Group();
cameraRig.add(camera);
scene.add(cameraRig);

// Move the rig, not the camera
cameraRig.position.set(0, 0, 5);

WebXR API 每帧都会设置摄像机相对于其父级的位置和旋转。直接移动摄像机会立即被覆盖。


XR 估算光照(AR)

import { XREstimatedLight } from 'three/addons/webxr/XREstimatedLight.js';

const xrLight = new XREstimatedLight(renderer);
xrLight.addEventListener('estimationstart', () => {
  scene.add(xrLight);
  scene.environment = xrLight.environment;
  // Remove default lights
});
xrLight.addEventListener('estimationend', () => {
  scene.remove(xrLight);
  scene.environment = null;
  // Restore default lights
});

参考链接

官方来源

评论

支持 Markdown