一、为什么这三个 Behavior 是 MR 的「三位一体」?
| Behavior | 在 MR 场景里的作用 | 比喻 |
|---|---|---|
| SurfaceMagnetismBehavior | 把物体「粘」在墙上、桌面、地板 | 磁铁 |
| FollowBehavior | UI 永远漂在你眼前,不挡视线 | 无人机 |
| HandConstraintBehavior | 工具面板长在手掌上,随叫随到 | 手表 |
HoloLens 3 和 Quest 3 的 MR 模式里,所有成功应用都在用这三件套:
-
Follow 管「看」------信息总在视野黄金区;
-
HandConstraint 管「调」------按钮在掌心,抬手就操作;
-
SurfaceMagnetism 管「放」------模型往墙上一扔,自动贴平。
三者组合 = 零代码实现「看-调-放」完整闭环。
二、三件套快速画像
TypeScript
import {
SurfaceMagnetismBehavior, FollowBehavior, HandConstraintBehavior,
WebXRHandTracking, WebXRFeaturesManager
} from '@babylonjs/core';
// 1. 吸附:往地板/墙上一扔,自动对齐
const magnet = new SurfaceMagnetismBehavior();
magnet.target = floorMesh; // 只吸附到 floor
magnet.rotationAlignment = SurfaceMagnetismBehavior.ROTATION_ALIGNMENT.ALIGN_TO_SURFACE_NORMAL; // 法线对齐
// 2. 跟随:UI 面板永远在你眼前 1.5 米
const follow = new FollowBehavior();
follow.defaultDistance = 1.5;
follow.pitchOffset = -10; // 略往下看 10°,不挡脸
follow.maximumVerticalViewOffset = 30; // 低头 30° 内才跟,太高就不跟
// 3. 手掌:工具栏长在左手背上
const hand = new HandConstraintBehavior();
hand.hand = WebXRHandTracking.HANDEDNESS.LEFT;
hand.handConstraint = HandConstraintBehavior.HAND_CONSTRAINT.PALM; // 掌心朝上
hand.handConstraintZone = HandConstraintBehavior.HAND_CONSTRAINT_ZONE.WRIST; // 手腕区
三、实战:一个「MR 家具摆放」完整场景
需求:用户左手抬起来出现「家具菜单」,右手抓取「沙发」模型,往墙上一放,自动贴墙。
3.1 初始化三件套
TypeScript
export class MRManager {
private xr: WebXRDefaultExperience;
private handTracker: WebXRHand;
private toolPalette: Mesh; // 左手 UI
private selectedModel: Mesh | null = null;
async initAsync(scene: Scene) {
this.xr = await scene.createDefaultXRExperienceAsync({
handTracking: { enabled: true } // 必须开手势追踪
});
this.handTracker = this.xr.baseExperience.featuresManager.enableFeature(
WebXRFeatureName.HAND_TRACKING
) as WebXRHand;
// 1. 左手掌 UI
this.toolPalette = this._createToolPalette();
const handBhv = new HandConstraintBehavior();
handBhv.handConstraint = HandConstraintBehavior.HAND_CONSTRAINT.PALM;
handBhv.handConstraintZone = HandConstraintBehavior.HAND_CONSTRAINT_ZONE.WRIST;
this.toolPalette.addBehavior(handBhv);
// 2. 选中模型的跟随(抓取前)
this.followBhv = new FollowBehavior();
this.followBhv.defaultDistance = 0.5; // 半米距离,方便对准
this.followBhv.lerpTime = 0.05; // 平滑系数
}
}
3.2 右手抓取 → 模型到手 → 关闭吸附
TypeScript
private _onRightGrab(mesh: Mesh) {
// 模型到手,不再跟随相机,改跟右手
mesh.removeBehavior(this.followBhv);
const sixDoF = new SixDoFDragBehavior();
sixDoF.allowMultiPointer = true;
mesh.addBehavior(sixDoF);
// 监听释放
sixDoF.onDragEndObservable.add(() => {
// 释放瞬间:如果靠近墙,就吸附;否则继续跟随
const wall = this._getNearestWall(mesh);
if (wall) {
sixDoF.detach();
mesh.addBehavior(this._createMagnet(wall)); // 见下节
} else {
sixDoF.detach();
mesh.addBehavior(this.followBhv); // 回到跟随
}
});
}
3.3 吸附到墙:最后一次抓放的关键
TypeScript
private _createMagnet(targetWall: Mesh): SurfaceMagnetismBehavior {
const magnet = new SurfaceMagnetismBehavior();
magnet.target = targetWall;
magnet.rotationAlignment = SurfaceMagnetismBehavior.ROTATION_ALIGNMENT.ALIGN_TO_SURFACE_NORMAL;
// 高级参数:别贴太近,留 2cm 空隙
magnet.surfaceDistance = 0.02;
magnet.surfaceNormalOffset = 0.01;
// 只响应一次:贴完就自毁,避免误触发
magnet.onMagnetizeObservable.addOnce(() => {
magnet.detach(); // 使命完成,自己卸载
});
return magnet;
}
四、避坑指南:MR 独有的陷阱
4.1 坐标系混乱:掌心的"上"不是世界的"上"
HandConstraintBehavior 的 handConstraintZone 五个值:
| 值 | 在掌心坐标系的位置 | 适合放什么 |
|---|---|---|
WRIST |
手腕,Z 轴指向手指 | 工具栏 |
THUMB |
拇指根 | 返回按钮 |
INDEX |
食指根 | 射线指示器 |
MIDDLE |
中指根 | 抓取提示 |
RING |
无名指 | 状态灯 |
坑 :toolPalette.lookAt(camera.position) 会歪,因为掌心坐标系是局部空间 。
解 :用 toolPalette.setParent(handTracker.joints[0]) 再调局部坐标。
4.2 深度冲突:UI 和墙面 Z-fighting
FollowBehavior 的 farDistance = 1.5 可能和墙面重叠。
解:开深度测试或调 shader:
TypeScript
toolPalette.material.depthFunction = BABYLON.Constants.ALWAYS; // UI 永远画在最上
4.3 性能:手势追踪 30fps,渲染 90fps
手势数据是 30fps 刷新,但 XR 要求 90fps。
解 :所有 Behavior 内部自带 lerpTime,别设成 0,让肉眼平滑过渡。
五、彩蛋:把「三件套」做成「MR 设计系统」
TypeScript
// 任何 Mesh 只要加这一行,自动变成「可抓、可贴、可跟随」
mesh.metadata = { mrType: 'grabbable' };
// 场景加载后统一扫描
scene.meshes
.filter(m => m.metadata?.mrType === 'grabbable')
.forEach(m => MRManager.makeGrabbable(m));
MRManager.makeGrabbable 内部就是 3.1~3.3 的完整逻辑。
从此设计师导出 GLB 只要在 metadata 里写一行 JSON,程序零改动。
六、一行总结
SurfaceMagnetism(贴)+ Follow(看)+ HandConstraint(控)= MR 交互黄金三角
Quest 3 上实测:从空场景到「左手菜单、右手拖拽、墙面吸附」完整体验,87 行代码 。
下次再看到 HoloLens 的宣传片,你可以直接说:"这玩意儿,我上午就写完了。"