【可视化学习】79-从入门到放弃WebGL(十三)
发表于:2024-08-09 |

前言

之前,我们说过canvas和css和webgl坐标系的转化,其实在绘图的过程中,我们有时候可以根据物体本身作为一个坐标系来进行绘图,能够更加简单的实现我们想要的效果。我简单举个我们前端都知道的例子,比如我们在绘制普通的前端页面的时候,有一个父级dom,和子级的dom,子级dom的位置是相对于父级偏移的,我们这时候就给父级dom会添加一个position:'relative',子级的dom添加position:'absolute'来使用,让子级更好的画出来,而不是使用子级dom相对于body来进行位置偏移。

已知某点的本地坐标系求世界坐标系

举例说明

就比如这样一个立方体
示意图
我们以他的中心点到上平面交接的点,作为我们这个点的观察点,在他的本地坐标系中,它的位置是(0,1,0),(我们假设立方体的高度为2),他本地坐标的位置是(5,0,0),我们可以经过自己的理解,它的世界坐标系原点是(0,0,0),得到它的位置世界坐标系是(5,1,0)
又比如我们将这个立方体向上(y)移动了5,那么我们可以得到的是,本地坐标系变成了(5,5,0),在本地坐标系中,它的位置依旧是(0,1,0),而它的世界坐标位置是(5,6,0)

通过以上俩个例子,我们大概可以得到本地坐标和世界坐标转化的规律,也就是本地坐标系的位置减去世界坐标系位置加上这个的本地坐标。

1
2
(5,1,0)=(5,0,0)-(0,0,0)+(0,1,0);
(5,6,0)=(5,5,0)-(0,0,0)+(0,1,0);

公式推演

那转化为矩阵的写法,那就是:

已知:

  1. 世界坐标系[O1;i1,j1,k1]
  2. 点P
  3. 点P所处的本地坐标系是[O2;i2,j2,k2]
  4. 世界坐标系[O1;i1,j1,k1]∋本地坐标系[O2;i2,j2,k2]

[O;i,j,k]中:

  • O 是坐标原点
  • i,j,k 是坐标向量

根据空间向量分解定理。

由世界坐标系[O1;i1,j1,k1]可解析出四维矩阵m1:

1
2
3
4
5
6
[
i1.x,j1.x,k1.x,0,
i1.y,j1.y,k1.y,0,
i1.z,j1.z,k1.z,0,
O1.x,O1.y,O1.z,1
]

同理,由本地坐标系[O2;i2,j2,k2]可解析出四维矩阵m2:

1
2
3
4
5
6
[
i2.x,j2.x,k2.x,0,
i2.y,j2.y,k2.y,0,
i2.z,j2.z,k2.z,0,
O2.x,O2.y,O2.z,1
]

点P的世界位是:

1
m1*m2*(x,y,z)

公式验证

在上一步我们推导出来了一个公式,这里我们验证一下这个公式,我们将i1,j1,k1分别定义为单位向量,P点本地坐标为(4,5,6),世界坐标系为(0,0,0),本地坐标系(1,2,3),根据我们第一步的计算,可以得到P点的世界坐标位置为(1-0+4,2-0+5,3-0+6)=(5,7,9)
我们接下来用矩阵来计算一下。

1
2
3
4
O1(0,0,0)
i1(1,0,0)
j1(0,1,0)
k1(0,0,1)

此时,矩阵为

1
2
3
4
5
6
[
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1
]

同理,将i2,j2,k2也变成单位向量

1
2
3
4
O2(1,2,3)
i2(1,0,0)
j2(0,1,0)
k2(0,0,1)

此时,矩阵为

1
2
3
4
5
6
[
1,0,0,0,
0,1,0,0,
0,0,1,0,
1,2,3,1
]

然后我们用矩阵计算得到的

1
2
3
P1=m1*m2*(4,5,6)
P1=(1+4,2+5,3+6)
P1=(5,7,9)

m1*m2为,因为矩阵乘以单位矩阵等于它本身

1
2
3
4
5
6
[
1,0,0,0,
0,1,0,0,
0,0,1,0,
1,2,3,1
]

然后再乘以(4,5,6)
[
1,0,0,0,
0,1,0,0
0,0,1,0
4,5,6,1
]
因为这都是列主序的矩阵,我们大学平时学的都是行主序的矩阵,你可以先转化一下再计算。计算规则就是每行乘以每一列,如下图
矩阵乘法
那么就可以得到P点的世界坐标系点位是(5,7,9),和我们猜想的一样。

借助Threejs验证公式

导入
1
import { Group, Matrix4, Object3D,Scene, Vector3, } from 'https://unpkg.com/three/build/three.module.js';
创建世界坐标系和本地坐标系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//世界坐标系
const m1 = new Matrix4()
m1.elements = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]

//本地坐标系
const m2 = new Matrix4()
m2.elements = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
1, 2, 3, 1
]
申明P的位置
1
const P2 = new Vector3(4, 5, 6)
世界坐标系加入到场景中
1
2
const universe = new Scene()
universe.applyMatrix4(m1)

applyMatrix4() 通过四维矩阵赋予对象坐标系

创建一个组放本地坐标系
1
2
const galaxy = new Group()
galaxy.applyMatrix4(m2)
创建点位
1
2
const sun = new Object3D()
sun.position.copy(P2)
设置包含关系
1
2
galaxy.add(sun)
universe.add(galaxy)
得到世界位置
1
2
3
4
const P1 = new Vector3()
sun.getWorldPosition(P1)
console.log(P1);
//{x:5,y:7,z:9}

这个结果和我们之前推理的是一样的。

位移法则

有了上面的讲解,我们大概可以联想到位移,就是将本地坐标系和世界坐标系进行相对位置的移动。那么我们可以多位移几次看看

m1

1
2
3
4
5
6
7
 [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]

m2

1
2
3
4
5
6
 [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
1, 2, 3, 1
]

m3

1
2
3
4
5
6
[
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
4, 5, 6, 1
]

p

1
2
3
4
5
6
[
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
7, 8, 9, 1
]

得到位置

1
2
3
P1=m1*m2*m3*(7,8,9)
P1=(1+4+7,2+5+8,3+6+9)
P1=(12,15,18)

借助Threejs验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import { Group, Matrix4, Object3D, Scene, Vector3, } from 'https://unpkg.com/three/build/three.module.js';

//世界坐标系-宇宙
const m1 = new Matrix4()
m1.elements = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]

//本地坐标系-银河系
const m2 = new Matrix4()
m2.elements = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
1, 2, 3, 1
]

//本地坐标系-太阳系
const m3 = new Matrix4()
m3.elements = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
4, 5, 6, 1
]

//本地坐标位-地球
const P3 = new Vector3(7, 8, 9)


//宇宙(世界坐标系是宇宙的本地坐标系)
const universe = new Scene()
universe.applyMatrix4(m1)
console.log(universe.position)
console.log(universe.matrix)

//银河系
const galaxy = new Group()
galaxy.applyMatrix4(m2)

//太阳系
const solar = new Group()
solar.applyMatrix4(m3)

//地球
const earth = new Object3D()
earth.position.copy(P3)

//包含关系
solar.add(earth)
galaxy.add(solar)
universe.add(galaxy)

//点P的世界位
const P1 = new Vector3()
earth.getWorldPosition(P1)
console.log(P1);
//{x: 12, y: 15, z: 18}

缩放法则

修改之前已知条件:

在银河系的本地坐标系[O2;i2,j2,k2]中,让j2是单位向量的2倍:

1
2
3
4
O2(1,2,3)
i2(1,0,0)
j2(0,2,0)
k2(0,0,1)

在太阳系的本地坐标系[O3;i3,j3,k3],让k3是单位向量的3倍:

1
2
3
4
O3(4,5,6)
i3(1,0,0)
j3(0,1,0)
k3(0,0,3)

求:地球的世界坐标位P1

解:

由银河系的本地坐标系可得矩阵m2:

1
2
3
4
5
6
[
1, 0, 0, 0,
0, 2, 0, 0,
0, 0, 1, 0,
1, 2, 3, 1
]

由太阳系的本地坐标系可得矩阵m3:

1
2
3
4
5
6
[
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 3, 0,
4, 5, 6, 1
]

求地球的世界坐标位P1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
P1=m1*m2*m3*(7,8,9)
m1*m2*m3=[
1, 0, 0, 0,
0, 2, 0, 0
0, 0, 3, 0
4+1,2*5+2,6+3,1
]
m1*m2*m3=[
1,0, 0,0,
0,2, 0,0,
0,0, 3,0,
5,12,9,1
]
P1=(7+5,16+12,27+9)
P1=(12,28,36)
测试

基于“位移法则”的three.js代码改改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//本地坐标系-银河系
const m2 = new Matrix4()
m2.elements = [
1, 0, 0, 0,
0, 2, 0, 0,
0, 0, 1, 0,
1, 2, 3, 1
]

//本地坐标系-太阳系
const m3 = new Matrix4()
m3.elements = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 3, 0,
4, 5, 6, 1
]

得到的位置依旧是P1=(12,28,36),可见缩放对我们求某点(已知本地坐标)求世界坐标是没有影响的,也就可以得到一个本地坐标转世界坐标的一个公式

1
P1=m1*m2*……*mn*pn

旋转法则

我们在有了这个公式

1
P1=m1*m2*……*mn*pn

就可以用旋转来计算本地坐标转世界坐标位置了
修改之前已知条件:

让银河系的本地坐标系[O2;i2,j2,k2]绕j2轴逆时针旋转20°。

设:c2=cos(-20°),s2=sin(-20°)

则:

1
2
3
4
O2(1,2,3)
i2(c2,0,-s2)
j2(0,1,0)
k2(s2,0,c2)

让太阳系的本地坐标系[O3;i3,j3,k3]绕k3轴逆时针旋转30°

设:c3=cos(30°),s3=sin(30°)

则:

1
2
3
4
O3(4,5,6)
i3(c3,-s3,0)
j3(s3,c3,0)
k3(0,0,1)

求:地球的世界坐标位P1

解:

由银河系的本地坐标系可得矩阵m2:

1
2
3
4
5
6
[
c2, 0, s2, 0,
0, 1, 0, 0,
-s2,0, c2, 0,
1, 2, 3, 1
]

由太阳系的本地坐标系可得矩阵m3:

1
2
3
4
5
6
[
c3, s3, 0, 0,
-s3, c3, 0, 0,
0, 0, 1, 0,
4, 5, 6, 1
]

求地球的世界坐标位P1:

1
2
3
4
5
6
7
P1=m1*m2*m3*(7,8,9)
m1*m2*m3=[
c2*c3, s3, s2*c3, 0,
-c2*s3, c3, -s2*s3, 0,
-s2, 0, c2, 0,
c2*4-s2*6+1,5+2,s2*4+c2*6+3,1
]

P1=(11.826885919330648,17.428203230275507,15.02200238270646)
注,上式很难像之前那样心算,可以直接用计算机算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//让银河系的本地坐标系[O2;i2,j2,k2]绕j2轴逆时针旋转20°
const ang2 = -20 * Math.PI / 180
const c2 = Math.cos(ang2)
const s2 = Math.sin(ang2)

//让太阳系的本地坐标系[O3;i3,j3,k3]绕k3轴逆时针旋转30°
const ang3 = 30 * Math.PI / 180
const c3 = Math.cos(ang3)
const s3 = Math.sin(ang3)


const m=new Matrix4()
m.elements = [
c2 * c3, s3, s2 * c3, 0,
-c2 * s3, c3, -s2 * s3, 0,
-s2, 0, c2, 0,
c2 * 4 - s2 * 6 + 1, 5 + 2, s2 * 4 + c2 * 6 + 3, 1
]
const P1 = P3.applyMatrix4(m)
console.log(P1);
Three验证
1
2
3
4
5
6
7
8
9
10
11
//本地坐标系-银河系
const ang2 = 20 * Math.PI / 180
const m2 = new Matrix4()
m2.makeRotationY(ang2)
m2.setPosition(1, 2, 3)

//本地坐标系-太阳系
const ang3 = 30 * Math.PI / 180
const m3 = new Matrix4()
m3.makeRotationZ(ang3)
m3.setPosition(4, 5, 6)

我们可以得到,俩个得到的结果是一样的

公式总结

本文一整篇下来,就是为了得到一个公式:
本地坐标转世界坐标的底层实现就是

1
P1=m1*m2*……*mn*pn

结语

本篇文章就到这里了,更多内容敬请期待,债见~

上一篇:
【可视化学习】80-图形系统如何表示颜色
下一篇:
【Canvas学习】04-canvas生成图形与曲线