OpenGL之矩阵变换的原理分析与数学推导

什么是矩阵变换?

矩阵变换示意图
这张图比较直观地展示了OpenGL矩阵变换的过程,下面详解一下其中的含义:

  • 首先OpenGL有个世界坐标系,我们渲染的物体就是在世界坐标系中,模型需要放到世界坐标系中,那么当还没放的时候,模型就和世界坐标系没有联系,它就还处于自己的坐标系中,叫做物体坐标系(模型坐标系、局部空间、局部坐标系),也就是图中的LOCAL SPACE
  • 当把模型放到世界坐标系中,模型就在世界坐标系里有了坐标,也就是原来在LOCAL SPACE中的那些坐标值,变成了世界坐标系中的坐标值,帮助我们完成这个变换的就是模型矩阵,对应图中的MODEL MATRIX,于是这样就把模型放到了世界坐标系WORLD SPACE中;
  • 放到世界坐标系后,是不是就确定了渲染出来看到的样子?还没有,大家可以想像一下,把一个东西放在世界坐标系的某个地方,可以从近处看观察它,也可以从远处观察它,还可以从上下左右观察它,甚至还可以倒着观察它,因些还需要确定观察它的状态。OpenGL里虚拟出了一个Camera(特别注意,这里的Camera不是指硬件的Camera,可以理解为一个观者者的角度),从API的层面上看,只需要设置Camera的位置、朝向的点坐标、以及Camera的上方向向量就能将观察状态定下来,而这些设置最终会转换成OpenGL中的视图矩阵,对应图中的VIEW MATRIX 。经过View Matrix的变换后,观察它的结果就确定了,图中是从距离它一定的距离、上往下观察它,这时候的点坐标就来到了视图坐标系下,对应图中的VIEW SPACE ,这时候,能看到什么东西,基本已经确定了。
  • 不过还有一步投影变换,这是什么东西?大家想像一下,看到同一个东西,是不是通常都是近大远小?那么如何实现近大远小?就要靠投影变换,OpenGL提供正交投影和透视投影,正交投影没有近大远小的效果,不管在什么距离上看,都一样大,透视投影则有近大远小的效果,也是符合我们实际生活的一种效果,透视投影应用得比较多,可看下面这张经典图: 完成投影变换就需要靠投影矩阵,即图中的PROJECTION MATRIX在这里插入图片描述
  • 可以从图中看到,经过投影变换后就到了裁剪坐标系CLIP SPACE,什么?裁剪坐标系?不是投影吗?裁剪什么东西?实际上,投影操作也顺带做了裁剪,所谓裁剪就是说把那些视野内看不到的东西去掉,什么是视野?就是在生成投影矩阵时会设置近平面、远平面、视角,这些东西会构成一个可见的空间,对应图中的虚线和近平面、远平面包围起来的空间;下一步就是上屏(如果是离屏渲染就是到一个frame buffer上),这些坐标毕竟只是OpenGL坐标系下的坐标,那么最终以什么样的大小呈现在屏幕上呢?就要通过视口变换映射到屏幕上。

矩阵变换的数学推导

模型矩阵(Model Matrix)推导

在数学中图形的变换存在平移、缩放、旋转三种基本变换,将模型放到世界坐标系中就是利用这三种变换的组合来实现的,下面来看一下平移、缩放、旋转三种变换对应的矩阵:

  • 平移变换
    平移变换
  • 缩放变换
    缩放变换
  • 旋转变换(绕x轴旋转、 绕y轴旋转、 绕z轴旋转)
    绕x轴旋转
    绕y轴旋转
    绕z轴旋转
视图矩阵(View Matrix)推导

一、视图矩阵对应Camera的位置、朝向的点坐标、以及Camera的上方向向量:
在这里插入图片描述
二、怎样通过Camera的位置、朝向的点坐标、以及Camera的上方向向量得到对应的View Matrix?

  • 给Camera定一个坐标系:
    在这里插入图片描述
  • 将Camera的坐标记为eye,朝向的点坐标记为lookat,上方向向量记为up,那么: N向量:eye - lookat,U向量:up X N并归一化,V向量:N X U并归一化;
  • 要把Camera以某种姿态放在世界坐标系中的某个地方,这个放的过程就是对应Camera的旋转和平移,这里表示为TR,其中T表示平稳变换矩阵,R表示旋转变换矩阵。虽然设置的是Camera,但最终动的是点坐标,因为Camera压根就不存在,是一个假想的东西。假设不动摄像机,动坐标点,那么对坐标点的变换就应该是对相机变换的逆变换 R^-1 T^-1(就是对TR整体求逆矩阵),注意,这里的 R^-1 T^-1 看起来貌不惊人,实际上就是要求的View Matrix。 根据前面的知识,就很容易得到T^-1:
    在这里插入图片描述
  • 这个直观上也好理解,比如本来是平移Tx,逆过来就是平移-Tx,依此类推。 再回顾一下目标 R^-1 T^-1 ,现在还差 R^-1,现在再次回到假想的Camera,前面说要对它做TR,当做完R后,Camera会旋转至某个姿态: 在这里插入图片描述
  • XYZ和UVN都可以看成是一组基,根据线性代数公式可将一个点在XYZ基下的坐标转成在UVN基下的坐标,R就相当于是把基XYZ变换成UVN的变换矩阵,其中: 在这里插入图片描述
  • 假设
    在这里插入图片描述
    则有
    在这里插入图片描述
    那么
    在这里插入图片描述
  • 由于R是正交矩阵,有性质:R^-1 = R^T (R^T代表R的转置),为什么R是正交矩阵?方阵A正交的充要条件是A的行(列) 向量组是单位正交向量组。 那么:
    在这里插入图片描述
  • 现在T^-1 和 R^-1 都有了,R^-1 T^-1 也就是最终的View Matrix可以很容易地计算出来了,因为OpenGL中坐标是4维的,所以这里将矩阵写成4*4的:
    在这里插入图片描述
投影矩阵(Projection Matrix)推导
  • 投影矩阵是由视野决定的,而视野又是由近平面、远平面和视角决定的,把视野在坐标系中画出来,请看下图:
    在这里插入图片描述
  • 简单起见,不妨把Camera摆在原点,让它朝z轴负方向来讨论问题: h表示近平面高度, w表示近平面宽度,n表示Camera到近平面的距离,f表示Camera到远平面的距, P代表视野中的一个点,那么接下来要求的投影矩阵,就是能将P点正确地投影到近平面上,设P(x0, y0, z0),从y轴正向往负向看,即看xoz平面,看到的画面是这样的:
    在这里插入图片描述
  • 设投影后的x坐标为x1 ,由三角形相似原理则易得:
    在这里插入图片描述
    那么
    在这里插入图片描述
  • 设l和r分别为近平面左、右边框的x坐标,则有l=-w/2,r=w/2,投影归一化后坐标范围为 -1~1,最左边是-1,最右边是1,l和r归一化至-1~1是线性变换,于是列一个kx+b类型的方程组并解得k和b:
    在这里插入图片描述
  • 令Xn表示点P的x坐标投影归一化后的值,代入kx+b得:
    在这里插入图片描述
    同理可得点P的y坐标投影归一化后的值Yn:
    在这里插入图片描述
  • 构造带有未知数的投影矩阵,然后求解,设待投影点为(x0,y0,z0,1),先构造投影矩阵的第一第二行:
    在这里插入图片描述
  • 投影矩阵仅完成投影变换,不会归一化,上面的x2、y2、z2指的是投影后归一化前的值,还记得前面计算的xn和yn吗?用一个括号把其中一个部分括了起来,外面乘了一个因子(-1/z0),后面会说这个因子是什么东西,现在只需要知道,x2、y2实际上就是前面括号里那堆东西,所以上面投影矩阵的第一行和第二行就自然能轻松地构造出来。
  • 接下来就构造第三第四行,先看第四行,第四行计算的结果是投影后的第四维坐标,也就是w,前面提到了归一化,而OpenGL的归一化操作就是通过将坐标除以其对应的w值来完成的,再回头看前面计算的xn和yn,它们是归一化后的值。还记得括号外面乘了一个因子(-1/z0)吗?乘(-1/z0)可以看成是除以-z0,因此希望w就是-z0,于是构造第四行让w的计算结果为-z0:
    在这里插入图片描述
  • 接下来就是最复杂的第三行,如何去构造第三行?第三行有4个值,现在都不知道是什么,需要构造4个未知数吗?对于解方程来说,在能解决问题的情况下,未知数能少就尽量少,不然只会徒增烦恼。 这里其实不需要4个未知数,为什么呢?那就要理解z2这个值是什么东西,它就是投影之后未归一化的深度值,而深度和x0、y0没有关系,这个如何理解?就是说我把一个东西放在左,上边,还是右边,不影响它的深度,要改变深度需要前后移动。 既然z2和x0、y0没有关系,那么x0、y0不管是什么值,都不会影响z2的值,因此用0去乘x0、y0,即第三行的第一第二个元素是0。 再看第三行的第三第四个元素,假设第三个元素是0,会发生是什么?那么z2就等于B,而B最后求出来放到矩阵中肯定是一个定值,这就意味着z2也是定值,于是z2就无法表示不同的点的不同深度,这不是我们想要的结果,因此第三个元素不能是0,是一个待求的未知数。同理,假设第四个元素是0会发生什么?这样投影矩阵第四列全是0,根据线性代数的知识,这个矩阵行列式等0,它必定不可逆,而我们希望投影矩阵是可逆的,这样我们可以对坐标做一些逆变换来实现一些特殊的功能,因此第四个元素也不能是0,于是设它为一个未知数。 这样,就构造出了一个包含未知数A和B的投影矩阵:
    在这里插入图片描述
  • 求解A和B:将z0为-f和-n代进去,-f就是远平面,-n就是近平面,求归一化后的坐标,-f最远,深度最深,归一化后是1,反之,-n代进去后是-1(深度是值越大越深),于是有:
    在这里插入图片描述
    可得:
    在这里插入图片描述
    于是投影矩阵为:
    在这里插入图片描述

矩阵变换的函数解析

  • 平移
    // 创建单元矩阵
    M3DMatrix44f m3;
    m3dLoadIdentity44(m3);

    /* m3dTranslationMatrix44(M3DMatrix44f m, float x, float y, float z)
       参数1:结果矩阵,平移之后的结果矩阵
       参数2:沿着X轴移动多少,正数\负数
       参数3:沿着Y轴移动多少,正数\负数
       参数4:沿着Z轴移动多少,正数\负数
     */
    m3dTranslationMatrix44(m3, 0.0f, 10.0f, 0.0f);
    
  • 旋转
     /* m3dRotationMatrix44(M3DMatrix44f m, float angle, float x, float y, float z);
       参数1:结果矩阵,旋转之后的结果矩阵
       参数2:旋转多少弧度
       参数3:是否围绕X轴旋转,是(1),不是(0)
       参数4:是否围绕Y轴旋转,是(1),不是(0)
       参数5:是否围绕Z轴旋转,是(1),不是(0)
     */
    m3dRotationMatrix44(m3, m3dDegToRad(45.0f), 1.0f, 0.0f, 0.0f);
  • 缩放
    /* void m3dScaleMatrix44(M3DMatrix44f m, float xScale, float yScale, float zScale)
       参数1:结果矩阵
       参数2:围绕X轴放大\缩小;放大x>1,缩小:0.5f
       参数3:围绕Y轴放大\缩小;放大x>1,缩小:0.5f
       参数4:围绕Z轴放大\缩小;放大x>1,缩小:0.5f
     */
    m3dScaleMatrix44(m3, 1.0f, 10.0f, 1.0f);

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/107357888
今日推荐