共计 4253 个字符,预计需要花费 11 分钟才能阅读完成。
ThreeJS 坐标系(旋转跳跃,永不停歇)
本文次要介绍 threejs 中 3D 坐标系的内容,内容蕴含 世界坐标系(world coordinate system)与 部分坐标系(local coordinate system)之间的区别与关系;3D 对象之间建设父子关系时平移、旋转、缩放转换;简略介绍底层数学层面如何反对 3D 对象进行平移、旋转、缩放。
3D 坐标系
3D 空间通常采纳 3 个坐标轴来进行示意别离是 x、y、z。这三个坐标轴有两种安排形式别离是左手坐标系和右手坐标系(它们是以坐标轴的朝向来命名的)。
openGL 采纳的是 右手坐标系,WebGL 是 openGL 的 JS 版本也是右手坐标系。ThreeJS 仍然采纳的右手坐标系。而 Direct3D 应用的坐标系则是左手坐标系。右手坐标系和 3D 笛卡尔坐标系是一样的。
3D 笛卡尔坐标系
ThreeJS 中有几个 2D、3D 坐标系,本文次要介绍最重要的世界坐标系(world coordinate system)、部分坐标系(local coordinate system)两个。残余的会在该系列的其余文章中进行介绍(屏幕坐标系、相机坐标系等)。
世界坐标系 & 部分坐标系
上一篇文章【加个传送门: ThreeJS 入门创立第一个 3D 对象】中有介绍 THREE.Scene 创立 scene 场景,创立后的 scene 就是个小宇宙,这个小宇宙就是世界空间,世界空间中的坐标系称为世界坐标系。
scene 定义世界坐标系
当咱们往 scene 中增加 3D 对象并进行平移、旋转、缩放等操作时,该对象就是绝对于 scene 的世界坐标系进行挪动。
scene 中的 Mesh
通常应用批改 position 值来实现平移;通过批改 rotation 值来实现旋转;应用设置 scale 来实现缩放。
// 创立场景
const scene = new THREE.Scene();
// 新建一个 mesh
const geometry = new THREE.BoxGeometry(1,1,1);
const material = new THREE.MeshBasicMaterial({color: 0x00000ff});
const meshA = new THREE.Mesh(geometry, material);
// 将 mesh 增加到 scene 中
scene.add(meshA);
// 将 meshA 延 x 轴方向挪动 5m,这里的 x 轴是绝对于世界坐标系而言
meshA.position.x = 5;
在 ThreeJS 中长度单位默认是米(meter), 通过设置 position 的值就是来进行平移。scene 中可增加 Mesh 对象、光照对象、相机对象等。Mesh 外部还波及到部分空间和部分坐标系(local coordinate system),Mesh 上还能够增加 Mesh(将一个 Mesh 作为另一个 Mesh 的 children,child Mesh 地位变换就会绝对于 parent Mesh 的部分坐标系进行变换计算)。再回过头来看看这张图:
scene 中的 Mesh
咱们应用一个简略的 demo 来介绍这两头的区别,创立两个 Mesh。meshA 棕红色 2 2 2 的立方体,meshB 红色的 1 1 1 的立方体。
第一次验证:两个 Mesh 平级增加到 scene 中 其中 meshA 延 X 轴挪动 1m,meshB 延 Y 轴挪动 1m。
第二次验证:将 meshB 增加到 meshA 上,meshB 作为 meshA 的 children。执行同样的挪动操作
开始试验前,咱们创立一个 scene 的全局辅助坐标系。
// AxesHelper:辅助察看的坐标系
const axesHelper = new THREE.AxesHelper(200)
scene.add(axesHelper)
红色 X 轴正方向,绿色 Y 轴正方向。成果如下:
试验一:两个 mesh 平级,执行挪动命令
// 新建一个 meshA 蓝色的 2*2*2 的立方体
const geometry = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshMatcapMaterial({
color: 0x862d2d,
opacity: 0.2,
})
const meshA = new THREE.Mesh(geometry, material)
meshA.position.set(1.1, 1.1, 0)
// 新建一个 meshB 红色 1*1*1 的立方体
const geometryB = new THREE.BoxGeometry(1, 1, 1)
const materialB = new THREE.MeshMatcapMaterial({color: 0xff0000})
const meshB = new THREE.Mesh(geometryB, materialB)
meshA.add(meshB)
// 为了将两个立方体地位展现进去,须要将 meshB 向 z 轴(相机方向凑近一点)meshB.position.z = 1
// 将 mesh 增加到 scene 中
scene.add(meshA)
// 新建一个 meshA 蓝色的 2*2*2 的立方体
const geometry = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshMatcapMaterial({
color: 0x862d2d,
opacity: 0.2,
})
const meshA = new THREE.Mesh(geometry, material)
// 新建一个 meshB 红色 1*1*1 的立方体
const geometryB = new THREE.BoxGeometry(1, 1, 1)
const materialB = new THREE.MeshMatcapMaterial({color: 0xff0000})
const meshB = new THREE.Mesh(geometryB, materialB)
// 为了将两个立方体地位展现进去,须要将 meshB 向 z 轴(相机方向凑近一点)meshB.position.z = 1
// 将 mesh 增加到 scene 中
scene.add(meshA)
scene.add(meshB)
// meshA 往 x 轴挪动 2m
meshA.position.x = 2
// meshB 往 y 轴挪动 2m
meshB.position.y = 2
此时两个立方体都是绝对于世界坐标系进行的挪动,相互之间没有影响。
试验二:将 meshB 增加到 meshA 上,meshB 作为 meshA 的 children。执行同样的挪动命令。
// 新建一个 meshA 蓝色的 2*2*2 的立方体
const geometry = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshMatcapMaterial({
color: 0x862d2d,
opacity: 0.2,
})
const meshA = new THREE.Mesh(geometry, material)
meshA.position.set(1.1, 1.1, 0)
// 新建一个 meshB 红色 1*1*1 的立方体
const geometryB = new THREE.BoxGeometry(1, 1, 1)
const materialB = new THREE.MeshMatcapMaterial({color: 0xff0000})
const meshB = new THREE.Mesh(geometryB, materialB)
meshA.add(meshB)
// 为了将两个立方体地位展现进去,须要将 meshB 向 z 轴(相机方向凑近一点)meshB.position.z = 1
// 将 mesh 增加到 scene 中
scene.add(meshA)
// 新建一个 meshA 蓝色的 2*2*2 的立方体
const geometry = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshMatcapMaterial({
color: 0x862d2d,
opacity: 0.2,
})
const meshA = new THREE.Mesh(geometry, material)
// 新建一个 meshB 红色 1*1*1 的立方体
const geometryB = new THREE.BoxGeometry(1, 1, 1)
const materialB = new THREE.MeshMatcapMaterial({color: 0xff0000})
const meshB = new THREE.Mesh(geometryB, materialB)
// 为了将两个立方体地位展现进去,须要将 meshB 向 z 轴(相机方向凑近一点)meshB.position.z = 1
// 将 mesh 增加到 scene 中
scene.add(meshA)
// !!!! 留神代码变更 scene.add(meshB) -> meshA.add(meshB)
meshA.add(meshB)
// meshA 往 x 轴挪动 2m
meshA.position.x = 2
// meshB 往 y 轴挪动 2m
meshB.position.y = 2
试验二比照试验一只有一行代码变更:scene.add(meshB) -> meshA.add(meshB)
执行了同样的挪动命令,然而成果确不一样。起因是:试验一,两个立方体都是绝对于世界坐标系进行的挪动,彼此之间互不影响 (矩阵计算上不会影响)。 试验二中 meshA 仍然是绝对于世界坐标系进行挪动,meshB 挪动参考的坐标系不再是世界坐标系而是 meshA 的部分坐标系。meshA 挪动的时候曾经将它的部分坐标系沿着世界坐标系进行挪动了 2m。
法则:每当咱们变换一个对象时,都是绝对于它的父坐标系进行的。
试验二中的关系:scene->meshA->meshB
计算时 meshA 挪动计算绝对于 scene 的坐标系(这里就是世界坐标系),meshB 挪动计算时绝对的坐标系是 meshA 的部分坐标系。
每个人都须要本人的房子,不然孩子没中央住。每个 mesh 都有本人的空间,所有的 children 都将参考 parent 的坐标系进行变换【挪动、旋转、缩放】。
尽管每个 mesh 都有本人的坐标系,然而最初出现到屏幕上咱们看见的还是基于世界坐标系的。
咱们介绍了平移操作(批改 position 值来实现),其余两种变换是怎么进行的呢?旋转和缩放操作的单位还是 meter 么?欧拉角、四元数是什么?为什么是三维坐标系最初矩阵计算的时候却是四行四列的矩阵计算呢?欢送持续跟踪 ThreeJS 系列文章。
本文由 mdnice 多平台公布