使用 three.js 开发
A-Frame是一个基于 three.js 的webvr开发框架,A-Frame能使用所有three.js API。本章将讲述如何访问底层three.js中的场景(scene),对象(objects)以及API。
A-Frame 和 three.js 场景图的关系
- A-Frame的
<a-scene>
和three.js的scene一一对应。 - A-Frame的
<a-entity>
对应于three.js中的一个或多个对象。 - three.js的对象包含一个A-Frame实体的引用,通过
.el
,该属性由A-Frame所设置。
父子关系
当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
,更确切的说是一个包含了各种 Object3D
的 THREE.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.MeshentityEl.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 position="1 2 3"> <a-entity 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
有 更多坐标转换的函数:
.localToWorld (vector)
.getWorldDirection ()
.getWorldQuaternion ()
.getWorldScale ()
世界坐标到局部坐标
要获得一个从世界空间到对象局部空间的变换矩阵,我们可以通过对象世界矩阵的求逆来获取。
var worldToLocal = new THREE.Matrix4().getInverse(object3D.matrixWorld) |
然后我们可以把 worldToLocal
矩阵应用到我们想转换的对象上去:
anotherObject3D.applyMatrix(worldToLocal); |