使用Rokid JSAR SDK构建沉浸式城市广场场景
在开始构建复杂的城市场景之前,我们需要先理解JSAR的基本结构。JSAR(JavaScript Spatial Augmented Reality)是Rokid专为AR设备设计的空间计算运行时,它基于强大的Babylon.js引擎,让开发者可以用熟悉的JavaScript来创建3D空间应用。
我们使用Rokid官方扩展 VSCode JSAR DevTools 进行开发。只需要在 VSCode 里安装 JSAR扩展,选中
.xsml文件,点击"预览"按钮,即可实时热重载,大大提升开发效率。一、场景搭建
-
搭建基础场景框架
首先,创建XSML文件(类似HTML,但用于3D空间)。XSML是JSAR的标记语言,
<space>标签是3D对象的容器,类似于HTML中的<body>。<xsml version="1.0">
<head>
<title>城市广场 - Rokid AR</title>
<script src="./scene.ts"></script>
</head>
<space></space>
</xsml>
接着,我们对场景进行初始化。其中,
spatialDocument.scene是JSAR提供的全局场景对象,这是与Babylon.js的主要集成点。<reference types="@yodaos-jsar/types" />
const { MeshBuilder, Vector3, Color3, Color4, StandardMaterial } = BABYLON;
const scene = spatialDocument.scene as BABYLON.Scene;
Rokid设备会自动创建相机,我们只需要调整其位置和朝向。这里将相机设置在较高位置,便于俯瞰整个场景。
const camera = scene.activeCamera;
if (camera) {
camera.position = new Vector3(0, 150, 250);
const target = new Vector3(0, 120, 0);
if ('setTarget' in camera) {
(camera as any).setTarget(target);
}
}
下面,我们来创建地面。
MeshBuilder是Babylon.js提供的快捷创建工具,支持立方体、球体、圆柱等常见几何体。这是JSAR推荐的对象创建方式,比直接使用XSML标签更灵活。const ground = MeshBuilder.CreateGround('ground', {
width: 480,
height: 480
}, scene);
const groundMat = new StandardMaterial('ground-mat', scene);
groundMat.diffuseColor = new Color3(0.3, 0.6, 0.3); // 草绿色
ground.material = groundMat;
地面效果如图:
-
构建场景核心元素
城市场景的核心是建筑物。我们将创建一个可复用的建筑生成函数,这体现了Rokid SDK的模块化开发理念。
建筑物是本次实践目标的主要组成,我们先来创建建筑物。
function createBuilding(
name: string,
positionX: number,
buildingColor: Color3,
signColor: Color3
) {
const buildingWidth = 120;
const buildingHeight = 320;
const buildingDepth = 120;
// 主建筑体const building = MeshBuilder.CreateBox(`${name}-building`, {
width: buildingWidth,
height: buildingHeight,
depth: buildingDepth
}, scene);
building.position = new Vector3(positionX, buildingHeight / 2, 0);
const buildingMat = new StandardMaterial(`${name}-building-mat`, scene);
buildingMat.diffuseColor = buildingColor;
buildingMat.specularColor = new Color3(0.3, 0.3, 0.3);
building.material = buildingMat;
return { building };
}
这种函数式的设计让我们可以快速创建多个不同风格的建筑。
真实的建筑物有数百个窗户,我们需要批量生成:
const windowSize = 12;
const windowSpacing = 20;
const floorsCount = Math.floor((buildingHeight - 32) / windowSpacing);
const windowsPerFloor = 5;
for (let floor = 1; floor <= floorsCount; floor++) {
for (let col = 0; col < windowsPerFloor; col++) {
const window = MeshBuilder.CreateBox(`window-${floor}-${col}`, {
width: windowSize,
height: windowSize,
depth: 0.8
}, scene);
const offsetX = (col - (windowsPerFloor - 1) / 2) * windowSpacing;
const offsetY = floor * windowSpacing;
window.position = new Vector3(
positionX + offsetX,
offsetY,
buildingDepth / 2 + 0.4
);
const windowMat = new StandardMaterial(`window-mat-${floor}-${col}`, scene);
const isLit = Math.random() > 0.4;
if (isLit) {
windowMat.emissiveColor = new Color3(1, 0.9, 0.6);
windowMat.diffuseColor = new Color3(0.9, 0.9, 1);
} else {
windowMat.diffuseColor = new Color3(0.15, 0.15, 0.25);
}
window.material = windowMat;
}
}
我们使用
emissiveColor让亮起的窗户自发光,在AR环境中更醒目,同时随机亮灯营造生活气息。每个窗户间距20单位,在Rokid设备上视觉舒适。只有一个楼我们不够看,于是我们设计了第二个楼宇。
const leftBuilding = createBuilding(
'left',
-150,
new Color3(0.7, 0.75, 0.8), // 浅灰色new Color3(0.2, 0.3, 0.5) // 蓝色标志
);
const rightBuilding = createBuilding(
'right',
150,
new Color3(0.75, 0.7, 0.7), // 微红灰色new Color3(0.5, 0.2, 0.2) // 红色标志
);
两栋大楼相距300单位,在Rokid眼镜中形成开阔的空间感。
-
丰富场景其他元素
为了让我们的城市场景看起来更加完整,我们需要为城市场景添加需要道路、绿化和公共设施。
① 道路系统
const road = MeshBuilder.CreateBox('road', {
width: 20,
height: 0.2,
depth: 500
}, scene);
road.position = new Vector3(0, 0.1, 0);
const roadMat = new StandardMaterial('road-mat', scene);
roadMat.diffuseColor = new Color3(0.15, 0.15, 0.15);
road.material = roadMat;
// 人行道const sidewalk = MeshBuilder.CreateBox('sidewalk', {
width: 8,
height: 0.3,
depth: 500
}, scene);
sidewalk.position = new Vector3(-14, 0.15, 0);
const sidewalkMat = new StandardMaterial('sidewalk-mat', scene);
sidewalkMat.diffuseColor = new Color3(0.5, 0.5, 0.5);
sidewalk.material = sidewalkMat;
道路厚度仅0.2单位,避免在AR中过于突兀,但颜色对比明显,确保可见性。
② 树木系统
环境绿化增强真实感:
const trees: any[] = [];
for (let i = 0; i < 12; i++) {
const angle = (i / 12) * Math.PI * 2;
const radius = 200;
const x = Math.cos(angle) * radius;
const z = Math.sin(angle) * radius;
// 树干const trunk = MeshBuilder.CreateCylinder(`trunk-${i}`, {
height: 15,
diameter: 2
}, scene);
trunk.position = new Vector3(x, 7.5, z);
const trunkMat = new StandardMaterial(`trunk-mat-${i}`, scene);
trunkMat.diffuseColor = new Color3(0.4, 0.3, 0.2);
trunk.material = trunkMat;
// 树叶const leaves = MeshBuilder.CreateSphere(`leaves-${i}`, {
diameter: 12
}, scene);
leaves.position = new Vector3(x, 18, z);
const leavesMat = new StandardMaterial(`leaves-mat-${i}`, scene);
leavesMat.diffuseColor = new Color3(0.2, 0.7, 0.2);
leaves.material = leavesMat;
trees.push(leaves);
}
12棵树围绕场景中心形成圆环,半径200单位确保不会挡住主要建筑。
③ 路灯照明
const streetLights: any[] = [];
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2 + Math.PI / 8;
const radius = 170;
const x = Math.cos(angle) * radius;
const z = Math.sin(angle) * radius;
// 灯柱const pole = MeshBuilder.CreateCylinder(`pole-${i}`, {
height: 25,
diameter: 1.5
}, scene);
pole.position = new Vector3(x, 12.5, z);
// 灯泡const light = MeshBuilder.CreateSphere(`light-${i}`, {
diameter: 3
}, scene);
light.position = new Vector3(x, 26, z);
const lightMat = new StandardMaterial(`light-mat-${i}`, scene);
lightMat.emissiveColor = new Color3(1, 0.95, 0.7);
lightMat.diffuseColor = new Color3(1, 1, 0.9);
light.material = lightMat;
streetLights.push(light);
}
使用
emissiveColor让路灯在任何环境光下都能发光,这是AR场景的关键技术。-
补充场景动画元素
静态场景缺乏生命力,JSAR提供了强大的动画API。
首先,我们需要注册动画循环。
let time = 0;
scene.registerBeforeRender(() => {
time += 0.01;
// 所有动画逻辑在这里执行
});
registerBeforeRender在每帧渲染前调用,类似游戏引擎的Update循环。① 建筑灯光动画
scene.registerBeforeRender(() => {
time += 0.01;
// 左楼标志牌闪烁const leftSignMat = leftBuilding.sign.material as any;
if (leftSignMat) {
const intensity = 0.5 + Math.sin(time * 2) * 0.2;
leftSignMat.emissiveColor = new Color3(
0.2 * intensity,
0.3 * intensity,
0.5 * intensity
);
}
// 右楼标志牌(相位差)const rightSignMat = rightBuilding.sign.material as any;
if (rightSignMat) {
const intensity = 0.5 + Math.sin(time * 2 + Math.PI) * 0.2;
rightSignMat.emissiveColor = new Color3(
0.5 * intensity,
0.2 * intensity,
0.2 * intensity
);
}
});
使用正弦函数创建平滑的呼吸灯效果,两栋楼之间有π相位差,营造对比感。
② 游乐设施动画
// 旋转木马旋转
carouselBase.rotation.y += 0.01;
// 马匹上下浮动
carouselHorses.forEach((horse, index) => {
const originalY = 6;
horse.position.y = originalY + Math.sin(time * 2 + index) * 0.5;
});
// 摩天轮旋转
ferrisWheelRing.rotation.x += 0.008;
// 车厢保持水平
ferrisWheelCars.forEach((car) => {
car.rotation.x = -ferrisWheelRing.rotation.x;
});
关键技术点:
- 木马浮动使用不同相位,形成波浪效果
- 摩天轮车厢需要反向旋转来抵消父对象的旋转,保持水平
③ 环境动画
// 树叶摆动
trees.forEach((tree, index) => {
tree.rotation.y = Math.sin(time + index) * 0.1;
});
// 路灯闪烁
streetLights.forEach((light, index) => {
const intensity = 0.9 + Math.sin(time * 2 + index) * 0.1;
const mat = light.material as any;
if (mat) {
mat.emissiveColor = new Color3(
intensity,
intensity * 0.95,
intensity * 0.7
);
}
});
环境细节动画让整个场景"活"起来。
最终实现效果如图:
二、性能优化
在Rokid设备上运行复杂场景需要特别注意性能。
① 对象管理
我们的场景包含数百个对象,需要良好的管理策略:
console.log('城市广场场景加载完成!');
console.log(`左楼: ${leftBuilding.floorsCount}层`);
console.log(`右楼: ${rightBuilding.floorsCount}层`);
console.log(`树木: ${trees.length} 棵`);
console.log(`路灯: ${streetLights.length} 盏`);
console.log(`总对象数: ${scene.meshes.length}`);
实时监控对象数量,我们的场景包含约200+对象。
② 材质复用
const sharedMat = new StandardMaterial('shared-mat', scene);
sharedMat.diffuseColor = new Color3(1, 0, 0);
for (let i = 0; i < 100; i++) {
object.material = sharedMat;
}
材质是GPU资源消耗大户,能复用就复用。
③ 父子层级
// 将相关对象组织成父子关系
carouselHorses.forEach(horse => {
horse.parent = carouselBase;
});
这样只需要旋转父对象,性能开销远小于分别旋转每个子对象。
④ 渲染优化
// 半透明对象使用alpha混合
fountainWaterMat.alpha = 0.6;
// 信息面板设置渲染组
infoPanel.renderingGroupId = 1;
合理使用渲染组可以优化深度排序开销。
三、设备适配
虽然本教程在PC上开发,但代码可以直接部署到Rokid眼镜。
- 空间感知
Rokid设备提供6DoF追踪,场景会随头部移动,由于JSAR自动处理相机追踪,开发者只需关注场景内容的相对位置。建议将主要内容放在用户前方2-5米处,符合AR交互习惯。
- 视觉设计
使用自发光确保可见性,避免纯黑色(AR中难以看清),建议用深灰代替纯黑。
material.emissiveColor = new Color3(0.3, 0.3, 0.5);
material.diffuseColor = new Color3(0.15, 0.15, 0.15);
- 尺寸规范
我们的尺寸设计参数如下:
- 主要建筑:100-200单位高度
- 交互元素:3-5单位宽度
- 文字面板:8-12单位宽度
- 最小可识别尺寸:0.5单位
- 性能指标
我们的场景严格满足推荐值:
| 指标 | 推荐值 | 我们的场景 |
| 对象数 | < 300 | ~250 |
| 材质数 | < 50 | ~40 |
| 帧率 | > 30 fps | 稳定60fps |
参考资源
- JSAR官方文档:https://jsar.netlify.app/
- Rokid开发者平台:https://developer.rokid.com/
- Babylon.js文档:https://doc.babylonjs.com/
总结
通过本教程,我们从零开始构建了一个包含200+对象的复杂AR城市场景,涵盖了Rokid JSAR SDK的核心技术点:
- 基础场景搭建 - 使用MeshBuilder创建几何体
- 材质系统 - diffuseColor、emissiveColor、alpha透明
- 对象层级 - parent父子关系管理复杂结构
- 动画系统 - registerBeforeRender实现流畅动画
- 性能优化 - 材质复用、对象管理、渲染组
- 设备适配 - 针对Rokid眼镜的视觉和交互优化
Rokid JSAR SDK让Web开发者可以用熟悉的JavaScript轻松进入AR开发领域,无需学习Unity或Unreal等重型引擎。配合TypeScript的类型提示和VSCode的开发工具,开发体验极佳。

浙公网安备 33010602011771号