「技术分享」Threejs 核心基础类-Object3D

阅读原文:【技术分享】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 条评论) “”
   
验证码:

相关文章

推荐文章