1.实验目的:
- 学习了解三维图形几何变换原理。
- 理解掌握OpenGL三维图形几何变换的方法。
- 理解掌握OpenGL程序的模型视图变换。
- 掌握OpenGL三维图形显示与观察的原理与实现。
2.实验内容:
(1)阅读教材有关三维图形变换原理,运行示范实验代码,掌握OPENGL程序三维图形变换的方法; (2)阅读实验原理,运行示范实验代码,理解掌握OpenGL程序的模型视图变换。 (3)请分别调整观察变换矩阵、模型变换矩阵和投影变换矩阵的参数,观察变换结果; (4)掌握三维观察流程、观察坐标系的确定、世界坐标系与观察坐标系之间的转换、平行投影和透视投影的特点,观察空间与规范化观察空间的概念。理解OpenGL图形库下视点函数、正交投影函数、透视投影函数。理解三维图形显示与观察代码实例。
3.实验原理:
首先来简单了解计算机图形学中四个主要变换概念: (1)视图变换:也称观察变换,指从不同的位置去观察模型; (2)模型变换:设置模型的位置和方向,通过移动、旋转或缩放变换,让模型具有合适的位置和大小; (3)投影变换:类似于为照相机选择镜头,将三维模型通过投影方式生成一幅二维投影图,同时确定视野,并确定哪些物体位于视野之内以及它们能够被看到的程度。投影变换主要分为透视投影和平行投影两种。 (4)视口变换:将投影变换得到的投影图映射到屏幕的视区上,确定最终图像在屏幕上所占的区域。 上述变换在OpenGL中实际上是通过矩阵乘法来实现。无论是移动、旋转还是缩放大小,都是通过在当前矩阵的基础上乘以一个新的矩阵来达到目的。OpenGL可以在最底层直接操作变换矩阵。同时,OpenGL也把这一切变换封装成一系列函数调用来实现不同的变换,以便于使用。
下面是这些变换函数使用时需要注意内容: (1)在OpenGL程序中,视图变换必须出现在模型变换之前,但可以在绘图之前的任何时候执行投影变换和视口变换。 (2)确定视图变换之前,应该使用glLoadIdentity函数把当前矩阵设置为单位矩阵,类似于变换初始化。 (3)在载入单位矩阵之后,使用gluLookAt函数指定视图变换。如果程序没有调用gluLookAt(),那么照相机会设定为一个默认的位置和方向,即照相机位于原点,指向z轴负方向,朝上向量为(0,1,0)。而gluLookAt(0.0,0.0,5.0,0.0,0.0,0.0,0.0,1.0,0.0)则把照相机放在(0,0,5),镜头瞄准(0,0,0),朝上向量定为(0,1,0)。 (4)一般而言,display函数包括:视图变换 模型变换 绘制图形的函数(如glutWireCube)。display会在窗口被移动或者原来先遮住这个窗口的东西被移开时,被重复调用,并经过适当变换,保证绘制的图形是按照希望的方式进行绘制。 (5)在调用glFrustum设置投影变换之前,在reshape函数中有一些准备工作:视口变换 投影变换 模型视图变换。由于投影变换,视口变换共同决定了场景是如何映射到计算机的屏幕上的,而且它们都与屏幕的宽度、高度密切相关,因此应该放在reshape函数中。reshape函数会在窗口初次创建,移动或改变时被调用。 总结起来,OpenGL中矩阵坐标之间的关系为:模型世界坐标→模型视图矩阵→投影矩阵→透视除法→规范化设备坐标→窗口坐标。
下面是代码中有关函数介绍: (1)glutReshapeFunc(reshape)是注册重绘回调函数,该函数在窗口大小改变以及初始窗口时被调用,完成关于坐标系显示的一系列初始化; (2)glViewport(0,0,width,height)是视口变换函数,用来设定了截取的图形以怎样的比例显示在视窗上,我们默认用原本窗体的比例; (3)glOrtho(左,右,下,上,近,远)为正投影函数,其中六个参数划分出了一个立方体空间,这个空间里物体将以正投影的模式表现,在移动的过程中,观察到的物体大小不会发生变化,这解释了为什么在正投影中移动物体,不能观察出物体形状变化; (4)gluPerspective(视角,宽高比,近距离,远距离)是透视投影函数,其中近距离和远距离分别指照相机镜头跟近裁剪平面和远裁剪平面的距离。
本节实验开启了深度测试,加入了环境光。开启深度测试函数为glEnable (GL_DEPTH_TEST),开启光照模式。在深度测试算法中,通过扫描投影在xOy平面上每一点的z坐标的大小,确定遮挡关系,只显示z坐标小的像素,进而完成遮挡效果。在光照模式下,只开启了一个白色的环境光源,代码为glEnable(GL_LIGHTING)及其后几行代码。OpenGL可设置多种光源,包括环境光、漫反射光、镜面反射光,构建光照模型,来模拟现实中的光照。
4.实验代码:
代码语言:javascript复制#include <stdlib.h>
#include <GL/glut.h>
float fTranslate;//整体平移因子
float fRotate = 0.0f;//整体旋转因子
float tRotate = 0.0f;//茶壶旋转因子
bool tAnim = false;//茶壶旋转
bool bPersp = false;//渲染
bool bAnim = false;//整体旋转
bool bWire = false;//填充、线框
int wHeight = 0;
int wWidth = 0;
float place[] = { 0, 0, 5 };
void Draw_Scene()
{
glPushMatrix();//当前矩阵压栈
glTranslatef(place[0], place[1], place[2]);//平移,放在桌面上的高度
glRotatef(90, 1, 0, 0); //茶壶绕x轴旋转的角度
glRotatef(tRotate, 0, 1, 0);
glScalef(1.8,1.8, 1.8);
glutSolidTeapot(5);//size
glPopMatrix();
}
void updateView(int width, int height)
{
glViewport(0, 0, width, height); // 视口变换
glMatrixMode(GL_PROJECTION); // 设置投影模式
glLoadIdentity(); // 变换矩阵初始化为单位阵
float whRatio = (GLfloat)width / (GLfloat)height;
if (bPersp)
gluPerspective(45, whRatio, 1, 100);
//透视模式下,物体近大远小,参数分别为(视角,宽高比,近处,远处)
else
glOrtho(-3, 3, -3, 3, -100, 100);
glMatrixMode(GL_MODELVIEW);//对模型视景矩阵堆栈应用随后的矩阵
}
void myReshape(int width, int height)
{
if (height == 0)
{
height = 1;
}
wHeight = height;
wWidth = width;
updateView(wHeight, wWidth);
}
void myIdle()
{
glutPostRedisplay();
}
float eye[] = { 0, 0, 8 };
float center[] = { 0, 0, 0 };
void myDraw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();//重置为单位矩阵
//gluLookAt定义一个视图矩阵,并与当前矩阵相乘
gluLookAt(eye[0], eye[1], eye[2],
center[0], center[1], center[2],
0, 1, 0); // 场景(0,0,0)的视点中心 (0,5,50),Y轴向上
//三个数组代表的分别是:相机在世界坐标中的位置
//相机对准的物体在世界坐标中的位置
//相机朝上的方向在世界坐标中的位置
if (bWire)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//线框模式
else
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//填充模式
glEnable(GL_DEPTH_TEST);//启用深度测试,根据坐标远近自动隐藏被遮住的物体
glEnable(GL_LIGHTING);//启用灯光
GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_pos[] = { 5, 5, 5, 1 }; //在Opengl中总共可以设置8个光源
glLightfv(GL_LIGHT0, GL_POSITION, light_pos);//设置0号光源的位置属性
glLightfv(GL_LIGHT0, GL_AMBIENT, white);//设置0号光源的环境光属性
glEnable(GL_LIGHT0);//启用0号光源
// glTranslatef(0.0f, 0.0f,-6.0f); // 把对象放置再中心
glRotatef(fRotate, 0, 1.0f, 0);
glRotatef(-90, 1, 0, 0);//绕x轴逆时针90
glScalef(0.2, 0.2, 0.2);
Draw_Scene();
if (bAnim)
fRotate = 0.5f;//旋转
if (tAnim)
tRotate = 0.5f;
glutSwapBuffers();//交换缓冲区
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);//对glut函数库进行初始化
//指定glutCreateWindow函数将要创建的窗口显示模式,RGB 深度缓存,双缓存模式
glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
glutInitWindowSize(480, 480);//设置窗口大小
int windowHandle = glutCreateWindow("茶壶三维显示与观察");
glutDisplayFunc(myDraw);//指定当前窗口需要重新绘制时调用的函数
glutReshapeFunc(myReshape); //当注册窗口大小改变时回调函数
//glutKeyboardFunc(myKey);//为当前窗口指定键盘回调
glutIdleFunc(myIdle);//可以执行连续动画
glutMainLoop();//进入glut时间处理循环,永远不会返回
return 0;
}
运行结果如图A.6(a)所示。
图A.6(a)
5.实验提高
设置键盘回调函数myKey(),实现键盘交互操作,实现上下前后移动、透视和平行投影模式切换、线框模式切换、退出等操作,见图A.6(b)。
图A.6 (b)