OpenGL中涉及到的矩阵变换

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xyh930929/article/details/83715368


先看两个问题:

  1. 我们有什么?答:模型的初始位置坐标(三维坐标)。
  2. 我们要得到什么?答:我想要把模型展示在屏幕上展示的二维坐标(二维坐标)。

从初始位置坐标到平面的二维坐标经过了下面的流程,后面逐一介绍。
在这里插入图片描述

1、局部坐标系

局部坐标系指的是图中的LOCAL SPACE,又被称作本地坐标系、本地空间、局部空间等。总是这就是一个小范围的概念,描述的只是模型本身的坐标。也是模型文件中存储的顶点坐标所在的坐标系。

2、世界坐标系

  • 世界坐标系指的是图中的WORLD SPACE。刚开始接触坐标变换时,会有这样的疑问,为什么要有世界坐标系?直接用局部坐标系作为世界坐标系为什么不行?原因是局部坐标系只是相对于单独一个模型的,当我们想在屏幕上看到很多个模型时,如果我们以某个模型所在的局部坐标系为基准去构造其他的模型,那么就很有可能会出现所有的模型都叠放在了原点。所以,世界坐标用来确定每个模型的局部坐标系的位置。也就是每个模型在三维空间中的位置。世界坐标系所描述的空间通常会被抽象成"场景"或者"世界"。

  • 为了能让场景中所有的模型都能进行统一的变换,所以必须将所有的模型自身相对于局部坐标系的坐标,转换为相对于世界坐标系中的坐标。这个过程只要将模型的坐标进行相应的平移变换就能得到。而这一步进行的平移变换,就是图中提到的模型矩阵(Model Matrix),用模型相对于局部坐标系的位置坐标右乘一个矩阵就可以得到相对于世界坐标系的坐标。

2.1 为什么要用矩阵?

到这里应该有个疑问,为什么平移要用到矩阵?其实不光是平移,模型在场景中的伸缩、旋转、视图变换、投影变换也是通过矩阵变换来做的。应用矩阵来计算这些变换的原因如下:

  1. 在线性代数中,所有的线性变换(见下面解释)都可以通过构造一个矩阵,并以矩阵乘法的方式完成。
  2. 在图形变换中,涉及到就是空间映射(通过一个坐标系空间的坐标求出对应另外一个坐标系中的坐标就是空间映射)和顶点变换,这这种变换都是线性变换。
  3. 所以,正好可以将图形中的变换应用线性代数中的矩阵变换来计算,这样做可以将所有的变换都统一成矩阵变换,也方便GPU在计算坐标时进行优化。

线性变换的概念。

  • 线性变换:指的是一个向量空间V到自身的映射。换句话说,一个n维向量在经过线性变换以后仍然是n维向量的变换,并不会导致它变成n+1维或者更大维度的向量。线性变换的数学定义——线性空间V上的一个变换A称为线性变换,对于V中任意的元素α,β和数域P中任意k,都有:
    在这里插入图片描述

2.2 模型矩阵(Model Matrix)

在模型顶点坐标由局部坐标系变换到世界坐标系的过程中。可以只经过平移变换来进行。除此之外,在这一过程中,也可以随模型的形态进行设置,例如伸缩、旋转等。

首先来看最简单的伸缩变换:假设有一个顶点的坐标为(x,y,z),那么将该点的x、y、z分别在其分量上伸缩 S x S_x S y S_y S z S_z ,则变换过程如下:

[ x S x y S y z S z ] = [ S x 0 0 0 S y 0 0 0 S z ] [ x y z ] = [ x 1 y 1 z 1 ] \left[ \begin{matrix} x*S_x \\ y *S_y\\ z *S_z \end{matrix} \right] = \left[ \begin{matrix} S_x &0 &0\\ 0 &S_y & 0\\ 0 &0&S_z \end{matrix} \right]*\left[ \begin{matrix} x \\ y \\ z \end{matrix} \right] = \left[ \begin{matrix} x_1 \\ y_1 \\ z_1 \end{matrix} \right]

发现伸缩的过程是可以写成一个顶点坐标左乘一个 3 3 3*3 矩阵(矩阵的乘法可参考这篇文章图形变换中涉及到的数学知识)来实现的。再看一下平移的过程:发现一个问题,如果像伸缩一样来处理平移变换,是没有办法将平移变换写成 3 3 3*3 矩阵右乘的形式的
[ x + T x y + T y z + T z ] = [ x 1 y 1 z 1 ] \left[ \begin{matrix} x+T_x \\ y +T_y\\ z +T_z \end{matrix} \right] = \left[ \begin{matrix} x_1 \\ y_1 \\ z_1 \end{matrix} \right]

解决此问题的方法是用齐次坐标(可参考这篇文章图形变换中涉及到的数学知识)来表示顶点坐标,给顶点坐标加一个维度再进行相应的计算,就可以将平移变换的过程转化成下面的流程。

同样,为了保证统一,伸缩的过程可以转换为:

旋转过程的变换矩阵有三种,分别是沿着x、y、z轴进行旋转的矩阵(三个矩阵的由来可参考这篇文章图形变换中涉及到的数学知识):

3、视图坐标系

       人眼在观察一个物体,在不同角度不同位置去观察,看到的效果是不一样的。所以,在三位场景中,我们除了定义模型本身以外,还需要定义观察点位置、观察方向等。定义完观察点的相关信息以后,就可以得到一个在该观察点位置所看到场景状态。这个场景的每个位置的坐标也是需要定义的。而定义这个图像坐标的坐标系,被成为视图坐标系

3.1 视图坐标系的定义

  • 观察点位置( v i e w P o s i t i o n viewPosition ):世界坐标中的一个位置坐标。
  • 观察点方向( v i e w D i r e c t i o n viewDirection ): v i e w D i r e c t i o n = v i e w P o s i t i o n w o r l d O r i g i n viewDirection=viewPosition-worldOrigin ,观察点方向等于观察点位置减去世界坐标原点。
  • z z v i e w D i r e c t i o n viewDirection 所在直线作为视图坐标系的z轴。该方向为z轴负方向, v i e w D i r e c t i o n -viewDirection 为z轴正方向。
  • x x :以观察点为始点,沿着世界坐标系的y轴,去一个向量,我们称为UpDirection。用 v i e w D i r e c t i o n viewDirection U p D i r e c t i o n UpDirection 进行叉乘,可以得到x轴(为什么叉乘可以得到x轴可参考这篇文章图形变换中涉及到的数学知识)。
  • y y :用z轴和x进行叉乘就可以得到y轴了。

叉乘的几何意义:两个向量叉乘可以得到一个与两个向量都垂直的一个向量。

3.2 视图矩阵

       视图矩阵(流程图中的View Matrix),使用矩阵的好处之一是如果你定义了一个坐标空间,里面有3个相互垂直的轴,你可以用这三个轴外加一个平移向量来创建一个矩阵,你可以用这个矩阵乘以任何向量来变换到那个坐标空间。这正是视图矩阵(流程图中的View Matrix)所做的,现在我们有了3个相互垂直的轴和一个定义摄像机空间的位置坐标,我们可以创建视图矩阵了。

4、投影坐标系

  • 以上的变换都是针对三维的顶点坐标进行变换。但是最终我们呈现在屏幕上的图像是一个二维坐标。将一定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection),因为使用投影矩阵能将3维坐标投影(Project)到很容易映射的2D标准化设备坐标系中。

  • 除了将三维坐标转换成二维坐标以外,场景中的模型坐标并不是所有的点都需要被呈现出来,因为屏幕的大小是固定的,如果模型被扩大了很多倍,或者场景内的模型非常多,不可能将场景内的内容全部都呈现出来,需要将场景进行裁剪。

例如,每个维度都是从-1000到1000。投影矩阵接着会将在它指定的范围内的坐标转换到标准化设备坐标系中(-1.0,1.0)。所有在在范围(-1.0,1.0)外的坐标都不会被绘制出来并且会被裁剪。在投影矩阵所指定的范围内,坐标(1250,500,750)将是不可见的,这是由于它的x坐标超出了范围,随后被转化为在标准化设备坐标中坐标值大于1.0的值并且被裁剪掉。

所以在顶点坐标经过视图坐标系到投影坐标系需要做两件事:将三维坐标转为二维坐标(通过投影实现)和裁剪(由投影空间决定)。为了将顶点坐标从观察空间转换到投影坐标系,我们需要定义一个投影矩阵(Projection Matrix),它指定了坐标的范围。

投影空间分为两种:正射投影(Orthographic Projection)透视投影(Perspective Projection).

  • 正射投影(Orthographic Projection):矩阵定义了一个类似立方体的平截头体,指定了一个裁剪空间,每一个在这空间外面的顶点都会被裁剪。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在这里插入图片描述

正射投影矩阵:一个立方体投影空间的确定需要6个,即 : left , right , bottom , top , near , far

(正射投影矩阵) 2 / ( r i g h t l e f t ) 0 0 ( r i g h t + l e f t ) / ( r i g h t l e f t ) 0 2 / ( t o p b o t t o m ) 0 ( t o p + b o t t o m ) / ( t o p b o t t o m ) 0 0 2 / ( f a r n e a r ) ( f a r + n e a r ) / ( f a r n e a r ) 0 0 0 1 \begin{matrix} 2 / (right - left) & 0 & 0 & -(right + left)/(right - left)\\ 0 & 2 / (top - bottom) & 0 & -(top + bottom)/(top - bottom)\\ 0 & 0 &-2 / (far - near) & -(far + near)/(far - near) \\ 0 & 0 & 0 & 1 \end{matrix} \tag{正射投影矩阵}

  • 透视投影(Perspective Projection):它是从某个投射中心将物体投射到单一投影面上所得到的图形。透视图与人们观看物体时所产生的视觉效果非常接近。

透视投影矩阵 : 四棱锥可视空间是由以视点为顶点的四棱锥构成 , 确定四棱锥可视空间的参数有fov(可视顶面和底面的夹角) , aspect(远/近裁切面的宽高比) , near(近裁切面位置) , far(远裁切面位置) 。

(正射投影矩阵) M a t h . c o s ( f o v / 2 ) / ( a s p e c t s i n ( f o v / 2 ) ) 0 0 0 0 M a t h . c o s ( f o v / 2 ) / M a t h . s i n ( f o v / 2 ) 0 0 0 0 ( f a r + n e a r ) / ( f a r n e a r ) 2 n e a r f a r / ( f a r n e a r ) 0 0 1 0 \begin{matrix} Math.cos(fov/2) /(aspect * sin(fov/2)) & 0 & 0 & 0\\ 0 & Math.cos(fov/2) /Math.sin(fov/2) & 0 &0\\ 0 & 0 & -(far + near) / (far - near) & -2 * near * far / (far - near)\\ 0 & 0 & -1& 0 \end{matrix} \tag{正射投影矩阵}

投影坐标系可以理解为投影空间中近裁切面接收投影时的一个二维坐标系。

5、屏幕坐标系

  • 最开始的顶点坐标在经过以上4个坐标系、3个变换矩阵。得到了当前的坐标。每个矩阵被运算的顺序是相反的(记住我们需要从右往左乘上每个矩阵)。最后的顶点应该被赋予顶点着色器中的gl_Position且OpenGL将会自动进行透视划分和裁剪。最终经过投影后的点的坐标为:
  • 顶点着色器的输出需要所有的顶点都在裁剪空间内,而这是我们的转换矩阵所做的。OpenGL然后在裁剪空间中执行透视划分从而将它们转换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(屏幕上的点就是由屏幕坐标系来定义的)。这个过程称为视口转换

到此为止,一个3维的顶点坐标经过一系列的变换最终被映射到了屏幕上。

猜你喜欢

转载自blog.csdn.net/xyh930929/article/details/83715368