OpenGL之 矩阵与变换

前言

首先附上一个我个人花了点时间整理的Demo,其中包含了绘制各基本图形、甜甜圈的代码。

一、向量和矩阵

1、向量

什么是向量,向量是一个有长度(模)和方向的有向线段。
在OpenGL中,它就是一个顶点,也就是一个向量。

1.1 向量与标量

标量其实可以理解成数字,它强调的是数值;而向量强调的长度和方向。

1.2 单位向量

长度为1的向量即为单位向量。
而对于一些只关心方向不关心大小的向量,使用单元向量会比较方便。这个时候我们可以对非单位且非0向量进行标准化,将其转换为单位向量。

1.3 向量的写法

通常,垂直书写的是列向量;水平书写的是行向量。

1.4 OpenGL中使用向量

在OpenGL中,可以使用math3d库来定义向量,这个库提供了两个关于向量的数据类型。分别是 M3DVector3f:(x,y,z)M3DVector4f:(x,y,z,w)
w是缩放因子,x、y、z可以通过除以w来做到缩放。一般情况下,w坐标值为1。

//三维向量/四维向量的声明
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
};
1.5 向量点乘

1.5.1 数学意义
向量点乘的优先级高于加法和减法,两个向量点乘即为其对应分量乘积相加的结果,其结果一个标量。
如 [a1,a2]·[b1,b2] = a1 * b1 + a2 * b2。

1.5.2 几何意义

点乘的结果在几何上表示两个向量的接近程度,点乘的结果越大,两个向量越接近。
a · b = |a||b|*cosθ,θ为两个向量之间的夹角,由此也可以看出,点乘是满足交换律的。


image.png

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

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

向量叉乘的优先级也高于加法和减法,它的结果是一个向量(如上图)且不满足交换律。这个向量垂直于两个向量,这个结果向量可以称之为a向量与b向量所在平面的法线。

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

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

2、矩阵

一个矩阵可以看做是若干个向量组成的。在OpenGL中,常常用它来做各种变换。如平移、缩放等。
在之前的OpenGL例子中,首先我们会在setupRC()方法中使用各种方式定义很多顶点数据,复制到批次类中,如果不做任何操作,直接渲染的话,那得到的就是一个普通图形。若我们需要对图形做各种变换的操作,则需要通过各种矩阵进行计算。OpenGL代码如下图。

void RenderScene(void)
{
   ...
    //压栈
    modelViewMatrix.PushMatrix();
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    
    //矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后简存储在堆栈的顶部
    modelViewMatrix.MultMatrix(mCamera);
    
    M3DMatrix44f mObjectFrame;
    //只要使用 GetMatrix 函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载。用来使用GLShaderManager 的使用。或者是获取顶部矩阵的顶点副本数据
    objectFrame.GetMatrix(mObjectFrame);
    
    //矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后简存储在堆栈的顶部
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    ...
}

1、矩阵的定义

在OpenGL中,math3d库也提供了两个数据类型来定义矩阵,分别是 M3DMatrix33f[9]M3DMatrix44f[16]

与其他变成标准不同的是,在OpenGL中,更倾向于使用一维数组来定义矩阵。原因是因为OpenGL使用的是 Column-Major(以列为主)矩阵排序的约定。

2、列矩阵

image.png

上图的矩阵为列矩阵,如图所示,这个矩阵的每一列都是一个由四个元素组成的向量。事实上,这样的一个矩阵可以确定空间中的一个位置。

当我们在OpenGL编程的时候,将一个对象的所有顶点数据乘以这个向量,即可让这个对象变化到空间中矩阵指向的位置和方向。

2、单元矩阵

对角线元素都为1的矩阵即为单元矩阵。单元矩阵具有以下特点

  • 任何矩阵乘以单元矩阵都等于原来的矩阵,相当于任何数字乘以1都等于原来的数字;
  • 单元矩阵满足交换律,任何矩阵乘以单元矩阵等于单元矩阵乘以对应矩阵。

二、变换

在之前的几篇文章中,很多都有涉及到从物体坐标转换到屏幕坐标的各种变换。如下图:

image.png

从物体坐标变换到裁剪坐标的过程是在顶点着色器中完成的,在这过程中,物体坐标先后经过了模型变换、视图变换、投影变换。而这些变换本质上其实就是矩阵的相乘。

在OpenGL的维度中,变换顶点向量的过程如下:

变换后顶点向量  = 投影矩阵 ✖️ 视图变换矩阵 ✖️ 模型矩阵 ✖️ 顶点
image.png

而在线性代数的维度,一般的坐标计算都是从左往右的顺序进行计算的。如下:

变换后顶点向量  =  顶点 ✖️  模型矩阵 ✖️  视图变换矩阵 ✖️ 投影矩阵
image.png

那么为什么在OpenGL维度与线性代数维度是相反的读法呢。

1、行向量和列向量的变换

由于向量实际上是某一维度为1的矩阵,那么根据矩阵乘法的规则,会出现下面的情况:

  • 行向量左乘矩阵
    在图形学中一般矩阵都是4x4的,行向量一般设置为1x4的矩阵(齐次坐标)。当行向量左乘矩阵的时候 (1x4)* (4x4)得到的是一个1x4的行向量
  • 行向量右乘矩阵
    这种情况,也就是(4x4)*(1x4),根据矩阵相乘的规则,这是不允许的
  • 列向量左乘矩阵
    这种情况,也就是(4x1)*(4x4),根据矩阵相乘规则,这也是不允许的
  • 列向量右乘矩阵
    这种情况,也就是(4x4)*(4x1),得到的是一个4x1的行向量

根据上述规则,可以得出,如果我们需要做各种变换,在计算的时候,矩阵之间相乘只能使用行向量左乘矩阵列向量右乘矩阵,这是矩阵乘法规则的限制。

而在OpenGL中,采取的是列向量,因此,我们使用的是列向量右乘矩阵的方式,也就是上图中 (4x4)(4x4)(4x4)*(4x1)的变换过程。

2、行向量和列向量在编程语言中的内存布局

假设有一个4x4的矩阵M,我们现在有一个顶点的坐标是V,通过M矩阵的变换可以把它变为VE,现在分别假设 V是行向量或者V是列向量,于是有以下两种情形:
(1)V是行向量 , 那么 VE = VM
(2)V是列向量, 那么 VE = M
V

举一个实际的例子来看一下。如图

image.png

在内存中,对于一个行矩阵,它的排列是[1,2,3,4,5,...,15,16],而对于列矩阵,它在内存中的排列是[1,5,9,13,2,6,...,12,16],仅此而已。从图中可以看出,两种 向量与矩阵的乘积的结果,其实互为转置。

回到OpenGL变换中,在OpenGL采用的列向量的方式,所以它是右乘的计算方式。因此,在代码中,我们一般看到的书写顺序是 先单元矩阵复制一份压栈,然后将栈顶的矩阵取出,乘以观察者矩阵,得到的结果赋值给栈顶,再乘以模型矩阵,得到的结果赋值给栈顶,最后,将投影矩阵乘以栈顶矩阵,最终得到的就是变换空间位置后的顶点的空间位置。

矩阵压栈出栈.png

但是,这仅仅是因为,在OpenGL中是列向量的形式,所以采取了右乘的方式计算,这与上述的坐标系变换过程并不冲突。

//压入栈操作,投影矩阵*观察者矩阵*模型视图矩阵
void publicPushStackOperation() {
    //复制一份单元矩阵
    modelViewStack.PushMatrix();
    M3DMatrix44f cameraMatrix;
    
    cameraFrame.GetCameraMatrix(cameraMatrix);
    modelViewStack.MultMatrix(cameraMatrix);
    
    M3DMatrix44f objectMatrix;
    objectFrame.GetMatrix(objectMatrix);
    
    modelViewStack.MultMatrix(objectMatrix);
    
//shaderManger. transformPipleLine.GetModelViewProjectionMatrix 这行代码的底层实现,
//其实是m3dMatrixMultiply44(_mModelViewProjection, _mProjection->GetMatrix(), _mModelView->GetMatrix());
//也就是 投影矩阵*观察者矩阵*模型视图矩阵
UseStockShader(GLT_SHADER_FLAT,transformPipleLine.GetModelViewProjectionMatrix(),vBlue);
}

2、各种OpenGL变换

2.1 视图变换

视图变换,其实主要是指定观察者的位置,它是物体应用到场景中的第一种变换,它⽤来确定场景中的有利位置,在默认情况下, 透视
投影中位于原点(0,0,0),并沿着 z 轴负⽅向进⾏观察 (向显示器内部”看过去”)。

从⼤局上考虑, 在应⽤任何其他模型变换之前, 必须先应⽤视图变换. 这样做是因为, 对于视觉坐标系⽽⾔, 视图变换移动了当前的⼯作的坐标系; 后续的变化都会基于新调整的坐标系进⾏。

2.2 模型变换

⽤于操纵模型的某种特定变换.。这些变换通过旋转,缩放,平移将对象移动到需要的位置。

此图来自于逻辑教育相关资料
2.3 投影变换
此图来自于逻辑教育相关资料

在OpenGL中,投影变换的目的就是得到一个取景体积(视景体),其作用有

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