欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL学习——glut/ 场景漫游,对gluLookAt()函数的深入理解

程序员文章站 2022-03-05 14:49:39
...

这个程序属于上一篇文章的扩展吧,星球旋转的那个程序主要是去了解它是怎么旋转的,很多函数是第一次见,学完之后可能有点懵。而且因为建模的问题,画圆的位置,视角位置,相机位置,设计的都不是太直观,不太好观察,并不便于理解函数的各个参数对呈现效果的影响。

所以这篇文章的程序会建出更容易理解和观察的模型:在x和z轴上绘制一个大四边形作为陆地,在陆地上绘制36个雪人,他们可以作为参照物。用户可以通过键盘调整相机(人眼位置),使其向前向后移动,向左向右移动,向上向下移动,以及绕着y轴旋转相机角度。

注意理解每种变换,是如何更改函数参数的,理解这个程序之后,一定会对gluLookAt()等函数有更好的理解,对视角有更好的理解。

学习要点

通过相机的变换呈现效果,以及相对应的gluLookAt()参数的变化去理解这个函数。

绘制场景的时候,多次用到glPushMatrix()和glPopMatrix()。

要点简单解析

矩形入栈出栈

对于矩形的入栈出栈,可以先这样理解,OpenGL在矩形所在的位置绘图,如果想要在其他位置绘图,需要对矩形做一个变换。然而绘制完之后,再次绘图是以当前这个矩形位置为基础的,如果想回到原位置还要对它进行方向操作。那么问题来了,如果对矩形的变换过多的话,还原起来就非常麻烦。所以解决办法就是:把原矩形入栈保护起来,当矩形变换完毕后,再出栈就可以恢复到原先状态。

视线变换思路

gluLookAt()函数使用的要点在于两个点的坐标,一个人眼点坐标,一个看的点坐标,所以需要正确设置这两个点的坐标。

下面简单讲一下本程序是如何设置这两个点坐标的。

设置xyz作为相机位置坐标,设置三个偏移量lx,ly,lz作为视线方向(偏移量,大小为0,0,-1),相机位置坐标加入视线方向,就是看的点的坐标。

旋转相机可能不太好理解,我画个图帮忙理解下吧。
OpenGL学习——glut/ 场景漫游,对gluLookAt()函数的深入理解
关于雪人是如何绘制的,这个有关具体绘制的操作,先不细究了吧,反正到时候贴图可能更多一点,不过要是有兴趣可以看看代码咋画的。这个代码还是蛮值得学学的哈,场景漫游我们回头也得做。最后有啥不太理解的一定要问哈。

#include <gl/glut.h>
#include <math.h>

static float angle = 0.0;//angle绕y轴的旋转角
static float x = 0.0f, y = 1.75f, z = 5.0f;//相机位置
static float lx = 0.0f, ly = 0.0f, lz = -1.0f;//视线方向,初始设为沿着Z轴负方向

//定义观察方式
void changeSize(int w, int h)
{
	glMatrixMode(GL_PROJECTION);    //投影变换
	glLoadIdentity();
	//设置视口为整个窗口大小
	glViewport(0, 0, w, h);
	//设置可视空间
	gluPerspective(45, 1.0f*w / h, 1, 1000); //角度45, 窗口纵横比, 眼睛所及距离(近和远)

	glMatrixMode(GL_MODELVIEW);     //模型变换
	glLoadIdentity();
	//相机(人眼)位置, 眼睛看的点(相机位置+视线方向), 观察者本身方向(角度,比如正立)
	gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}

//绘制雪人
void drawSnowMan()
{
	glColor3f(1.0f, 1.0f, 1.0f);    //白色

	//画身体
	glTranslatef(0.0f, 0.75f, 0.0f);
	glutSolidSphere(0.75f, 20, 20); //画圆

	//画头
	glTranslatef(0.0f,1.0f,0.0f);
	glutSolidSphere(0.25f,20,20);   //画圆

	//画眼睛
	glPushMatrix();
	glColor3f(0.0f,0.0f,0.0f);  //黑色
	glTranslatef(0.05f,0.10f,0.18f);
	glutSolidSphere(0.05f,10,10);   //画圆
	glTranslatef(-0.1f,0.0f,0.0f);
	glutSolidSphere(0.05f,10,10);   //画圆
	glPopMatrix();

	//画鼻子
	glColor3f(1.0f,0.5f,0.5f);  //有点像粉色
	glRotatef(0.0f,1.0f,0.0f,0.0f);
	glutSolidCone(0.08f,0.5f,10,2); //画圆锥  参数::半径,高
}

//渲染场景,画地面,画雪人
void renderScene(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空颜色和深度缓冲

	//画地面
	glBegin(GL_QUADS);  //四边形
	glVertex3f(-100.0f, 0.0f, -100.0f);
	glVertex3f(-100.0f, 0.0f, 100.0f);
	glVertex3f(100.0f, 0.0f, 100.0f);
	glVertex3f(100.0f, 0.0f, -100.0f);
	glEnd();

	//画36个雪人
	for (int i= -3; i< 3; i++)
		for (int j = -3; j< 3; j++) {
			glPushMatrix(); //把当前矩形压栈,这样后面的操作不会影响到原矩形(标准位置/变换的矩形)
			//问题:那现在操作的是哪个矩形呢??压栈保存了原矩形,但是当前的矩形还是标准(原)矩形,可以操作
			glTranslatef(i*10.0, 0, j*10.0);
			drawSnowMan();  //绘制雪人
			glPopMatrix();  //当把标准矩形移到了预期位置,再把原矩形弹栈恢复,给下次操作提供标准矩形
		}
	glutSwapBuffers();  //交换缓冲区
}

//旋转相机,绕y轴旋转
void orientMe(float ang)
{
	lx = sin(ang);
	lz = -cos(ang);
	glLoadIdentity();
	gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
	//把所看的点(即视线方向上的点)理解为在一个圆上旋转,那设置的点的坐标应该是旋转的,通过圆半径计算坐标
	//!!!画图好理解!!!
}

//前后移动相机
void move_Front_Back(int direction)
{
	x = x + direction*(lx)*0.1;
	z = z + direction*(lz)*0.1;
	glLoadIdentity();
	gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
    //同时移动相机和所看的点坐标,只用修改x和z,视线参数不用修改,
}

//左右移动相机,一定要画图理解
void move_Left_Right(int direction)
{
    x = x + direction*(lz)*0.1;
	z = z - direction*(lx)*0.1;
    glLoadIdentity();
    gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}

void move_High_Low(int direction)
{
    y = y + direction * 0.1;
    glLoadIdentity();
    gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}

//键盘响应
void inputKey(unsigned char key, int x, int y) {
	switch (key)
	{
	case 'q':
		angle -= 0.03f;
		orientMe(angle);
		break;
	case 'e':               //q,e键调用相机旋转
		angle += 0.03f;
		orientMe(angle);
		break;
    case 'w':
        move_Front_Back(1);
		break;
    case 's':
        move_Front_Back(-1);
		break;
    case 'a':
        move_Left_Right(1);
        break;
    case 'd':
        move_Left_Right(-1);
        break;              //wasd调整相机前后左右移动
    case '1':
        move_High_Low(1);
        break;
    case '2':
        move_High_Low(-1);
        break;              //1,2键上下移动相机
	default:
		break;
	}
}

int main(int argc,char **argv)
{
    //初始化,建立窗口
	glutInit(&argc, argv);
	//深度缓冲,双缓冲,颜色缓冲
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(640, 360);
	glutCreateWindow("snowman test");

	glEnable(GL_DEPTH_TEST);        //开启深度缓冲区
	glutKeyboardFunc(inputKey);     //键盘响应事件
	glutDisplayFunc(renderScene);   //绘制回调函数
	glutIdleFunc(renderScene);      //闲置时回调函数
	glutReshapeFunc(changeSize);    //调整窗口大小回调函数
	glutMainLoop();
    return 0;
}
//有待深入学习,理解
//glClear()的清楚深度缓冲区啥意思
//具体的绘图细节,需要搭建模型,具体设计
//关于矩形的弹出弹入
//changeSize的用法,窗口回调函数