[OpenGL] 茶壶的投影、旋转、平移


[OpenGL] 茶壶的投影、旋转、平移_第1张图片

      [OpenGL] 茶壶的投影、旋转、平移_第2张图片

1.投影模式、观察方位、视口显示等的初始化

 

        glutReshapeFunc(reshape);//注册重绘回调函数

        该函数在窗口大小改变以及初始窗口时被调用,在这个函数里,我们完成关于坐标系显示的一系列初始化。


         glViewport(0,0, width , height );

        调用该函数设定了截取的图形以怎样的比例显示在视窗上,我们默认用原本窗体的比例。


         接下来是设置投影模式,在设置之前先要将矩阵模式转换为投影。

          ·正投影

           [OpenGL] 茶壶的投影、旋转、平移_第3张图片

        glOrtho(-3, 3, -3, 3, -100, 100);        (左,右,下,上,近,远 )


         六个参数划分出了一个立方体空间,这个空间里物体将以正投影的模式表现。也就是说,不同于透视投影的近大远小,在移动的过程中,观察到的物体大小不会发生变化。这也就解释了为什么在正投影下让物体前后移动,是无法被观测出来的。

 

        ·透视投影

        [OpenGL] 茶壶的投影、旋转、平移_第4张图片

        gluPerspective(45, whRatio, 1,100);      (视角,宽高比,近处,远处)

        透视投影中,物体的显示更加符合人眼的观察,前后移动物体时,物体会产生大小的变化。

 

         在绘制的开始,我们通过gluLookAt函数设定了观察物体的方式。

        gluLookAt(eye[0], eye[1], eye[2],

                center[0], center[1],center[2],

                  0, 1, 0);           

      该函数的每一行参数分别代表了观察点位置,望向的位置,观察方向(头顶方向)。


         glutKeyboardFunc(key); // 注册按键回调函数

         在这里,因为我们希望完成通过按键对物体操控的操作,我们需要注册按键回调函数,它不是在程序启动时执行,而是等待用户完成了某些操作后,再通过函数指针调用函数。

 

2.光源、深度的初始化

 

        在本次实验中,开启了深度测试,加入了环境光。


        glEnable(GL_DEPTH_TEST); //开启深度测试

        glEnable(GL_LIGHTING);//开启光照模式

 

         在深度测试算法中,我们通过扫描投影在x0y平面上每一点的z坐标的大小,来判断谁在前面,谁在后面,也就是确定遮挡关系,只显示z坐标小的像素,进而完成遮挡效果。

         在光照模式下,我们只开启了一个白色的环境光源。事实上,在opengl中,我们可以设置多种光源,包括环境光、漫反射光、镜面反射光,构建光照模型,来模拟现实中的光照。

 

3.茶壶和桌子的移动

 

         首先,要明确一点的是,在我们未对物体进行平移旋转等操作时,物体始终处于原点,而之后进行的一系列操作是在物体的局部坐标系上进行的,初始和世界坐标系重合,而进行矩阵操作后将发生变化。

        在绘制茶壶和桌子时,它们被绘制在了原点处,中间经历了一些旋转变换以方便桌子的绘制。

        当我们希望茶壶和桌子向上下左右移动时,除了可以直接让它们自身移动,也就是乘以glTranlatef矩阵,也可以考虑让观察者动起来,那么,看到的效果,也同样是物体的移动。

         那么我们很容易分析出,当我们希望物体向左移动时,我们的眼睛和望向的位置,都应该向右移动相同的距离,如下图所示。这样,我们的物体就会出现在视野的左边,并且,我们看到的依旧是茶壶的正方向。

  [OpenGL] 茶壶的投影、旋转、平移_第5张图片

         我们来考虑两种错误的操作方式:


          1)视点右移,观察位置不变。             

         [OpenGL] 茶壶的投影、旋转、平移_第6张图片

        这样的操作下,我们的眼睛始终盯着茶壶,随着其右移,茶壶呈现出旋转的效果,而位置却始终处于正中央。当然,这和一般意义上的旋转有所差异,因为我们不断右移的过程中,永远无法观测到茶壶嘴正对着我们的画面。

 

    2)观察位置右移,视点不变。 

         [OpenGL] 茶壶的投影、旋转、平移_第7张图片

        这种情况下,物体确实在视野中“左移”了,但仔细观察后,会发现,观察到的不再是物体的正面了,而是略有一个倾斜的角度。

 

4.茶壶的移动与旋转

 

         单独物体的移动,应在绘制茶壶时直接对物体进行平移、旋转操作,即调用glTranslate,glRotatef函数,这是前一次实验的主要内容,所以在这里不做过多阐述。我们把位置信息存在数组里,在键盘指令下修改数组内容,并通过参数传入来完成绘制。

          我们发现在按键回调函数中,我们只是修改了一些参数信息,却并没有再次调用绘制函数,然而物体依旧被重绘了。这是因为在设计时,我们采用的并不是修改时重绘,而是空闲时重绘,系统会不断地调用绘制函数,当我们把一些参数改变后,绘制回调函数就会使用新的参数,动画效果就产生了。

         当移动因子超过了桌面大小时,我们让其等于桌面边缘坐标。

 

         gluPerspective(45, whRatio, 1,100);

 

         这里选择了45度的视角,根据调节参数这个视角观察到的物体更加令人舒适。whRatio的选取一般来说,要和视窗的宽高比等同。最后两个参数是近点和远点,它们不能为负值,否则可能消失在视野中。根据正投影的-100,100,这里选取了一个相近的参数。 

 

        glOrtho(-3 ,3, -3, 3,-100,100);

 

        这里x,y的坐标范围较小,因为物体的实际大小也不会很大,而在z轴上,大小的选取不会影响到投影效果,所以我们可以选得更大一些。


//WASDZC  控制相机上下左右前后移动

//P       切换投影方式(正投影与透视投影)

//O       切换渲染方式(填充模式与线框模式)

//空格键  启动与暂停旋转(桌子与茶壶一起绕桌子中心轴旋转)

//IKJL    控制茶壶前后左右移动。

//E       茶壶旋转

//Q       退出


#include <stdlib.h>
#include "glut.h"

float fRotate    = 0.0f; //旋转因子(茶壶和桌子)
float fScale     = 1.0f; //缩放因子

float tRotate = 0.0f; //旋转因子(茶壶)

bool bPersp = false; //是否为透视投影 (vs 正投影)
bool bAnim = false; // 茶壶和桌子是否旋转
bool bWire = false; // 绘制模式是否为线形 (vs 填充)
bool isRotate = false; //茶壶是否旋转

int wHeight = 0;
int wWidth = 0;

int min(int x, int y)
{
	return x < y ? x : y;
}

//绘制腿部
void Draw_Leg() 
{
	glScalef(1, 1, 3);
	glutSolidCube(1.0);
}

void Draw_Scene(float place[])
{
	//画茶壶
	glPushMatrix();
	glTranslatef(place[0], place[1], place[2]);//控制平移
	glRotatef(90, 1, 0, 0);
	glRotatef(tRotate, 0, 1, 0);//控制旋转
	glutSolidTeapot(1);
	glPopMatrix();

	//画桌面
	glPushMatrix();
	glTranslatef(0, 0, 3.5);
	glScalef(5, 4, 1);
	glutSolidCube(1.0);
	glPopMatrix();

	//画四条腿
	glPushMatrix();
	glTranslatef(1.5, 1, 1.5);
	Draw_Leg();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(-1.5, 1, 1.5);
	Draw_Leg();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(1.5, -1, 1.5);
	Draw_Leg();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(-1.5, -1, 1.5);
	Draw_Leg();
	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); //透视投影
		//参数:

		//GLdouble fovy 视角
		//GLdouble aspect 宽高比
		//GLdouble zNear 近处
		//GLdouble zFar 远处
	}
	else
		glOrtho(-3 ,3, -3, 3,-100,100); //正投影
	    //参数

		//GLdouble left,
		//GLdouble right,
		//GLdouble bottom,
		//GLdouble top,
		//GLdouble near,
		//GLdouble far

	glMatrixMode(GL_MODELVIEW);	//设置矩阵模式为模型
}

void reshape(int width, int height)
{
	if (height==0)	//如果高度为0
	{
		height=1;	//让高度为1(避免出现分母为0的现象)
	}

	height = width = min(height, width);
	wHeight = height;
	wWidth = width;
	
	updateView(wHeight, wWidth); //更新视角
}

void idle() 
{
	glutPostRedisplay();//调用当前绘制函数 
}

float eye[] = {0, 0, 8};
float center[] = {0, 0, 0};
float place[] = {0, 0, 5};

//按键回调函数
void key(unsigned char k, int x, int y)
{
	switch(k)
	{
	case 'q': {exit(0); break; } //退出
	case 'p': {bPersp = !bPersp; updateView(wHeight, wWidth);break; } //切换正投影、透视投影

	case ' ': {bAnim = !bAnim; break;} //旋转模式的切换
	case 'o': {bWire = !bWire; break;} //渲染方式的切换

	//整体操作

	case 'a': { //向左移动
		center[0] += 0.1f;
		eye[0] += 0.1f;
		break;
			  }
	case 'd': { //向右移动
		center[0] -= 0.1f;
		eye[0] -= 0.1f;
		break;
			  }
	case 'w': { //向上移动
		center[1] -= 0.1f;
		eye[1] -= 0.1f;
		break;
			  }
	case 's': { //向下移动
		center[1] += 0.1f;
		eye[1] += 0.1f;
		break;
			  }
	case 'z': { //向前移动
		center[2] -= 0.1f;
		eye[2] -= 0.1f;
		break;
			  }
	case 'c': { //向后移动
		center[2] += 0.1f;
		eye[2] += 0.1f;
		break;
			  }

	//茶壶相关操作

	case 'l': { //右移茶壶
		place[0] += 0.1f;
		if (place[0] > 1.5f)place[0] = 1.5f; //不超出桌面范围
		break;
			  }
	case 'j': { //左移茶壶
		place[0] -= 0.1f;
		if (place[0] < -1.5f)place[0] = -1.5f;
		break;
			  }
	case 'i': { //后移茶壶
		place[1] += 0.1f;
		if (place[1] > 1.5f)place[1] = 1.5f;
		break;
			  }
	case 'k': { //前移茶壶
		place[1] -= 0.1f;
		if (place[1] < -1.5f)place[1] = -1.5f;
		break;
			  }
	case 'e': { //旋转茶壶
		isRotate = !isRotate;
		break;
			  }
	}
}


void redraw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色缓存和深度缓存
	glLoadIdentity(); //初始化矩阵为单位矩阵

	gluLookAt(eye[0], eye[1], eye[2],
		center[0], center[1], center[2],
		0, 1, 0);			

	//  观察位置(eye[0],eye[1],eye[2]
	//	物体位置(center[0],center[1],center[2]
	//	观察方向(x, y, z) 

	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};  //定义光源位置

	glLightfv(GL_LIGHT0, GL_POSITION, light_pos); //设置第0号光源的光照位置
	glLightfv(GL_LIGHT0, GL_AMBIENT, white); //设置第0号光源多次反射后的光照颜色(环境光颜色)
	glEnable(GL_LIGHT0); //开启第0号光源

	glRotatef(fRotate, 0, 1.0f, 0);	//旋转
	glRotatef(-90, 1, 0, 0); //旋转
	glScalef(0.2, 0.2, 0.2); //缩放
	Draw_Scene(place); //场景绘制
	
	if (bAnim) fRotate += 0.5f; //茶壶桌子
	if (isRotate) tRotate += 0.5f; //茶壶旋转

	glutSwapBuffers(); //交换缓冲区  
}

int main (int argc,  char *argv[])
{
	glutInit(&argc, argv);//对glut的初始化 
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);//初始化显示模式:RGB颜色模型,深度测试,双缓冲   
	glutInitWindowSize(480,480);//设置窗口大小  
	int windowHandle = glutCreateWindow("Ex 3");//设置窗口标题   

	glutDisplayFunc(redraw);//注册绘制回调函数  
	glutReshapeFunc(reshape);//注册重绘回调函数 
	glutKeyboardFunc(key);//注册按键回调函数
	glutIdleFunc(idle);//注册全局回调函数:空闲时调用  

	glutMainLoop();// glut事件处理循环
	return 0;
}




你可能感兴趣的:([OpenGL] 茶壶的投影、旋转、平移)