1 引子

在上一篇读书笔记中,我们对书本中给出的例子进行详细的分析。首先是搭出一个框架;然后填充初始化函数,在初始化函数中向OpenGL提供顶点信息(缓冲区对象)和顶点属性信息(顶点数组对象),并启用顶点数组对象;最后填充绘制函数,首先清空颜色缓存,然后调用glDrawArray来绘制基本图形。例子中使用的坐标都是二维坐标,所以画出来的图形是二维图形(这里是两个三角形),而我们知道OpenGL最主要是用来进行三维图形的渲染的,所以有必要在学习OpenGL相关API之前对三维变换做一个简要的介绍。其实这一部分应该属于红宝书中第五章的内容,这里我们将其提前了,在读书笔记(二)就拿出来介绍——这是我们三维渲染的最基本的知识点,也是最关键的知识点,理解起来也有一定的难度。本次读书笔记主要讲述平移、旋转、缩放变换的变换矩阵,投影变换将在下一篇读书笔记再做记录。本篇读书笔记主要是自己对一些数学概念的理解和记录,仅供参考,如有不同理解的,大家可以一起讨论哈!

2  点、坐标系与向量

讨论三维变换之前,得先了解点、向量和坐标系这些基本数学概念。这部分内容可能比较抽象,下面记录的是我对这些概念的一些理解。

2.1 位置的相对性

在日常生活中,我们在向别人描述一个陌生的地方的时候,通常会选择一个他熟悉的地方作为一个参考点。例如:我们向老外介绍河北的一座古城邯郸,老外知道北京,我们就会说邯郸在北京往西南走300km;如果老外知道石家庄,那我们也可以告诉他,邯郸在石家庄往南走100km。这说明,位置是一个相对的概念,要描述一个位置,首先要选择参考点;参考点的选择是任意的,所选取的参考点不同,位置的描述也就不同。

在几何中,位置用“点”这一概念来描述,即点是一个只有位置没有大小的量。描述一个点和描述一个位置是一回事,刚才已经说了位置是一个相对的概念,所以首先就要用到参考点。我们以最简单的一维数轴为例来说明描述点的位置,如下图所示:

对于数轴上同一点AA,要描述AA点的位置,先要选取任意一个参考点,如果选择的参考点是O1O1,则AA点在O1O1点右边l1l1的地方;如果选择的参考点是O2O2,则AA点在O2O2点左边l2l2的地方。通过数轴和参考点,我们就将数轴上的几何点用抽象的数字表达出来了。

2.2 坐标系与向量

从图上可以看出,数轴上的点只能沿着数轴方向进行变化,即它是一维的。如果点在一个平面上或一个空间中变化,那么数轴这一工具是无法描述的。这时就要引入二维坐标系和三维坐标系来描述点的位置。介绍坐标系之前,首先介绍一下向量的概念。

在我们还是十七八岁学习高一几何的时候,我们就已经接触到了向量——既有大小,又有方向的量,用一个有向线段来表示。说白了,向量定义了一个方向、一个长度和一个单位长度。如上图中,O1AO1A和AO2AO2就是两个向量,大小分别为l1l1和l2l2,方向为水平向右。

一个平面上,有无数这样的向量。但是关于向量,有一个非常重要的法则——使用平行四边形法则来对任何一个向量进行分解。平行四边形法则来自于物理学中力的分解与合成,后被引入数学中加以抽象来描述向量的分解与合成。所谓平行四边形法则,指的是任何一个平面向量都可以用一个不共线的两个向量表示。于是,平面中无数的向量就可以用两个不共线的向量来表示。由这两个向量及它们的公共起点构成的数学结构就是二维坐标系,用坐标系就可以描述二维平面上的任意点,当然也可以描述二维平面上的任意向量,这两个向量就是线性代数中的基向量。我们知道,在数学中,向量是位置无关的(即自由向量),只要大小相等,方向相同的两个向量就是同一个向量(这和物理学中的力不一样)。所以要描述二维空间中的点,还需要一个参考点,于是就定义了这两个向量的公共起点作为参考点——即我们熟知的坐标原点。坐标轴向量和坐标原点就构成了坐标系,可以用坐标系来描述其中的任何向量和任一点。

三维坐标系和二维坐标系是类似的,使用两次平行四边形法则,从而将任意一个三维向量表示为三个不共面的三维向量(基向量)来表示,这三个向量移到一起的公共起点定义为三维坐标系的坐标原点。二维和三维笛卡尔坐标系就是基向量垂直的二维和三维坐标系,也是应用最为广泛的坐标系,也称为平面直角坐标系空间直角坐标系

下面,我们来看看向量的表示方法。同样在我们懵懵懂懂的青春岁月里,我们就已经知道向量有两种表示方法:第一种是符号表示法,如a,ba,b等;另一种是坐标表示法,这里对坐标表示做较详细的说明。刚才已经说了,任意一个二维向量都可以用两个不共线的向量来表示,假设两个基向量为ii和jj,且长度为1。则对任一个向量a=xai+yaja=xai+yaj,这样,向量aa可用一个有序对(xa,ya)(xa,ya)来唯一表示,这就是向量的坐标表示。三维乃至NN维向量的坐标表示都是一样的。在这里,博主还是想强调一下,向量的坐标并不是该向量在坐标轴上的投影,只有笛卡尔坐标是向量在基向量上的投影。所以,在普通坐标系下,一个向量的坐标不是很好求,但在直角坐标系下,就变得很好求了——求投影,这也是笛卡尔坐标系应用的如此广泛的原因。下面我们来看看,什么是投影,其实高一数学中也已经接触到了,如下图所示:

假设cc为向量aa在向量bb上的投影,那么:

c=acos<a,b>(1)(1)c=acos<a,b>

所以,在二维直角坐标系中,如果二维向量aa长度为ll,该向量与xx轴和yy轴的夹角分别为αα和ββ,则我们很容易得到该向量的坐标表示为=(lcosα,lcosβ)T=(lcos⁡α,lcos⁡β)T;同样地,对三维空间向量bb,其长度为LL,与xx轴、yy轴和zz轴的夹角分别为αα、ββ和γγ,则其坐标表示为b=(Lcosα,Lcosβ,Lcosγ)Tb=(Lcos⁡α,Lcos⁡β,Lcos⁡γ)T。

2.3 点的表示

刚才我们定义了坐标系——坐标原点和三个不共面的向量组成,并且三维空间中的任意向量都可以由这三个向量唯一表示,但我们没有讲点怎么由坐标系来定义。设在三维笛卡尔坐标系中,坐标原点为OO,三个基向量分别为ii,jj,kk,我们要求PP点的坐标,那么

OP→=x1i+y1j+z1kOP→=x1i+y1j+z1k

于是,点PP可以表示为

P=x1i+y1j+z1k+OP=x1i+y1j+z1k+O

所以,要想表示一个三维的点,可以用四维坐标来表示,例如刚才的PP可以表示为P=(x1y1z11)P=(x1y1z11),这就是齐次坐标。对顶点来说,齐次坐标才是其真正的表示方式。向量可以表示为v=(x1y1z10)v=(x1y1z10)。

3 线性变换与齐次坐标

3.1 概述

代数中的线性变换的概念很抽象,涉及到向量空间、线性映射之类的概念,在这里不做过多解释,如下想了解可以度娘或必应。给一个通俗点的解释,三维线性变换就是将点/向量的坐标值做一个运算,使其坐标值发生改变,这在几何中的反映就是几何体的形状被改变了。在计算机图形学中,线性变换一般是指平移、旋转、缩放、投影(正交投影和透视投影)以及这些基本变换的综合运算。通过刚才的描述,我们知道一下几点信息:几何中的点或向量由四个坐标值确定,而坐标值是由坐标系确定的,坐标系又是由三个不共面的向量和坐标原点构成。也就是说,对于同一点,在不同的坐标系下,描述它的坐标值是不一样的,而变换就是建立这两种不同描述之间的联系——所以在以前我们称之为坐标变换。例如:在坐标系O1−i1j1k1O1−i1j1k1坐标系下,某一点可以描述为PP点可以用四元祖(x1,y1,z1,o1)(x1,y1,z1,o1)描述,

P=(x1y1z1o1)⎛⎝⎜⎜⎜i1j1k1O1⎞⎠⎟⎟⎟=(i1j1k1o1)⎛⎝⎜⎜⎜x1y1z1o1⎞⎠⎟⎟⎟(2)(2)P=(x1y1z1o1)(i1j1k1O1)=(i1j1k1o1)(x1y1z1o1)

在另一个坐标系为O2−i2j2k2O2−i2j2k2,可以用另一个有序元组描述它,设为(x2,y2,z2,o2)(x2,y2,z2,o2)

P=(x2y2z2o2)⎛⎝⎜⎜⎜i2j2k2O2⎞⎠⎟⎟⎟=(i2j2k2O2)⎛⎝⎜⎜⎜x2y2z2o2⎞⎠⎟⎟⎟(3)(3)P=(x2y2z2o2)(i2j2k2O2)=(i2j2k2O2)(x2y2z2o2)

那么怎么建立(3)(3)和(2)(2)之间的联系呢?还是之前我们说的,任意一个三维向量都可以表示用三个不共面的向量表示,所以i2,j2,k2i2,j2,k2可以用i1,j1,k1i1,j1,k1线性表出:

i2=T11i1+T21j1+T31k1+0O1i2=T11i1+T21j1+T31k1+0O1
j2=T12i1+T22j1+T32k1+0O1j2=T12i1+T22j1+T32k1+0O1
k2=T13i1+T23j1+T33k1+0O1k2=T13i1+T23j1+T33k1+0O1
O2=T14i1+T24j1+T34k1+T44O1O2=T14i1+T24j1+T34k1+T44O1

即:

(i2j2k2O2)=(i1j1k1O1)⎛⎝⎜⎜⎜T11T21T310T12T22T320T13T23T330T14T24T34T44⎞⎠⎟⎟⎟(i2j2k2O2)=(i1j1k1O1)(T11T12T13T14T21T22T23T24T31T32T33T34000T44)

于是,我们就可以写出从(x1y1z1o1)T(x1y1z1o1)T变换到(x2y2z2o2)T(x2y2z2o2)T的变换表达式为:

⎛⎝⎜⎜⎜x2y2z2o2⎞⎠⎟⎟⎟=(i1j1k1O1)⎛⎝⎜⎜⎜T11T21T310.0T12T22T320.0T13T23T330.0T14T24T34T44⎞⎠⎟⎟⎟⎛⎝⎜⎜⎜x1y1z1o1⎞⎠⎟⎟⎟(x2y2z2o2)=(i1j1k1O1)(T11T12T13T14T21T22T23T24T31T32T33T340.00.00.0T44)(x1y1z1o1)

其中,将

T=⎛⎝⎜⎜⎜T11T21T310.0T12T22T320.0T13T23T330.0T14T24T34T44⎞⎠⎟⎟⎟T=(T11T12T13T14T21T22T23T24T31T32T33T340.00.00.0T44)

称为坐标变换矩阵。接下来主要就是讲解怎么求基本的坐标变换(仿射变换)矩阵。

3.2 缩放

缩放应该是所有线性变换中最简单的变换了。执行缩放操作,例如将一个向量缩放为原来的ss倍,相当于原点不变,xx、yy、zz三个坐标轴缩放为原来的ss倍。根据3.1介绍的,缩放操作的变换矩阵为:

Ts=⎛⎝⎜⎜⎜s0000s0000s00001⎞⎠⎟⎟⎟Ts=(s0000s0000s00001)

3.3 平移

所谓平移,就是在坐标系中的三个坐标轴保持不变,原点沿着平移向量移动到新位置。假设平移向量为vp=(xpypzp0)vp=(xpypzp0)同样,根据可以得到,平移操作的变换矩阵为:

Tp=⎛⎝⎜⎜⎜⎜100001000010xpypzp1⎞⎠⎟⎟⎟⎟Tp=(100xp010yp001zp0001)

3.4 旋转

最后来推导最难的旋转变换矩阵。与平移、旋转矩阵的不同,旋转矩阵就不那么直观了。下面,我们来具体看一下旋转矩阵的推导,这个推导是执行三次向量的平行四边形法则进行分解得到,整个分解过程如下图所示:

三次分解由不同的颜色表示出来了,分别是红色、浅蓝色和紫色。

已知条件:ii、jj和kk是三维笛卡尔坐标系的基向量,原点为OO,旋转轴为uu,也是单位向量,向量i′i′为xx方向的基向量ii绕旋转轴uu旋转θθ后的新向量——旋转后坐标系xx轴的基向量。

我们的目的:将向量i′i′用基向量ii、jj和kk表示出来。

第一步向量分解:将i′i′分解为沿着旋转轴uu的向量OA→OA→和垂直于uu的向量OB→OB→,则:

i′=OA→+OB→(4)(4)i′=OA→+OB→

且:

OA→=uxu=u2xi+uxuyj+uxuzk(5)(5)OA→=uxu=ux2i+uxuyj+uxuzk

第二步向量分解:将ii分解为沿着旋转轴uu的向量OA→OA→和垂直于uu的向量OC→OC→,则

i=OA→+OC→i=OA→+OC→

且:

OC→=i−uxu=(1−u2x)i−uxuyj−uxuzk(6)(6)OC→=i−uxu=(1−ux2)i−uxuyj−uxuzk

第三步向量分解:建立OB→OB→与OC→OC→之间的联系,将向量OB→OB→分解为沿着OC→OC→方向的向量OD→OD→和垂直于OB→OB→的向量OE→OE→,则

OB→=OD→+OE→(7)(7)OB→=OD→+OE→

根据66可得:

OD→=|OB→|cosθOC→|OC→|=cosθOC→=cosθ(i−uxu)=(1−u2x)cosθi−uxuycosθj−uxuzcosθk(8)(8)OD→=|OB→|cos⁡θOC→|OC→|=cos⁡θOC→=cos⁡θ(i−uxu)=(1−ux2)cos⁡θi−uxuycos⁡θj−uxuzcos⁡θk

另外,注意到OE→OE→和OC→OC→垂直,uu是旋转轴,则uu与平面OEBDOEBD垂直,所以uu与OEOE垂直,则OE→OE→在向量uu和向量OC→OC→的叉乘向量上,假设 OF→=u×OC→OF→=u×OC→,于是:

OE→=kOF→=ku×OC→=ku×(i−uxu)=ku×iOE→=kOF→=ku×OC→=ku×(i−uxu)=ku×i

所以现在求出kk就可以了,由叉乘定义:|OF→|=|u||OC→|sin(90)=|OC→|=|OB→||OF→|=|u||OC→|sin(90)=|OC→|=|OB→|,所以:k=sinθk=sin⁡θ,最后得到

OE→=sinθu×(i−uxu)=sinθ⎛⎝⎜iux1juy0kuz0⎞⎠⎟=uzsinθj−uysinθk(9)(9)OE→=sin⁡θu×(i−uxu)=sin⁡θ(ijkuxuyuz100)=uzsin⁡θj−uysin⁡θk

将(8)(8)和(9)(9)代入(7)(7)得:

OB→=(1−u2x)cosθi−(uxuycosθ−uzsinθ)j−(uxuzcosθ+uysinθ)k(10)(10)OB→=(1−ux2)cos⁡θi−(uxuycos⁡θ−uzsin⁡θ)j−(uxuzcos⁡θ+uysin⁡θ)k

最后,将(5)(5)和(10)(10)代入(4)(4)可得

i′=(cosθ+u2x(1−cosθ))i+(uxuy(1−cosθ)+uzsinθ)j+(uxuz(1−cosθ)−uysinθ)k+0Oi′=(cos⁡θ+ux2(1−cos⁡θ))i+(uxuy(1−cosθ)+uzsin⁡θ)j+(uxuz(1−cos⁡θ)−uysin⁡θ)k+0O

其余两个变换后的基向量i′i′和j′j′也可以由ii、jj和kk表示出来,最终得到齐次旋转矩阵为

Mr=⎛⎝⎜⎜⎜⎜cosθ+u2x(1−cosθ)uyux(1−cosθ)+uzsinθuzux(1−cosθ)−uysinθ0uxuy(1−cosθ)−uzsinθcosθ+u2y(1−cosθ)uzuy(1−cosθ)+uxsinθ0uxuz(1−cosθ+uysinθuyuz(1−cosθ)−uxsinθcosθ+u2z(1−cosθ)00001⎞⎠⎟⎟⎟⎟Mr=(cos⁡θ+ux2(1−cos⁡θ)uxuy(1−cos⁡θ)−uzsin⁡θuxuz(1−cos⁡θ+uysin⁡θ0uyux(1−cos⁡θ)+uzsin⁡θcos⁡θ+uy2(1−cos⁡θ)uyuz(1−cos⁡θ)−uxsin⁡θ0uzux(1−cos⁡θ)−uysin⁡θuzuy(1−cos⁡θ)+uxsin⁡θcos⁡θ+uz2(1−cos⁡θ)00001)

4 总结

最后总结一下,在这篇博文中我们讲述了点及其相对性,接着介绍了向量的概念,由平行四边形法则引出坐标系的概念,然后介绍了点在坐标系下的表示,最后介绍了坐标变换和变换矩阵的概念,给出了三种基本变换——平移变换、旋转变换和缩放变换的变换矩阵。这些矩阵综合运用,就构成了三维空间中复杂的变换了,三维变换是三维图形绘制的基础,也是学习OpenGL时较难理解的知识点之一。

标签: OpenGL

OpenGL学习之路(二)的更多相关文章

  1. OpenGL学习之路(四)

    1 引子 上次读书笔记主要是学习了应用三维坐标变换矩阵对二维的图形进行变换,并附带介绍了GLSL语言的编译.链接相关的知识,之后介绍了GLSL中变量的修饰符,着重介绍了uniform修饰符,来向着色器 ...

  2. OpenGL学习之路(五)

    1 引子 不知不觉我们已经进入到读书笔记(五)了,我们先对前四次读书笔记做一个总结.前四次读书笔记主要是学习了如何使用OpenGL来绘制几何图形(包括二维几何体和三维几何体),并学习了平移.旋转.缩放 ...

  3. OpenGL学习之路(一)

    1 引子 虽然是计算机科班出身,但从小对几何方面的东西就不太感冒,空间想象能力也较差,所以从本科到研究生,基本没接触过<计算机图形学>.为什么说基本没学过呢?因为好奇(尤其是惊叹于三维游戏 ...

  4. OpenGL学习之路(三)

    1 引子 这些天公司一次次的软件发布节点忙的博主不可开交,另外还有其它的一些事也占用了很多时间.现在坐在电脑前,在很安静的环境下,与大家分享自己的OpenGL学习笔记和理解心得,感到格外舒服.这让我回 ...

  5. Redis——学习之路二(初识redis服务器命令)

    上一章我们已经知道了如果启动redis服务器,现在我们来学习一下,以及如何用客户端连接服务器.接下来我们来学习一下查看操作服务器的命令. 服务器命令: 1.info——当前redis服务器信息   s ...

  6. zigbee学习之路(二)点亮LED

    一.前言 今天,我来教大家如何点亮led,这也是学习开发板最基础的步骤了. 二.原理分析 cc2530芯片跟虽然是51的内核,但是它跟51单片机还是有区别的,51单片机不需要对IO口进行配置,而cc2 ...

  7. OPENGL学习之路(0)--安装

    此次实验目的: 安装并且配置环境. 1 下载 https://www.opengl.org/ https://www.opengl.org/wiki/Getting_Started#Downloadi ...

  8. Android开发学习之路-二维码学习

    这个月装逼有点少了,为什么呢,因为去考软件射鸡师了,快到儿童节了,赶紧写篇博纪念一下逝去的青春,唔,请忽略这句话. 二维码其实有很多种,但是我们常见的微信使用的是一种叫做QRCode的二维码,像下面这 ...

  9. Python学习之路二

    今天主要学习了列表,python的列表真的事太强大了,由于内容比较多,今天就先简单的介绍一下新学的几个成员函数吧. 首先我们要了解list是一种序列类型,其构造方式有四种形式: (1)空列表 [] ( ...

随机推荐

  1. Include Native *.so Library in APK With Android Studio

    Originally posted on:http://www.kylethielk.com/blog/include-native-so-library-in-apk-with-android-st ...

  2. 华为OJ:火车进站

    火车进站 给定一个正整数N代表火车数量,0<N<10,接下来输入火车入站的序列,一共N辆火车,每辆火车以数字1-9编号.要求以字典序排序输出火车出站的序列号. 输入描述: 有多组测试用例, ...

  3. 用C语言写个程序推算出是星期几?(用泰勒公式实现)

    在日常生活中,我们常常遇到要知道某一天是星期几的问题.有时候,我们还想知道历史上某一天是星期几.比如: “你出生的那一天是星期几啊?” “明年五一是不是星期天?我去找你玩?” 通常,解决这个问题的最简 ...

  4. 使用 Nginx 和 GeoIP 模块来处理不同国家的访问

    安装 Nginx因为要用到 http_geoip_module 模块,系统自带的 nginx 一般不带这个模块,所以要下载 nginx 源代码后自行编译: # wget http://nginx.or ...

  5. SPRING IN ACTION 第4版笔记-第十一章Persisting data with object-relational mapping-004JPA例子的代码

    一.结构 二.Repository层 1. package spittr.db; import java.util.List; import spittr.domain.Spitter; /** * ...

  6. scp 传文件

    1. 从远程服务器上拷贝文件(指定远程服务器的用户名:IP:文件    本地文件名) scp root@121.43.16.131:/etc/supervisor/supervisord.conf ~ ...

  7. 一个简单的以User权限启动外部应用程序(用NetUserAdd函数和USER_INFO_1结构体动态添加用户,然后用CreateProcessWithLogonW启动程序)

    版权声明:本文为博主原创文章,未经博主允许不得转载. BOOL ExecuteAsUser(LPCWSTR lpszUserName, LPCWSTR lpszPassword, LPCWSTR lp ...

  8. 【原创】如何在Android中为TextView动态设置drawableLeft等

    如何在Android中为TextView动态设置drawableLeft等   两种方式:   方式1:手动设置固有边界 Drawable drawable = getResources().getD ...

  9. Eclipse配置Flex开发环境(转)

    Eclipse配置Flex开发环境 开发环境:Eclipse3.2.Flex Builder31.下载安装Flex Builder3,下载地址:http://subject.csdn.net/adob ...

  10. Android EditText获取光标位置并插入字符删除字符

    1.获取光标位置 int index = editText.getSelectionStart(); 2.在光标处插入字符 int index = editText.getSelectionStart ...