前言
在前面的几章中,我们创建了一个场景,并且在场景中添加了相机
、灯光
、模型
,并将他们渲染到页面上。我们通过改变相机
的一些参数来改变我们观察模型
的角度,通过添加灯光
让我们得以看到模型,这些过程中我们为了方便观察模型
,更改了相机
和灯光
的位置,这种方式称为平移
,是变换
的一种,下面我们将介绍另外两种:旋转
和缩放
转换:平移、旋转和缩放
每当我们在3D空间中移动对象时,我们都会使用称为转换
的数学运算来进行,在前面的章节中,我们已经了解了一种转换方式:平移translation
,存储在对象.position
属性中
还有两种分别是旋转:rotation
,存储在.rotation
中,与存储在.scale
属性中的缩放一起,这些构成了我们将用于场景中移动对象的三个基本变换
,简称:TRS
;来指代:平移
、旋转
和缩放
可以使用scene.add
添加到场景中的每个对象都具有这些属性,包括网格
、灯光
和相机
,而材质
和几何图形
则没有
前面我们使用.position
来设置相机
和灯光
的位置
camera.position.set(0,0,10)
light.position.set(0,0,0)
Object3D
基类
不是为每种类型的对象多次重新定义.position
、.rotation
和.scale
属性,而是在Object3D
基类上定义一次这些属性,这样可以添加到场景中的所有其他类都从该基类派生。包括 网格、相机、点、线助手,甚至 场景本身,我们将非正式地派生自Object3D
的类称为场景对象
场景图
在介绍转换
之前,我们先了解下场景图
,因为供我们操作转换
的目标对象
都存在于场景图
中,了解了场景图,有助于我们更好的学习转换
scene.add(mesh)
.add
方法也是在Object3D
中定义并在场景类上被继承,就像.position
、.rotation
和.scale
,所有其他派生类也继承了这个方法,继承给了我们light.add
、mesh.add
、camera.add
等等,这就意味着对象彼此可以互相添加
创建一个顶部是场景
的树形结构,称为场景图(如下图)
我们将A对象添加到B对象中时,我们称B为父对象,A为子对象
场景是顶级父级,上图中场景有三个子对象,两个网格一个灯光,其中一个网格有两个孩子。
每个对象(顶级场景除外)都只有一个父对象,并且可以有任意数量的子对象
renderer.render(scene, camera)
当我们渲染场景时,渲染器遍历场景图,从场景开始,并使用 每个对象相对于其父对象的位置、旋转和缩放来确定在哪里绘制它
访问场景对象的子对象
所有添加到场景scene
中的对象,都会放在children
数组之中,可以使用.children
来访问场景对象的所有子对象
坐标系:世界空间和局部空间
世界空间和局部空间书中讲了很多,总结就是:
- 世界空间:就是整个场景的空间
- 局部空间:场景内对象自身的内部空间
如果我们操作的对象的父级是场景,那这个对象就是在世界空间中的转换,否则,则是在局部空间中的转换
顶级场景定义了世界空间,而其他每个对象都定义了自己的局部空间
每个对象都有一个坐标系
第一个转换:平移
三种转换中最简单的就是平移
,前面的章节中我们使用平移来改变相机和灯光的位置,我们通过更改对象的.position
属性来执行平移
注:每个对象都从其父对象坐标系内的原点开始
平移的方向
注:平移的单位是 米
- X轴正向指向屏幕 右侧
- Y轴正向指向屏幕 上方
- Z轴正向指向屏幕 外面,也就是屏幕前的你
平移的使用
// 写法一:
mesh.position.x = 1;
mesh.position.x = 2;
mesh.position.x = 3;
// 写法二
mesh.position.set(1,2,3)
// 上面的两种写法的结果是一样的
// src/World/components/cube.js
import { BoxGeometry, Mesh, MeshBasicMaterial, MeshStandardMaterial } from "three";
const createCube = () => {
const geometry = new BoxGeometry(2, 2, 2)
const material = new MeshStandardMaterial({color: 'purple'})
const cube = new Mesh(geometry, material)
// 沿Y轴正方向移动 2 米
cube.position.set(0, 2, 0)
return cube
}
export {createCube}
第二个转换 缩放
只要我们在三个轴上缩放相同的数量,缩放对象就会使其变大或变小。如果我们按不同的量缩放轴,对象就会被压扁或拉伸,因此缩放是可以改变对象形状的三个基本变换中唯一的一个
缩放的值是相对于对象的初始大小
缩放是没有单位的,比例值与对象的初始大小成比例关系:
- 1 表示初始大小的 100%
- 2 表示初始大小的 200%
- 0.5 表示初始大小的 50%
统一缩放:对所有三个轴使用相同的值,
- 一个
mesh.scale.set(1, 1, 1)
的缩放,表示100%
的比例缩放X轴、Y轴和Z轴,这是一个默认值 - 一个
mesh.scale.set(2, 2, 2)
的缩放,表示200%
的比例缩放X轴、Y轴和Z轴,该对象增长到其原始的2倍 - 一个
mesh.scale.set(0.5, 0.5, 0.5)
的缩放,表示50%
的比例缩放X轴、Y轴和Z轴,该对象缩小到其原始的一半
非均匀缩放:每个轴上的缩放值不同
// src/World/components/cube.js
const createCube = () => {
...
// x轴不变,Y、Z轴变为原来的2倍
cube.scale.set(1, 2, 2)
...
}
export {createCube}
负比例值镜像对象
小于0
的缩放值除了使对象变小或变大之外,还会 镜像对象,缩放值-1
在任何单轴上都会镜像对象而不影响大小
- 小于
0
且大于-1
的值将镜像并被 挤压 - 小于
-1
的值将镜像和 拉伸对象
注:相机和灯光无法被缩放
最后一个转换:旋转
与平移和缩放相比,旋转要更加的小心,主要是 旋转顺序很重要,不同的旋转顺序会造成不同的结果
表示旋转的类:Euler
类(欧拉角)和Quaternions
(四元数)
Vector3
类
前面两种转换类型:平移 和 缩放,他们的位置都是被存储在Vector3
类中,这是一个用于表示3D向量的特殊类,这个类有.x
、.y
和.z
属性和方法.set
来帮助我们操作它们。每当我们创建任何场景对象时,例如Mesh
,Vector3
都会被自动创建并存储在.position
中
欧拉角
旋转类Euler
,与.position
和.scale
一样,当我们创建一个新的场景对象时,会自动创建一个Euler
实例并为其赋予默认值
与Vector3
一样,有.x
、.y
和.z
属性,以及.set
方法:
mesh.rotation.x = 2;
mesh.rotation.y = 2;
mesh.rotation.z = 2;
mesh.rotation.set(2, 2, 2);
旋转单位:弧度
上学的时候我们应该都学过弧度制,圆的一周是360°
,即2π
,所以2π
是180°
,一个直角就是π/2
,之前透视相机的视野是用度数指定的,但是其他的角度都是使用 弧度 而不是 度数 指定的
度数转弧度
import { MathUtils } from 'three';
const rads = MathUtils.degToRad(90); // 1.57079... = π/2
注: 因为
π = 180°
,1° = π/180
,旋转30°
就是30π/180
四元数
由于书中并没有详细的讲解四元数的使用,我就简单描述一下他们的优缺点吧
- 欧拉角的缺点:我们不能将两个欧拉角进行相加(更著名的是,它们还存在一种叫做 万向锁的问题)
- 四元数的缺点:虽然四元数没有欧拉角的缺点,但是它比欧拉角更难使用,所以我们一般还是坚持使用更简单的
Euler
类
两种旋转对象的方法:
- 使用欧拉角,使用
Euler
类表示并存储在.rotation
属性中 - 使用四元数,使用
Quaternion
类表示并存储在.quaternion
属性中
转换矩阵
前面我们已经将 转换 的相关内容全部说完了,最后还有一个转换矩阵的概念还需要了解下
因为 向量 和 欧拉角 对我们人来说相对容易使用,但是计算机的处理效率并不高,当我们追求 每秒60帧 这一难以捉摸的目标时,我们必须在 易用性 和 效率 之间找到一条平衡线。为此,将对象的 平移、旋转和缩放 组合成一个称为 矩阵 的 数学对象
这是尚未转换的对象的矩阵的样子。
因为矩阵的使用还是比较复杂的,我学习的也不是很深入,我就不具体讲了😂😂😂,想学习的话还是看书中的讲解吧 矩阵
因为与单独的变换相比,矩阵对CPU和GPU的处理效率要高的多
总结
这一章其实没有什么代码,很多都是一些概念性的东西,也不需要去特别的记忆,因为这些都是一些基础知识,只要写代码,肯定会用到它们,写的多了慢慢地就掌握了
像最后说到的转换矩阵
,我觉得要到进阶阶段
才会去专门学习吧(毕竟自身现在还是个小白)。不过随着项目的不断迭代,体量逐渐增大,渲染速度和响应速度将会变的很慢,这时候就需要提出一些优化解决方案,使用转换矩阵
也许就是一种;当然了,如果有能力提前掌握也是不错的选择😏😏😏
如果文章中有描述不准确或错误的地方,欢迎各位大佬指正😀😀😀