02总结--010--OpenGL 基础变换:向量和矩阵的深入理解【重点】

image

在正式进入本章节内容之前,先来正视几个观念。

1. 3D 数学在OpenGL中充当什么角色?

对于学习OpenGL 有?个误区,就是大家认为如果不能精通那些3D图形数学知识,会 让我们?步难行,其实不然。就像我们不需要懂得任何关于汽车结构和内燃机?面的 知识也能每天开车。但是,我们最好能对汽?车有?够的了解,以便我们意识到什么时 候需要更换机油、定期加油、汽?常规保养工作。

同样要成为一名可靠和有能力的OpenGL程序员,至少需要理解这些基础知识,才知道 能作什么?以及哪些工具适合我们要做的?作。

对于初学者,经过一段时间的实践,就会渐渐理解矩阵和向量。并且培养出一种更为直观 的能力,能够在实践中充分利用所学的内容。

2. GLTools 库中有一个组件叫Math3d

其中包含了了?量好?的OpenGL一致的3D数学和数据类型。虽然我们不必亲?进行所有的矩阵和向量的操作,但我然知道它们是什么?以及如何运?它们.

(一)向量和矩阵

1.1 向量

向量的定义

向量=方向+标量

  • 方向:点(1,0,0),在x方向为1,y和z的方向都为0
  • 标量:可以理解为长度,同样是点(1,0,0),在x方向的长度为1,y和z的为0
向量
  • 单位向量:标量(长度)为1的向量
  • 标准化:将一个向量的长度缩放为1
  • 向量长度(模):
    image

向量的使用

math3D 库中有两种数据类型

  • M3DVector3f:三维向量 (x, y, z)
  • M3DVector4f:四维向量 (x, y, z, w),w表示缩放因子,通常设为1,x,y,z的值通过除以w,来进行缩放。而除以1.0是不会改变x,y,z的值。
//三维向量/四维向量的声明
typedef float M3DVector3f[3]; 
typedef float M3DVector4f[4];
//声明一个三维向量 M3DVector3f:类型 vVector:变量名 
M3DVector3f vVector;
//声明一个四维向量并初始化?个四维向量 
M3DVector4f vVertex = {0,0,1,1};
//声明一个三分量顶点数组,例如?成一个三?形 
M3DVector3f vVerts[] = {
    -0.5f,0.0f,0.0f, 
    0.5f,0.0f,0.0f, 
    0.0f,0.5f,0.0f
};

向量的点乘

向量可以进行 加法,减法计算. 但是向量里有?个在开发中使用价值?常?的操作,叫做“点乘(dot product)”。点乘只能发?在2个向量之间进行

2个(三维向量)单元向量 之间进行点乘运算将得到?个标量(不是三维向量,是?个标量)。它表示两个向量之间的夹角。

image
  1. 条件: 2个向量必须为单位向;
  2. 动作: 2个三维向量之间进?点乘
  3. 结果:返回一个[-1,1]范围的值,这个值其实就是 夹?的cos值(余弦值),点乘结果是一个标量
  4. 用途:游戏中计算子弹的夹角

math3d 库中提供了关于点乘的API

//1.m3dDotProduct3 函数获得2个向量之间的点乘结果;
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);
//2.m3dGetAngleBetweenVector3 即可获取2个向量之间夹?的弧度值; 
float m3dGetAngleBetweenVector3(const M3DVector3f u,const
M3DVector3f v);

向量的叉乘

向量之间的叉乘(cross product) 也是在业务开发?非常有用的一个计算?式;;2个向量之间叉乘就可以得到另外一个向量,新的向量会与原来2个向量定义的平?垂直。同时进?叉乘,不必为单位向量。

image
  1. 前提:两个普通向量
  2. 动作:向量与向量叉乘
  3. 结果:向量(垂直于原来2个向量定义的平面),叉乘结果是一个向量
  4. 注意:这两个向量的顺序不一样,得到的结果向量方向相反
  5. 用途:游戏求法线(垂直于平面的线)

math3d 库中提供了关于叉乘的API

//1.m3dCrossProduct3 函数获得2个向量之间的叉乘结果得到一个新的向量
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f  u ,const
M3DVector3f v);

1.2 矩阵

矩阵的定义

  1. 矩阵的定义

假设, 在空间有?个点.使? xyz 描述它的位置. 此时让其围绕任意位置旋转一定?角度
后. 我们需要知道这个点的新的位置. 此时需要通过矩阵进行行计算; 为什么?
因为新的位置的x 单纯与原来的x还和旋转的参数有关. 甚?至于y和z坐标有关

image
  • 矩阵只有一行或者一列都是合理的。
  • 只有一行或一列的数字可以成为向量,也可以称为矩阵。

声明向量,用的是

  • M3DVector3f[];
  • M3DVector4f[];

声明矩阵,用的是

  • M3DMatrix33f[];
  • M3DMatrix44f[];
//三维矩阵/四维矩阵的声明
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];

在OpenGL中,推荐使用一维数组来定义矩阵,也可以使用二维数组

  1. 行矩阵和列矩阵
image
  • 行矩阵:行优先矩阵排序,图中按行读取,A0->A1->A2->A3->...
  • 列矩阵:列优先矩阵排序,图中按列读取,A0->A1->A2->A3->...
  • OpenGL 中以列优先,在数学中,称列矩阵为转置矩阵

如何识别列矩阵,如下图中的例子

  1. 矩阵的最后一行都是0
  2. 最后一个元素为1
image
  • 这16个值,每一个都对应一个特定的值
  • 每一列分别对应一个“轴”
  • 矩阵的最后一行都是0
  • 最后一个元素为1
  1. 单元矩阵

向量中有单位向量(长度为一的向量),矩阵中也有单元矩阵

  • 定义:
    对角线上都为1的矩阵

  • 单元矩阵初始化方式1

GLFloat m[] = {
           1,0,0,0, //X Column
           0,1,0,0, //Y Column
           0,0,1,0, //Z Column
           0,0,0,1  // Translation
           }
  • 单元矩阵初始化方式 2
M3DMatrix44f m = {
            1,0,0,0, //X Column
            0,1,0,0, //Y Column
            0,0,1,0, //Z Column
            0,0,0,1 // Translation 
            }
  • 单元矩阵初始化?式3
void m3dLoadIdentity44f(M3DMatrix44f m);

矩阵的乘法

  1. 矩阵相乘的前提条件:矩阵1 x 矩阵2矩阵1的列 = 矩阵2的行
  2. 矩阵A x 单元矩阵 = 矩阵A
image
  • 左边:4(行)4(列)
  • 右边:4(行)1(列)

满足条件,所以可以做乘法

image
  • 左边:4(行)1(列)
  • 右边:4(行)4(列)

不满足条件,结果不可预测

矩阵左乘

在线性代数的维度,坐标的计算,都是按照从左往右顺序进行计算的:

  • 变换后的顶点向量 = V_local * M_model * M_view * M_pro
  • 变换后的顶点向量 = 顶点 x 模型矩阵 x 观察矩阵 x 投影矩阵

为了方便记忆,我们按照 MVP 的顺序来记

  • M:model,表示模型矩阵
  • V:view,表示视图矩阵
  • P:projection,表示投影矩阵
image
  • 从上图的过程中可以看出,实际计算中,不管多少个顶点,都是两个两个逐个计算的,不会一次性的全部计算
  • 上图中的矩阵是行矩阵,不是OpenGL的习惯

OpenGL的习惯——矩阵左乘

我们知道矩阵的乘法是有顺序的,上面的是按照线性代数的习惯,进行的乘法,而且是使用的行矩阵,但是在OpenGL中,我们习惯使用列矩阵,通过相反的顺序 PVM 来进行乘法,过程如下图所示:

image
  • 变换后的顶点向量 = M_pro * M_view * M_model * V_local
  • 变换后的顶点向量 = 投影矩阵 x 观察矩阵 x 模型矩阵 x 顶点

OpenGL ES 中的代码片段

image

(二)理解变换以及模型视图矩阵

投影(projection):将3D数据“压扁”成2D数据的处理过程

前面讲的正投影和透视投影只是变换中的投影变换而已,还有视图变换、模型变换等多种变换类型。如下表:

OpenGL 变换术语概念

变换 应 用
视图 指定观察者或照相机的位置
模型 在场景中移动物体
模型视图 描述视图和模型变换的二元性
投影 改变视景体的大小或重新设置它的形状
视口 这是一种伪变换,只是对窗口上的最终输出进行缩放

上面将矩阵的时候就提到过了,有三种矩阵,MVP的名词在变换场景中同样适用

  • M:model,表示模型矩阵
  • V:view,表示视图矩阵
  • P:projection,表示投影矩阵

2.1 视觉坐标

视觉变换
  • 无论进行何种变换,我们都可以将它们视为“绝对的”屏幕坐标;
  • 视觉坐标表示一个虚拟的固定坐标系,通常作为参考坐标系使用;
  • a图:表示我们垂直于手机屏幕视角;
  • b图:表示我们的头稍微歪了一点,屏幕里面表示“最深的”地方;

2.2 视图变换(View)

  • 透视投影:观察点位于原点(0,0,0),并沿z轴负方向(向显示器内部“看进去”)。观察点相对于视觉坐标系进行移动,来提供特定的有利位置;
  • 正投影:观察者被认定在z轴正方向无穷远的位置(上帝视角),能够看到视景体中的任何东西;
  • 视图变换:允许我们将观察点放在任何位置,并允许在任何方向上观察场景;
  • 确认视图变换:就是确认观察者的位置,以及观察者所观察的方向
  • 从大局上考虑,在应用任何其他模型变换之前,必须先应用视图变换。原因是,对于视觉坐标系而言,视图变换移动了当前的工作坐标系,所有后续变换都会基于新调整的坐标系进行,所以视图变换应该在其他所有的变换之前。

2.3 模型变换(Model)

常见的模型变换有三种

  • 平移:Translation
  • 旋转:Rotation
  • 缩放:Scaling
平移
  • 参数1:结果矩阵,将平移后的结果放到这个矩阵中;
  • 参数234:xyz轴上的移动距离;
void m3dTranslationMatrix44(M3DMatrix44f m, floata x, float y, float z);
旋转
  • 参数1:弧度,m3dDegToRad():度数转弧度;
  • 参数234:选择围绕哪个轴旋转,如果沿x轴旋转,则为(1,0,0),如果有其他组合轴,以具体需求来设定xyz之间的关系;
m3dRotationMatrix44(m3dDegToRad(45.0), floata x, float y, float z);
缩放
  • 参数1:结果矩阵,将缩放后的结果放到这个矩阵中;
  • 参数234:xyz轴上的缩放因子;
  • 翻转:如果沿某个轴缩放-1,则表示在该轴翻转,例如Xa x -1 = -Xa
void m3dScaleMatrix44(M3DMatrix44f m, floata xScale, float yScale, float zScale);
变换顺序
  • 不同顺序的变换结果是不一样的
  • 矩阵a x 矩阵b矩阵b x 矩阵a 结果不一样,矩阵部分讲过了
  • 上图中:先旋转后平移 和 先平移后旋转的结果不一样

综合变换公式(矩阵乘法)

void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f b);
  • 参数1:结果矩阵,(注意两个矩阵的顺序,这个说了很多回了)
  • 参数2:目标矩阵1
  • 参数3:目标矩阵2

2.4 模型视图的二元性(ModelView)

模型视图变换
  • 视图和模型变换的结果其实是一样的,区分开是为了程序员的方便;(“后视镜里的世界,越来越远的道别”——周杰伦《一路向北》)
  • 模型视图:值这两种变换在变换管线中进行组合,成为一个单独的矩阵,即模型视图矩阵
  • 使用视图变换的原因,在绘制对象之前应用到一个虚拟对象(观察者)之上的模型变换。在加入更多对象到场景之后,还会指定更多新的变换。初始变换(视图变换)是所有其他变换参考的基础。

2.5 投影变换(Projection)

投影投影

2.6 视口变换

  • 二维投影:上面所有变化完成之后的一个结果;
  • 视口变换:将二维投影映射到物理窗口坐标的变换,颜色缓冲区和窗口像素存在一一对应的关系;
  • 图形硬件自动完成,不需要我们手动处理。

(三)模型视图矩阵案例

3.1 案例一:正方形的旋转移动

//类型
GLMatrixStack::GLMatrixStack(int iStackDepth = 64);
//在堆栈顶部载?一个单元矩阵
void GLMatrixStack::LoadIdentity(void);
//在堆栈顶部载入任何矩阵 
//参数:4*4矩阵
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
//矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
//获取矩阵堆栈顶部的值 GetMatrix 函数 
//为了适应GLShaderMananger的使?用,或者获取顶部矩阵的副本
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);

3.2 案例二:使用三角形批次类创建球体、花托、圆柱(锥)、磁盘

这些图形都有些什么特点?显而易见,都是用三角形构成的。怎么通过三角形来构成这些图形呢,这些就不是我们操心的东西了,很多OpenGL的先驱者已经完成了这些特殊形状的绘制,我们只需要调用接口即可。——“给我一个三角形批次类,我还你世界万物”

关于移动、深度测试、边框、混合、背面消除等技术,这里就不做过多解释,前面的文章已经介绍的非常清楚了。

定义一组批次类

//球
GLTriangleBatch     sphereBatch;
//环
GLTriangleBatch     torusBatch;
//圆柱
GLTriangleBatch     cylinderBatch;
//锥
GLTriangleBatch     coneBatch;
//磁盘
GLTriangleBatch     diskBatch;

1. 绘制球体

/*
  gltMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);
 参数1:sphereBatch,三角形批次类对象
 参数2:fRadius,球体半径
 参数3:iSlices,从球体底部堆叠到顶部的三角形带的数量;其实球体是一圈一圈三角形带组成
 参数4:iStacks,围绕球体一圈排列的三角形对数

 建议:一个对称性较好的球体的片段数量是堆叠数量的2倍,就是iStacks = 2 * iSlices;
 绘制球体都是围绕Z轴,这样+z就是球体的顶点,-z就是球体的底部。
 */
gltMakeSphere(sphereBatch, 3.0, 10, 20);
球体

2. 绘制花托

/*
 gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
 参数1:torusBatch,三角形批次类对象
 参数2:majorRadius,甜甜圈中心到外边缘的半径
 参数3:minorRadius,甜甜圈中心到内边缘的半径
 参数4:numMajor,沿着主半径的三角形数量
 参数5:numMinor,沿着内部较小半径的三角形数量
 */
gltMakeTorus(torusBatch, 3.0f, 0.75f, 15, 15);
花托

3. 绘制圆柱

/*
 void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
 参数1:cylinderBatch,三角形批次类对象
 参数2:baseRadius,底部半径
 参数3:topRadius,头部半径
 参数4:fLength,圆形长度
 参数5:numSlices,围绕Z轴的三角形对的数量
 参数6:numStacks,圆柱底部堆叠到顶部圆环的三角形数量
 */
圆柱

4. 绘制圆柱
圆锥其实就是没有顶部的圆柱,所以还是使用同一个批次类,只是第2个参数不一样而已

/*
 void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
 参数1:cylinderBatch,三角形批次类对象
 参数2:baseRadius,底部半径
 参数3:topRadius,头部半径
 参数4:fLength,圆形长度
 参数5:numSlices,围绕Z轴的三角形对的数量
 参数6:numStacks,圆柱底部堆叠到顶部圆环的三角形数量
 */
//圆柱体,从0开始向Z轴正方向延伸。
//圆锥体,是一端的半径为0,另一端半径可指定。
gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
圆锥

5. 绘制磁盘

/*
void gltMakeDisk(GLTriangleBatch& diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);
 参数1:diskBatch,三角形批次类对象
 参数2:innerRadius,内圆半径
 参数3:outerRadius,外圆半径
 参数4:nSlices,圆盘围绕Z轴的三角形对的数量
 参数5:nStacks,圆盘外网到内围的三角形数量
 */
磁盘

(四)变换管线

4.1 矩阵堆栈

矩阵堆栈的作用

矩阵堆栈的作用是对矩阵的构造和管理提供便捷,矩阵堆栈有以下一些特性:

  1. 默认的堆栈深度是64
  2. 矩阵堆栈初始化时,在堆栈中默认保存了单元矩阵
  • 初始化单元矩阵:m3dLoadIdentity44方法的源码并没有找到。注释说实现在 Math3d.cpp 文件中,我们并没有获取到这个文件,所以猜测它的实现原理应该跟下面的 LoadMatrix 类似
inline void LoadIdentity(void) { 
    m3dLoadIdentity44(pStack[stackPointer]); 
    }
// LoadIdentity
// Implemented in Math3d.cpp
void m3dLoadIdentity33(M3DMatrix33f m);
void m3dLoadIdentity33(M3DMatrix33d m);
void m3dLoadIdentity44(M3DMatrix44f m);
void m3dLoadIdentity44(M3DMatrix44d m);
  • 加载一个矩阵:从源码中可以看出,加载一个矩阵,实际上就是将一个矩阵放入当前堆栈的栈顶位置。跟压栈不一样的地方是,压栈是在栈顶上方新压入一个矩阵,而load是修改当前栈顶的矩阵
inline void LoadMatrix(const M3DMatrix44f mMatrix) { 
    m3dCopyMatrix44(pStack[stackPointer], mMatrix); 
    }
    
inline void m3dCopyMatrix44(M3DMatrix44d dst, const M3DMatrix44d src)
    { memcpy(dst, src, sizeof(M3DMatrix44d)); }    

矩阵堆栈的操作

压栈和出栈一定是成对出现
压栈和出栈一定是成对出现
压栈和出栈一定是成对出现

  • 压栈:复制当前矩阵值,将结果放在堆栈顶部
void PushMatrix(const M3DMatrix44f mMatrix) {
        if(stackPointer < stackDepth) {
            stackPointer++;
            // 复制当前矩阵,存入栈顶
            m3dCopyMatrix44(pStack[stackPointer], mMatrix);
            }
        else
            lastError = GLT_STACK_OVERFLOW;
        }
  • 出栈:移除顶部矩阵,恢复它下面的值
inline void PopMatrix(void) {
        if(stackPointer > 0)
            stackPointer--;
        else
            lastError = GLT_STACK_UNDERFLOW;
        }
  • 乘法:获取栈顶矩阵,与目标矩阵相乘,将结果矩阵放入栈顶
inline void MultMatrix(const M3DMatrix44f mMatrix) {
    M3DMatrix44f mTemp;
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mMatrix);
    }
  • 获取矩阵:两种方式,根据具体需求进行选择使用
    • 一种是带返回值,直接返回栈顶矩阵:但是外部矩阵(变量)不能被修改。如果在外面修改的堆栈中的矩阵,可能会造成意想不到的结果;
    • 拷贝栈顶矩阵到传进来的参数地址:因为这里是拷贝了一份新的矩阵,所以不用担心外面的修改会影响到堆栈里面的矩阵。
// Two different ways to get the matrix
const M3DMatrix44f& GetMatrix(void) { return pStack[stackPointer]; }
void GetMatrix(M3DMatrix44f mMatrix) { m3dCopyMatrix44(mMatrix, pStack[stackPointer]); }

矩阵堆栈的仿射变换

矩阵堆栈处理提供管理堆栈的功能,还提供了对创建旋转、平移、缩放矩阵的支持

  1. 通过API获取仿射变换需要的数据;
  2. 创建仿射变换矩阵;
  3. 获取栈顶矩阵;
  4. 将仿射变换矩阵和栈顶矩阵相乘的结果放入栈顶

这三个内建API的过程也是我们自己在使用矩阵堆栈的时候需要用到的

// 缩放
void Scale(GLfloat x, GLfloat y, GLfloat z) {
    M3DMatrix44f mTemp, mScale;
    m3dScaleMatrix44(mScale, x, y, z);
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mScale);
    }

// 平移
void Translate(GLfloat x, GLfloat y, GLfloat z) {
    M3DMatrix44f mTemp, mScale;
    m3dTranslationMatrix44(mScale, x, y, z);
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mScale);           
    }

// 旋转
void Rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) {
    M3DMatrix44f mTemp, mRotate;
    m3dRotationMatrix44(mRotate, float(m3dDegToRad(angle)), x, y, z);
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mRotate);
    }

4.2 管理管线

管线的作用就是记录模式视图(MV)矩阵和投影(P)矩阵,在获取MVP(模式视图投影)矩阵时,将MV矩阵和P矩阵相乘的结果返回。这么做有什么好处呢?

  1. 标准化:我们上面讲过了,矩阵的乘法是有顺序的,而MV矩阵和P矩阵哪个在前哪个在后,如果是我们手写的矩阵乘法的时候,难免会出现错误。而在管线里面,它将会以一个固定的顺序来做乘法,外部是不需要知道实现细节的。具体的源码实现,后面会讲。
  2. 封装性:如果我们每次在使用MVP矩阵的时候,都手动去做乘法,这就太不程序员了。

管线源码解读

作为一个程序员,从源码的角度来理解管线。

  1. 管线API:真的是异常的简单
管线源码
  • 三个setter方法
  • 四个getter方法
  1. setter方法:跟一般setter方法一样,就就是保存一个地址
inline void SetModelViewMatrixStack(GLMatrixStack& mModelView) { _mModelView = &mModelView; }

inline void SetProjectionMatrixStack(GLMatrixStack& mProjection) { _mProjection = &mProjection; }

inline void SetMatrixStacks(GLMatrixStack& mModelView, GLMatrixStack& mProjection) {
    _mModelView = &mModelView;
    _mProjection = &mProjection;
    }

管线管理啥,只管理 模型视图矩阵(MV)和 投影矩阵(P),因为接口里面只提供了这两个矩阵的设置入口??????

  1. getter方法
const M3DMatrix44f& GetModelViewProjectionMatrix(void)
    {
    m3dMatrixMultiply44(_mModelViewProjection, _mProjection->GetMatrix(), _mModelView->GetMatrix());
    return _mModelViewProjection;
    }

inline const M3DMatrix44f& GetModelViewMatrix(void) { return _mModelView->GetMatrix(); }
inline const M3DMatrix44f& GetProjectionMatrix(void) { return _mProjection->GetMatrix(); }

const M3DMatrix33f& GetNormalMatrix(bool bNormalize = false)
    {
    m3dExtractRotationMatrix33(_mNormalMatrix, GetModelViewMatrix());

    if(bNormalize) {
        m3dNormalizeVector3(&_mNormalMatrix[0]);
        m3dNormalizeVector3(&_mNormalMatrix[3]);
        m3dNormalizeVector3(&_mNormalMatrix[6]);
        }

    return _mNormalMatrix;
    }

其实管线的 getter 方法只有两个,中间两个是 inline 方法,并不支持外部调用。

GetNormalMatrix 这个方法,暂时还没用到,这里不做解释,那就只剩下一个方法了。

GetModelViewProjectionMatrix
GetModelViewProjectionMatrix
GetModelViewProjectionMatrix

一般重要的东西,至少要默写三遍,就是这个MVP矩阵。

如果你还有印象,我们前面在介绍着色器的时候,平面着色器和上色着色器中是不是有一个参数就是MVP矩阵。管线就是为了给我们提供这个边界,不需要我们每次都手动去做乘法,它直接提供了MVP的结果给我们。

image

学习!记录!知识总是环环相扣!前面的困惑在后面的学习中总会柳暗花明又一村!

4.3 照相机(观察者)

角色?。篏LFrame

这个类型是GLTools库中的一种数据结构,只在OpenGL阶段使用,在后面的OpenGL ES阶段中边不在使用了,所以这里就不做过多的介绍。

protected:
        M3DVector3f vOrigin;    // Where am I?
        M3DVector3f vForward;   // Where am I going?
        M3DVector3f vUp;        // Which way is up?
  • vOrigin:对应x轴
  • vForward:对应z轴
  • vUp:对应y轴

GLFrame的操作

源码很长,也看不懂??,他是怎么转换的,我们就不用去探索了,知道怎么用就行。

  • 获取矩阵:GetMatrix,结果是一般矩阵,对应世界坐标系
//4.创建矩阵mObjectFrame
M3DMatrix44f mObjectFrame;
//从ObjectFrame 获取矩阵到mOjectFrame中
objectFrame.GetMatrix(mObjectFrame);
//将modelViewMatrix 的堆栈中的矩阵 与 mOjbectFrame 矩阵相乘,存储到modelViewMatrix矩阵堆栈中
modelViewMatrix.MultMatrix(mObjectFrame);
  • 获取照相机矩阵:GetCameraMatrix,结果对应的是观察者坐标系
//3.获取摄像头矩阵
M3DMatrix44f mCamera;
//从camereaFrame中获取矩阵到mCamera
cameraFrame.GetCameraMatrix(mCamera);
//模型视图堆栈的 矩阵与mCamera矩阵 相乘之后,存储到modelViewMatrix矩阵堆栈中
modelViewMatrix.MultMatrix(mCamera);

照相机(观察者)

这只是一个逻辑存在的事物,在实际中并不存在,为什么这么说呢?

它本质是啥,就是一个矩阵。那为什么这个矩阵要叫观察者呢?

当我们在使用这个矩阵的时候,它产生的变化效果,看起来是从观察者角度去移动的。所以称它为观察者。

果然学习总是环环相扣,前面不理解不要紧,后面自然就会懂了。

4.4 变换流程

前面三小节,分别从代码和抽象的角度对某些现象进行了解释。如果是认真的看到这里,那下面这个流程图,不需要记,自然会刻在你的脑海里。

以下面这段代码为例

// 1.模型视图矩阵栈堆,压栈
modelViewMatrix.PushMatrix();

// 2.获取摄像头矩阵
M3DMatrix44f mCamera;
//从camereaFrame中获取矩阵到mCamera
cameraFrame.GetCameraMatrix(mCamera);
//模型视图堆栈的 矩阵与mCamera矩阵 相乘之后,存储到modelViewMatrix矩阵堆栈中
modelViewMatrix.MultMatrix(mCamera);

// 3.创建矩阵mObjectFrame
M3DMatrix44f mObjectFrame;
//从ObjectFrame 获取矩阵到mOjectFrame中
objectFrame.GetMatrix(mObjectFrame);
//将modelViewMatrix 的堆栈中的矩阵 与 mOjbectFrame 矩阵相乘,存储到modelViewMatrix矩阵堆栈中
modelViewMatrix.MultMatrix(mObjectFrame);

//4. 使用矩阵
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);

//5. pop
modelViewMatrix.PopMatrix();
矩阵堆栈流程图

认真读完上面关于矩阵堆栈的操作,这个流程图相对来说就是非常简单了。核心是每一步操作会后矩阵堆栈中栈顶矩阵的变换。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351