WebGL+Three.js—第九章 物体的位移旋转与缩放

751 阅读8分钟

9.1 坐标辅助器与轨道控制器

9.1.1 坐标辅助器

        1、three.js坐标系

            当我们在three.js中创建3D场景时,我们需要使用一个坐标系来定位和控制对象的位置和方向。three.js使用的坐标系是右手坐标系,这意味着X轴向右,Y轴向上,Z轴向前。

            在右手坐标系中,我们可以使用右手规则来确定一个向量的方向。右手规则指的是,将右手伸出,并将大拇指指向X轴的正方形,食指指向Y轴的正方向,中指指向Z轴的正方向。那么,当我们沿着一个向量的方向时,大拇指指向的方向就是向量的正方向。

image.png

        2、添加坐标辅助器

            在three.js中,它提供了一个AxesHelper类,可以用它来生成一个坐标辅助器。

const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 5;
camera.lookAt(0, 0, 0);

// 添加世界坐标辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

image.png

            实例化AxesHelper类的参数代表的是轴的线段长度,红色代表X轴,绿色代表Y轴,蓝色代表Z轴。

            这里我们只看到X轴和Y轴,那是因为目前摄像机的位置是正对着物体,也就是说相机的位置与Z轴重叠在一起了,只需要调整一下X和Y轴即可。

// 创建一个相机 视点
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
// 设置相机的位置
camera.position.x = 2;
camera.position.y = 2;
camera.position.z = 5;
camera.lookAt(0, 0, 0);

// 添加世界坐标辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

image.png

9.1.2 轨道控制器

        轨道控制器是three.js中非常实用的一个工具,它可以帮助用户更轻松地探索3D场景。使用轨道控制器,用户可以通过鼠标或触摸屏来旋转、缩放和平移相机,从而改变相机的视角,观察场景中的不同物体或角度。

        具体来说,轨道控制器可以通过鼠标或触摸屏来旋转相机,改变场景的视角,调整场景的大小,移动场景中的物体。

        1、引入插件库

import {OrbitControls} from "three/examples/jsm/controls/OrbitControls.js"

        2、实例化轨道控制器

            实例化之后并不是添加到场景就完事,而是需要调用它的update()方法然后重新渲染才能生效,一般会在它的动画函数里调用update()方法。

// 添加轨道控制器
addOrbitControls() {
  this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement);
},
// 动画函数
animation() {
  this.orbitControls.update();
  // 渲染
  this.renderer.render(this.scene, this.camera);
  requestAnimationFrame(this.animation);
}

d947354a-4186-44a6-a5ea-74c8699838e1.gif

            注意:这里旋转缩放的并不是物体,而是相机。它是监听鼠标的拖拽和滚轮来控制相机的位置,是相机绕着物体在旋转或缩放。

        3、设置带阻尼惯性

            设置阻尼的意思是它滑动的时候可以带有一定的惯性,就是滑动完之后不是立即停下来,而是滑动一下再慢慢停下,比较人性化。

// 添加轨道控制器
addOrbitControls() {
  this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement);
  // 设置带阻尼的惯性
  this.orbitControls.enableDamping = true;
},

f2b888da-5b29-4650-85a6-9bee6b7f4d8c.gif

        4、设置阻尼系数

            我们可以通过设置阻尼系数,控制它最后滑动的时长,数值越小,惯性越大。

// 添加轨道控制器
addOrbitControls() {
  this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement);
  // 设置带阻尼的惯性
  this.orbitControls.enableDamping = true;
  // 设置阻尼系数
  this.orbitControls.dampingFactor = 0.01;
}

a7fd9fe7-7705-42aa-ac70-b9af10f34845.gif

        5、设置自动旋转

            它提供了一个autoRotate属性,设置为true就会自动旋转,无需手动滑动。

// 添加轨道控制器
addOrbitControls() {
  this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement);
  // 设置带阻尼的惯性
  this.orbitControls.enableDamping = true;
  // 设置阻尼系数
  this.orbitControls.dampingFactor = 0.01;
  // 设置自动旋转
  this.orbitControls.autoRotate = true;
}

fba9a1cc-a0f5-454d-a002-36553e268578.gif

        6、改变绑定的DOM事件

            它在实例化的时候传入的第二个参数是domElement,也就是说绑定的dom元素,我们这里传入的是renderer.domElement,这个是canvas元素事件。当然如果想换一个dom元素来监听也是可以的,例如这里换成body元素。

// 添加轨道控制器
addOrbitControls() {
  this.orbitControls = new OrbitControls(this.camera, document.body);
  // 设置带阻尼的惯性
  this.orbitControls.enableDamping = true;
  // 设置阻尼系数
  this.orbitControls.dampingFactor = 0.01;
  // 设置自动旋转
  this.orbitControls.autoRotate = true;
}

        上述只是列举了轨道控制器的一部分属性,其余的属性可以参考官方文档:threejs.org/docs/index.…

9.1.3 代码示例

9.2 物体的位移与父子元素

9.2.1 物体的位移

        一个三维对象基本上都存在position属性,它是用来控制这个对象的位置。它的类型是一个三维变量,默认值为(0, 0, 0)

        1、直接修改position属性

const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = 2;

        2、调用set方法

const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(2,0,0);

image.png

9.2.2 父子元素

        position属性在文档上描述的是:表示对象局部位置。它并不是说绝对位置,如果它是添加在场景中的,那这个位置就是世界位置;如果它是添加在某个父元素里的,那它就是相对于父元素的局部位置。有点类似于CSS中的绝对定位和相对定位的概念。

        three.js的元素不仅可以添加在场景中,还可以添加到某个物体中,形成父子关系。

        1、添加一个父元素

addCube() {
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
  const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  const parentCubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
  let parentCube = new THREE.Mesh(cubeGeometry, parentCubeMaterial);

  // 将子元素添加到父元素里
  parentCube.add(cube);

  // 添加父元素到场景里
  this.scene.add(parentCube);
},

        2、设置父和子元素的position

addCube() {
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
  const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  const parentCubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
  let parentCube = new THREE.Mesh(cubeGeometry, parentCubeMaterial);

  // 将子元素添加到父元素里
  parentCube.add(cube);
  
  parentCube.position.set(-3, 0, 0);
  cube.position.set(3, 0, 0);

  // 添加父元素到场景里
  this.scene.add(parentCube);
},

        此时,绿色立方体为父元素,红色立方体为子元素。由于父元素的x轴坐标为-3,因此子元素的x轴设置成3,实际上是相对父元素而言,所以子元素的x轴真实坐标为0。

image.png

9.2.3 代码示例

9.3 物体的缩放与旋转

9.3.1 物体的缩放

        在three.js每个物体都有scale缩放属性,它的类型是一个三维变量,分别对应x、y、z3个轴的缩放倍数。

        1、使用scale进行缩放

cube.scale.set(2,2,2)

image.png

        2、父子元素缩放关系

            如果元素存在父子关系,这个时候就要稍微注意一下,因为scale属性跟position一样,也是相对缩放的关系。

            (1)父元素缩放

addCube() {
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
  const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  const parentCubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
  let parentCube = new THREE.Mesh(cubeGeometry, parentCubeMaterial);

  // 将子元素添加到父元素里
  parentCube.add(cube);
  parentCube.position.set(-3, 0, 0);
  cube.position.set(3, 0, 0);

  // 设置父元素放大
  parentCube.scale.set(2,2,2)

  // 添加父元素到场景里
  this.scene.add(parentCube);
}

                这里可以发现,父元素缩放,子元素也会跟着缩放。

image.png

            (2)父子元素同时缩放

addCube() {
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
  const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  const parentCubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
  let parentCube = new THREE.Mesh(cubeGeometry, parentCubeMaterial);

  // 将子元素添加到父元素里
  parentCube.add(cube);
  parentCube.position.set(-3, 0, 0);
  cube.position.set(3, 0, 0);

  // 设置父元素放大
  parentCube.scale.set(2,2,2)
  
  // 设置子元素放大
  cube.scale.set(2,2,2)

  // 添加父元素到场景里
  this.scene.add(parentCube);
}

                所以如果父子元素同时缩放,对于子元素而言,它是父元素缩放倍数与自身缩放倍数相乘。

image.png

9.3.2 物体的旋转

        旋转使用的是rotation,它的参数是一个Euler欧拉角对象。欧拉角描述一个旋转变化,通过指定轴顺序和其各个轴向上的指定旋转角度来旋转一个物体。

        对Euler欧拉角实例进行遍历将按相应的顺序生成它的分量(x,y,z,order)。意思是说它默认会按照x、y、z轴的顺序进行旋转,但可以对这个顺序进行调整,注意旋转顺序不同会影响旋转的效果。因此它还提供了一个order参数,让我们设置它的旋转顺序,例如order参数传入'YZX'(必须大写)那旋转顺序就是yzx轴了,不过我们按正常思维基本上还是xyz这个顺序为主。         

        1、使用rotation进行旋转

            设置rotation使用的是弧度制,2PI是360°,PI是180°。

cube.scale.set(2,2,2)

image.png

        2、父子元素旋转关系

            如果元素存在父子关系,它跟scale缩放一样的道理,也是相对旋转的关系。

            (1)父元素旋转

addCube() {
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
  const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  const parentCubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
  let parentCube = new THREE.Mesh(cubeGeometry, parentCubeMaterial);

  // 将子元素添加到父元素里
  parentCube.add(cube);
  parentCube.position.set(-3, 0, 0);
  cube.position.set(3, 0, 0);

  // 设置父元素旋转
  parentCube.rotation.x = Math.PI / 4;
}

                这里可以发现,父元素旋转,子元素也会跟着旋转。

image.png

            (2)父子元素同时旋转

addCube() {
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
  const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  const parentCubeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
  let parentCube = new THREE.Mesh(cubeGeometry, parentCubeMaterial);

  // 将子元素添加到父元素里
  parentCube.add(cube);
  parentCube.position.set(-3, 0, 0);
  cube.position.set(3, 0, 0);

  // 设置父元素旋转
  parentCube.rotation.x = Math.PI / 4;
  // 设置子元素旋转
  cube.rotation.x = Math.PI / 4;

  // 添加父元素到场景里
  this.scene.add(parentCube);
}

                所以如果父子元素同时旋转,对于子元素而言,它是父元素旋转的角度加上与自身旋转角度。这里父元素旋转了45°,子元素旋转了父元素45°+自身旋转45°=90°

image.png

9.3.3 示例代码