OpenGL学习——glut/ 场景漫游,对gluLookAt()函数的深入理解
这个程序属于上一篇文章的扩展吧,星球旋转的那个程序主要是去了解它是怎么旋转的,很多函数是第一次见,学完之后可能有点懵。而且因为建模的问题,画圆的位置,视角位置,相机位置,设计的都不是太直观,不太好观察,并不便于理解函数的各个参数对呈现效果的影响。
所以这篇文章的程序会建出更容易理解和观察的模型:在x和z轴上绘制一个大四边形作为陆地,在陆地上绘制36个雪人,他们可以作为参照物。用户可以通过键盘调整相机(人眼位置),使其向前向后移动,向左向右移动,向上向下移动,以及绕着y轴旋转相机角度。
注意理解每种变换,是如何更改函数参数的,理解这个程序之后,一定会对gluLookAt()等函数有更好的理解,对视角有更好的理解。
学习要点
通过相机的变换呈现效果,以及相对应的gluLookAt()参数的变化去理解这个函数。
绘制场景的时候,多次用到glPushMatrix()和glPopMatrix()。
要点简单解析
矩形入栈出栈
对于矩形的入栈出栈,可以先这样理解,OpenGL在矩形所在的位置绘图,如果想要在其他位置绘图,需要对矩形做一个变换。然而绘制完之后,再次绘图是以当前这个矩形位置为基础的,如果想回到原位置还要对它进行方向操作。那么问题来了,如果对矩形的变换过多的话,还原起来就非常麻烦。所以解决办法就是:把原矩形入栈保护起来,当矩形变换完毕后,再出栈就可以恢复到原先状态。
视线变换思路
gluLookAt()函数使用的要点在于两个点的坐标,一个人眼点坐标,一个看的点坐标,所以需要正确设置这两个点的坐标。
下面简单讲一下本程序是如何设置这两个点坐标的。
设置xyz作为相机位置坐标,设置三个偏移量lx,ly,lz作为视线方向(偏移量,大小为0,0,-1),相机位置坐标加入视线方向,就是看的点的坐标。
旋转相机可能不太好理解,我画个图帮忙理解下吧。
关于雪人是如何绘制的,这个有关具体绘制的操作,先不细究了吧,反正到时候贴图可能更多一点,不过要是有兴趣可以看看代码咋画的。这个代码还是蛮值得学学的哈,场景漫游我们回头也得做。最后有啥不太理解的一定要问哈。
#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的用法,窗口回调函数