变换包括:旋转,平移,缩放扭曲,投影。在指定了顶点之后,在屏幕上显示顶点之前,会进行三种变换:视图变换,模型变换,投影变换。
术语如下表:
变换类型 | 用途 |
视图(Viewing) | 指定观察者和照相机的位置 |
模型(Modeling) | 场景中的物体变换 |
模型视图(ModelView) | 描述视图和模型变换的对偶性 |
投影(Projection) | 改变可视区域的大小和形状 |
视口(Viewport) | 一种伪变换,对窗口的最终输出进行缩放 |
视点坐标是观察者的视角,与发生的变换无关。视点坐标是一个虚拟的固定的坐标系,通常作为参考系使用。许多变换都是相对于视点坐标系而言的。
如下:左图是观察者的观察角度是垂直于显示屏幕,右图对视点坐标做了旋转,这样就能看到z轴的相对位置。
视图变换是场景所应用的第一个变换。它用于确定场景的拍摄点。默认情况下,在透视投影钟观察者是从原点向z轴负方向看过去。这个观察点相对于视点坐标系系统进行移动,以提供一个特定的拍摄点。当观察点位于原点时,场景中所绘制的z值为正的物体就位于观察者的后面。
视图变换允许吧观察点放在任意位置,并在任何方向上观察场景。确定视图变换就像在场景中放置照相机并让它指向某个方向。
模型变换可以操作模型以及模型中的物体。模型变换包括移动物体,旋转物体,缩放物体。下图分别是平移,旋转,缩放
模型变换的最终结果还取决于你的变换顺序。下图展示了先旋转后平移,和先平移后旋转所得到的不同结果。
导致这种最终结果不同的原因是,每一次变换都是基于上一次变换的结果的。上面的变换是物体坐标系的变换。
事实上,这模型变换和视图变换在最终呈现的效果上市一样的。之所以把这两种变换区分开来,是为了方便程序员。相对于坐标向后移动物体和相对于物体把坐标系往前移的效果是一样的。视图变换像是把模型变换应用于整个场景。即在应用视图变换之后,像是每个物体独立进行了模型变换。模型视图变换在变换管道中用一个矩阵来表示,即模型视图矩阵。
视图变换可以理解为在绘制物体之前,先把模型变换应用于观察者。当在场景中放置更多的物体时,需要重新指定新的变换。所有其他的变换都是以最初的那个变换作为参考系的。
在模型视图变换之后,投影变换才应用于顶点。投影变换定义了可视区域和裁剪面。裁剪面决定了几何图元是否能被观察者看到。投影变换决定了在所有变换做完之后的场景投影到屏幕上的最终图像。投影变换有两种:正交投影和透视投影
正交投影即平行投影,所有多边形按照指定的大小出现在屏幕上。直线和多边形使用平行直线直接映射到2D屏幕上。不管物体有多远,都按照相同的大小进行绘制。通常用于CAD或二维图像的渲染。
透视投影显示的场景则更加真实。远处的物体看上去比相同大小的近处物体小一些。
下图是正投影和透视投影的比较
正投影的方框
透视投影的方框:
在所有变换进行完之后,获得的是将被映射到屏幕上的某个窗口的场景的二维投影。这种映射到物理窗口坐标的变换是最后做的变换——视口变换。通常,在颜色缓冲区和窗口像素之间有一一对应的关系,但并不总是如此。
上图是简单的变换管道。首先你的顶点被转化为1x4的矩阵,头三个值是x,y,z坐标,第四个数值代表着缩放因子,通常为1不需要更改。然后顶点乘以模型视图矩阵,产生经过转换后的视点坐标。然后视点坐标乘以投影矩阵,得到裁剪坐标。OpenGL会剔除所有在裁剪空间外的数据。然后裁剪坐标进行透视除法 除以w坐标得到规格化的设备坐标,原因是w值在模型视图变换和投影变换的过程中可能会被改变。最终坐标通过视口变换映射到二维平面上。
模型视图矩阵是一个4x4的矩阵,代表着你要放置的物体的位置和朝向的变换坐标。图元中的顶点(一个单列的矩阵)乘以模型视图矩阵得到变换过后的相对于视点坐标系系统的坐标。
void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
这个函数有三个参数分别代表着向x,y,z轴平移。调用这个函数之后,OpenGL会构造一个适当的举证乘以当前矩阵栈栈顶的矩阵。例子:
//向Y轴平移十个单元
glTranslatef(0.0f, 10.0f, 0.0f);
//画立方体
gluWireCube(10.0f);
void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
这个函数告诉OpenGL要沿着指定的坐标轴旋转多少角度。x,y,z为(1,0,0)则代表着沿x轴旋转,你可以沿着任意坐标轴旋转,用x,y,z值指定这个坐标轴。
// 沿着(1,1,1)坐标轴旋转45度
glRotatef(45.0f, 1.0f, 1.0f, 1.0f);
glutWireCube(10.0f);
void glScalef(GLfloat x, GLfloat y, GLfloat z);
沿x,y,z轴缩放多少倍。例子:
glScalef(2.0f, 1.0f, 2.0f);
gluWireCube(10.0f);
对角线为1,除了对角线之外的元素为0的矩阵称为单位阵。单位阵,即所有的矩阵乘以单位阵都得到原矩阵。OpenGL中重新加载单位阵到模型视图矩阵(重置模型视图矩阵)可以用下面两个函数调用:
glMatrixMode(GL_MODEVIEW);
glLoadIdentity();
有时我们需要保存当前的变换状态,然后绘制物体,再恢复它。那我们可以使用OpenGL提供的矩阵栈来实现。
glPushMatrix可以保存当前矩阵到矩阵栈中,glPopMatrix从矩阵栈弹出矩阵。
矩阵栈有最大的存储数量。通过glGet(GL_MAX_MODELVIEW_STACK_DEPTH)可以获得模型视图矩阵栈的容量。glGet(GL_MAX_PROJECTION_STACK_DEPT)获得投影矩阵栈的容量。
例子:
#include
#include
#include
static void SetupRC()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
}
static void RenderScene()
{
static GLfloat fEffect = 0.0f;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//先往里平移画原子核
glTranslatef(0.0f, 0.0f, -70.0f);
//红色的原子核
glColor3f(1.0f, 0.0f, 0.0f);
glutSolidSphere(20.0f, 15, 15);
//青色的电子
glColor3f(0.0f, 1.0f, 1.0f);
glPushMatrix();
glRotatef(fEffect, 0.0f, 1.0f, 0.0f);
glTranslatef(80.0f, 0.0f, 0.0f);
glutSolidSphere(10.0f, 15, 15);
glPopMatrix();
//黄色的电子
glColor3f(1.0f, 1.0f, 0.0f);
glPushMatrix();
glRotatef(45.0f, 0.0f, 0.0f, 1.0f);
glRotatef(fEffect, 0.0f, 1.0f, 0.0f);
glTranslatef(80.0f, 0.0f, 0.0f);
glutSolidSphere(10.0f, 15, 15);
glPopMatrix();
//绿色的电子
glColor3f(0.0f, 1.0f, 0.0f);
glPushMatrix();
glRotatef(-45.0f, 0.0f, 0.0f, 1.0f);
glRotatef(fEffect, 0.0f, 1.0f, 0.0f);
glTranslatef(80.0f, 0.0f, 0.0f);
glutSolidSphere(10.0f, 15, 15);
glPopMatrix();
fEffect += 10.0f;
if (fEffect > 360.0)
{
fEffect = 0.0f;
}
glutSwapBuffers();
}
static void ChangeSize(GLsizei w, GLsizei h)
{
GLfloat nRange = 200.0f;
if (h == 0)
{
h = 1;
}
//设置视口
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//设置可视区域
GLfloat aspect = (GLfloat)w / (GLfloat)h;
//设置为透视
gluPerspective(85.0, aspect, 80.0, 300.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
static void Timer( int value)
{
glutPostRedisplay();
glutTimerFunc(100, Timer, 1);
}
int main( int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(400,400);
glutCreateWindow( "atom Test");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
glutTimerFunc(100, Timer, 1);
SetupRC();
glutMainLoop();
return 0;
}
更高级的例子:
#include
#include
#include
static GLfloat whiteLight[] = {0.2f, 0.2f, 0.2f, 1.0f};
static GLfloat sourceLight[] = {0.8f, 0.8f, 0.8f, 1.0f};
static GLfloat lightPos[] = {0.0f, 0.0f, 0.0f, 1.0f};
static void SetupRC()
{
//开启深度测试
glEnable(GL_DEPTH_TEST);
glFrontFace(GL_CCW);
//启用裁剪面
glEnable(GL_CULL_FACE);
//开启光照
glEnable(GL_LIGHTING);
//设置光照参数
glLightModelfv(GL_AMBIENT_AND_DIFFUSE, whiteLight);
glLightfv(GL_LIGHT0, GL_AMBIENT, sourceLight);
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
//开启光源LIGHT0
glEnable(GL_LIGHT0);
//设置材料属性
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
static void RenderScene()
{
static GLfloat fEarth = 0.0f;
static GLfloat fMoon = 0.0f;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
//往屏幕里面平移
glTranslatef(0.0f, 0.0f, -300.0f);
//先关闭光照
glDisable(GL_LIGHTING);
//画日
glColor3ub(255, 255, 0);
glutSolidSphere(20.0, 15, 15);
//开启光照
glEnable(GL_LIGHTING);
//LIGHT0的发光点
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
//画地球
glRotatef(fEarth, 0.0f, 1.0f, 0.0f);
glColor3ub(0,0,255);
glTranslatef(105.0f, 0.0f, 0.0f);
glutSolidSphere(15.0, 15, 15);
//画月亮
glColor3ub(200, 200, 200);
glRotatef(fMoon, 0.0f, 1.0f, 0.0f);
glTranslatef(30.0f, 0.0f, 0.0f);
glutSolidSphere(10.0, 15, 15);
//月球的旋转
fMoon += 15.0f;
if (fMoon > 360.0f)
{
fMoon = 0.0f;
}
//地球的旋转
fEarth += 10.0f;
if (fEarth > 360.0f)
{
fEarth = 0.0f;
}
glPopMatrix();
glutSwapBuffers();
}
static void ChangeSize(GLsizei w, GLsizei h)
{
if (h == 0)
{
h = 1;
}
//设置视口
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//设置可视区域
GLfloat aspect = (GLfloat)w / (GLfloat)h;
gluPerspective(45.0, aspect, 1.0, 400.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void TimerFunc( int value)
{
glutPostRedisplay();
glutTimerFunc(100, TimerFunc, 1);
}
int main( int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(400,400);
glutCreateWindow( "solar Test");
glutReshapeFunc(ChangeSize);
glutTimerFunc(100, TimerFunc, 1);
glutDisplayFunc(RenderScene);
SetupRC();
glutMainLoop();
return 0;
}