本文为稀土技能社区首发签约文章,14 天内制止转载,14 天后未获授权制止转载,侵权必究!
本篇是 Three 从入门到进阶的第 3 篇,重视专栏

前言

上一篇文章用拍摄的故事来敲开 Threejs 国际的大门 讲解了 Three 入门基础,本文讲解 Three 中十分中心的概念——场景图,用好场景图,能极大程度提高开发效率,更好的了解 Three 烘托机理。

场景图

什么叫场景图那?

Three.js’s core is arguably its scene graph. A scene graph in a 3D engine is a hierarchy of nodes in a graph where each node represents a local space. ——官方文档

场景图在 3D 引擎是一个图中节点的层次结构,其中每个节点代表了一个部分空间。

听起来有些难以了解,小包来举个例子。

在太阳系中,太阳是中心,地球绕着太阳转,月亮又绕着地球转,与此一同它们都进行自转。那现在需求来了,如何来完成太阳、地球、月亮的旋转事例。

咱们来拆解一下上述需求的完成步骤:

  1. 要完成太阳、地球、月亮三个球体
  2. 三个球体都发生自转
  3. 地球绕着太阳旋转,月亮绕着地球旋转

上一篇文章讲过,Three 基础三件套之一为场景 scene,一切的模型放置于 scene 中,先将太阳、地球、月亮三个模型一股脑都增加到 scene 中。

Three 中大部分目标都承继于 Object3D 类,Object3D 目标上有 rotation 特点,来控制物体的部分旋转。

模型和 scene 都承继于 Object3D,因而自转能够运用模型上的 rotation 特点进行完成。

scene 相同具备 rotation 特点,假如 scene 中只要地球和太阳,可经过设置 scene 旋转完成。但月亮就没有立锥之地了,除非去核算月亮随太阳的滚动方程,这未免有些舍近求远,假如月亮能和地球绑定在一同,月亮只针对地球设置将简单多了。详细演示见下图:

Threejs进阶之场景图

实在事例中一般会有多组相关的旋转或许更杂乱的联系,只要一个 scene 很难进行完成,于是 Three 就提出了场景图的概念,它允许咱们开启部分空间,也能够了解为总 scene 下包含多个子 scene,以此类推能够构成一个 scene 树。

接下来咱们来复现一下太阳-地球-月亮的事例,体悟一下 Three 中场景图的详细运用。

Threejs 的默许装备请参考用拍摄的故事来敲开 Threejs 国际的大门,这里不做赘述。

修正一下 camera 的根本装备:

const camera = new `Three`.PerspectiveCamera(
  40,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(0, 50, 0);
camera.up.set(0, 0, 1);
camera.lookAt(0, 0, 0);

三个模型本质上是相同的,先完成一个公共的几何体,后续经过 scale 办法进行放大和缩小。

const radius = 1;
const widthSegments = 6;
const heightSegments = 6;
const sphereGeometry = new `Three`.SphereGeometry(
  radius,
  widthSegments,
  heightSegments
);

由上面制作的演示图可知,这个事例中会有多个模型以及部分空间的旋转,咱们预设一个旋转数组,render 函数更新时同步更新旋转数组中的一切节点。

// 要更新旋转角度的目标数组
const rotations = [];
const render = (time) => {
  time *= 0.001;
  renderer.render(scene, camera);
  controls && controls.update();
  rotations.forEach((obj) => {
    obj.rotation.y = time;
  });
  requestAnimationFrame(render);
};

Step1: 完成地日旋转

// 界说太阳模型
const sunMaterial = new `Three`.MeshPhongMaterial({ emissive: 0xffff00 });
const sunMesh = new `Three`.Mesh(sphereGeometry, sunMaterial);
sunMesh.scale.set(5, 5, 5); // 经过scale放大模型
scene.add(sunMesh);
rotations.push(sunMesh); // 增加到旋转数组中
// 界说地球模型
const earthMaterial = new `Three`.MeshPhongMaterial({
  color: 0x2233ff,
  emissive: 0x112244,
});
const earthMesh = new `Three`.Mesh(sphereGeometry, earthMaterial);
// 移动一下地球位置,要不会被太阳遮盖
earthMesh.position.x = 10;
rotations.push(earthMesh);
scene.add(earthMesh);

Threejs进阶之场景图

太阳和地球的自转就完成了,场景图结构如下图:

Threejs进阶之场景图

接下来咱们为太阳和地球开启一块部分空间,一同设置部分空间的自转。

// 经过 Object3D 来声明空的场景图
const sunEarchScene = new `Three`.Object3D();
scene.add(sunEarchScene);
rotations.push(sunEarchScene);
const sunMaterial = new `Three`.MeshPhongMaterial({ emissive: 0xffff00 });
const sunMesh = new `Three`.Mesh(sphereGeometry, sunMaterial);
sunMesh.scale.set(5, 5, 5);
rotations.push(sunMesh);
// scene.add(sunMesh); // 增加至新的场景图节点中
// 界说地球模型
const earthMaterial = new `Three`.MeshPhongMaterial({
  color: 0x2233ff,
  emissive: 0x112244,
});
const earthMesh = new `Three`.Mesh(sphereGeometry, earthMaterial);
// 移动一下地球位置,要不会被太阳遮盖
earthMesh.position.x = 10;
rotations.push(earthMesh);
// scene.add(earthMesh);  // 增加至新的场景图节点中

Threejs进阶之场景图

场景图结构如下:

Threejs进阶之场景图

Step2: 完成地月旋转

地月坐落一个新的部分空间中,再次增加一个空场景图节点 earchMoonScene,将地球和月亮放置到里面,一同将 earchMoonScene 增加到 sunEarchScene 中。

const earchMoonScene = new `Three`.Object3D();
earchMoonScene.position.x = 10;
sunEarchScene.add(earchMoonScene);
rotations.push(earchMoonScene);
// 增加月亮
const moonMaterial = new `Three`.MeshPhongMaterial({
  color: 0x888888,
  emissive: 0x222222,
});
const moonMesh = new `Three`.Mesh(sphereGeometry, moonMaterial);
moonMesh.scale.set(0.5, 0.5, 0.5);
moonMesh.position.x = 2;
earchMoonScene.add(moonMesh);
rotations.push(moonMesh);

Threejs进阶之场景图

最终的场景图结构如下:

Threejs进阶之场景图

假如还不能完全了解上述进程,能够增加坐标轴辅助线 AxesHelper 目标来辅助了解。

// 为每个节点增加一个AxesHelper
objects.forEach((node) => {
  const axes = new `Three`.AxesHelper();
  axes.material.depthTest = false;
  axes.renderOrder = 1;
  node.add(axes);
});

Threejs进阶之场景图

增加辅助线后,咱们能够特别明晰的看到地球和太阳处有两个坐标系,这别离来自于本身模型以及部分空间。

留意

上面讲到 Object3DThree 中大多数目标的基类,基类作为基石一般的存在,最好不要在代码中反复呈现,因而 Three 官方提出了 Group 的概念,来增加层级结构之间的明晰性及逻辑性,Group 本质上与 Object3D 是完全相同的,因而更推荐运用 Group 目标来创建新的场景(部分空间)。

层次结构模型

太阳-地球-月亮制作的场景图结构讲的官方一些就是 Three 的层次结构模型,它本质是一个树结构,子节点经过 Object3D 或许 Group 来扩展。

层次结构树的建立首要经过两个办法,都承继于 Object3D 目标:

  • add: 给父目标增加子目标
  • remove: 删去父目标中的子目标
  • children(顺便讲一下): 获取父目标的一切子目标

add 办法事例中现已反复运用过,例如 scene 中增加模型,增加新的部分空间以及部分空间中增加太阳、地球、月亮等。经过 Group 和 add 办法,就能够一层一层的建立对应需求的层次结构树。

日常开发中,无论是自己建立层次结构树,仍是引进外部模型,都难免对模型上的某个节点进行操作。例如经典的换肤事例,中心思想在于 Mesh 模型材质特点的修正。因而就难免产生了另一种需求,需求遍历层次结构树上的一切节点,对某些指定节点进行操作。

上文讲到了 children 办法,但 children 仅能获取当时父目标的子目标,不能获取一切的节点。对此,Three 专门设计了 API .traverse() 递归遍历办法,能够获取层次结构树的一切子节点,每个节点都带有本身的特点标识,借此能够区别 Mesh、Group 以及其他类型,便能够完成各类炫酷的作用,下面来看一下详细用法。

scene.traverse((obj) => {
  if (obj.type === "Group") {
    console.log("Group ", obj);
  }
  if (obj.type === "Mesh") {
    console.log("Mesh ", obj);
  }
});

Threejs进阶之场景图

其它更精密的操作及运用,后面的模型篇会进行更详尽的讲解。

总结

场景图是 Three 十分中心的概念,了解场景图是用好 Three 的要害一步。Three 触及三维立体空间,物体的旋转和移动或许需求一些杂乱的数学逻辑,经过场景图,将事例进行合理有用区分,能极大程度下降核算难度。例如文章说到的太阳-地球-月亮的事例,月亮绕太阳的运动核算起来就十分杂乱。

码上

后语

我是战场小包,一个快速成长中的小前端,希望能够和我们一同进步。

假如喜爱小包,能够在重视我,相同也能够重视我的小小大众号——小包学前端

一路加油,冲向未来!!!

疫情提前结束 人间恢复和平

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。