场景是每个Three.js项目中放置内容的容器,同时也可以拥有多个场景进行切换展示,可以在场景中放置模型、灯光和相机,还可以通过调整场景的位置让场景内的所有内容都一起跟着调整位置。

场景的结构

在学JavaScript基础的时候免不了要操作DOM对象,而DOM对象的结构都是树形结构的,Three.js也遵循这样的理念,将所有可以添加到场景内的结构梳理成了一种树形的结构,以便能够更好的理解Three.js。

将scene想象成一个body,在body内可以添加DOM对象,scene内也可以添加它的3D对象,这样一层层的嵌套组成所需的项目。所以,在Three.js中,为了方便操作,将所有3D对象共同的内容抽象成了一个基类,就是THREE.Object3D。

THREE.Object3D

为了方便操作,Three.js将每个能够直接添加到场景内的对象都继承至一个基类THREE.Object3D,将继承至这个基类的对象称为3D对象,判断一个对象是否继承至THREE.Object3D,可以通过以下的方法来判断:

//继承至THREE.Object3D返回true,否则返回false
obj instanceof THREE.Object3D

THREE.Object3D中封装上了一些常用的方法,以下分别介绍相关的内容:

方法(属性)描述
add(object)用于向场景中添加对象,使用该方法还可以创建对象组
children用于返回一个场景中所有对象的列表,包括相机和光源
getObjectByName(name, recursive)

在创建对象时可以指定唯一的标识name,使用该方法可以查找特定名字的对象

  • 当参数recursive设置为false时,在调用者子元素上查找
  • 当参数recursive设置为true时,在调用者的所有后代对象上查找
remove(object)object为场景中对象的引用,使用该方法可以将对象从场景中移除
traverse(function)该方法也可以遍历调用者和调用者的所有后代,function参数是一个函数,被调用者和每一个后代对象调用function方法
fog使用该属性可以为场景添加雾化效果,可以产生隐藏远处物体的浓雾效果
overrideMaterial使用该属性可以强制场景中的所有物体使用相同的材质

1、向场景中添加一个3D对象

scene.add(mesh); //将网格添加到场景中

以上方法是将一个立方体添加到场景中显示,但是这个方法不光能够在场景中使用,而且也可以将一个3D对象添加到另一个3D对象中:

parent.add(child);

2、获取一个3D对象

获取一个3D对象可以使用getObjectByName通过3D对象的name值进行获取,在获取之前首先要设置当前3D对象的name值:

object3D.name = "first";
scene.add(object3D);
scene.getObjectByName("first"); //返回第一个匹配的3D对象

另外一种方式就是通过使用getObjectById通过3D对象的id值进行获取,3D对象的id值是只能读取的,它是在添加到场景时,按照1,2,3,4,5......的顺序默认生成的一个值,无法自定义:

scene.getObjectById(1); //返回id值为1的3D对象

3、删除一个3D对象

如果想要隐藏一个3D对象而不让它显示,可以通过设置它的visible的值:

mesh.visible = false; //设置为false,模型将不会被渲染到场景中

如果一个模型将不再被使用需要彻底的删除掉时,可以使用remove方法进行删除:

scene.add(mesh); //将一个模型添加到场景中

scene.remove(mesh); //将一个模型从场景中彻底删除

4、获取到所有的子类

每一个3D对象都有一个children属性,这是一个数组,里面包含了所有添加的3D对象:

scene.add(mesh1);
scene.add(mesh2);

console.log(scene.children);
//[mesh1, mesh2]

如果想获取3D对象下所有的3D对象,可以通过traverse方法进行获取:

mesh1.add(mesh2); //mesh2是mesh1的子元素
scene.add(mesh1); //mesh1是场景对象的子元素

scene.traverse(function(child){
  console.log(child);
});
//将按顺序分别将mesh1和mesh2打印出来

5、获取3D对象的父元素

每一个3D对象都有一个父元素,可以通过parent属性进行获取:

scene.add(mesh); //将模型添加到场景
console.log(mesh.parent === scene); //true

修改3D对象的位置、大小和转向

以上介绍了场景的结构以及场景3D对象的增删查,以下将介绍对场景内模型的一些操作:

1、修改位置方式

通过设置模型的position属性进行修改模型当前位置的操作,具体方法如下:

  • 单独设置每个方向的属性:
mesh.position.x = 3; //将模型的位置调整到x正轴距离原点为3的位置
mesh.position.y += 5; //将模型的y轴位置以当前的位置向上移动5个单位
mesh.position.z -= 6; //将模型的z轴位置向空间内部移动了6个单位
  • 直接一次性设置所有的方向属性:
mesh.position.set(3, 5, -6);  //直接将模型的位置设置在x轴为3,y轴为5,z轴为-6的位置
  • Three.js的模型的位置属性是一个THREE.Vector3(三维向量)的对象,可以对其重新赋值一个新的对象:
mesh.position = new THREE.Vector3(3, 5, -6); //也可以通过THREE.Vector3一次性设置所有位置

2、修改大小的方式

模型导入以后在很多情况下都需要调整模型的大小,可以通过设置模型的scale属性来调整大小:

  • 第一种方式是单独设置每个方向的缩放
mesh.scale.x = 2; //模型沿x轴放大一倍
mesh.scale.y = 0.5; //模型沿y轴缩小一倍
mesh.scale.z = 1; //模型沿z轴保持不变
  • 第二种方式是使用set方法一次性设置每个方向的缩放:
mesh.scale.set(2, 2, 2); //每个方向等比放大一倍

mesh.scale.set(0.5, 0.5, 0.5); //每个方向等比缩小一倍
  • 第三种方式通过赋值的方式重新修改,因为scale属性也是一个三维向量:
mesh.scale = new THREE.Vector3(2, 2, 2); //每个方向都放大一倍

mesh.scale = new THREE.Vector3(0.5, 0.5, 0.5); //每个方向都缩小一倍

3、修改模型的转向

很多情况下需要对模型进行旋转以达到将模型显示出它需要显示的方位,可以通过设置模型的rotation属性进行旋转(注意:旋转Three.js使用的是弧度而不是角度):

  • 第一种方式是单独设置每个轴的旋转:
mesh.rotation.x = Math.PI; //模型沿x旋转180度
mesh.rotation.y = Math.PI * 2; //模型沿y轴旋转360度,跟没旋转一样的效果
mesh.rotation.z = - Math.PI / 2; //模型沿z轴逆时针旋转90度
  • 第二种方式是使用set的方式一次性赋值:
mesh.rotation.set(Math.PI, 0, - Math.PI / 2); //旋转效果和第一种方式显示的效果相同

正常模型的旋转都是按照XYZ依次旋转,如果想改变模型的旋转顺序则需要进行一定的修改,可能存在的情况有:YZX,ZXY,XZY,YXZ 和 ZYX:

//先沿y轴旋转180度,再沿z轴旋转0度,最后沿x轴逆时针旋转90度
mesh.rotation.set(Math.PI, 0, - Math.PI / 2, "YZX"); 
  • 第三种方式,模型的rotation属性其实是一个欧拉角对象(THREE.Euler)故而可以通过重新赋值一个欧拉角对象来实现旋转调整:
mesh.rotation = new THREE.Euler(Math.PI, 0, - Math.PI / 2, "YZX"); 

使用dat.GUI实现页面的调试

使用dat.GUI插件用于对模型的位置或者大小的调整进行测试。

  • 首先,需要将插件的源码引入到页面中,可以直接使用cdn的链接:
<script src="https://cdn.bootcss.com/dat-gui/0.7.1/dat.gui.min.js"></script>
  • 创建一个对象,在这个对象中设置需要修改的一些数据:
controls = {
    positionX:0,
    positionY:0,
    positionZ:0
};
  • 使用dat.GUI对象,将需要修改的配置添加到对象中并监听变化的回调:
gui = new dat.GUI();
gui.add(controls, "positionX", -1, 1).onChange(updatePosition);
gui.add(controls, "positionY", -1, 1).onChange(updatePosition);
gui.add(controls, "positionZ", -1, 1).onChange(updatePosition);

function updatePosition() {
    mesh.position.set(controls.positionX, controls.positionY, controls.positionZ);
}

只要每次修改对象中的值后都会触发updatePosition回调来更新模型的位置。

1、生成一个输出框

dat.GUI能够根据controls值的不同生成不同的操作方法,如果值的类型为字符串或者数字类型,则可以生成一个默认的输入框:

//生成一个输入框
gui.add(controls, "positionX");
gui.add(controls, "positionY");
gui.add(controls, "positionZ");

2、生成一个滑块

使用gui.add()方法,如果值为数字类型并且传入第三个值(最小值)和第四个值(最大值),也就是限定了值的取值范围,就能生成可以滑动的滑块:

//创建一个滑块
gui.add(controls, "positionX", -1, 1); //设置了最小值和最大值,可以生成滑块
gui.add(controls, "positionY").max(1); //只设置了最大值,无法生成滑块
gui.add(controls, "positionZ").min(-1); //只设置了最小值,无法生成滑块

可以通过step()方法来限制每次变化的最小值,也就是每次增加或者减少的变化值都是这个值的倍数:

//step()设置每次滑块变化
gui.add(controls, "positionX", -10, 10).step(1); //限制必须为整数
gui.add(controls, "positionY", -10, 10).step(0.1); //每次变化都是0.1的倍数
gui.add(controls, "positionZ", -10, 10).step(10); //每次变化都是10的倍数

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐