计算机图形学基础(OpenGL版) 实验五 视图变换

本文深入探讨OpenGL中的模型视图变换,通过太阳系模型实验,解析视图、模型、投影及视口变换原理,展示如何运用gluLookAt、glTranslatef及glRotatef等函数实现天体运动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

计算机图形学基础 实验五 视图变换


1.实验目的:
理解掌握OpenGL程序的模型视图变换。

2.实验内容:
(1)阅读实验原理,运行示范实验代码,理解掌握OpenGL程序的模型视图变换;

(2)根据示范代码,尝试完成实验作业;

3.实验原理:
我们生活在一个三维的世界——如果要观察一个物体,我们可以:

1、从不同的位置去观察它(人运动,选定某个位置去看)。(视图变换)

2、移动或者旋转它,当然了,如果它只是计算机里面的物体,我们还可以放大或缩小它(物体运动,让人看它的不同部分)。(模型变换)

3、如果把物体画下来,我们可以选择:是否需要一种“近大远小”的透视效果。另外,我们可能只希望看到物体的一部分,而不是全部(指定看的范围)。(投影变换)

4、我们可能希望把整个看到的图形画下来,但它只占据纸张的一部分,而不是全部(指定在显示器窗口的那个位置显示)。(视口变换)

这些,都可以在OpenGL中实现。

从“相对移动”的观点来看,改变观察点的位置与方向和改变物体本身的位置与方向具有等效性。在OpenGL中,实现这两种功能甚至使用的是同样的函数。

由于模型和视图的变换都通过矩阵运算来实现,在进行变换前,应先设置当前操作的矩阵为“模型视图矩阵”。设置的方法是以GL_MODELVIEW为参数调用glMatrixMode函数,像这样:

glMatrixMode(GL_MODELVIEW);

该语句指定一个4×4的建模矩阵作为当前矩阵。

通常,我们需要在进行变换前把当前矩阵设置为单位矩阵。把当前矩阵设置为单位矩阵的函数为:

glLoadIdentity();

我们在进行矩阵操作时,有可能需要先保存某个矩阵,过一段时间再恢复它。当我们需要保存时,调用glPushMatrix()函数,它相当于把当前矩阵压入堆栈。当需要恢复最近一次的保存时,调用glPopMatrix()函数,它相当于从堆栈栈顶弹出一个矩阵为当前矩阵。OpenGL规定堆栈的容量至少可以容纳32个矩阵,某些OpenGL实现中,堆栈的容量实际上超过了32个。因此不必过于担心矩阵的容量问题。

通常,用这种先保存后恢复的措施,比先变换再逆变换要更方便,更快速。

注意:模型视图矩阵和投影矩阵都有相应的堆栈。使用glMatrixMode来指定当前操作的究竟是模型视图矩阵还是投影矩阵。

在代码中,视图变换必须出现在模型变换之前,但可以在绘图之前的任何时候执行投影变换和视口变换。

1.display()程序中绘图函数潜在的重复性强调了:在指定的视图变换之前,应该使用glLoadIdentity()函数把当前矩阵设置为单位矩阵。

2.在载入单位矩阵之后,使用gluLookAt()函数指定视图变换。如果程序没有调用gluLookAt(),那么照相机会设定为一个默认的位置和方向。在默认的情况下,照相机位于原点,指向Z轴负方向,朝上向量为(0,1,0)。

3.一般而言,display()函数包括:视图变换 + 模型变换 + 绘制图形的函数(如glutWireCube())。display()会在窗口被移动或者原来先遮住这个窗口的东西被一开时,被重复调用,并经过适当变换,保证绘制的图形是按照希望的方式进行绘制。

4.在调用glFrustum()设置投影变换之前,在reshape()函数中有一些准备工作:视口变换 + 投影变换 + 模型视图变换。由于投影变换,视口变换共同决定了场景是如何映射到计算机的屏幕上的,而且它们都与屏幕的宽度,高度密切相关,因此应该放在reshape()中。reshape()会在窗口初次创建,移动或改变时被调用。

OpenGL中矩阵坐标之间的关系

物理坐标模型视图矩阵投影矩阵透视除法规范化设备坐标——〉窗口坐标

(1)视图变换函数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)朝上向量为照相机指定了一个唯一的方向。如果没有调用gluLookAt,照相机就设定一个默认的位置和方向,在默认情况下,照相机位于原点,指向Z轴的负方向,朝上向量为(0,1,0)

glLoadIdentity()函数把当前矩阵设置为单位矩阵。

(2)使用模型变换的目的是设置模型的位置和方向

(3)投影变换,指定投影变换类似于为照相机选择镜头,可以认为这种变换的目的是确定视野,并因此确定哪些物体位于视野之内以及他们能够被看到的程度。

除了考虑视野之外,投影变换确定物体如何投影到屏幕上,OpenGL提供了两种基本类型的投影,1、透视投影:远大近小;2、正投影:不影响相对大小,一般用于建筑和CAD应用程序中

(4)视口变换

视口变换指定一个图象在屏幕上所占的区域

(5)绘制场景

4.示范代码: 太阳系

#include "Dependencies\glew\glew.h"
#include "Dependencies\freeglut\freeglut.h"
#include <amp_graphics.h>

static int year = 0, day = 0;

void init(void)
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	glColor3f(1.0, 1.0, 1.0);
	glPushMatrix();
	glutWireSphere(1.0, 20, 16); /* draw sun */
	glRotatef((GLfloat)year, 0.0, 1.0, 0.0);
	glTranslatef(2.0, 0.0, 0.0);
	glRotatef((GLfloat)day, 0.0, 1.0, 0.0);
	glutWireSphere(0.2, 10, 8); /* draw smaller planet */
	
	glPopMatrix();
	glutSwapBuffers();
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
	case 'd':
		day = (day + 10) % 360;
		glutPostRedisplay();
		break;
	case 'D':
		day = (day - 10) % 360;
		glutPostRedisplay();
		break;
	case 'y':
		year = (year + 5) % 360;
		glutPostRedisplay();
		break;
	case 'Y':
		year = (year - 5) % 360;
		glutPostRedisplay();
		break;
	case 27:
		exit(0);
		break;
	default:
		break;
	}
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
	
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMainLoop();
	return 0;
}

代码说明:
上面所描述的这个程序绘制一个简单的太阳系,其中有一颗行星和一颗太阳,它们是用同一个球体绘制函数绘制的。为了编写这个程序,需要使用glRtate*()函数让这颗行星绕太阳旋转,并且绕自身的轴旋转。还需要使用glTranslate*()函数让这颗行星远离太阳系原点,移动到它自己的轨道上。记住,可以在glutWireSphere()函数中使用适当的参数,在绘制两个球体时指定球体的大小。

为了绘制这个太阳系,首先需要设置一个投影变换和一个视图变换。在这个例子中,可以使用glutPerspective()和gluLookat().

绘制太阳比较简单,因为它应该位于全局固定坐标系统的原点,也就是球体函数进行绘图的位置。因此,绘制太阳时并不需要移动,可以使用glRotate*()函数绕一个任意的轴旋转。绘制一颗绕太阳旋转的行星要求进行几次模型变换。这颗行星需要每天绕自己的轴旋转一周,每年沿着自己的轨道绕太阳旋转一周。

为了确定模型变换的顺序,可以从局部坐标系统的角度考虑。首先,调用初始的glRotate*()函数对局部坐标系统进行旋转,这个局部坐标系统最初与全局固定坐标系统是一致的。接着,可以调用glTranslate*()把局部坐标系统移动到行星轨道上的一个位置。移动的距离应该等于轨道的半径。因此,第一个glRotate*()函数实际上确定了这颗行星从什么地方开始绕太阳旋转(或者说,从一年的什么时候开始)。

第二次调用glRotate*()使局部坐标轴进行旋转,因此确定了这颗行星在一天中的时间。当调用了这些函数变换之后,就可以绘制这颗行星了。

  1. 实验作业:
    (1)尝试在太阳系中增加一颗卫星,一颗行星。提示:使用glPushMatrix()和glPopMatrix()在适当的时候保存和恢复坐标系统的位置。如果打算绘制几颗卫星绕同一颗行星旋转,需要在移动每颗卫星的位置之前保存坐标系统,并在绘制每颗卫星之后恢复坐标系统。

(2)尝试把行星的轴倾斜。

实验内容原文链接

实验提高代码

#include "Dependencies\glew\glew.h"
#include "Dependencies\freeglut\freeglut.h"
#include <amp_graphics.h>

static int year = 0, day = 0,day01=0,sday=0;

void init(void)
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	glColor3f(1.0, 1.0, 1.0);

	glPushMatrix();
	glutWireSphere(1.0, 30, 16); /* draw sun */

	glRotatef((GLfloat)year, 0.0, 1.0, 0.5);
	glTranslatef(2.0, 0.0, 0.0);

	glRotatef((GLfloat)day, 0.0, 1.0, 0.0);
	glutWireSphere(0.2, 10, 8); /* draw smaller planet */

	glRotatef((GLfloat)sday, 0.0, 1.0, 0.0);
	glTranslatef(0.5, 0.0, 0.0);
	
	glutWireSphere(0.1, 10, 8); /* draw 卫星 */
	//glRotatef((GLfloat)day, 0.0, 0.5, 0.0);
	
	
	glPopMatrix();

	glPushMatrix();
	glRotatef((GLfloat)year, 0.0, 1.0, 0.0);
	glTranslatef(3.0, 0.0, 0.0);
	glRotatef((GLfloat)day01, 0.0, 1.0, 0.0);
	glutWireSphere(0.3, 10, 8); /* draw 第二个行星 */
	glPopMatrix();

	glutSwapBuffers();
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
	//Y是0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0
	//原来是0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0
}

void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
	case 'd':
		day = (day + 10) % 360;
		day01 = (day01 + 10) % 360;
		sday = (sday + 10) % 360;
		glutPostRedisplay();
		break;
	case 'D':
		day = (day - 10) % 360;
		day01 = (day01 - 10) % 360;
		glutPostRedisplay();
		break;
	case 'y':
		year = (year + 5) % 360;
		glutPostRedisplay();
		break;
	case 'Y':
		year = (year - 5) % 360;
		glutPostRedisplay();
		break;
	case 'a':
		year = (year + 5) % 360;
		day = (day + 10) % 360;
		day01 = (day01 + 10) % 360;
		glutPostRedisplay();
		break;
	case 'A':
		year = (year - 5) % 360;
		day = (day - 10) % 360;
		day01 = (day01 - 10) % 360;
		glutPostRedisplay();
		break;
	case 27:
		exit(0);
		break;
	default:
		break;
	}
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
	
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMainLoop();
	return 0;
}
实验OpenGL+GLUT开发平台搭建 5 小实验1: 开发环境设置 5 小实验2: 控制窗口位置和大小 6 小实验3:默认的可视化范围 6 小实验4:自定义可视化范围 7 小实验5: 几何对象变形的原因 8 小实验6: 视口坐标系及视口定义 8 小实验7:动态调整长宽比例,保证几何对象不变形 9 实验二 动画和交互 10 小实验1: 单缓冲动画技术 10 小实验2: 双缓冲动画技术 11 小实验3:键盘控制 13 小实验4:鼠标控制【试着单击鼠标左键或者右键,试着按下鼠标左键后再移动】 14 实验三 几何变换、观察变换、三维对象 16 小实验1:二维几何变换 16 小实验2:建模观察(MODELVIEW)矩阵堆栈 17 小实验3:正平行投影1 19 小实验4:正平行投影2 19 小实验5:正平行投影3 20 小实验6:透射投影1 21 小实验6:透射投影2 22 小实验7:三维对象 24 实验四 光照模型和纹理映射 26 小实验1:光照模型1----OpenGL简单光照效果的关键步骤。 26 小实验2:光照模型2----光源位置的问题 28 小实验3:光照模型3----光源位置的问题 31 小实验4:光照模型4----光源位置的问题 33 小实验5:光照模型5----光源位置的问题 35 小实验6:光照模型6----光源位置的问题 38 小实验7:光照模型7----光源位置的动态变化 40 小实验8:光照模型8----光源位置的动态变化 43 小实验9:光照模型9---光源位置的动态变化 45 小实验10:光照模型10---聚光灯效果模拟 48 小实验11:光照模型11---多光源效果模拟 50 小实验12:光照效果和雾效果的结合 53 小实验13:纹理映射初步—掌握OpenGL纹理映射的一般步骤 56 小实验13:纹理映射—纹理坐标的自动生成(基于参数的曲面映射) 59 小实验14:纹理映射—纹理坐标的自动生成(基于参考面距离) 61
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值