opengl简单入门实例
- 实现任务目标:
- 使用纹理贴图,增强可视效果
- 应用坐标变换,实现场景中不同物体重建
- 采用双缓冲技术,实现场景实时绘制
- 具有一定的鼠标、键盘交互功能
- 先放效果
鼠标的交互功能有:右键暂停转动,左键继续转动,滚轮向前放大,向后缩小
- IDE:opengl实现需要库函数。用的编译环境是visual studio。附上一个很好的教程【1】:在vs2017下配置opengl。(vs2019也可以用)
- 一个很好的入门教程【2】:OpenGL入门教程(精)。讲得很仔细,通俗易懂。前几课用到的库都没有超过glut的范围。
- 事实上,对于opengl的实现主要是对于各种库函数的调用,所以对于各种库函数的认知很重要。这里也给出一个很好的教程【3】:OpenGL库函数汇总。
- ok,在看了上面的教程以后肯定对于opengl有了一定认识,尤其是第二个教程中讲解得非常仔细。所以本文接下来的内容是建立在对那个教程的学习基础之上,对一些我在实践中遇到的问题作出补充。
- 下面就进入正文。
- 所包含的头文件目录
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
- 最基本的功能是当然是创建自己的图形并显示出来,如上图我创建的是日地月系统。需要的函数为display()和main()。
- 这其中很重要的一个知识点就是图像的视图变换/模型变换、投影变换和视口变换。有关这块的内容个人觉得教程【2】中讲得不够清楚,可以参考一些别的教程。比如:OpenGL(六) gluLookAt和gluPerspective函数解析;Opengl---gluLookAt函数详解。
- 这里要介绍一下opengl中的坐标轴。x轴水平向右为正,y轴竖直向上为正,z轴垂直屏幕向外为正。符合右手定则。
- 2020/5/15 13:06:37 对于图像的各种变换做一个小的补充
- 视图变换即设置/改变观察点的位置,可以这么理解,相当于选择一个位置和方向设置一台照相机。针对glLookAt()函数而言,它一共有九个参数,3对坐标值。第一对三维坐标是观察点(照相机)在世界坐标中的位置,第二对三维坐标是被观察点(物体)的位置。从第一对坐标到第二对坐标的向量其实就指定了照相机的方向。比如说人站在台阶上,这是人在世界坐标的位置,然后人可以朝天空看,也可以朝地上看,可以朝北方看,也可以朝南方看。这个方向就是由两对坐标所造成的向量来决定的。第三对坐标是人头部的正向,可以指定人站着看,也可以倒立着看,类似于这样。
- 模型变换则是改变物体本身的位置与方向。用到的函数有glTranslate,glRotate,glScale。这些都是对物体的坐标做变换的,相当于乘以一个变换矩阵。那么这里面有两个需要注意的点。
- 1 变换的顺序是逆向的。也就是说,如果我们写的顺序是先平移再旋转,那么实际得到的结果应该是先旋转再平移。所以我们可以用堆栈来实现。对于堆栈的概念不多作解释了,如果不清楚可以去查数据结构。堆栈用到的函数是glPushMatrix()和glPopMatrix()。用法呢其实就是先声明push,然后按照想要的顺序写好矩阵函数,最后Pop一下,就能得到想要的结果。
- 2 比如连续使用偏移函数,则第二个偏移的结果其实是在第一次作偏移的基础上再做偏移的。那么如果我不想这样算怎么办呢?清空矩阵。用到的函数就是glLoadIdentity()。它的作用是把当前矩阵设置为单位矩阵。一般在开始做变换前都是需要调用一次这个函数的。
- 那么事实上,在OpenGL中,因为视图变换和模型变换的效果是类似的,所以这两个变换放在一个模式里面。在进行这两种变换前,需要声明glMatrixMode(GL_MODEVIEW)。看到这个'Matrix'是不是很眼熟呢?没错,上面讲堆栈函数的时候用到了。相信你们也会有个疑问,如果直接调用堆栈函数,这个堆栈是在哪里的呢?这个堆栈段不需要自己声明了,但它其实是属于这个模式的堆栈段。因为在接下来要将的投影变换的模式下也有自己的堆栈段。所以我也是从这个角度理解为什么要分为这两个模式的原因。
- 投影变换事实上时指定了一个可视空间。相当于你在外部架好了照相机,但你仍然可以在照相机的镜头里设置要不要放大看到的景象。在教程【2】里有有关于这个的图。所以首先我们要声明模式glMatrixMode(GL_PROJECTION),并单位化矩阵glLoadIdentity()。
- 在这个模式里有透视投影和正投影(我们做3D一般用的都是透视投影)。正投影的函数有glOrtho()和gluOrtho2D(),透视投影的函数有glFrustum()和gluPerspective()。我们最常用的当然就是gluPerspective()啦。
- gluPerspective()中的第一个参数是角度,它相当于人的眼皮要睁开多大,也就是2*仰角(仰角=俯角=1/2这个角度)。第二个参数是比例,应该是跟显示的屏幕的宽高比例有关(但是这点我不是很确定,只是暂时这么理解,如果有更好的解释,欢迎在评论区提出)。第三、四个参数则是表示截取的范围,相当于两堵墙,两墙之间的东西能看,墙外的都忽略。这就是透视的意义。
- 那么最后就是视口变换,用到的函数是glViewport()。这个就不多做介绍了。
void display(void)
{
glEnable(GL_DEPTH_TEST); //3、5行代码中跟DEPTH有关的函数是为了在同一个窗口内创建多个图像而不会被后创建的图像覆盖。
glClearColor(, , , ); //设置“空”色。之前看到一个很好的解释,白纸是白色的,所以白纸上的“空”色为白色。那么信封上的“空”色就是信封的颜色。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //COLOR的那个参数是清除颜色缓存,设为“空”色 glMatrixMode(GL_PROJECTION); //投影变换
glLoadIdentity();
gluPerspective(60.0, , 1.0, 100.0); glMatrixMode(GL_MODELVIEW); //视图变换/模型变换
glLoadIdentity(); //加载单位矩阵
gluLookAt(0.0, 0.0, 60.0, , , , 0.0, 1.0, );
//太阳
glColor3f(1.0, , );
glutSolidSphere(, , );
//地球
glColor3f(0.0, , 1.0);
glTranslatef(-20.0, , ); //偏移矩阵
glutSolidSphere(, , );
//月球
glColor3f(1.0, 1.0, );
glTranslatef(-6.0, , ); //这里的偏移量是在上面已经偏移的基础上再进行偏移
glutSolidSphere(, , ); glutSwapBuffers(); //双缓冲函数用到,相关内容看上面的教程【2】里
}
- main()中的函数就不具体解释了,应该都懂
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(, );
glutInitWindowPosition(, );
glutCreateWindow("name");
glutDisplayFunc(&display);
glutMainLoop();
return ;
}
- 现在在现有程序的基础上加入动画需要4步
- 1 加入全局变量
static GLfloat angle = 0.0f;
- 2 在display()里面加入旋转的函数。由于效果是让整个画面都转,这句话我选择加在gluLookAt()后面。需要加入的语句已标红。
void display(void)
{
…… ……
glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); //加载单位矩阵
gluLookAt(place_x, 0.0, place_z, , , , 0.0, 1.0, );
glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋转,改变的是x轴分量 glColor3f(1.0, , );
…… ……
}
- 3 编写myIdle()函数
void myIdle(void)
{
angle += 1.8f;
if (angle >= 360.0f)
angle = 0.0f;
display();
}
- 4 在主函数加入glutIdleFunc(&myIdle);可以加在刚刚的display语句下面。
int main(int argc, char** argv)
{
…… ……
glutDisplayFunc(&display);
glutIdleFunc(&myIdle);
…… ……
glutMainLoop();
return ;
}
- ok。接下来就要为我们的程序加上纹理了。首先在网上找了两张星空的网图。而且,为了方便起见,我把它们的格式改成了24位色的bmp图片,尺寸为258*258。
- 至于怎么改格式:1 24位色可以对图片另存为时在下拉菜单里选择。2 修改尺寸可以用win自带的图片编辑器。
- 我的两张照片分别命名为“wall.bmp”,"ground.bmp"。放在源程序的同一个子目录里面
- 有关纹理贴图的详细内容继续参考教程【2】。这里附上我写的程序和说明。
- 一共分为3步。
- 1 搭建矩形框架【对我的程序来说相当于有一个支架,然后把按照点对点的方式纹理图贴上去】
- 在这一步中先只写上矩形各个点的坐标,为后面建立矩形做准备。

//全局变量
static const GLfloat vertex_list[][] = {
- 15.0f, -20.0f, -10.0f, //事实上6、7两个点是用不到的,作为完整性就一起写了。贴图只在背面和底面贴了图,为了更好的演示效果。
40.0f, -20.0f, -10.0f,
40.0f, 20.0f, -10.0f,
-15.0f, 20.0f, -10.0f,
-15.0f, -20.0f, 10.0f,
40.0f, -20.0f, 10.0f,
-15.0f, 20.0f, 10.0f,
40.0f, 20.0f, 10.0f,
};
- 2 将纹理图读入。写了一个读文件的函数,还是参考之前的教程【2】,不多作解释了。以及一个参考教程:OpenGL(十二) 纹理映射(贴图)
//全局变量
#define BMP_Header_Length 54
//函数
// 函数power_of_two用于判断一个整数是不是2的整数次幂
int power_of_two(int n)
{
if (n <= )
return ;
return (n & (n - )) == ;
}
/* 函数load_texture
* 读取一个BMP文件作为纹理
* 如果失败,返回0,如果成功,返回纹理编号
*/
GLuint load_texture(const char* file_name)
{
GLint width, height, total_bytes;
GLubyte* pixels = ;
GLuint last_texture_ID = , texture_ID = ; // 打开文件,如果失败,返回
FILE* pFile;
23 errno_t err;
24 err = fopen_s(&pFile, file_name, "rb"); //在vs中使用fopen_s()函数的示例。
25 if (!pFile) exit(0); // 读取文件中图象的宽度和高度
fseek(pFile, 0x0012, SEEK_SET);
fread(&width, sizeof(width), , pFile);
fread(&height, sizeof(height), , pFile);
fseek(pFile, BMP_Header_Length, SEEK_SET); // 计算每行像素所占字节数,并根据此数据计算总像素字节数
{
GLint line_bytes = width * ;
while (line_bytes % != )
++line_bytes;
total_bytes = line_bytes * height;
} // 根据总像素字节数分配内存
pixels = (GLubyte*)malloc(total_bytes);
if (pixels == )
{
fclose(pFile);
return ;
} // 读取像素数据
if (fread(pixels, total_bytes, , pFile) <= )
{
free(pixels);
fclose(pFile);
return ;
} // 对就旧版本的兼容,如果图象的宽度和高度不是的整数次方,则需要进行缩放
// 若图像宽高超过了OpenGL规定的最大值,也缩放
{
GLint max;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
if (!power_of_two(width)
|| !power_of_two(height)
|| width > max
|| height > max)
{
const GLint new_width = ;
const GLint new_height = ; // 规定缩放后新的大小为边长的正方形
GLint new_line_bytes, new_total_bytes;
GLubyte* new_pixels = ; // 计算每行需要的字节数和总字节数
new_line_bytes = new_width * ;
while (new_line_bytes % != )
++new_line_bytes;
new_total_bytes = new_line_bytes * new_height; // 分配内存
new_pixels = (GLubyte*)malloc(new_total_bytes);
if (new_pixels == )
{
free(pixels);
fclose(pFile);
return ;
} // 进行像素缩放
gluScaleImage(GL_RGB,
width, height, GL_UNSIGNED_BYTE, pixels,
new_width, new_height, GL_UNSIGNED_BYTE, new_pixels); // 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height
free(pixels);
pixels = new_pixels;
width = new_width;
height = new_height;
}
} // 分配一个新的纹理编号
glGenTextures(, &texture_ID);
if (texture_ID == )
{
free(pixels);
fclose(pFile);
return ;
} // 绑定新的纹理,载入纹理并设置纹理参数
// 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
GLint lastTextureID = last_texture_ID;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID);
glBindTexture(GL_TEXTURE_2D, texture_ID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexImage2D(GL_TEXTURE_2D, , GL_RGB, width, height, ,
GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, lastTextureID); //恢复之前的纹理绑定
free(pixels);
return texture_ID;
}
- 3 在display()中打开状态机->读取纹理图片->搭起矩形框架->贴图->关闭状态机。
- 这里踩过的坑就是关于状态机的开闭问题。如果没有关闭状态机,显示的图像中之前画的几个球都是全黑的。这是因为纹理贴图会干扰别的颜色。
- 其中用到的glTexCoord2f()函数可以参考百度的这个示例。
//全局变量
GLuint texGround;
GLuint texWall;
//函数补充
void display(void)
{
…… ……//之前内容的后面加入一下内容
glEnable(GL_TEXTURE_2D); //开启状态机
texGround = load_texture("ground.bmp");
texWall = load_texture("wall.bmp"); //绘制底面
glBindTexture(GL_TEXTURE_2D, texGround);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[]); //点对点
glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[]);
glEnd();
//绘制立面
glBindTexture(GL_TEXTURE_2D, texWall);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[]);
glEnd();
glDisable(GL_TEXTURE_2D);//关闭状态机
glutSwapBuffers();
}
- ok。那么到这里我们已经完成了纹理贴图、双缓冲绘制和场景重建的任务啦。接下来还有鼠标交互的任务。那么在这里先插入一个新的函数讲解:reshape()。
- 关于reshape()的原理呢可以去查查资料。我说说我的理解吧。简单来说呢就是在你显示窗口时,如果你拉动边框,窗口内的图像不会随着你拉动而改变。
- 附上一个简单的图片示例。
可以看到在右边的图中,我拉动了窗口的边框,则图像的形状也改变了。
- reshape()就能在窗体大小被改变时,窗口大小不变,图像比例也不变。
- 那么同样的,完成这个功能需要2步。
- 1 写一个reshape()函数
void reshape(int w, int h)
{
glViewport(, , , );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, , , 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(, 0.0, 60.0, , , , 0.0, 1.0, );
}
- 2 在main函数中加入一句
int main(int argc, char** argv)
{
…… ……
glutDisplayFunc(&display);
glutReshapeFunc(&reshape);
glutIdleFunc(&myIdle);
…… ……
}
- ok。最后的最后,要完成鼠标的交互了。
- 我所设置的鼠标的功能包括:右键暂停、左键继续;滚轮向上放大,滚轮向下缩小。
- 前两个改变的是转过的角度angle,后两个则跟我们所建立的视图模型,也就是之前用过glLookAt()函数的参数有关。
- 对于鼠标交互用到的函数及参量是void myMouse(int button, int state, int x, int y);关于这个更多的信息可以自行查找。
- 那么同样的,完成这个需要2 / 3步。但是我分为两个部分来讲。首先是对于右键暂停和左键继续的部分。
- 1 之前的显示函数里已经有了一个angle变量用来控制角度,所以我们要做的就是停掉这个angle变量的自增,所以我们要停用myIdle函数。
void myMouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
glutIdleFunc(&myIdle);
}
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
{
glutIdleFunc(NULL);
}
}
- 2 在主函数中加入语句。
int main(int argc, char** argv)
{
…… ……
glutCreateWindow("name");
glutMouseFunc(&myMouse);
glutDisplayFunc(&display);
…… ……
}
- 对于缩放.
- 1 因为要涉及到之前显示函数display()中的glLookAt()的改变,所以我们将其中的值设为全局变量。
//全局变量
static float place_z = 60.0f;
static float place_x = 0.0f;
//修改函数参数
void display(void)
{
…… ……
gluLookAt(place_x, 0.0, place_z, , , , 0.0, 1.0, );
glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋转
…… ……
}
- 2 在之前的鼠标的函数中加入对滚轮的控制语句
//全局变量
#define GLUT_WHEEL_UP 3
#define GLUT_WHEEL_DOWN 4
//函数中
void myMouse(int button, int state, int x, int y)
{
…… ……
if (state == GLUT_UP && button == GLUT_WHEEL_UP)
{
glutReshapeFunc(NULL);
place_z -= 5.0;
display();
}
if (state == GLUT_UP && button == GLUT_WHEEL_DOWN)
{
glutReshapeFunc(NULL);
place_z += 5.0;
display();
}
}
- 这样就ok啦。到这里就完成了一开始提出四个目标以及一个reshape()函数。效果就如最开始的gif动画一样。
- 这里还需要提到的一点是,动画播放的速度在不同的cpu里是不一样的,如果太快或太慢可以通过myIdle函数的angle自增的大小来控制。
- 为了避免混乱,最后附上完整的源代码。
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h> static const GLfloat vertex_list[][] = {
- 15.0f, -20.0f, -10.0f,
40.0f, -20.0f, -10.0f,
40.0f, 20.0f, -10.0f,
-15.0f, 20.0f, -10.0f,
-15.0f, -20.0f, 10.0f,
40.0f, -20.0f, 10.0f,
-15.0f, 20.0f, 10.0f,
40.0f, 20.0f, 10.0f,
};
GLuint texGround;
GLuint texWall; #define BMP_Header_Length 54
static GLfloat angle = 0.0f;
static float place_z = 60.0f;
static float place_x = 0.0f;
#define GLUT_WHEEL_UP 3
#define GLUT_WHEEL_DOWN 4 // 函数power_of_two用于判断一个整数是不是2的整数次幂
int power_of_two(int n)
{
if (n <= )
return ;
return (n & (n - )) == ;
} /* 函数load_texture
* 读取一个BMP文件作为纹理
* 如果失败,返回0,如果成功,返回纹理编号
*/
GLuint load_texture(const char* file_name)
{
GLint width, height, total_bytes;
GLubyte* pixels = ;
GLuint last_texture_ID = , texture_ID = ; // 打开文件,如果失败,返回
FILE* pFile;
errno_t err;
err = fopen_s(&pFile, file_name, "rb");
if (!pFile) exit(); // 读取文件中图象的宽度和高度
fseek(pFile, 0x0012, SEEK_SET);
fread(&width, sizeof(width), , pFile);
fread(&height, sizeof(height), , pFile);
fseek(pFile, BMP_Header_Length, SEEK_SET); // 计算每行像素所占字节数,并根据此数据计算总像素字节数
{
GLint line_bytes = width * ;
while (line_bytes % != )
++line_bytes;
total_bytes = line_bytes * height;
} // 根据总像素字节数分配内存
pixels = (GLubyte*)malloc(total_bytes);
if (pixels == )
{
fclose(pFile);
return ;
} // 读取像素数据
if (fread(pixels, total_bytes, , pFile) <= )
{
free(pixels);
fclose(pFile);
return ;
} // 对就旧版本的兼容,如果图象的宽度和高度不是的整数次方,则需要进行缩放
// 若图像宽高超过了OpenGL规定的最大值,也缩放
{
GLint max;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
if (!power_of_two(width)
|| !power_of_two(height)
|| width > max
|| height > max)
{
const GLint new_width = ;
const GLint new_height = ; // 规定缩放后新的大小为边长的正方形
GLint new_line_bytes, new_total_bytes;
GLubyte* new_pixels = ; // 计算每行需要的字节数和总字节数
new_line_bytes = new_width * ;
while (new_line_bytes % != )
++new_line_bytes;
new_total_bytes = new_line_bytes * new_height; // 分配内存
new_pixels = (GLubyte*)malloc(new_total_bytes);
if (new_pixels == )
{
free(pixels);
fclose(pFile);
return ;
} // 进行像素缩放
gluScaleImage(GL_RGB,
width, height, GL_UNSIGNED_BYTE, pixels,
new_width, new_height, GL_UNSIGNED_BYTE, new_pixels); // 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height
free(pixels);
pixels = new_pixels;
width = new_width;
height = new_height;
}
} // 分配一个新的纹理编号
glGenTextures(, &texture_ID);
if (texture_ID == )
{
free(pixels);
fclose(pFile);
return ;
} // 绑定新的纹理,载入纹理并设置纹理参数
// 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
GLint lastTextureID = last_texture_ID;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID);
glBindTexture(GL_TEXTURE_2D, texture_ID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexImage2D(GL_TEXTURE_2D, , GL_RGB, width, height, ,
GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, lastTextureID); //恢复之前的纹理绑定
free(pixels);
return texture_ID;
}
void display(void)
{
glEnable(GL_DEPTH_TEST);
glClearColor(, , , );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, , 1.0, 100.0); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); //加载单位矩阵
gluLookAt(place_x, 0.0, place_z, , , , 0.0, 1.0, );
glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋转 glColor3f(1.0, , );
glutSolidSphere(, , ); glColor3f(0.0, , 1.0);
glTranslatef(-20.0, , );
glutSolidSphere(, , ); glColor3f(1.0, 1.0, );
glTranslatef(-6.0, , );
glutSolidSphere(, , ); glEnable(GL_TEXTURE_2D);
texGround = load_texture("ground.bmp");
texWall = load_texture("wall.bmp"); //绘制底面
glBindTexture(GL_TEXTURE_2D, texGround);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[]);
glEnd();
//绘制立面
glBindTexture(GL_TEXTURE_2D, texWall);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[]);
glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[]);
glEnd();
glDisable(GL_TEXTURE_2D);
glutSwapBuffers();
}
void myIdle(void)
{
angle += 1.8f;
if (angle >= 360.0f)
angle = 0.0f;
display();
}
void myMouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
glutIdleFunc(&myIdle);
}
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
{
glutIdleFunc(NULL);
}
if (state == GLUT_UP && button == GLUT_WHEEL_UP)
{
glutReshapeFunc(NULL);
place_z -= 5.0;
display();
}
if (state == GLUT_UP && button == GLUT_WHEEL_DOWN)
{
glutReshapeFunc(NULL);
place_z += 5.0;
display();
}
} void reshape(int w, int h)
{
glViewport(, , , );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, , , 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(, 0.0, 60.0, , , , 0.0, 1.0, );
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(, );
glutInitWindowPosition(, );
glutCreateWindow("name");
glutMouseFunc(&myMouse);
glutDisplayFunc(&display);
glutReshapeFunc(&reshape);
glutIdleFunc(&myIdle);
glutMainLoop();
return ;
}
opengl简单入门实例的更多相关文章
- vue服务端渲染简单入门实例
想到要学习vue-ssr的同学,自不必多说,一定是熟悉了vue,并且多多少少做过几个项目.然后学习vue服务端渲染无非解决首屏渲染的白屏问题以及SEO友好. 话不多说,笔者也是研究多日才搞明白这个服务 ...
- Sed简单入门实例
1. Sed简介 sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后 ...
- 权限框架 - shiro 简单入门实例
前面的帖子简单的介绍了基本的权限控制,可以说任何一个后台管理系统都是需要权限的 今天开始咱们来讲讲Shiro 首先引入基本的jar包 <!-- shiro --> <dependen ...
- Windows10下Django虚拟环境配置和简单入门实例
环境win10家庭版64位 + python 3.5 + Django 1.8.2 1.创建virtualenv目录 开始/运行/cmd回车,进入cmd窗口,到自己指定的目录下创建virtualenv ...
- 002.Kubernetes简单入门实例
一 环境准备 1.1 基础环境 Kubernetes模式:单机版 系统环境:CentOS 7/172.24.9.157 部署方式:yum快速部署 其他设置:开启NTP.关闭防火墙及SELinux 二 ...
- Express4+Mongodb超简单入门实例
开始前,请确保mongodb已经能正常工作,安装教程:windows下MongoDB的安装及配置 , 请自行安装配置.下面进入正文: 第一步:命令行创建数据库.表,并插入一条数据 命令如下: //创建 ...
- Spring 简单入门实例
首先新建一个Web 项目 导入相应Jar 包 <?xml version="1.0" encoding="UTF-8"?> <beans xm ...
- Oracle——存储过程简单入门实例
1.连接plsql developer,打开一个SQL Window 2.SQL Window中创建表user_info -- Create table create table USER_INFO ...
- Struts简单入门实例
转自http://www.cnblogs.com/xing901022/p/3961661.html 有改动 struts2其实就是为我们封装了servlet,简化了jsp跳转的复杂操作,并且提供了易 ...
随机推荐
- webpack 中常用安装插件的一些命令
1:npm install html-webpack-plugin --save-dev //自动快速的帮我们生成HTML.2:npm install css-loader style-loader ...
- Upload-Labs 实验操作记录
0x01 安装 下载:https://github.com/c0ny1/upload-labs 环境:简单搭建phpstudy环境即可,记得在upload-labs根目录下创建该文件夹 0x02 文件 ...
- awd平台搭建
1.先是使用 https://github.com/m0xiaoxi/AWD_CTF_Platform 这个平台搭建 这个平台很好用,是python脚本自动搭建,基本不需要怎么更改,自带了四道题的源码 ...
- [YII2.0] 高级模板简单安装教程
YIICHINA官网教程就很完善:http://www.yiichina.com/tutorial/692 但是在yii2框架安装运行init.bat报错php.exe不是内部或外部命令, 解决办法: ...
- ApiPost V3创事记:一个痛并快乐着的创业故事
前言 无论是对于国家,还是对于我们个人,2020年4月,是注定是一个不同往年的4月.一场突如起来的疫情打破了我们原来的生活曲线,让我们知道了什么是苦难,什么是团结,什么是坚持,什么是胜利. 一.大幕开 ...
- python 进阶篇 浅拷贝与深拷贝
阐述引用.浅拷贝和深拷贝前,首先需要要了解 Python 的世界里,一切皆对象,每个对象各包含一个 idendity.type 和 value. 引用(Reference) >>> ...
- 快速从零开始安装Laravel5.2教程
前面 本文默认你Win电脑什么都没装,也就是从零开始安装Laravel,并且环境都由我来指定分配哈! 环境 首先搭建运行环境,先到 PhpStudy官网 下载PhpStudy的Windows版本.然后 ...
- axios的使用小技巧:如何绕过字符串拼接,直接传递对象
Vue.js官方推荐使用axios作为发送http请求的工具,在使用axios中,有些小技巧是不容易发现的.当我们不知道这些技巧时,我们可能会使用其他"奇技淫巧",比如,我们很容 ...
- 深入实践Spring Boot1.4 运行与发布
1.4 运行与发布 本章实例工程的完整代码可以使用IDEA直接从GitHub的https://github.com/chen-fromsz/spring-boot-hello.git中检出,如图1-1 ...
- L3.二.return
# 函数的返回值 def get_max(a,b,c): max_num=a if b > max_num: max_num = b if c > max_num: max_num = c ...