阅读原文:【技术分享】Threejs 核心基础类——Object3D
点击关注“八戒技术团队”,阅读更多技术干货
在学习threejs过程中,涉及到各种各样的类和方法,比如Camera、Light、Mesh等等。对于初学者而言,遇到问题只知其然而不知其所以然,面对数量庞大且关联密切的类和方法往往会犯迷糊,所以深入的理清各个类之间的联系、继承关系,以及共用的方法变得尤为重要。基于此,本文将着重介绍threejs中的基础核心类之一—Object3D。
Object3D作为threejs中最重要的基础类之一,包括上面提到的Camera、Light、Mesh在内的大量类均继承自Object3D。它为需要用到位置、方向等数据的对象提供大量的公共属性和方法。这些方法可以用来设置项目中3d对象的position、rotation等属性,或者设置多个3d对象的共用属性。只要理解这些共用的属性或方法,相同的机制会作用于继承自Object3D的其他任何类。
在本文中,我们将梳理Object3D类中较常用到的基础知识,并直接运用Object3D中的属性方法。当然,我们也将会在使用相机、网格等类的同时运用这些方法和属性,以便加深印象。与此同时,也会接触到与空间变换相关的其他重要类,比如Vector3和Euler。
一、position属性
在threejs中,通常情况下我们不会直接使用Object3D类中的方法和属性,而是通过间接地使用继承它的类中的方法和属性。如果在特殊性情况下,需要直接使用Object3D类的方法,建议通过new关键字创建THREE.Object3D的实例进行操作。
var obj = new THREE.Object3D();// {"x":0,"y":0,"z":0}console.log(JSON.stringify(obj.position));obj.position.set(3, 4, 5); // {"x":3,"y":4,"z":5}console.log(JSON.stringify(obj.position));上面的例子中,使用了position属性,其值是一个Vector3的实例。Vector3可以理解为三维向量,可用于表示空间中的点(x,y,z)。position属性设置的是场景中3d对象的中心点。如果是父子对象,那么子对象的位置将是相对父对象的位置设置的。
二、rotation属性
rotation是Object3D中另外一个常用的属性,它的值为Euler类的实例。Euler实例表示的是欧拉角(Euler Angles,简单来说,就是几何体自身的坐标系,相对于全局坐标系,以有序的方式绕全局坐标轴和自身坐标轴旋转所产生的一组角度,它用于描述几何体在空间中的旋转。详细内容这里不展开,有兴趣的小伙伴可以自行查阅相关资料)。当我们创建或更改Euler实例的值时,可以用set方法设置值,有4个参数,前3个为从0到2的弧度,表示相对于对应坐标轴的旋转角度,最后一个为旋转顺序,默认为XYZ。设置值的另一种方法为copy方法,复制另外一个实例的值。
接下来,将通过一段简短的代码来说明具体的使用方法。
// 建立场景 var scene = new THREE.Scene(); // 创建实例var obj = new THREE.Object3D();// 设置旋转角度 obj.rotation.set(0, 0, Math.PI * 1.75); // 创建网格对象 var mesh = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial()); // 将obj 的rotation属性值复制给mesh对象 mesh.rotation.copy(obj.rotation); scene.add(mesh); // 相机 var camera = new THREE.PerspectiveCamera(4, 4 / 3, .5, 100); camera.position.set(2, 2, 2); camera.lookAt(0, 0, 0); // 渲染 var renderer = new THREE.WebGLRenderer(); renderer.setSize(640, 480);document.getElementById('myDemo').appendChild(renderer.domElement); renderer.render(scene, camera);在本示例中,我们先创建了一个Object3D的实例obj,然后使用Euler类的set方法,为实例设置旋转角度。接着创建一个Mesh类的实例mesh,由于Mesh类继承自Object3D类,因此mesh也拥有rotation属性和对应的方法,最后我们使用mesh继承来的copy方法为自己设置与obj相同的旋转属性值。眼尖的小伙伴可能注意到了,上面的实例中用到了一个方法:lookAt,我们常常在Camera实例中见到并用来设置相机的方向,但是它依然是继承自Object3D,你甚至可以在Mesh的实例上调用此方法!它也可以被看作是设置旋转值的另一种方式,它可以接受3个参数:一组代表x,y,z数值的数字,也可以接受Vector3的实例作为参数。下面我们将用lookAt方法设置Object3D的rotation。
接下来,看看可以如何在其他继承自Object3D的类上使用lookAt方法。
var scene = new THREE.Scene(); var mesh = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial() ); scene.add(mesh); var camera = new THREE.PerspectiveCamera(45, 4 / 3, .5, 100); camera.position.set(2, 2, 2);camera.lookAt(0, 0, 0);// 很形象,转头,看向camera.position代表的位置 mesh.lookAt(camera.position); var renderer = new THREE.WebGLRenderer(); renderer.setSize(640, 480); document.getElementById('demo').appendChild(renderer.domElement); renderer.render(scene, camera);下面,我们将结合上面讲到内容进一步给出一个简单的旋转动画示例,将涉及到Vector3实例,以及将lookAt方法运用在调整模型角度上。
// 实际上Scene也是继承自Object3D var scene = new THREE.Scene(); // GridHelper同样是继承自Object3D var gridHelper = new THREE.GridHelper(4, 4); // 这里我们可以直接使用继承来的scale属性来设置缩放 gridHelper.scale.set(2.5, 2.5, 2.5); scene.add(gridHelper); // 不用多说Mesh依然是继承自Object3D var box = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial()); scene.add(box); var sphere = new THREE.Mesh( new THREE.SphereGeometry(0.25, 20, 20), new THREE.MeshNormalMaterial()); scene.add(sphere); // Camera同样是继承自Object3D var camera = new THREE.PerspectiveCamera(45, 4 / 3, .5, 100); camera.position.set(10, 10, 10); camera.lookAt(0, 0, 0); var renderer = new THREE.WebGLRenderer(); renderer.setSize(640, 480); document.getElementById('myDemo').appendChild(renderer.domElement); var state = { frame: 0, maxFrame: 100, fps: 30, lt: new Date(), vector: new THREE.Vector3(3, 0, 0) // and instance of vercor3 }; var update = function (state) { state.vector.z = -5 + 10 * state.bias; // 用state的vector属性设置sphere的position sphere.position.copy(state.vector); // 让box以lookAt的方法调整旋转角度,始终面向sphere box.lookAt(state.vector); }; var loop = function () { state.per = state.frame / state.maxFrame; state.bias = 1 - Math.abs(state.per - 0.5) / 0.5; var now = new Date(); secs = (now - state.lt) / 1000; requestAnimationFrame(loop); if (secs > 1 / state.fps) { update(state); renderer.render(scene, camera); state.frame += state.fps * secs; state.frame %= state.maxFrame; state.lt = now; } }; loop();在上面的例子中,我们建立了一个包含Vector3实例属性值的state对象,并通过改变state.vector的z轴值来使sphere对象平行于z轴作来回运动,并使box对象按照sphere的路径位置旋转。
三、Group
Group类用于将一系列3d实例对象组合成集合,它同样继承自Object3D,其add方法来自于Object3D,也就是说add方法存在于任何继承于Object3D的类或对象中。所以可以用这一系列的类实现实例对象分组的功能,当然也包括Object3D本身。
接下来,我们将展示一个如何创建3d对象组的示例。
var createCubeStack = function (original) { var stack = {}, original = original || new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial()), cube; // 直接通过Object3D创建group对象 stack.group = new THREE.Object3D(); // 定义set方法用来设置3d对象集合中元素的位置和旋转方向 stack.set = function (per) { var bias = 1 - Math.abs(0.5 - per) / 0.5, arr = stack.group.children, len = arr.length; arr.forEach(function (cube, i) { var y = -len / 2 + i + 2 * bias; cube.position.set(0, y, 0); cube.rotation.set(0, Math.PI * 2 * (i / len) + Math.PI * 2 * per, 0); }); }; // 创建多个3d对象实例 var i = 0, len = 3, per; while (i < len) { per = i / len; cube = original.clone(); cube.position.set(0, -len / 2 + i, 0); cube.rotation.set(0, Math.PI * 2 * per, 0); stack.group.add(cube) i += 1; } return stack; };上面例子中,我们创建了一个方法,用来生成并返回一个3d对象实例的group,并包含了设置group中元素位置,旋转角度的方法。接下来,我们将把此方法用到具体的场景中去:
var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(45, 4 / 3, .5, 100); camera.position.set(5, 5, 5); camera.lookAt(0, 0, 0); var stack = createCubeStack(); scene.add(stack.group); var renderer = new THREE.WebGLRenderer(); renderer.setSize(320, 240); document.getElementById('demo').appendChild(renderer.domElement); var frame = 0, maxFrame = 100; var loop = function () { requestAnimationFrame(loop); renderer.render(scene, camera); stack.set(frame / maxFrame); frame += 1; frame = frame % maxFrame; }; renderer.render(scene, camera); loop();当我们运行代码后,会得到一堆立方体,旋转、跳跃。当然直接用Object3D来设置3d实例对象的分组并不是最优的方案,毕竟人家已经明确给出了Group类用来实现分组的功能。但是我们只是想通过这个例子来进一步说明,threejs中Object3D和其他类之间的继承关系。
四、其他
在Object3D中还有很多有用的属性。比如:name属性,可以为3d实例对象设置name属性,并通过getObjectByName获取相应的实例对象;scale属性,其值是Vector3的实例,可以用来设置实例对象的缩放;userData属性,可以用来存储用户的自定义数据,方便在各个实例中读取并使用。除了列出的这些,还有很多实用的属性和方法。
本文并不打算对Object3D中所有的属性和方法作一一列举。仅仅旨在通过一些典型的方法和属性示例,来说明threejs中各个类的继承关系及如何使用共有的方法和属性,并以此为初学者提供一种新的系统性的学习思路。同时也希望本文对大家有所帮助。
希望以上内容能对有需要的人有所帮助
欢迎大家留言写下自己希望了解的技术方向
欢迎大家一起探讨交流
| 留言与评论(共有 0 条评论) “” |