计算机系统应用教程网站

网站首页 > 技术文章 正文

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

btikc 2024-11-27 11:56:07 技术文章 32 ℃ 0 评论

阅读原文:【技术分享】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中各个类的继承关系及如何使用共有的方法和属性,并以此为初学者提供一种新的系统性的学习思路。同时也希望本文对大家有所帮助。

希望以上内容能对有需要的人有所帮助

欢迎大家留言写下自己希望了解的技术方向

欢迎大家一起探讨交流

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表