ArchieOpenGL基础教程第七课:坐标变换

1、变换种类

OpenGL提供了计算机图形学中最基本的三维变换,包括:视点变换(gluLookAt)、模型变换、投影变换、剪取变换(附加裁剪面)和视口变换等,同时提供了特殊的变换和用法,如矩阵堆栈。


视点变换(gluLookAt):通过改变相机的方向和位置,可以改变出现在取景器中的景物,而景物在世界坐标系中并不发生变化。

模型变换:简单的改变被摄物体在取景器中的位置。

投影变换:使用变焦镜头gluPerspective还可以在不改变相机与被摄物体之间的空间距离的情况下,改变被摄物体在取景器中投影的大小。

视口变化(glViewport):冲洗相片底片时,还可以通过调整成像的位置,仅仅使影像的一部分显现出来。

最终,三维的现实世界变换成了二维的图像。

由于在某个方向上移动摄像机与向相反的方向移动物体对象所产生的效果是一样的,所以视点变换(gluLookAt)和模型变换统称为几何变换。

2、坐标系与坐标变换

现实中的三维对象本身包含了以现实世界的坐标形式,这个坐标系称为世界坐标系。而屏幕上的二维平面本身又定义了一个坐标系,称为屏幕坐标系。
屏幕坐标系中,可以定义一个视口,视景体投影后的图形就在视口glViewport中显示出来。(参考 第三课glViewport与glOrtho理解 ,glPerspective与glOrtho原理相同,一个是透视投影,一个是正交投影)
视口的坐标系与物理设备的坐标系之间可能存在差异,还需要做一些适应物理设备的坐标变换。物理设备的坐标系称为物理设备坐标系。
坐标变换操作顺序
[x,y,z,w]→ 几何变换矩阵→人眼坐标→ 投影变换矩阵→裁剪坐标→ 视图处理矩阵→设备坐标→ 视口变换矩阵→窗口坐标
在程序中,视点变换(gluLookAt)必须在模型变换(GL_MODELVIEW改变被摄物体在取景器中的位置)之前完成,但是投影变换和视口变换可以在绘图之前的任何时候指定。
mode取值及含义
Mode 含义
GL_MODELVIEW 指定后续矩阵操作的对象是几何矩阵(世界坐标系)堆栈
GL_PROJECTIONVIEW 指定后续矩阵操作的对象是投影矩阵堆栈
GL_TEXTURE 指定后续矩阵操作的对象是纹理矩阵堆栈
GL_COLOR 指定后续矩阵操作的对象是颜色矩阵堆栈
当前矩阵M对定点对象进行左乘操作,比如v=(v[0],v[1],v[2],v[3])
m指向包含16个元素的数组,那么几何变换矩阵M(v)按下式计算
M(v)=m[0]  m[4]   m[8]    m[12]          *       v(0]
     m[1]   m[5]  m[9]    m[13]                  v[1]
     m[2]   m[6]  m[10]   m[14]                  v[2]
     m[3]   m[7]  m[11]   m[15]                  v[3]
OpenGL中的矩阵表示与普通C语言的矩阵相反,OpenGL矩阵总是先列后行放置,而C语言是先行后列放置。因此,矩阵元素 mij在OpenGL中指矩阵的第i列第j行元素,而在C中指第i行第j列元素
OpenGL矩阵乘法这样的,假设当前矩阵是C,用glMultMatrix(M)函数或任意变换命令所指定的矩阵是M。在进行乘法操作之后,最后的矩阵是CM,而不是MC。
下面的代码用3个变换来绘制一个点
glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glMultMatrixf(N);
	glMultMatrixf(M);
	glMultMatrixf(L);
	glBegin(GL_POINTS);
	glVertex3f(v);
	glEnd();
这段代码中, 矩阵模型矩阵中相继含有I,N,N×M,N×M×L矩阵,变换后的顶点为N×M×L×v。因此顶点实际变换为N×(M×(L×v)),即 实际变换顺序与指定顺序相反

3、平移变换绘制基本图形

为了便于建模,通常以物体坐标系缺省的原点作为模型的初始位置,建模完成后再将物体移至它在场景中应出的位置。
在OpenGL中,平移用glTranslate完成。
平移变换是在模型矩阵中进行的,如果不能确定当前操作是模型矩阵,需使用glMatrixMode指定、
glTranslate函数可以作用于几何矩阵、投影矩阵和纹理坐标变换矩阵。如果当前矩阵为几何矩阵,函数的功能是将物体 局部参考坐标系的原点移到(x,y,z)所指的位置,形成新的物体坐标系。调用此函数之后的所有顶点的坐标都以新的位置作为原点。也就是说,平移之后所画的几何物体都做了相同的平移。
glTranslate函数平移的是坐标系而不是物体,该函数仅影响在它被调用之后所绘的物体。
glRotate旋转的也是坐标系,对其后所绘对象都有影响,遵循右手定则
glScale缩放变换,执行结果是局部坐标系的拉伸和压缩。
平移旋转缩放针对的都是局部坐标系,而不是所绘物体,并对之后所有所绘物体都有影响,一般情况是
在绘制物体之前用glPushMatrix()压入当前矩阵
在绘制结束之后用glPopMatrix()弹出保存矩阵
------------------------------------------------------------------------------------------------
为了与Nehe教程保持一致,现在先取消gluLookAt设置,注释掉OnDraw中的gluLookAt
// gluLookAt(100,0,-1,100,0,0,0,1,0);
如果不注释掉这句,会发现后面的平移旋转缩放都是相反的,比如glTranslatef(2.0,0.0,0.0);发现图像并没有向右而是相左移动,此时是相机像右移动了。现在解释有点复杂,所以先不设置gluLookAt,也就是说保持相机在原点,只考虑坐标系和要绘制物体的关系。以下参考Nehe教程第二课
-------------------------------------------------------------------------------------------------------------------------------------------
BOOL COpenglbaseView::RenderScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// 清除屏幕及深度缓存
	glLoadIdentity();	

当您调用glLoadIdentity()之后,您实际上将当前局部坐标系原点移到了屏幕中心,X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。中心左面的坐标值是负值,右面是正值。移向屏幕顶端是正值,移向屏幕底端是负值。移入屏幕深处是负值,移出屏幕则是正值。

glTranslatef(x, y, z)沿着 X, Y 和 Z 轴移动。根据前面的次序,下面的代码沿着X轴左移1.5个单位,Y轴不动(0.0f),最后移入屏幕6.0f个单位。注意在glTranslatef(x, y, z)中当您移动的时候,您并不是相对屏幕中心移动,而是相对当前所在的局部坐标系原点位置。

glTranslatef(-1.5f,0.0f,-6.0f);						// 物体局部坐标系左移 1.5 单位,并移入屏幕 6.0

现在我们已经移到了屏幕的左半部分,并且将视图推入屏幕背后足够的距离以便我们可以看见全部的场景-创建三角形。glBegin(GL_TRIANGLES)的意思是开始绘制三角形,glEnd() 告诉OpenGL三角形已经创建好了。

glBegin(GL_TRIANGLES);							// 绘制三角形
	glVertex3f( 0.0f, 1.0f, 0.0f);					// 上顶点
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 左下
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 右下
glEnd();

在屏幕的左半部分画完三角形后,我们要移到右半部分来画正方形。为此要再次使用glTranslate。这次右移,所以X坐标值为正值。因为前面左移了1.5个单位,这次要先向右移回屏幕中心(1.5个单位),再向右移动1.5个单位。总共要向右移3.0个单位。

glTranslatef(3.0f,0.0f,0.0f);						// 右移3单位

现在使用GL_QUADS绘制正方形。与绘制三角形的代码相类似,画四边形也很简单。唯一的区别是用GL_QUADS来替换了GL_TRIANGLES。并增加了一个点。我们使用顺时针次序来画正方形-左上-右上-右下-左下。采用顺时针绘制的是对象的后表面。这就是说我们所看见的是正方形的背面。逆时针画出来的正方形才是正面朝着我们的。现在这对您来说并不重要,但以后您必须知道。

	glBegin(GL_QUADS);							//  绘制正方形
	glVertex3f(-1.0f, 1.0f, 0.0f);					// 左上
	glVertex3f( 1.0f, 1.0f, 0.0f);					// 右上
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 左下
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 右下
	glEnd();	

完整的渲染绘制代码
BOOL COpenglbaseView::RenderScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// 清除屏幕及深度缓存
	glLoadIdentity();							// 重置当前的模型观察矩阵
	glTranslatef(-1.5f,0.0f,-6.0f);						// 左移 1.5 单位,并移入屏幕 6.0
	glBegin(GL_TRIANGLES);							// 绘制三角形
	glVertex3f( 0.0f, 1.0f, 0.0f);					// 上顶点
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 左下
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 右下
	glEnd();
	
	glTranslatef(3.0f,0.0f,0.0f);						// 右移3单位
	glBegin(GL_QUADS);							//  绘制正方形
	glVertex3f(-1.0f, 1.0f, 0.0f);					// 左上
	glVertex3f( 1.0f, 1.0f, 0.0f);					// 右上
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 左下
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 右下
	glEnd();	

	return TRUE;
}

------------------------------------------------------------------------------

为多边形添加颜色

这节不是必须,为了有更漂亮的效果,给上面的三角形和四边形添加颜色。

使用Flat coloring(单调着色)给四边形涂上固定的一种颜色。使用Smooth coloring(平滑着色)将三角形的三个顶点的不同颜色混合在一起,创建漂亮的色彩混合。

下一行代码是我们第一次使用命令glColor3f(r,g,b)。括号中的三个参数依次是红、绿、蓝三色分量。取值范围可以从0,0f到1.0f。类似于以前所讲的清除屏幕背景命令。

我们将颜色设为红色(纯红色,无绿色,无蓝色)。接下来的一行代码设置三角形的第一个顶点(三角形的上顶点),并使用当前颜色(红色)来绘制。从现在开始所有的绘制的对象的颜色都是红色,直到我们将红色改变成别的什么颜色。

	glColor3f(1.0f,0.0f,0.0f);				// 设置当前色为红色
	glVertex3f( 0.0f, 1.0f, 0.0f);					// 上顶点


设置第二个绿色顶点。三角形的左下顶点被设为绿色。

	glColor3f(0.0f,1.0f,0.0f);				// 设置当前色为绿色
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 左下


开始绘制之前将颜色设为蓝色。这将是三角形的右下顶点。glEnd()出现后,三角形将被填充。但是因为每个顶点有不同的颜色,因此看起来颜色从每个角喷出,并刚好在三角形的中心汇合,三种颜色相互混合。这就是平滑着色。

	glColor3f(0.0f,0.0f,1.0f);				// 设置当前色为蓝色
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 右下
	glEnd();
 
 

现在我们绘制一个单调着色-蓝色的正方形。最重要的是要记住,设置当前色之后绘制的所有东东都是当前色的。即便是在完全采用纹理贴图的时候,glColor3f仍旧可以用来调节纹理的色调。等等....,以后再说吧。

我们必须要做的事只需将颜色一次性的设为我们想采用的颜色(本例采用蓝色),然后绘制场景。每个顶点都是蓝色的,因为我们没有告诉OpenGL要改变顶点的颜色。最后的结果是.....全蓝色的正方形。再说一遍,顺时针绘制的正方形意味着我们所看见的是四边形的背面。

	glColor3f(0.5f,0.5f,1.0f);					// 一次性将当前色设置为蓝色
	glBegin(GL_QUADS);							//  绘制正方形
	glVertex3f(-1.0f, 1.0f, 0.0f);					// 左上
	glVertex3f( 1.0f, 1.0f, 0.0f);					// 右上
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 左下
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 右下
	glEnd();	


ArchieOpenGL基础教程第七课:坐标变换_第1张图片

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4、旋转

学习目的:学会如何旋转三角形和四边形。左图中的三角形沿Y轴旋转,四边形沿着X轴旋转。

我们增加两个变量来控制这两个对象的旋转。这两个变量加在程序的开始处其他变量的后面( bool fullscreen=TRUE;下面的两行)。它们是浮点类型的变量,使得我们能够非常精确地旋转对象。浮点数包含小数位置,这意味着我们无需使用1、2、3...的角度。你会发现浮点数是OpenGL编程的基础。新变量中叫做m_fRotTriangle的用来旋转三角形,m_fRotQuadrangle旋转四边形。

public:
	GLfloat m_fRotTriangle;
	GLfloat m_fRotQuadrangle;


Archie注:进行初始化,虽然这一步不是必须的,但对于养成良好的变量定义并初始化的编程习惯还是很有帮助的。

COpenglbaseView::COpenglbaseView()
{
	// TODO: add construction code here
	m_hRC=NULL;
	m_pDC=NULL;
	m_fRotQuadrangle=20.0f;
        m_fRotTriangle=7.0f;


下一行代码是新的。glRotatef(Angle,Xvector,Yvector,Zvector)负责让对象绕某个轴旋转。这个命令有很多用处。 Angle 通常是个变量代表对象转过的角度。 Xvector , Yvector 和 Zvector 三个参数则共同决定旋转轴的方向。比如(1,0,0)所描述的矢量经过X坐标轴的1个单位处并且方向向右。(-1,0,0)所描述的矢量经过X坐标轴的1个单位处,但方向向左。Archie注:根据右手定则确定转的方向
D. Michael Traub:提供了对 Xvector , Yvector 和 Zvector 的上述解释。
下面的一行代码中,如果m_fRotTriangle等于7,我们将三角形绕着Y轴从左向右旋转7 。您也可以改变参数的值,让三角形绕着X和Y轴同时旋转。

glTranslatef(-1.5f,0.0f,-6.0f);						// 左移 1.5 单位,并移入屏幕 6.0
	glRotatef(m_fRotTriangle,0.0f,1.0f,0.0f);				// 绕Y轴旋转三角形
	glBegin(GL_TRIANGLES);							// 绘制三角形
	glColor3f(1.0f,0.0f,0.0f);				// 设置当前色为红色
	glVertex3f( 0.0f, 1.0f, 0.0f);					// 上顶点
	glColor3f(0.0f,1.0f,0.0f);				// 设置当前色为绿色
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 左下
	glColor3f(0.0f,0.0f,1.0f);				// 设置当前色为蓝色
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 右下
	glEnd();

您会注意下面的代码中我们增加了另一个glLoadIdentity()调用。目的是为了重置模型观察矩阵。如果我们没有重置,直接调用glTranslate的话,会出现意料之外的结果。因为坐标轴已经旋转了,很可能没有朝着您所希望的方向。所以我们本来想要左右移动对象的,就可能变成上下移动了,取决于您将坐标轴旋转了多少角度。试试将glLoadIdentity() 注释掉之后,会出现什么结果。

重置模型观察矩阵之后,X,Y,Z轴都以复位,我们调用glTranslate。您会注意到这次我们只向右移了1.5单位,而不是上节课的3.0单位。因为我们重置场景的时候,焦点又回到了场景的中心(0.0处)。这样就只需向右移1.5单位就够了。
当我们移到新位置后,绕X轴旋转四边形。正方形将上下转动。

	glLoadIdentity();
	glTranslatef(1.5f,0.0f,-6.0f);//右移1.5单位	
         glRotatef(m_fRotQuadrangle,1.0f,0.0f,0.0f);			//  绕X轴旋转四边形
	glColor3f(0.5f,0.5f,1.0f);					// 一次性将当前色设置为蓝色
	glBegin(GL_QUADS);							//  绘制正方形
	glVertex3f(-1.0f, 1.0f, 0.0f);					// 左上
	glVertex3f( 1.0f, 1.0f, 0.0f);					// 右上
	glVertex3f( 1.0f,-1.0f, 0.0f);					// 左下
	glVertex3f(-1.0f,-1.0f, 0.0f);					// 右下
	glEnd();	


 

根据段落2实际变换顺序与指定顺序相反可知,变换矩阵是按先平移后旋转,实际处理时则是先旋转后平移。

下两行是新增的。倘若把m_fRotTriangle和 m_fRotQuadrangle 想象为容器,那么在程序的开始我们创建了容器( GLfloat m_fRotTriangle, 和 GLfloat m_fRotQuadrangle)。当容器创建之后,里面是空的。下面的第一行代码是向容器中添加0.2。因此每次当我们运行完前面的代码后,都会在这里使 m_fRotTriangle容器中的值增长0.2。后面一行将 m_fRotQuadrangle容器中的值减少0.15。同样每次当我们运行完前面的代码后,都会在这里使m_fRotQuadrangle容器中的值下跌0.15。下跌最终会导致对象旋转的方向和增长的方向相反。

尝试改变下面代码中的+和-,来体会对象旋转的方向是如何改变的。并试着将0.2改成1.0。这个数字越大,物体就转的越快,这个数字越小,物体转的就越慢。

	m_fRotTriangle+=0.2f;						// 增加三角形的旋转变量
	m_fRotQuadrangle-=0.15f;						// 减少四边形的旋转变量
	return TRUE;						// 继续运行


 ArchieOpenGL基础教程第七课:坐标变换_第2张图片

这里并没有看到不断变化的图像,原因就在于我们没有刷新图像。为了看到连续变化的情况,我们添加一个计时器。

添加计时器,定时刷新

在OnCreate函数中设置定时器1刷新频率20ms。

m_pDC=new CClientDC(this);
	SetTimer(1,20,NULL);
	if(!InitializeOpenGL(m_pDC))
		return 0;


OnDestroy中销毁定时器KillTimer(1)

void COpenglbaseView::OnDestroy() 
{
	CView::OnDestroy();
	//////////////////////////////////////////////////////////////////////////
	//删除调色板和渲染上下文、定时器
	wglMakeCurrent(NULL,NULL);
	if(m_hRC)
		wglDeleteContext(m_hRC);
	
	m_hRC=0;
	if (m_pDC)
	{
		delete m_pDC;
	}
	KillTimer(1);
	
}

添加OnTimer消息响应函数

void COpenglbaseView::OnTimer(UINT nIDEvent) 
{
	// TODO: Add your message handler code here and/or call default
	//////////////////////////////////////////////////////////////////////////
	//添加定时器响应函数和场景和更新函数
	Invalidate(FALSE);
	//////////////////////////////////////////////////////////////////////////
	
	CView::OnTimer(nIDEvent);
}


大功告成,终于可以看到连续旋转的多边形了。


 

 

点击打开链接
 

你可能感兴趣的:(ArchieOpenGL基础教程第七课:坐标变换)