WebGL 深入认知三维世界

581 阅读7分钟

1-用位移矩阵做实验

1-1-示例

已知:

  • 宇宙universe

  • 宇宙的本地坐标系是[O1;i1,j1,k1]

    • O1(0,0,0)
    • i1(1,0,0)
    • j1(0,1,0)
    • k1(0,0,1)
  • 宇宙包含万物,其本地坐标系就是万物的世界坐标系

  • 银河系galaxy

  • 银河系的本地坐标系是[O2;i2,j2,k2]

    • O2(1,2,3)
    • i2(1,0,0)
    • j2(0,1,0)
    • k2(0,0,1)
  • 太阳sun

  • 太阳在银河系内的本地坐标位是P2(4,5,6)

  • 太阳∈银河系∈宇宙

求:太阳的世界位P1

解:

由宇宙坐标系[O1;i1,j1,k1]解矩阵m1:

[    1,0,0,0,    0,1,0,0,    0,0,1,0,    0,0,0,1]

由银河系[O2;i2,j2,k2]解矩阵m2:

[    1,0,0,0,    0,1,0,0,    0,0,1,0,    1,2,3,1]

点P的世界坐标位是:

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

接下来我们拿three.js验证一下

1-2-验证

1.从three.js 中引入我们要用到的方法

import { Group, Matrix4, Object3D,Scene, Vector3, } from 'https://unpkg.com/three/build/three.module.js';

2.基于世界坐标系和本地坐标系构建矩阵

//世界坐标系-宇宙
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
] 

3.声明太阳在银河系内本地坐标P2

//本地坐标位-太阳
const P2 = new Vector3(4, 5, 6)

4.创造一个宇宙

const universe = new Scene()
universe.applyMatrix4(m1)

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

5.同理,创造银河系

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

6.创造太阳

const sun = new Object3D()
sun.position.copy(P2)

太阳的position属性便是其在银河系中的相对位

7.宇宙、银河系和太阳的包含关系:太阳∈银河系∈宇宙

galaxy.add(sun)
universe.add(galaxy)

8.计算太阳的在宇宙中的世界位

const P1 = new Vector3()
sun.getWorldPosition(P1)
console.log(P1);
//{x:5,y:7,z:9}

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

接下来咱们借此深度探究一下位移的法则。

2-位移法则

如果我们不想求太阳的位置,而是想求太阳系内的地球的位置,那是否还可以按照我们之前的思路来求解?

答案是肯定的。

2-1-示例

调整一下之前的已知条件。

  • 把太阳改成太阳系solar

  • 太阳系的本地坐标系是[O3;i3,j3,k3]

    • O3(4,5,6)
    • i3(1,0,0)
    • j3(0,1,0)
    • k3(0,0,1)
  • 地球earth

  • 地球在太阳系内的本地坐标位是P3(7,8,9)

  • 地球∈太阳系∈银河系∈宇宙

求:地球的世界坐标位P1

解:

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

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

求地球的世界坐标位P1:

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

2-2-验证

按照之前的原理用three.js验证一番:

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}

2-3-推理

我们可以从上面的结论中得到一个规律:

当一点P和宇宙之间存在n层嵌套

点P的本地坐标位是Pn

第n层世界的本地坐标系所对应的矩阵是mn

则点P的世界位P1是:

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

上面的公式,我们就暂且叫它“本地坐标转世界坐标公式”了,我不知其有没有学名,就先这么叫着了。

接下来,我们再思考一个问题。

之前我们对所有坐标系只是进行了位移操作,那如果我们对其做了缩放和旋转操作,上式是否成立呢?

3-缩放法则

3-1-示例

修改之前已知条件:

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

    • O2(1,2,3)
    • i2(1,0,0)
    • j2(0,2,0)
    • k2(0,0,1)
  • 在太阳系的本地坐标系[O3;i3,j3,k3],让k3是单位向量的3倍:

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

求:地球的世界坐标位P1

解:

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

[    1, 0, 0, 0,    0, 2, 0, 0,    0, 0, 1, 0,    1, 2, 3, 1]

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

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

求地球的世界坐标位P1:

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)

3-2-测试

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

//本地坐标系-银河系
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
]

运行代码,可得到和我们刚才计算的一样的结果。

由此可见,当坐标系发生缩放时,本地坐标转世界坐标公式依旧成立

接下咱们再说旋转。

4-旋转法则

4-1-示例

修改之前已知条件:

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

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

    则:

    • 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°)

    则:

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

求:地球的世界坐标位P1

解:

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

[    c2, 0, s2, 0,    0,  1, 0,  0,    -s2,0, c2, 0,    1,  2, 3,  1]

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

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

求地球的世界坐标位P1:

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)

注,上式很难像之前那样心算,可以直接用计算机算:

//让银河系的本地坐标系[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);

4-2-验证

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

//本地坐标系-银河系
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)

运行代码,可得到和我们刚才计算的一样的结果。

由此可见,当坐标系发生旋转时,本地坐标转世界坐标公式依旧成立

然而,细心的同学可能会发现一个问题:

我在旋转矩阵的时候,只是在让矩阵绕xyz轴的某一个坐标向量进行旋转。

那我能不能让矩阵绕任意向量旋转呢?

亦或者,能不能先绕x旋转angX度,再绕y轴旋转angY度?

这肯定是可以的,接下来我们就对旋转法则进行深度探索。