1 引子

不知不觉我们已经进入到读书笔记(五)了,我们先对前四次读书笔记做一个总结。前四次读书笔记主要是学习了如何使用OpenGL来绘制几何图形(包括二维几何体和三维几何体),并学习了平移、旋转、缩放坐标变换矩阵的理论推导和实践应用。

这一次读书笔记,我们一起来学习OpenGL中常用的坐标系以及坐标变换。在OpenGL中有几个坐标系,初学者常常被它们搞得晕头转向:为什么需要这些坐标系?各个坐标系有什么作用?……本文就来学习一下这些OpenGL中常用坐标系。

之后来看看投影矩阵的推导,投影变换矩阵比较特殊,不能通过平移、旋转、缩放变换得到。投影分为简单的正交投影和复杂一些的透视投影,我们都将逐一进行推导。最后展示一下透视投影的显示效果,以供大家参考。

三维图形绘制和二维最大区别就是坐标变换变得更复杂了。在三维中,理解好坐标、坐标系(坐标空间)以及坐标变换之类知识点不是很容易的,楼主也是花了不少时间才大致有所理解。不过呢,如果线性代数、线性变换之类学的比较好的童鞋,理解三维坐标变换应该很简单——他们都是牛人。其实理解了之后,也就是那么回事(先藐视它一下吧!)。好了,废话也不多说了,下面我们正式踏上OpenGL学习之路——第五站。

2 OpenGL中的坐标系

OpenGL绘制三维图形时涉及多种坐标系,各个坐标系都有它各自的作用。这一节将介绍以下四个常见的坐标系:模型(物体)坐标系、世界坐标系、相机(人眼)坐标系和窗口坐标系;以及与之相对应的坐标变换为模型变换、视图变换、投影变换这三大变换。红宝书着色器代码中常常会有MVP开头的变换矩阵就是这三大变换矩阵的综合运算得到的变换矩阵,其中:M为Model的缩写,代表模型变换;V为View的缩写,代表视图变换;P为Projection的缩写,代表投影变换。下面我们逐个讲解这些坐标系的作用和坐标变换。

2.1 四大坐标系

1、模型坐标系 

模型坐标系是在进行三维建模时所用的坐标系,例如3D max建模时采用的坐标系、医学图像扫描所用坐标系,该坐标系的坐标原点一般是选择在物体上的某个点,长度单位一般是实际物理单位,如mm。这一坐标系一般在制作游戏模型时,由建模软件给出,也称为物体局部坐标系。

2、世界坐标系

我们绘制三维图像时,一般都会有很多几何体,如果采用上面所说的几何体的模型坐标来绘制,则不同的物体有各自的模型坐标系。而OpenGL要在一个场景下绘制这些几何体,所以需要引入一个新的坐标系来描述所有几何体的顶点坐标,为此,OpenGL就定义了世界坐标系。在绘制前要将几何体之前,首先将几何体的坐标从模型坐标系下变换到世界坐标系下。世界坐标系的定义方式是这样的:以图像窗口中心作为坐标原点、水平向右为x轴、垂直向上为y轴,垂直屏幕指向屏幕外为z轴(见下图);长度按如下定义:视口范围按此单位恰好是$(-1,-1)$(左下角)到$(1,1)$(右上角)。世界坐标系和模型坐标系之间的关系如下图:

3、相机坐标系

“横看成岭侧成峰,远近高低各不同”,这是中学时期耳熟能详的诗句,这是在使人严重山峰的形态,其中横看、侧看说的就是不同的相机坐标系下山峰对象的成像效果;远近高低各不同则是后面讲到的透视投影的效果。下面就来讲讲实现“横看成岭侧成峰”神奇效果的相机坐标系吧!

相机坐标系也称为人眼坐标系、视点坐标系。OpenGL的成像原理与照相机拍照、人眼看世界的原理类似,想想我们在看风景时,是不是一直在调整眼睛位置,以达到更好的观景效果?其实在调整的过程中,我们一直在改变人眼坐标系。同样地,在摄影时,我们也会选取一个较好的角度进行拍摄;或者在拍照片的时候,我们常常会摆个好看的pose,等等都是一样的。OpenGL渲染本质上也是在拍照片,所以也同样要定义相机坐标系(人眼坐标系)——这样才能定义遮挡关系,才能描述观察者离被观察物体的远近,才能确定物体前后的遮挡关系,所以相机坐标系在三维绘制中是非常重要的。像极坐标系如下图所示:

相机坐标系一般以相机所在位置为原点、视线方向为z方向,另外还要求给出一个向上的方向(见上图),这样才可以确定相机坐标系了。熟悉OpenGL API的童鞋都知道,定义视图变换矩阵的命令如下:

gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez,
GLdouble centerx, GLdouble centery, GLdouble center z,
GLdouble upx, GLdouble upy, GLdouble upz);

该接口指定了共需要输入九个分量:前三个分量人眼所在的位置,即人眼坐标系原点坐标;中间三个分量指定观察点的坐标,由此可以确定z轴向量;最后三个分量指定y轴向量,最后根据右手法则(D3D中是左手法则)确定出x轴向量,这就可以构成了世界坐标系到像极坐标系的变换矩阵了。当然,采用着色器编程之后,可以直接将变换矩阵提交到着色器,由着色器来完成顶点变换即可。

4、屏幕坐标系

OpenGL渲染出来的图像最终以像素的形式在图形显示器,就要在图形显示器上定义一个坐标系,这一坐标系成为屏幕坐标系,或视口坐标系。在OpenGL中,视口坐标系的原点位于视口的左下角,x轴和y轴分别为水平向右和垂直向上,单位为像素,如下图所示。

图1 应用程序窗口

从上图可以看出,视口其实是应用窗口中的一块区域,默认情况下,视口区域和应用程序窗口区域大小一致,我们也可以使用一个OpenGL命令来改变视口区域,其函数签名为:

void glViewport(GLint x, GLint y, GLint width, GLint height)
x ——视口左下角x坐标(以下单位均为像素)
y ——视口左下角y坐标
width ——视口区域宽度
height ——视口区域高度

2.2 四大坐标变换

对应于上述四大坐标系,这一小节将介绍四大坐标变换。即将顶点从模型坐标系变换到世界坐标系的模型变换;从世界坐标系变换到人眼坐标系的视图变换;在人眼坐标系下进行实现近大远小效果并实现归一化的投影变换;确定视口位置和大小的视口变换。

1 模型变换

将几何体的坐标表示从模型坐标系变换到世界坐标系的过程成为模型变换,也称为模型-世界变换。每个物体都有它对应的模型变换矩阵,这一变换矩阵可以通过之前介绍的平移变换、缩放变换和旋转变换综合运算得到。经过模型变换,场景中的物体经过模型变换,其顶点表示就从模型坐标系边变到了OpenGL世界坐标系下了。

2 视图变换(取景变换)

关于视图变换,《指南》中有句话概括的很好——将场景放置在观察者眼睛之前。我们都拍过照片,拍照的时候,我们常常要对准待拍摄的物体,这个过程就是取景的过程;或者拍证件照的时候,拍摄者让我们头摆正,这也是取景。在OpenGL中,这一过程通过视图变换矩阵来实现。经过视图变换,场景中物体顶点的表示就从世界坐标系变到了人眼坐标系下了。

3 投影变换

由于三维图像最终要显示在二维平面中的,所以需要引入投影变换,如下图所示:

图2 投影变换

投影变换分两种:

透视投影变换:具有近大远小的特点,这也是人眼或摄像机成像方式;

正交投影变换:其视景体是一个长方体,不具有近大远小的特点,这是AutoCAD等制图软件渲染时所采用的成像方式。

最后需要指出的是,虽然投影变换希望得到归一化后的二维成像平面上的坐标,但它仍然保留深度信息,即变换前z值大的点,变换之后z值也大一些,反之亦然——这么做是为了让OpenGL渲染过程中确定几何体的遮挡关系了。

至此,我们大致介绍了一下模型变换、视图变换和投影变换,这是在OpenGL中渲染时最常用的三个变换(即MVP变换),主要在顶点着色器中完成。经过上述三种变换之后,就可以将顶点输送给OpenGL绘制管线的其他部分进行进一步的处理了。

4 视口变换

我们知道,最终图像要显示在应用程序给定的窗口中的,如图1,所以需要指定最终图像的显示区域,即视口。视口变换比较简单,不需要变换矩阵,只需要告诉OpenGL最终渲染出来的图像在视口中的位置及长宽即可,所调API为:

void glViewPort(GLint x, GLint y, GLint width, GLint height)
x — 视口左下角x坐标
y — 视口左下角y坐标
width   — 视口的宽度
height — 视口的高度

3 投影矩阵的推导与应用

本节主要记录和学习投影变换矩阵的推导及应用,对应于红宝书5.2节中的透视投影和正交投影。三维图像最终要显示到二维平面上的,投影坐标变换就是实现人眼坐标系下的三维坐标顶点到二维平面上的顶点的变换,如图2所示。之前也提及,为了OpenGL渲染几何体时能确定几何体间的遮挡关系,在投影变换时要保留深度信息,即$z$坐标。

关于投影矩阵的推导,《红宝书(第八版)》中做了简单地介绍,起初楼主看了好久没看明白,在网上一些博文的帮助下,最终总算弄明白了这一推导过程,在此将推导过程记录下来,也让以后学习三维变换的童鞋能更好地理解其推导过程。其实这一部分的推导还是蛮复杂的。

3.1 透视投影矩阵的推导

下面这幅图是从红宝书上取下来,在图中添加了一些注释,我们将依据此图进行透视投影矩阵的理论推导。

关于投影矩阵的推导,《红宝书(第八版)》分了两种情况:

对称的视锥体:中心对称的视锥体,$z$轴位于视锥体的中心;

不对称的视锥体:$z$轴不在视锥体的中央,就好比我们靠近窗户去观察景色,但没有正对窗户中心。

这里我们也分这两种情况来推导:

情况一:对称的视锥体

第一步:根据相似三角形的性质,有:

$$ x_{proj} = \frac{-z_{near}}{z} \cdot x, y_{proj} = \frac{-z_{near}}{z} \cdot y$$

第二步:为使视景体内的顶点投影到近平面上,其坐标映射到[-1.0, 1.0]范围内,投影后的点要分别除以$\frac{width}{2}$和$\frac{height}{2}$,其中$width, height$分别为近投影平面的宽度与高度,根据第一步结果,有:

$$x_{proj} = -\frac{1}{z}\frac{z_{near}}{width/2} \cdot x , y_{proj} =  -\frac{1}{z}\frac{z_{near}}{height/2} \cdot y$$

第三步:这一步推导深度部分变换,根据第一步和第二步,可以假设投影矩阵具有如下形式:

$$P = \left(\begin{array}{cccc}  \frac{z_{near}}{width/2} & 0 & 0 & 0 \\ 0 & \frac{z_{near}}{height/2} & 0 & 0 \\ 0 & 0 & A & B \\ 0 & 0 &  -1 & 0 \end{array}\right)$$

得到$z_{proj} = \frac{A \cdot z + B}{z}$,或$A \cdot z + B = z \cdot z_{proj}$将远平面和近平面$z$坐标代入上式得(注意注意:在这里,视景体内的深度坐标投影之后,有$z_{proj}\in [-1,1]$,虽然红宝书中说$z$映射值$[0,1]$范围内,但投影矩阵式将$z$映射值$[-1,1]$范围内的,这点一定要注意!!!):

$$\left\{\begin{array}{c} A \cdot -z_{far} + B = -z_{far} \\  A \cdot -z_{near} + B = z_{near} \end{array}\right.$$

得到:

$$\left\{ \begin{array}{c}A = \frac{z_{far} + z_{near}}{z_{far} - z_{near}} \\ B = \frac{2z_{far}z_{near}}{z_{far} - z_{near}} \end{array}\right.$$

至此,对称视锥体的透视投影变换矩阵就推导完毕了,最终得到的透视投影变换矩阵为:

$$P = \left(\begin{array}{cccc}  \frac{z_{near}}{width/2} & 0 & 0 & 0 \\ 0 & \frac{z_{near}}{height/2} & 0 & 0 \\ 0 & 0 & \frac{z_{far} + z_{near}}{z_{far} - z_{near}} & \frac{2z_{far}z_{near}}{z_{far} - z_{near}} \\ 0 & 0 &  -1 & 0 \end{array}\right)$$

情况二:不对称的视锥体

这一情况看起来较上面复杂一些,其实也没复杂多少,首先,深度投影没什么变化;其次,对于不对称视锥体的$x$和$y$方向上的投影,只要在对称视锥体的投影基础上平移一下即可,其实只有第一步和第二步有所区别,如下:

第一步:投影,按如下方式进行平移$l_x$和$l_y$,

\begin{equation}x_{proj} = -\frac{1}{z}\left(\frac{z_{near}}{width/2} \cdot x  + l_x\right)\end{equation}

\begin{equation}y_{proj} =  -\frac{1}{z}\left(\frac{z_{near}}{height/2} \cdot y + l_y\right)\end{equation}

第二步:将近平面(注:$z=-z_{near}$)的左、右边缘x坐标代入(1)得:

$$\left\{ \begin{array}{c} \frac{-left}{width/2} + l_x = -1.0 \\  \frac{right}{width/2} + l_x = 1.0 \end{array} \right.$$

得:

$$ l_x = \frac{left + right}{width/2}$$

同样,将近平面的上、下边缘$y$坐标代入(2)得:

$$l_y = \frac{top+botton}{height / 2}$$

这样,最终的投影变换矩阵为:

$$P = \left(\begin{array}{cccc}  \frac{z_{near}}{width/2} & 0 & \frac{left + right}{width/2} &  0 \\ 0 & \frac{z_{near}}{height/2} &  \frac{top+bottom}{height / 2} & 0 \\ 0 & 0 &  \frac{z_{far} + z_{near}}{z_{far} - z_{near}}  & \frac{2z_{far}z_{near}}{z_{far} - z_{near}} \\ 0 & 0 &  -1 & 0 \end{array}\right)$$

可以看到,要确定一个透视投影变换,需要下面这几个参数:

$top$     ——近平面上边缘离中心的距离

$bottom$   ——近平面下边缘离中心的距离

$left$       ——近平面左边缘离中心的距离

$right$     ——近平面右边缘离中心的距离

$z_{near}$   ——近平面离视点的距离

$z_{far}$   ——元平面离视点的距离

OpenGL中相关API就需要指定这些参数,来构建投影矩阵,如下:

void glFrustum(    GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble nearVal,
GLdouble farVal);

参数见上,另外还有一个,本质也是一样的,它应该是设置对称的视锥体的透视投影矩阵,即:

void gluPerspective(    GLdouble fovy,
GLdouble aspect,
GLdouble zNear,
GLdouble zFar);
fovy —— y方向上的视角
aspect —— 宽高比
zNear —— 近平面离视点距离
zFar —— 远平面离视点距离

通过fov和aspect,可以求出宽度和高度,进而求出透视投影矩阵。

3.2 正交投影矩阵的推导

正交投影矩阵和透视投影矩阵比起来简单太多了,我们还是先来看看正交投影的视景体吧,如下图所示:

这幅图像是从红包书上直接拿下来了,没添加任何注解,如果看不懂的童鞋可以参考下上面透视投影视锥体图像。可以看到,正交投影视锥体是一个立方体,下面我们就来看看正交投影矩阵的推导吧!首先还是看$z$轴位于立方体中心的情况:

第一步:$x$和$y$坐标变换,没啥好说的,就是归一化即可,即$x_{proj} = \frac{1}{width/2} \cdot x$和$y_{proj} = \frac{1}{height/2} \cdot y$;

第二步:深度方向投影,同样假设投影矩阵如下:

$$P = \left(\begin{array}{cccc}  \frac{1}{width/2} & 0 & 0 & 0 \\ 0 & \frac{1}{height/2} & 0 & 0 \\ 0 & 0 & A & B \\ 0 & 0 &  0 & 1 \end{array}\right)$$

这样,投影后深度坐标$z_{proj}$可表示为$z_proj = A\cdot z + B$,将近平面的深度和远平面的深度代入得:

$$\left\{\begin{array}{c} A \cdot -z_{near} + B = -1 \\ A \cdot -z_{far} + B = 1\end{array}\right.$$

得到:

$$\left\{\begin{array}{c} A = -\frac{2}{z_{far} - z_{near}} \\ B = -\frac{z_{far} + z_{near}}{z_{far} - z_{near}}\end{array}\right.$$

最后得到正交投影矩阵为:

$$P = \left(\begin{array}{cccc}  \frac{1}{width/2} & 0 & 0 & 0 \\ 0 & \frac{1}{height/2} & 0 & 0 \\ 0 & 0 & -\frac{2}{z_{far} - z_{near}} & -\frac{z_{far} + z_{near}}{z_{far} - z_{near}} \\ 0 & 0 &  0 & 1 \end{array}\right)$$

接下来看看$z$轴不在立方体中心的情况,同样对$x$和$y$的投影方式做一个平移,如下:

\begin{equation}x_{proj} = \frac{1}{width/2} \cdot x + l_x\end{equation}

\begin{equation}y_{proj} =  \frac{1}{height/2} \cdot y + l_y\end{equation}

同样,将近平面左、右边缘的x坐标代入(3)式得:

$$\left\{\begin{array}{c} -\frac{1}{width/2} \cdot left + l_x = -1 \\ -\frac{1}{width/2} \cdot right + l_x = 1 \end{array}\right.$$

可以求得:

$$l_x=-\frac{left + right}{width}$$

类似可以求得:

$$l_y=-\frac{top+ bottom}{height}$$

最终得到$z$轴不位于立方体中心的投影变换矩阵为:

$$P = \left(\begin{array}{cccc}  \frac{1}{width/2} & 0 & 0 & \frac{1}{height/2} \\ -\frac{left + right}{width} & 0 & 0 & -\frac{top+ bottom}{height} \\ 0 & 0 & -\frac{2}{z_{far} - z_{near}} & -\frac{z_{far} + z_{near}}{z_{far} - z_{near}} \\ 0 & 0 &  0 & 1 \end{array}\right)$$

正交矩阵在OpenGL中对应的API为:

void glOrtho(    GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble nearVal,
GLdouble farVal);

3.3 透视投影效果

讲完理论部分,在此对透视投影做一个效果演示。首先给出人眼坐标系下的坐标值,这里绘制四边形:

// -------------准备顶点数据-------------
GLfloat vertices[][] = {
{ 0.9, 0.9, -, - },
{ -0.9, 0.9, -, - },
{ 0.9, -0.9, -, - },
{ -0.9, -0.9, -, - }
};

演示效果图:

此图参数如下:

// -------------创建投影矩阵-------------
Camera camera;
camera.setFarClipDistance(); // 远投影面
camera.setNearClipDistance(); // 近投影面
camera.setFOVy(); // 张角
camera.setAspectRatio(); // 近平面宽高比
Matrix4X4 matProjection = camera.createProjectMatrix();

调整一下参数,如下:

// -------------准备顶点数据-------------
GLfloat vertices[][] = {
{ 0.9, 0.9, -, - },
{ -0.9, 0.9, -, - },
{ 0.9, -0.9, -, - },
{ -0.9, -0.9, -, - }
};
// -------------创建投影矩阵-------------
Camera camera;
camera.setFarClipDistance();
camera.setNearClipDistance(0.1);
camera.setFOVy();
camera.setAspectRatio();
Matrix4X4 matProjection = camera.createProjectMatrix();

得到下面效果——相当于沿着z轴往前移了一点,同时也变得更长了:

代码也贴出来吧!主程序代码:

 #include <iostream>
#include "GameFramework/StdAfx.h"
#include "Resource/GPUProgram.h"
#include "AlgebraicEntity/Camera.h" void initialize_06()
{
// -------------准备顶点数据-------------
GLfloat vertices[][] = {
{ 0.9, 0.9, -, - },
{ -0.9, 0.9, -, - },
{ 0.9, -0.9, -, - },
{ -0.9, -0.9, -, - }
};
GLfloat vertex_colors[][] = {
{ 1.0, 0.0, 0.0, 1.0 },
{ 0.0, 1.0, 0.0, 1.0 },
{ 0.0, 0.0, 1.0, 1.0 },
{ 1.0, 1.0, 0.0, 1.0 }
}; // -------------生成缓存对象-------------
GLuint Buffer_ID;
glGenBuffers(, &Buffer_ID);
glBindBuffer(GL_ARRAY_BUFFER, Buffer_ID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(vertex_colors), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, , sizeof(vertices), vertices);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(vertex_colors), vertex_colors); // -------------生成顶点数组对象-------------
GLuint VAO;
glGenVertexArrays(, &VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, , BUFFER_OFFSET());
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, , BUFFER_OFFSET(sizeof(vertices)));
glEnableVertexAttribArray();
glEnableVertexAttribArray(); // -------------着色器对象-------------
GPUProgram gpuProgram;
gpuProgram.AddShader(GL_VERTEX_SHADER, "F:/VC++游戏编程/OpenGLGuide/OpenGL06/projection.vert");
gpuProgram.AddShader(GL_FRAGMENT_SHADER, "F:/VC++游戏编程/OpenGLGuide/OpenGL06/projection.frag");
GLuint program = gpuProgram.CreateGPUProgram();
glUseProgram(program); // -------------创建投影矩阵-------------
Camera camera;
camera.setFarClipDistance();
camera.setNearClipDistance(0.1);
camera.setFOVy();
camera.setAspectRatio();
Matrix4X4 matProjection = camera.createProjectMatrix(); // -------------得到Uniform变量位置-------------
GLuint projection_loc = glGetUniformLocation(program, "mat_projection");
glUniformMatrix4fv(projection_loc, , GL_TRUE, matProjection._m); glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
} void display_06()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.3f, 0.3f, 0.3f, 1.0f); glDrawArrays(GL_TRIANGLE_STRIP, , );
glFlush();
} int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
glutInitWindowSize(, );
glutInitContextVersion(, );
glutInitContextProfile(GLUT_CORE_PROFILE);
glutCreateWindow("学习之路(五)"); glewExperimental = GL_TRUE;
if (GLEW_OK != glewInit())
{
std::cerr << "Unable to initialize GLEW... Exiting..." << std::endl;
std::exit(EXIT_FAILURE);
} initialize_06();
glutDisplayFunc(display_06);
glutMainLoop(); return ;
}

顶点着色器代码:

 #version  core

 uniform mat4 mat_projection;

 layout(location = ) in vec4 vertex_position;
layout(location = ) in vec4 vertex_color; out vec4 temp_color; void main()
{
gl_Position = mat_projection * vertex_position;
temp_color = vertex_color;
}

片元着色器代码:

 #version  core

 in  vec4 temp_color;

 out vec4 out_color;

 void main()
{
out_color = temp_color;
}

Camera类的代码,头文件:

 #pragma once

 #include "Matrix.h"

 /// \brief    represent a camera object, maintain the data related to
///
class ALGOBRAIC_ENTITY_EXPORT Camera
{
public:
enum ProjectType
{
PT_PROJECTIVE, // 透视投影
PT_ORTHOGONAL // 正交投影
}; Camera(void);
~Camera(void); Matrix4X4 createProjectMatrix() const; void setFarClipDistance(double dFarClipDistance);
double getFarClipDistance() const; void setNearClipDistance(double dNearClipDistance);
double getNearClipDistance() const; void setFOVy(double dFOVy);
double getFOVy() const; void setAspectRatio(double dAspectRatio);
double getAspectRatio() const; private:
double m_dFarClipDistance;
double m_dNearClipDistance;
double m_dFOVy;
double m_dAspectRatio;
};

实现文件:

 #include "Camera.h"
#include <cmath>
#include <iostream> Camera::Camera(void)
{} Camera::~Camera(void)
{} Matrix4X4 Camera::createProjectMatrix() const
{
double dHalfHeight = m_dNearClipDistance * std::tan(m_dFOVy * 0.5 * PI / );
double dHalfWidth = m_dAspectRatio * dHalfHeight; Matrix4X4 matProject;
matProject.m[][] = m_dNearClipDistance / dHalfWidth;
matProject.m[][] = m_dNearClipDistance / dHalfHeight;
matProject.m[][] = -(m_dFarClipDistance + m_dNearClipDistance)
/ (m_dFarClipDistance - m_dNearClipDistance);
matProject.m[][] = * m_dFarClipDistance * m_dNearClipDistance
/ (m_dFarClipDistance - m_dNearClipDistance);
matProject.m[][] = -1.0;
matProject.m[][] = ; return matProject;
} void Camera::setFarClipDistance( double dFarClipDistance )
{
m_dFarClipDistance = dFarClipDistance;
} double Camera::getFarClipDistance() const
{
return m_dFarClipDistance;
} void Camera::setNearClipDistance( double dNearClipDistance )
{
m_dNearClipDistance = dNearClipDistance;
} double Camera::getNearClipDistance() const
{
return m_dNearClipDistance;
} void Camera::setFOVy( double dFOVy )
{
m_dFOVy = dFOVy;
} double Camera::getFOVy() const
{
return m_dFOVy;
} void Camera::setAspectRatio( double dAspectRatio )
{
m_dAspectRatio = dAspectRatio;
} double Camera::getAspectRatio() const
{
return m_dAspectRatio;
}

另外,如果需要源代码的童鞋,也可以自行去百度云盘下载:http://pan.baidu.com/s/1pLVab1L。(读书笔记(五))

4 总结

本文主要是对前几篇博文的总结,同时对投影变换矩阵做了推导,至此,三维坐标矩阵变换部分应该是告一段落了。从下一篇文章开始,我们将介绍3ds文件的载入与显示,当然也会用到矩阵变换的知识,敬请期待!

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

  1. OpenGL学习之路(四)

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

  2. OpenGL学习之路(一)

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

  3. OpenGL学习之路(三)

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

  4. OpenGL学习之路(二)

    1 引子 在上一篇读书笔记中,我们对书本中给出的例子进行详细的分析.首先是搭出一个框架:然后填充初始化函数,在初始化函数中向OpenGL提供顶点信息(缓冲区对象)和顶点属性信息(顶点数组对象),并启用 ...

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

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

  6. redis——学习之路五(简单的C#使用redis)

    redis官方推荐使用的客户端程序 打星星表示推荐使用的客户端程序,一个笑脸表示最近6个月内有过正式活动的.http://redis.io/clients/#c 从这里我们可以判断官方推荐我们使用Se ...

  7. zigbee学习之路(五):定时器1(查询方式)

    一.前言 今天,我们来学习几乎所有单片机都有的功能,定时器的使用,定时器对单片机来说是相当重要的,有了它,单片机就可以进行一些复杂的工作. 二.原理与分析 谈到定时器的控制,我们最先想到的是要给它赋初 ...

  8. MongoDB学习之路(五)

    MongoDB $type 操作符 类型 数字 备注 Double 1 String 2 Object 3 Array 4 Binary data 5 Undefined 6 已废弃 Object i ...

  9. Java学习之路(五):常见的对象操作

    Object对象 我们先来介绍一下API API(Application Programming Interface):应用程序编程接口 Java API 就是Java提供给我们使用的类,这些类将底层 ...

随机推荐

  1. [原创]关于ORACLE的使用入门

    Oracle===============================数据库:Oracle------>甲骨文(Oracle) 49+%DB2---------->IBM 49+%Sq ...

  2. Java8中的default方法

    default方法 Java 8中引入了一个新的概念,叫做default方法,也可以称为Defender方法,或者虚拟扩展方法(Virtual extension methods). Default方 ...

  3. [LintCode] Toy Factory 玩具工厂

    Factory is a design pattern in common usage. Please implement a ToyFactory which can generate proper ...

  4. DotNetOpenAuth实践

    DotNetOpenAuth实践之搭建验证服务器 DotNetOpenAuth是OAuth2的.net版本,利用DotNetOpenAuth我们可以轻松的搭建OAuth2验证服务器,不废话,下面我们来 ...

  5. PHP操作数据库PDO

    PHP操作数据库 载入数据库驱动 訪问phpinfo.php能够查看是否已经载入数据库驱动,例如以下显示还没有载入mySql数据库驱动. 在c盘找到php.ini配置文件开启载入mySql驱动,例如以 ...

  6. webapp中绝对定位/固定定位与虚拟键盘冲突的问题

    $('body,html').height(document.body.clientHeight); 进入页面的时候就把高度固定住,这样虚拟键盘打开页面高度不会变化,你的布局也不会乱. 测试有效. 当 ...

  7. python函数的参数细节

    按"指针"传递 python中变量赋值.参数传递都是通过"指针"拷贝的方式进行的.除了按"指针"拷贝,还有一种按值拷贝的方式,关于按值.按指 ...

  8. 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)

    一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...

  9. NFC读写电子便签总结

    编写NFC程序的基本步骤 1)设置权限,限制Android版本.安装的设备: 1 2 3 4 <uses-sdk android:minSdkVersion="14"/> ...

  10. phpmailer使用qq邮箱、163邮箱成功发送邮件实例代码

    以前使用qq邮箱.163服务器发送邮件,帐号直接使用密码,现在不行了,得使用授权码,简单记录下 1.首先开通POP3/SMTP服务,qq邮箱——帐号——设置,找到POP3/SMTP点开启,输入短信会有 ...