webgl和threejs的坐标系以及坐标转换

发布时间:2024年01月22日

一、模型坐标系和模型坐标

模型坐标系是用来描述模型内部构造的。它的原点是(0,0,0)

在模型坐标系下,定义的坐标,本文称之为模型坐标。当然你也可以有自己的命名。

物理建模的模型,由一个个小的三角形组成,每个三角形都有三个顶点组成。顶点的模型坐标是基于模型中心点的。中心点的坐标为(0,0,0)。

上图是个简单的正方形的模型,正方形长和宽为20。中心点在正方形的中心,中心点模型坐标为(0,0,0),则其中四个顶点的模型坐标为

(-10,10,0)、(10,10,0)、(-10,-10,0)、(10,-10,0)

这些顶点的模型坐标不会随着模型具体的位置的变化而变化。

你可以把自己想成一个模型,你身高2米,当自己是个纸片人时,不考虑z坐标。你的中心点在两脚之间,你的头的模型坐标为(0,2,0)。当你的位置从上海移动到北京时,你的头的模型坐标仍然为(0,2,0)。不可能你人到了北京,你的头距离你的脚就3米了。

二、世界坐标系和世界坐标

世界坐标系是描述整个3D场景的。它的原点是(0,0,0)

我们将模型放置到世界坐标系中,默认是放到原点的。

这里切记,并不是说模型坐标的原点在(0,0,0),模型放在世界坐标系中,就是放在世界坐标系的中心点。虽然都是(0,0,0),但是意义是不一样的。

更多时候我们需要把模型放在世界坐标系原点以外的地方,那么这时候就需要进行变换。这些变换包括平移、旋转和缩放,我们称之为模型变换(model matrix)。图形学中用矩阵去描述这些变换。

你可以想象一下,孙悟空出厂设置在世界中心点,他跑步,翻跟头、放大、缩小着去取经了,他的世界坐标不断在变化着。

现在再看模型的这些顶点,在模型坐标系中它有自己的模型坐标,在世界坐标系中,它又有自己的世界坐标。

仍以上面的正方形模型为例。如果我们把模型沿世界坐标系的x轴移动10个单位,那么各顶点的世界坐标为

(0,10,0)、(20,10,0)、(0,-10,0)、(20,-10,0)

但是各顶点的模型坐标仍为

(-10,10,0)、(10,10,0)、(-10,-10,0)、(10,-10,0)

如何验证顶点的模型坐标不会变化这个问题呢,我们写一个简单的代码。

const box = new THREE.PlaneGeometry(20, 20);
const mesh = new THREE.Mesh(box, new THREE.MeshBasicMaterial({ color: new THREE.Color(0xffffff) }));

我们没有对这个正方形的模型进行任何的变换,它默认是在世界坐标的(0,0,0)位置。我们看到顶点坐标是这样的。

然后我们对正方形沿着x轴方向移动10个单位(mesh.translateX(10),我们打印看一下,顶点坐标仍是上图中显示的那样。

那问题来了,我们如何知道顶点的世界坐标呢???模型坐标与模型的世界变换矩阵相乘就得到了世界坐标

我们可以手动计算,代码如下:

const array = mesh.geometry.getAttribute('position').array;
const size = mesh.geometry.getAttribute('position').count;
mesh.updateMatrixWorld();
for (let i = 0; i < size; i += 1) {
    console.log(
        new THREE.Vector3(array[i * 3 + 0], array[i * 3 + 1], array[i * 3 + 2]).applyMatrix4(mesh.matrixWorld),
    );
}

打印结果如下:

threejs中的matrix和matrixWorld

matrix:局部变换矩阵。我们可以理解为模型和模型父级坐标的变换。如果该模型没有父级,则模型坐标乘以matrix就是世界坐标了。

matrixWorld:世界坐标矩阵。若这个模型没有父级,那它和matrix是一样的。模型坐标乘以matrixWorld就是世界坐标。

我们用代码去验证:

const box = new THREE.PlaneGeometry(20, 20);
const mesh = new THREE.Mesh(box, new THREE.MeshBasicMaterial({ color: new THREE.Color(0xffffff) }));
mesh.translateX(10);
scene.add(mesh);

打印mesh,可以看到当mesh没有父级时matrix和matrixWorld相同

我们将模型放置在一个父级中再放入到场景,执行代码

const box = new THREE.PlaneGeometry(20, 20);
const mesh = new THREE.Mesh(box, new THREE.MeshBasicMaterial({ color: new THREE.Color(0xffffff) }));
mesh.translateX(10);
const group = new THREE.Group();
group.translateX(10);
group.add(mesh);

我们看到下图中,模型的世界变换矩阵为(group.matrix)*(mesh.matrix)。世界变换矩阵是两次变换矩阵相乘的结果。

最后用一张图来描述模型坐标系到世界坐标系的转换,这张图来自opengl的学习文档

把模型通过旋转、平移、缩放等操作,放置到世界坐标系下,现在模型每个顶点处于世界坐标系下,变化矩阵*顶点的模型坐标,就得到了模型顶点在世界坐标系下的世界坐标。

三、观察坐标系和观察坐标

在3D场景中,我们引入了相机的概念。

相机的三要素:原点(0,0,0),朝向z轴的负方向,向上方向,也就是threejs中相机的up方向为y轴正方向。这三个要素构成了观察坐标系

一般我们的相机的位置坐标不会设置在原点,这样相机就做了平移的变化。如我们把相机位置设置在(0,0,1000)的位置,我们查看相机的局部变换矩阵,这里查看相机的变换矩阵。

在此改变基础上,我再修改相机的up方向为(1,0,0),即up方向为x轴的正方向。我们再查看相机的变换矩阵

了解旋转矩阵的可以看出来,相机进行了一个旋转-90度且平移1000的操作。所以改变相机的up方向也会更新相机的变换矩阵。这两个改变同样可以在threejs相机对象的position和rotation属性中查看到。

世界坐标转观察坐标

我们想一下,相机向左动3个单位,世界坐标不变,那是不是和相机不动,世界坐标整体向右移动3个单位的效果是一样的。其他方向同样,旋转也同样逻辑。所以,要想获取到各个顶点在观察坐标系中的观察坐标,只需要执行如下操作

视图矩阵的逆矩阵*各顶点世界坐标

四、裁剪坐标系

到目前为止,我们已经得到了一个以相机为中心的观察坐标系了。但是观察坐标系中不是所有的模型都可以看到的。实际生活中,相机能够拍到范围是有限的,这就需要根据一些参数(比如人眼能看到的最近距离,以及最远距离,以及摄像机视锥体垂直视野角度)去裁剪观察坐标系可视范围。这个裁剪范围是由投影矩阵决定的。

一共有两种投影方式,透视投影和正交投影,透视投影更加符合生活,有近大远小的效果。而正交投影并不会有这样的效果。

如上图,投影就是物体到近平面的投影,正交投影比较简单,透视投影原理其实就是相似三角形。是缩放和平移变换。

上图中主要是x,y的转换,z的转换是个线性的,具体透视矩阵如何推导,这篇文章中不会去推导。本文主要讲各种坐标的意义以及坐标的转换。

裁剪矩阵变换如下(存储在threejs 的camera projectMatrix中)

注意:这里不做坐标转化,我们先将裁剪空间转为标准化空间后再做坐标转换。

五、标准化设备空间

接下来裁剪空间需要进一步转换称标准化设备空间,所以需要进一步的进行新一轮的坐标变换

这样我们就得到一个标准立方体了,也就是标准化设备空间。标准化设备矩阵是如下:

我们通过以下方式获得标准化设备坐标:

标准化设备矩阵*观察坐标

六、屏幕坐标

将标准化设备空间转换到屏幕坐标,这样物体就在屏幕上的合适位置显示了。

屏幕变换矩阵如下:

经过这样的转换,我们就可以在屏幕上看到场景中的模型了。

七、实操

为了更好的理解这些坐标的作用,我现在用planeGeometry绘制一个正方形,边长为1,然后正方形的世界坐标为(10,10,0.1),我们去计算一下正方形的屏幕坐标。

1. 正方形的模型坐标和模型坐标系如下:

2. 设置正方形坐标为(10,10,0.1),模型变换矩阵为 (m1)

3. 设置相机的坐标为(0,0,50),则观察坐标系和观察矩阵如下,注意,在坐标转换过程中我们用视图的逆矩阵(m2)

4. 设置透视相机,fov为60,near:0.1 far:1000,canvasWidth:1607,canvasHeight:915。?计算裁剪矩阵

注意,near和far在裁剪坐标系中为-0.01和-1000,所以在下图计算时请注意看前面的符号

5. 将4的裁剪空间,转成标准化空间[-1,1]的立体空间中,坐标转换如下:(m3)


6. 屏幕坐标系以及坐标矩阵转换,坐标转换如下:(m4)

dom.getBoundingClientRect().left = 0,dom.getBoundingClientRect().left = 79

经过 m1、m2、m3、m4四次矩阵变换,position最终转换为屏幕坐标(961,378)

然后去电脑屏幕上验证一下确实是(961,378)

总结

如何将一个世界坐标下的模型,绘制到屏幕上呢?

屏幕变换*标准设备变换*视图变换*模型变换*模型坐标

文章来源:https://blog.csdn.net/qq_37200686/article/details/134186363
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。