视图变换和模型变换/投影变换/视口变换

视图变换,是指变换照相机的位置,角度。 
模型变换,是指变换被照物体的位置,角度。

这两个变换,都会影响最终图形中,物体的位置,角度。而这两个变换,可以达到相同的效果。比如,你想要一个倒着的水杯图形,可以把你自己倒立,这样看到的水杯就是倒立的了。或者把水杯倒立,自己直立,也能看到倒立的水杯。 
这里写图片描述 
如图所示,这两种变换,可以看做达到目的的不同途径。甚至可以同时使用视图变换和模型变换,只要最终拿到了我们想要的图像就可以了。至于使用的是视图变换,还是模型变换,看我们理解问题的角度。

3.2.1 对变换进行思考

变换顺序,对最终的结果影响很大。 
看下面的例子: 
这里写图片描述 
图中有两个操作,旋转和移动。一个是沿原点绕z轴逆时针旋转45度,另一个是沿x轴向下平移。左图中,是先旋转,再移动,物体最终在x轴上。右图中,是先移动,再旋转,物体最终在x=y轴上。变换顺序不同,导致物体最终位置不同,这就是变换顺序的影响。

变换顺序,在OpenGL中的具体实现。 
在OpenGL中,所有的变换,都是通过矩阵来实现的。一个矩阵,表示一个或多个变换。模型视图变换,是通过模型视图矩阵来实现的。由于这个矩阵经常变换,需要进行管理,OpenGL中是通过矩阵堆栈来对矩阵进行管理的。

当前模型视图矩阵如果用C来表示,在当前模型视图基础上,进行一个变换,这个变换使用的矩阵为M。那么一个顶点v的变换之后的坐标为CMv。也就是说,M变换先作用于顶点v,然后再是当前模型视图矩阵C。

看下面例子:

glMatrixMode(GL_MODELVIEW);
glLoadIndentity();
glMultMatrixf(N);       //变换N
glMultMatrixf(M);       //变换M
glMultMatrixf(L);       //变换L
glBegin(GL_POINTS);
glVertex3f(v);
glEnd();

这段代码中,模型视图矩阵按顺序分别包含了I, N, NM,最后是NML,其中I表示单位矩阵。经过变换的顶点是NMLv。因此,顶点变换就是N(M(Lv)),也就是说,v首先与L相乘,Lv再与M相乘,MLv再与N相乘,而不是按它们指定的顺序出现的。

全局固定坐标系

图3-4中,左图中的先旋转,再平移,代码实现如下:

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMultMatrixf(T);   //平移
glMultMatrixf(R);   //旋转
draw_the_object();

只要记住一点,物体的变换顺序,和矩阵的出现顺序,正好相反。

局部移动坐标系统

这个通常用于模型的关节控制。比如机器人手臂。比如画汽车轮子上的螺钉,这个螺钉的位置,相对于汽车轮子,这个轮子上建立的坐标系,叫局部移动坐标系。而轮子的位置,又是相对于汽车本身,最后,汽车本身,是在全局坐标系中指定。

3.2.2 模型变换

模型变换,主要涉及三个函数,移动、旋转、缩放。有了这三个函数的组合,我们可以进行任意变换。

void glTranslate{fd}(TYPE x, TYPE y, TYPE z);
void glRotate{fd}(TYPE angle, TYPE x, TYPE y, TYPE z);
void glScale{fd}(TYPE x, TYPE y, TYPE z);

这三个函数,之前接触过。glTranslate,进行平移,平移的偏移量,由(x, y, z)指定。 glRotate,旋转,以逆时针方向绕着从原点到点(x, y, z)的直线旋转角度angle。glScale,按照一定比列进行缩放,比例在x轴,y轴,z轴方向上的量是(x, y, z)。

3.2.3 视图变换

视图变换,相关的三个函数是glTranslate,glRotate和gluLookAt函数。

这个glTranslate和glRotate在模型变换中,我们已经见过了。怎么模型变换和视图变换,使用的是同样的函数呢?因为变换是相对的。比如,让模型和照相机距离5个单位长度,假设模型和照相机放在一起。我们可以将物体向前移动5个单位长度,也可以将照相机向后移动5个单位长度。所以,视图变换,也是使用glTranslate和glRotate。只是这个参数的含义相反罢了。 
比如

glTranslatef(0.0, 0.0, -5.0);

这个函数在场景中把物体沿z轴移动-5个单位,相当于把照相机沿z轴移动+5个单位。

使用工具函数gluLookAt()

void gluLookAt(
    GLdouble eyex, GLdouble eyey, GLdouble eyez,
    GLdouble centerx, GLdouble centery, GLdouble centerz,
    GLdouble upx, GLdouble upy, GLdouble upz);

定义一个视图矩阵,并把它与当前矩阵进行右乘。目标观察点eyex, eyey, eyez。centerx, centery和centerz参数指定了视线上的任意一点。upx,upy和upz参数表示哪个方向是朝上的(也就是说,在视景体中自底向上的方向)。

默认情况下,照相机位于原点,指向z轴的负方向,以y轴的正方向为朝上方向。相当于调用:

gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.0);

参考点的z值是-100.0, 但它也可以是任意的负值,因为它不会影响视线的方向。

下面是使用gluLookAt的一个例子。

gluLookAt(4.0, 2.0, 1.0, 2.0, 4.0, -3.0, 2.0, 2.0, -1.0);

<img src="https://img-blog.csdn.net/20150315175335303" alt="这里写图片描述" title="" style="font-family: 'microsoft yahei'; line-height: 26px; border: none; max-width: 100%; background-color: rgb(255, 255, 255);" /><span style="font-family: 'microsoft yahei'; line-height: 26px; background-color: rgb(255, 255, 255);"> </span>

这个函数,将摄像机移动到了(4.0, 2.0, 1.0)这个点,摄像机朝向(2.0, 4.0, -3.0)方向,摄像机向上的方向为(2.0, 2.0, -1.0)。

强烈推荐,理解这几个函数(glTranslate, glRotate, gluLookAt),使用Nate Robin的教程。网上有下的。

创建自定义的工具函数

创建自定义的函数,其实就是在自己的函数中,调用glTranslate和glRotate这两个函数。 
要创建自定义的工具函数,主要是弄清楚两个东西:一个是参数是相对于哪个坐标系的,第二个就是照相机的变换顺序,与glTranslate,glRotate出现的顺序相同。

比如编写一个飞机模拟器,并且以飞机的驾驶员座位观察点显示飞机外面的景象。我们可以用一个圆点位于跑道上的坐标系统来描述整个场景,飞机相对于坐标(x, y, z)。然后,假设飞机还有倾侧角、螺旋角和航向改变角(这些都是飞机相对于它的重心的旋转角度)。下面这个函数可以作用视图变换函数使用。

void pilotView(GLdouble planex, GLdouble planey, GLdouble planez, GLdouble roll, GLdouble pitch, GLdouble heading)
{
    glRotated(roll, 0.0, 0.0, 1.0);
    glRotated(pitch, 0.0, 1.0, 0.0);
    glRotated(heading, 1.0, 0.0, 0.0);
    glTranslated(-planex, -planey, -planez);
}

这个其实很好理解,因为这个roll, pitch, heading,都是相对于飞机的。而飞机,就是我们的相机。所以,先将飞机旋转到一定角度,然后移动到点(planex, planey, planez)上面。因为这个planex, planey, planez是相对于机场跑道的坐标系,而我们移动的飞机相当于相机,所以都要取负号。

**********************      投影变换    ********************************

投影变换的目的是定义一个视景体。视景体定义了场景的可视范围,以及场景如何投射到屏幕上。投影有两种:透视投影 和 正交投影。相关的函数有4个: GL库中两个, GLU库中两个。 GL库中的两个分别为glFrustum和glOrtho,GLU库中的两个分别为gluPerspective和gluOrtho2D。

3.3.1 透视投影

透视投影的特点就是,物体离照相机越远,它在最终图像中看上去就越小。即所谓的近大远小。

void glFrustum(GLdouble left, GLdouble right,

                             GLdouble bottom, GLdouble top,

                              GLdouble near, GLdouble far);

创建一个透视视图平截头体的矩阵,并把它与当前矩阵相乘。平截头体的视景体是由这个函数的参数定义的:(left, bottom, -near)和(right, top, -near)分别指定了近侧裁剪平面左上角和右下角(x, y, z)坐标。near 和far分别表示从观察点到近侧和远侧裁剪平面的距离,它们的值应该是正的。 

这里写图片描述

由于glFrustum使用起来并不很直观。所以通常使用GLU库中的gluPerspective()函数。

void gluPerspective(GLdouble fovy,  GLdouble aspect,  GLdouble near,  GLdouble far);

创建一个表示对称透视视图平截头体的矩阵,并把它与当前矩阵相乘。fovy是yz平面上视野的角度,它的取值范围是【0.0, 180.0】。aspect是这个平截头体的纵横比,也就是它的宽度除以高度。near和far分别是观察点与近侧裁剪平面以及远裁剪平面的距离(沿z轴负方向),这两个值都应该是正的。

OpenGL中的数值,并没有单位,唯一的规则是必须使用一致的测量单位。

3.3.2 正投影

正投影,与透视投影不同,物体与照相机的距离并不影响它看上去的大小。这种类型的投影,常用于建筑蓝图,或者计算机辅助设计的应用程序。

正投影使用的函数,叫glOrtho。

void glOrtho(GLdouble left,  Gldouble right,  GLdouble bottom,  GLdouble top,  GLdouble near,  GLdouble far);

这个函数的参数,和glFrustum函数的参数完全一样。只是最终定义的视景体不一样。glOrtho定义的视景体如下图所示。 

这里写图片描述 
其中(left, bottom, -near)和(right, top, -near)是近侧裁剪平面上的点,分别映射到视口的左下角和右上角。(left, bottom, -far)和(right, top, -far)是远侧裁剪平面上的点,分别映射到视口的左下角和右上角。near和far可以是正值或负值,甚至可以设置为0。但是,near和far不应该取相同的值。

3.3.3 视景体裁剪

定义了视景体之后,场景中物体的顶点通过模型视图矩阵和投影矩阵进行变换之后,位于视景体之外的所有图元都将裁剪掉。6个裁剪平面就是定义视景体6个侧面的平面。还可以自定义裁剪平面,参照3.7节。


****************************     视口变换    ********************************

视口变换,相当于选择照片大小。视口是一个矩形的窗口区域,图像就是在这个区域中绘制的。

3.4.1 定义视口

定义视口,使用函数glViewport。这个函数之前用过好多遍了。

void glViewport(GLint x,  GLint y,  GLsizei width,  GLsizei  height);

在窗口中定义一个像素矩形,最终的图像将映射到这个矩形中。(x, y)参数指定了视口的左下角,width和height表示这个视口矩形的宽度和高度。在默认情况下,视口的初始值是(0, 0, winWidth, winHeight),其中winWidth和winHeight指定了窗口的大小。

这里注意,视口的纵横比一般和视景体的纵横比相同。如果这两个纵横比不同,当图像投影到视口时就会变形。如下图所示。 
这里写图片描述

为了避免变形可以修改投影的纵横比,使它与视口相匹配。

int  width = 400;
int  height = 200;
double aspect = width / height;
gluPerspective(fovy, aspect, near, far);
glViewport(0, 0, width, height);

3.4.2 变换深度坐标

在OpenGL中,深度坐标(z坐标)总是被认为位于0.0~1.0的范围之间。当我们需要将坐标范围限制的更小的时候,就需要变换深度坐标。使用的函数叫glDepthRange。

void glDepthRange(GLclampd near,  GLclampd  far);

为z坐标定义了一种编码形式,它是在视口变换期间执行的。near和far值表示经过调整后可以存储在深度缓冲区中的最小值和最大值。在默认情况下,它们分别是0.0和1.0,适用于绝大多数应用程序。这两个参数的范围被限定在【0, 1】之间。也就是说,我们可以使用这个函数,限定比【0,1】更小的范围,比如【0.2, 0.5】。


转自:http://blog.csdn.net/snail_hunan/article/details/44278949

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值