A-Frame中文教程

使用three.js开发

A-Frame是一个基于 three.js 的webvr开发框架,A-Frame能使用所有three.js API。本章将讲述如何访问底层three.js中的场景(scene),对象(objects)以及API。

A-Frame 和 three.js 场景图的关系

父子关系

当A-Frame实体被嵌套在父子关系中,它们所对应的three.js对象也是如此。例如,下面这个A-Frame的场景:

<a-scene>
<a-box>
<a-sphere></a-sphere>
<a-light></a-light>
</a-box>
</a-scene>

three.js场景图将相应的看起来如下:

THREE.Scene
THREE.Mesh
THREE.Mesh
THREE.Light

访问three.js API

three.js 可作为window上的全局对象来使用:

console.log(THREE);

使用three.js对象

A-Frame是基于three.js的一个上层抽象,但我们仍然可以和three.js底层交互,A-Frame的元素包含到three.js场景图的入口。

访问three.js场景(Scene)

three.js场景可通过 <a-scene> 元素的 .object3D 来访问:

document.querySelector('a-scene').object3D; // THREE.Scene

而且每个A-Frame实体也可以通过 .sceneEl 来引用 <a-scene> :

document.querySelector('a-entity').sceneEl.object3D; // THREE.Scene

从一个组件(component)中,我们从其实体(i.e., this.el)来访问场景:

AFRAME.registerComponent('foo', {
init: function () {
var scene = this.el.sceneEl.object3D; // THREE.Scene
}
});

访问一个实体的three.js对象

每个A-Frame 实体 (e.g., <a-entity>) 都有它自己的 THREE.Object3D,更确切的说是一个包含了各种 Object3DTHREE.Group。一个实体的这个THREE.Group根对象通过 .object3D 来访问:

document.querySelector('a-entity').object3D; // THREE.Group

实体可以由各种不同的Object3D组成。比如,一个实体可以同时是一个THREE.Mesh 和一个 THREE.Light,通过同时拥有一个几何模型(geometry)组件和一个光照(light)组件:

<a-entity geometry light></a-entity>

组件把网孔(mesh) 和 光照(light) 添加在实体的根THREE.Group对象下面。 对mesh和light的引用被存储为不同类型的three.js对象,在实体的.object3DMap中。

console.log(entityEl.object3DMap);
// {mesh: THREE.Mesh, light: THREE.Light}

But we can access them through the entity’s .getObject3D(name) method:

entityEl.getObject3D('mesh'); // THREE.Mesh
entityEl.getObject3D('light'); // THREE.Mesh

现在我们再来看看这些three.js对象在开始时是如何设置的。

在实体中设置 Object3D

在实体中设置 Object3D 其实是把 Object3D 添加到该实体的 Group 中, 这样该 Object3D 成为three.js的一部分。我们通过实体的.setObject3D(name)方法来设置 Object3D,这里name参数表示Object3D的用处。

例如,在一个组件中设置一个点光源:

AFRAME.registerComponent('pointlight', {
init: function () {
this.el.setObject3D('light', new THREE.PointLight());
}
});
// <a-entity light></a-entity>

我们设置光源的名字为 light。后面我们可以通过实体的 .getObject3D(name) 方法来获取该对象:

entityEl.getObject3D('light');

而当我们在一个A-Frame实体中设置一个three.js对象时,A-Frame将通过 .el 来从three.js对象中设置一个A-Frame实体的引用。

entityEl.getObject3D('light').el; // entityEl

还有一个 .getOrCreateObject3D(name, constructor) 方法用来创建并设置一个 Object3D,如果没有同名对象被设置过的话。这通常用于 THREE.Mesh,这里geometry 和 material组件同时需要用来创建一个mesh。先被初始化的那个组件创建mesh,然后另一个组件获取mesh。

从实体中删除一个 Object3D

要从一个实体中删除一个 Object3D ,相应的从three.js场景中删除,我们可以使用实体的 .removeObject3D(name) 方法。回到上面那个点光源的例子,我们在组件detach的时候删除这个light:

AFRAME.registerComponent('pointlight', {
init: function () {
this.el.setObject3D('light', new THREE.PointLight());
},
remove: function () {
// Remove Object3D.
this.el.removeObject3D('light');
}
});

坐标空间变换

每个物体和场景(世界)一般都有各自的坐标空间。一个父对象的位置、旋转和缩放转换也会应用于其子对象身上。考虑一下这个场景:

<a-entity id="foo" position="1 2 3">
<a-entity id="bar" position="2 3 4"></a-entity>
</a-entity>

从世界坐标为参考系,foo的位置为(1,2,3),而bar的坐标为(3, 5, 7),因为foo的转换也应用于bar上。以foo为参考系来看,foo的坐标为(0, 0, 0),bar的位置为(2, 3, 4)。我们通常需要处理这些参照点和坐标之间转换。上面例子比较简单,但是我们可能想做一些其他操作,比如找到bar的世界空间坐标位置,或者转换绝对坐标为foo的相对坐标。在3D编程中,这些操作是通过矩阵变换完成的,不过three.js提供了一些helper类,使这些操作更容易。

局部坐标和全局坐标的转换

通常,我们需要在父 Object3D 上调用 .updateMatrixWorld () 方法,不过three.js默认把Object3D.matrixAutoUpdate设置为 true。我们可以使用three.js的.getWorldPosition ().getWorldRotation ()方法来获取一个Object3D的世界坐标:

entityEl.object3D.getWorldPosition();

和世界旋转角度:

entityEl.object3D.getWorldRotation();

three.js Object3D更多坐标转换的函数

世界坐标到局部坐标

要获得一个从世界空间到对象局部空间的变换矩阵,我们可以通过对象世界矩阵的求逆来获取。

var worldToLocal = new THREE.Matrix4().getInverse(object3D.matrixWorld)

然后我们可以把 worldToLocal 矩阵应用到我们想转换的对象上去:

anotherObject3D.applyMatrix(worldToLocal);