WebARonARCore开发记录

WebARonARCore开发记录

之前是直接跑现成的demo,现在尝试自己一步步复现

由于WebXR Device API可能会经常变更,因此只有一小部分的开发版本的Chrome支持,可支持的版本为Chrome Canary/Dev versions 70-72.

先前已经设置好了浏览器的Flag

1. Install Web Server for Chrome,配置,具体如下:

a) 设置Web Server URL,类似默认的文件路径

b) 设置Automatically show index.html (浏览器就会直接展示所在文件夹的index.html的网页)

c) 通过STOP和RESTATRT按钮来控制服务器

d) Verify that at least one Web Server URL appears, 确保默认路径设置成功,默认本地URL为http://127.0.0.1:8887

2. 所在设备也要进行相应的设置

a) Go to chrome://inspect, 选择Port forwarding,添加8887端口

\3. 进入文件所在路径,加载index.html

State of AR on the web

最先实现Web XR Device API的是Chrome 67 (需要设置flag 为#webxr),初始化的支持AR的相关特性在Chrome 70+(需要设置flag为#webxr-hit-test)实现,不久后,所有支持WebVR的浏览器都将支持WebXR Device API。

现在WebXR Device API支持的AR特性很少,light estimation,surfaces,meshes等特性未来可能会支持。

回顾一下之前的方块demo:

image-20190426170811990

我们将在此基础上进一步改进

3. Add a cube to a surface

现在我们已经确认我们有实时视频输入,我们的设备姿势设置我们的相机的位置和方向,以及3D立方体渲染在顶部,是时候开始使用命中测试与现实世界交互。我们希望能够在现实世界中找到一个表面(例如在地面上),并在那里放置一个立方体。

Hit Test?

命中测试通常是从某个方向上的空间点投出直线并确定它是否与我们感兴趣的任何物体相交的方法。在我们的例子中,我们将点击我们AR的屏幕设备,所以想象一条光线从你的手指穿过你的设备,并直接进入你设备的相机所看到的物理世界。

WebXR Device API将告诉我们这条光线是否与现实世界中的任何物体相交,这些物体由基础AR功能和对世界的理解决定。如果我们通过我们的设备观察世界,稍微朝向地面倾斜,我们点击我们的手机,以便射线将撞击地面,我们应该期望发生碰撞的位置的响应。

img

设置场景

index.htmlwith ID中添加一个div,stabilization向表示稳定状态的用户显示动画,提示他们随身携带设备。这将在我们处于AR时显示,并在光罩找到表面后隐藏,通过<body>类控制。

1
2
3
4
5
  <div id="stabilization"></div>
<script src="../third_party/three.js/three.js"></script>
...
</body>
</html>

现在,app.js要做的第一件事是摆脱浮动立方体场景。在onSessionStarted功能中,用DemoUtils.createCubeScene()替换new THREE.Scene()

1
2
// this.scene = DemoUtils.createCubeScene();
this.scene = new THREE.Scene();

在创建场景之后,我们需要创建一个在碰撞过程中放置的对象。three.js中的可渲染对象表示为THREE.Mesh对象,其中包含几何和材质。这是创建几何体和材质的代码,以及调整立方体的变换以使其原点位于其底面。现在,只创建网格,将其转换一次,然后将其存储为this.model

1
2
3
4
const geometry = new THREE.BoxBufferGeometry(0.5, 0.5, 0.5);
const material = new THREE.MeshNormalMaterial();
geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.25, 0));
this.model = new THREE.Mesh(geometry, material);

onSessionStarted获取引用框架之后,我们在函数中添加构造函数。在onXRFrame函数中,调用reticle的update方法,并在'stabilized'Reticle找到一个表面后向body 添加一个类。这会隐藏稳定助手动画,提示用户移动他们的设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class App {
...
onSessionStarted(session) {
...
this.reticle = new Reticle(this.session, this.camera);
this.scene.add(this.reticle);

this.frameOfRef = await this.session.requestFrameOfReference('eye-level');
this.session.requestAnimationFrame(this.onXRFrame);
...
}
onXRFrame(time, frame) {
let session = frame.session;
let pose = frame.getDevicePose(this.frameOfRef);

this.reticle.update(this.frameOfRef);
if (this.reticle.visible && !this.stabilized) {
this.stabilized = true;
document.body.classList.add('stabilized');
}
...
}
}

我们现在只想在点击屏幕时执行命中测试,因此我们需要一个事件处理程序。

  • 在构造函数中,将单击处理程序绑定到实例。
  • 此外,在事件结束时绑定事件onSessionStarted,因为我们希望在添加事件之前确保浏览器支持所有内容。
  • 最后,为我们的新onClick处理程序添加一个空类方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class App {
constructor() {
...
this.onClick = this.onClick.bind(this);
}

onSessionStarted(session) {
...
this.reticle = new Reticle(this.session, this.camera);
this.scene.add(this.reticle);

window.addEventListener('click', this.onClick);
}

onClick(e) {
console.log('click!');
}
}

4. 触发命中测试

现在我们已经将模型和触发绑定到事件。XRSession的API需要一个原点,一个方向,以及我们之前创建的XRFrameOfReference。我们的原点是我们的设备在WebXR坐标系中的位置,方向是我们设备背面的矢量点。three.js有一些方便的函数用于从一个点投射出一个向量,所以让我们使用它们。我们可以利用来自three.js的raycaster为我们处理数学,这给了我们一个原点和向量作为THREE.Vector3s。x和y位置setFromCamera 是屏幕空间中的位置,此函数采用-1和1之间的标准化设备坐标中的值。我们只想尝试将对象放在屏幕中间,因此我们的x和y值仅为0。

requestHitTest返回一个解析为数组的promise,该数组XRHitResults存储命中发生位置的矩阵。如果我们设备的直线撞到墙壁或地板,则撞击的位置将是该直线与墙壁或地板相交的位置。如果我们找到一个命中,请进行第一次击中(最接近的击中)并将其变为a THREE.Matrix4。然后我们可以把我们之前创建的立方体放在碰撞位置。我们还确保将立方体添加到场景中,以便我们可以渲染它。由于我们现在需要使用await onClick,因此我们必须创建onClick一个异步函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class App {
...
async onClick(e) {
const x = 0;
const y = 0;

this.raycaster = this.raycaster || new THREE.Raycaster();
this.raycaster.setFromCamera({ x, y }, this.camera);
const ray = this.raycaster.ray;

const origin = new Float32Array(ray.origin.toArray());
const direction = new Float32Array(ray.direction.toArray());
const hits = await this.session.requestHitTest(origin,
direction,
this.frameOfRef);

if (hits.length) {
const hit = hits[0];
const hitMatrix = new THREE.Matrix4().fromArray(hit.hitMatrix);

this.model.position.setFromMatrixPosition(hitMatrix);

this.scene.add(this.model);
}
}
}

5. 预计成果

  • 在运行应用程序时,应该能够看到跟踪地板表面的光罩。如果没有,请尝试使用手机慢慢环顾四周。
  • 一旦看到光罩,在敲击时,应在立体光栅上放置一个立方体。
  • 可能需要移动一点,以便底层AR平台可以更好地检测现实世界中的表面。低照明和没有特征的表面会降低场景理解的质量,并增加不会发现命中的机会。

img

给咱来个🍰,啾咪