OpenGL基础变换

第四章 OpenGL基础变换 向量和矩阵

       在第三章,我们讨论了如何绘制3D点、线和三角形。为了将一系列图形转换到连续的场景,我们必须将它们相对于其他图形和观察者进行排列。在本章,我们开始学习在坐标系中移动图形。

本章内容:

  • 什么是向量,以及为什么要了解它
  • 什么是矩阵,以及为什么要更认真地了解它
  • 我们如何使用矩阵和向量来移动几何图形
  • OpenGL对于模型视图和投影矩阵的约定
  • 什么是照相机,以及如何应用它转换
  • 如何将一个点光源位置转换到视点坐标系

一、3D图形数学

       GLTools库中有一个组建叫做Math3d,其中包含了大量好用的与OpenGL一致的3D数学例程和数据结构。
向量:
       一个顶点是XYZ坐标空间上的一个位置,同时也是一个向量。
       向量指出方向;同时也代表数量,一个向量的数量就是这个向量的长度。
Math3d库有两种数据类型:M3DVector3f表示一个三维向量(X,Y,Z);
M3DVector4f表示一个四维向量(X, Y, Z, W)。
       点乘:两个单位向量之间的点乘运算将得到一个标量。它表示两个向量之间的夹角。要进行这种运算,两个向量必须为单位长度。而返回的结果将在-1和+1之间,实际是两个向量之间夹角的余弦值。

//返回余弦值
float m3dDotProduct3(const M3DVector3f u, M3DVector3f v);
//返回弧度值
float m3dGetAngleBetweenVetors3(const M3DVector3f u, M3DVector3f v);

       叉乘:两个向量之间叉乘所得的结果是另外一个向量,这个新的向量与原来两个向量定义的平面垂直。叉乘的两个向量都不必为单位向量。两个向量位置交换叉乘值不同,即叉乘不满足交换律。

//返回结果向量
float m3dCrossProduct3(M3DVector3f result, const M3DVector3f u, M3DVector3f v);

矩阵:
       如果空间中有一点,有X,Y,Z坐标定义,将它围绕任意点沿任意方向旋转一定角度后,我们需要知道这个点现在的位置,就要用到矩阵。数学上,矩阵为一组排列在统一的行和列中的数字,用程序设计语言来说就是一个二维数组。
3D程序设计中用到的几乎全是两种维度的矩阵,即3×3和4×4;在math3d库中有两种维度的矩阵类型:
       typedef float M3DMatrix33f[9];
       typedef float M3DMatrix44f[16];

二、理解变换

视觉坐标:
       视觉坐标是相对于观察者的视角而言的,无论可能进行何种变换,我们都可以将它们视为“绝对的”屏幕坐标。
视图变换:
       视图变换是应用到场景中的第一种变换。对于视觉坐标系而言,视图变换移动了当前的工作坐标系。所有后续变换随后都会基于新调整的坐标系进行。然后,在实际开始考虑如何进行这些变换时,就会更容易地看到这些变换是如何实现的了。
模型变换:
       模型变换用于操纵模型和其中的特定对象。这些变换将对象移动到需要的位置,然后再对它们进行旋转和缩放。
模型视图的二元性:
       实际上,视图和模型变换按照它们内部效果和对场景的最终外观来说是一样的。将这两者分开纯粹是为了程序员的方便。将对象象后移动和将参考系坐标向前移动在视觉上没有区别,效果是相同的。视图变换和模型变换一样,都应用在整个场景中,在场景中的对象常常在进行视图变换后单独进行模型变换。术语“模型视图”是指这两种变换在变换管线中进行组合,成为一个单独的矩阵,即模型视图矩阵。
投影变换:
       投影变换将在模型视图变换之后应用到顶点上,这种投影实际上定义了视景体并创建了裁剪平面。投影变换指定一个完成的场景(所有模型变换都已完成)是如何投影到屏幕上的最终图形。
       在正投影中,所有多边形都是精确地按照指定的相对大小来在屏幕上绘制的。线和多边形使用平行线来直接映射到2D屏幕上,无论物体位置远近,都按照相同大小来进行绘制。典型情况下,这个投影用于渲染如屏幕菜单等二维图像。
       透视投影所显示的场景与现实生活中更接近,透视投影的特点就是透视缩短,这种特性使得远处的物体看起来比近处同样大小的物体更小一些。
视口变换:
       当上述变换完成时,就得到了一个场景的二维投影,它将被映射到屏幕上某处的窗口上,这种到物理窗口坐标的映射是我们最后要做的变换,成为视口变换。

矩阵变换:

 /*
 mView: 平移
 mModel: 旋转
 mModelView: 模型视图
 mModelViewProjection: 模型视图投影MVP
 */
M3DMatrix44f mView, mModel, mModelView, mModelViewProjection;

//mModel旋转矩阵,绕y轴旋转yRot度
m3dRotationMatrix44(mModel, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);

//mView平移矩阵,沿z轴移动-2.5
m3dTranslationMatrix44(mView, 0.0f, 0.0f, -2.5f);

//mModelview = mView * mModel
m3dMatrixMultiply44(mModelview, mView, mModel);

//mModelViewProjection = ProjectionMatrix * mView * mModel
 m3dMatrixMultiply44(mModelViewProjection,
                     viewFrustum.GetProjectionMatrix(),
                     mModelview);

更多对象:
       GLBatch类,这个类的目的是为了解决容纳一个顶点列表并将它们作为一个特定类型的图元批次来进行渲染。而GLTriangleBatch,这个类是专门作为三角形的容器的,每个顶点都可以有一个表面法线,已进行光照计算和纹理坐标。
变换管线:

使用矩阵堆栈:
       我们在矩阵储存时可能用到以下实例:

GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLFrame             cameraFrame;
GLFrame             objectFrame;
//其中,cameraFrame用于存储观察者矩阵,objectFrame用于存储模型矩阵。projectionMatrix只用于存储投影矩阵,我们操作最多的是modelViewMatrix。

//模型矩阵绕世界坐标系y轴旋转-5.0度
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
//观察者矩阵向后退15.0,GLFrame中默认的朝向是z轴的负方向;即(0.0, 0.0, -1.0);向前走-15.0,即(0.0, 0.0, -1.0 * -15.0) = (0.0, 0.0, 15.0)
cameraFrame.MoveForward(-15.0f);渲染过程中矩阵栈操作,获取MVP矩阵的计算结果
//压栈
modelViewMatrix.PushMatrix();
//获取观察者矩阵
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
//栈顶矩阵乘以传入矩阵,相乘的结果简存储在栈顶
modelViewMatrix.MultMatrix(mCamera);
//获取模型矩阵
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
//由于先乘的观察者矩阵,现在再乘以模型矩阵
//栈顶 = M_view * M_model
modelViewMatrix.MultMatrix(mObjectFrame);
...
//由于初始化时传入了,modelViewMatrix和projectionMatrix的引用
//transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
//下面的代码会计算projectionMatrix * modelViewMatrix
//结合上面的代码,等价于M_projection * M_view * M_model
transformPipeline.GetModelViewProjectionMatrix()
...
//出栈
modelViewMatrix.PopMatrix();

       其中,入栈,是为了保存当前的矩阵栈状态;出栈,是为了恢复入栈前的矩阵栈状态。这个操作类似于iOS的Core Graphic中context的保存与恢复。
       //保存
       void CGContextSaveGState(CGContextRef c);
       //恢复
       void CGContextRestoreGState(CGContextRef c);

优化矩阵栈操作:
       不同的实现方式,之前我们用cameraFrame和objectFrame来记录相机和模型的变化,用到了两个对象。但我们可以固定一个对象,变化另一个对象。例如,之前的做法需要观察者向后退,同时物体旋转,把变化作用到了两个物体上,所以用到了两个矩阵分别记录两个物体的变化,最后再使用矩阵相乘把两个变化合并起来。
如果固定一个物体,那么根据相对运动,就是把之前两个物体的变化,相对地作用到另一个物体上,就可以少用一个矩阵了。

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

推荐阅读更多精彩内容