OpenGL绘制自由落体小球
一、 程序运行的软硬件环境
本次设计在window10系统下进行,运用C++进行编写,在CodeBlocks环境下使用OpenGL进行设计。
所需环境配置分为2部分,第一部分是CodeBlocks的配置,第二部分为OpenGL的相关配置。
Codeblocks配置:
- 打开搜索引擎,搜索CodeBlocks,点击相关结果进入CodeBlocks官网:codeblocks.org。
- 选择Download选项卡
- 点击Download the binary release
- 选择带mingw编译器的版本codeblocks-16.01mingw-setup.exe
- 下载完成后,直接安装,除了安装路径可以改,其他的都选择默认的即可。
- 打开CodeBlocks即可使用。
OpenGL的配置:
- 将h文件拷贝到MinGwincludeGL目录下
- 将dll这个动态链接库文件拷贝到相应文件夹下,此处注意文件夹根据机器操作系统位数不同,拷贝的文件夹路径也不同,具体区别为,如果是32位操作系统,则应该将glut32.dll文件拷贝到C:WindowsSystem32文件夹下,如果是64位操作系统,则应将该文件拷贝到C:WindowsSysWOW64文件夹下,如果拷贝错误,将导致程序编译的时候因为找不到该文件而无法通过,以64位机器为例。
- 将a文件放置到MinGwlib文件夹下。
- 创建openGL工程,打开CodeBlocks,点击file->new->project->GLUT
project->go,然后一直下一步,直至进入工程创建完毕,点击工具栏project->build options.在build
options中Debug->Linker
settings->Add,将我们第三步放置好的libglut32.a文件添加进来,保存退出。然后自带的openGL实例程序即可正常编译运行,至此CodeBlocks下的openGL开发配置完成。
二、涉及的相关算法的原理
本次设计运用C++编写,利用OpenGL进行图像设计,设计的内容为:绘制一个球体,在其表面进行纹理映射,并且球体保持作自由落体运动,同时,可以对球体进行旋转、缩放等操作。
纹理映射的原理:
纹理映射使用一个图案或者纹理来确定渲染流水线中片元处理阶段片元的颜色。简单来讲,纹理就是矩形的数据数组,例如颜色数据、亮度数据、颜色和alpha数据。纹理数组中的单个值常常称为纹理单元,也叫纹素(texel),这里让它区别于像素,主要是为了强调它的应用方式。 OpenGL支持1D、2D、3D以及立方体纹理,现在主要考虑2D纹理。
纹理映射就是要实现,如何把纹素映射到几何对象的每个点。一个2D的纹理有一个宽度和高度,通过宽度和高度相乘即可得到有多少个纹素。
那么如何来指定顶点的纹素呢?通过坐标来指定,但是这个坐标不应该是具体纹理的中坐标,而应该是抽象的纹理坐标空间中的坐标;否则通过指定具体纹理的坐标,当更换纹理,例如改变纹理的宽度和高度时,这些坐标值可能变得无意义,而不得不更新所有顶点的坐标值,因此需要使用抽象的纹理坐标空间的坐标。纹理坐标一般都规范化到[0,1]范围内。例如一个纹理宽度为320,高度为200,而纹理坐标(0.5,0.1)则表示纹素的位置在:
(320*0.5,200*0.1)=(160,20)。通常使用UV坐标系来表示纹理坐标系:
这里注意,OpenGL中V轴从下往上是正方向,U轴从左往右是正方向。在具体使用时,这与应用中纹理Y方向有关。如果纹理从上到下,则需要将纹理的Y方向翻转来满足这个图形所示的纹理坐标。与纹理映射有关的一个特性是,当模型进行变换时,纹理坐标仍然会跟着模型的顶点,他们并不进行变换(当然也有其他方法可以改变纹理坐标),就好像粘着顶点一样。例如下图所示的三角形,如果在其中应用一个小的纹理:
当对三角形进行变换时,纹理坐标保持不变,这样当模型进行旋转、拉伸和放缩时,纹理也会跟着变化,如下图所示:
与纹理有关的另一个特性是纹理采样。当把纹理坐标映射到纹素数组时,正好得到对应纹素的中心位置的情况很少出现。解决这一问题的一种方法是,从纹素数组中取这样一个纹素,该纹素的位置,最佳逼近通过光栅化模块计算输出的纹理坐标。这样一方法成为点采样(Point sampling),也叫做nearest filtering。例如坐标(152.34,745.14)的纹素,就使用(152,745)来代替。用点采样容易产生走样误差。
另外一种方法是线性滤波方法(linear filtering)。例如,如果计算出一个纹理坐标位于(152.34,745.14),那么这个值对应的最近的4个纹理坐标为: ( (152,745), (153,745), (152,744) , (153,744) )。那么我们可以利用这4个纹理坐标的对应的颜色值进行线性插值,例如计算这一组纹素的加权平均值,并把该值作为纹理坐标映射到纹素数组时的纹素值。这种方法计算量要比点菜用大,效果一般比点采样好。
OpenGL支持多种滤波类型,可以通过设置来进行选择。
在进行纹理映射时,还需要考虑纹素与屏幕像素之间的对应关系。单个的纹素通常并不与屏幕像素对应,当纹素比单个像素大时,屏幕上多个像素对应于单个像素,称之为放大(manification);当纹素比单个像素小,屏幕上单个像素对应多个纹素,则称之为缩小(minification)。关系如下图所示:
自由落体实现原理:
定义2个常量和1个变量,常量分别是G=9.8为重力加速度、t=0.002为时间间隔;变量为布尔类型的direction,初始化为direction=true,记录球体运动方向,当direction=true时,球体下落,下落到达下极限时,改变direction=false,球体开始上弹。
定义一个结构体,储存球体对象,即:
在此结构中,定义了球体的y坐标属性y和y坐标上的速度属性vy。在球体运动过程中,运用牛顿运动定律计算球体的位置及速度,通过OpenGL里glTranslated()方法对绘制中心重定义,然后对球体进行重新绘制。
在球体运动过程中,球体位移公式为:
direction = true à ball.y = ball.y – ( ball.vy*t + 0.5*G*t*t);
direction = false à ball.y = ball.y + ( ball.vy*t – 0.5*G*t*t);
旋转、缩放实现原理:
旋转和缩放将用到OpenGL自带的3个函数实现,即:glRotatef()、gluLookAt()和glScalef()。glRotatef()和gluLookAt()控制左右、前后旋转,glScalef()控制缩放。整个控制旋转、缩放的控制放在keyboard()方法下实现。
三、程序设计思想和设计过程
OpenGL中纹理映射的步骤如下:
1、创建纹理对象,并为他指定一个纹理
2、确定纹理如何应用到每个像素上
3、启用纹理贴图功能
4、绘制场景,提供纹理坐标和几何图形坐标
实现纹理映射主要关系到4个概念:纹理对象(the texture object), 纹理单元(the texture unit),
采样器对象(the sampler object )采样器变量(sampler uniform in the
shader).他们的关系如下图所示:
纹理对象并不直接绑定到着色器,而是绑定到一个纹理单元,纹理单元的索引将会传递给着色器。要绑定到一个纹理单元,先要将其激活,可以使用glActiveTexture函数,例如glActiveTexture(GL_TEXTURE0)将激活单元0。可以使用多个纹理单元,每个纹理单元可以绑定到相同或者不同的纹理对象。有一点值得注意,只要纹理对象的类型不同,一个纹理单元可以绑定多个纹理对象。例如你可以分别将两个纹理对象绑定到同一个纹理单元的1D和 2D不同的目标上。可以通过采样器变量来使用多个纹理,这个uniform变量有’sampler1D’, ‘sampler2D’, ‘sampler3D’, ‘samplerCube’等不同形式。在片元着色器中,采样函数需要通过采样器变量来访问多个纹理单元。采样器对象与纹理对象不相同。纹理对象中包含了纹理数据,以及配置采样操作的参数,这些参数是采样状态的一部分。然而,你也可以创建一个采样对象,用采样状态参数配置它,并把它绑定到纹理单元中。这样,采样器对象会覆盖纹理对象中定义的采样状态。目前我们并不使用这一对象。
球体运动控制的流程图:
对流程图进行简单的说明:初始化即对球体的y坐标和y轴上的速度vy进行定义,ball.y=10,ball.vy=0;通过glTranslated()函数实现球体的移动,在移动过程中由牛的你运动定律确定球体的坐标以及速度的变化;每次移动都进行判断,是否到达下极限-4,如果还没有到达则继续下落,如果已经到达下极限则改变球体运动方向,使球体上升,每一次上升操作都判断是否到达上极限,如果还没有到达则继续上升,否则改变球体运动方向。
用到的主要OpenGL函数:
函数名 | 参数 | 实现功能 |
makeStripeImage | Void | 制作纹理图形 |
init | Void | 对程序数据进行初始化 |
idle | Void | 空闲时运行的函数 |
display | Void | 控制图形界面的显示,包括图形绘制以及一些属性的控制 |
glutPostRedisplay | Void | 对图形进行重绘 |
glTranslated | x、y、z | 对图形进行移动,x、y、z分别为对应轴上移动的距离 |
glutSolidSphere | GLdouble radius , GLint slices , GLint stacks | 渲染一个球体 |
glVertex3f | x、y、z | 确定一个顶点 |
reshape | int w , int h | 对显示窗口的控制 |
keyboard | unsigned char key,int x,int y | 实现对图形旋转、缩放的控制 |
遇到的问题:
在功能实现过程中,遇到一个非常棘手且奇怪的问题,只怪我对openGL掌握得还不是很熟练加上时间也不是很充足,所以至今没有解决,这个问题就是:
当t=0.002时,球体运动的下限不能低于-4,上限不能高于10,否则球体在第二次往返时将不能正常改变方向;当t=0.003时,下限不能低于-3,上限不能高于10,否则结果与上述一样。
四、程序源代码和使用说明
1)程序源代码
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include
#include
#include
#define stripeImageWidth 32
#define G 9.8
GLubyte stripeImage [4*stripeImageWidth];
static GLuint texName;
bool direction = true;
double t = 0.002;
typedef struct b //定义储存球体的结构
{
GLdouble y;
GLdouble vy;
}Ball;
Ball ball;
void makeStripeImage(void){ //制作纹理条纹
int j;
for(j = 0; j < stripeImageWidth; j++){
stripeImage[4*j] = (GLubyte)((j<=4)?255:0); stripeImage[4*j+1] = (GLubyte)((j>4)?255:0);
stripeImage[4*j+2] = (GLubyte)0;
stripeImage[4*j+3] = (GLubyte)255;
}
}
static GLfloat xequalzero[] = {1.0,0.0,0.0,0.0};
static GLfloat *currentCoeff;
static GLenum currentPlane;
static GLint currentGenMode;
void init(void){
ball.y = 10 ; //初始化球体属性
ball.vy = 0 ;
glClearColor(0.0,0.0,0.0,0.0);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
makeStripeImage();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
glGenTextures(1,&texName);
glBindTexture(GL_TEXTURE_1D,texName);
glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage1D(GL_TEXTURE_1D,0,GL_RGBA,stripeImageWidth,0,GL_RGBA,GL_UNSIGNED_BYTE,stripeImage);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
currentCoeff = xequalzero;
currentGenMode = GL_OBJECT_LINEAR;
currentPlane = GL_OBJECT_PLANE;
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,currentGenMode);
glTexGenfv(GL_S,currentPlane,currentCoeff);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_LIGHTING); //开启光照
glEnable(GL_LIGHT0); //开启光源0
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glFrontFace(GL_CW);
glMaterialf(GL_FRONT,GL_SHININESS,64.0);
}
void idle(void) //空闲函数
{
glutPostRedisplay();
}
void display(void){
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_1D);
glBindTexture(GL_TEXTURE_1D,texName);
glPushMatrix(); //将当前矩阵压入矩阵堆栈,本//次操作结束之后将其弹出,使//得本次操作不影响下次操作
glTranslated(0,ball.y,0); //将绘图中心移动到球体的中心
glutSolidSphere(2,50,50); //绘制一个球体
if(direction){
ball.y = ball.y – (ball.vy*t+0.5*G*t*t); //根据牛顿运动定律计算出球
//的位移公式
ball.vy = ball.vy + G*t; //根据牛顿运动定律计算出球体的速度
if(ball.y <=-4){ direction = false;
//触发转向条件,改变direction的值,使球//体运动方向改变 } }else{ ball.y = ball.y +
(ball.vy*t-0.5*G*t*t); ball.vy = ball.vy – G*t; if(ball.y >= 10){
direction = true;
}
}
glPopMatrix(); //将当前矩阵弹出
glDisable(GL_TEXTURE_1D);
glFlush();
}
void reshape(int w,int h){
glViewport(0,0,(GLsizei)w,(GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-15,15,-15*(GLfloat)h/(GLfloat)w,15*(GLfloat)h/(GLfloat)w,-15,15);
else
glOrtho(-15*(GLfloat)h/(GLfloat)w,15*(GLfloat)h/(GLfloat)w,-15,15,-15,15);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void keyboard(unsigned char key,int x,int y){ //控制键盘输入,键盘输入a、d、//s、w时分别实现左右前后的旋转,
switch(key){ //输入e、r的时候实现放大和缩小
case ‘a’:
glRotatef(5.0,0.0,0.0,1.0);
glutPostRedisplay();
break;
case ‘d’:
glRotatef(-5.0,0.0,0.0,1.0);
glutPostRedisplay();
break;
case ‘s’:
gluLookAt(0,0.01,0.01,0,0,0,0,1,0);
glutPostRedisplay();
break;
case ‘w’:
gluLookAt(0,-0.01,0.01,0,0,0,0,1,0);
glutPostRedisplay();
break;
case ‘e’:
glScalef(1.1,1.1,1.1);
glutPostRedisplay();
break;
case ‘r’:
glScalef(0.9,0.9,0.9);
glutPostRedisplay();
break;
default:
break;
}
}
int main(int argc,char** argv){
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB|GLUT_DEPTH);
glutInitWindowSize(600,600);
glutInitWindowPosition(450,100);
glutCreateWindow(argv[0]);
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutIdleFunc(idle);
glutMainLoop();
return 0;
}
2)程序说明:
该程序引入了3个OpenGL的包,分别是gl.h、glu.h、glut.h,都是一些OpenGL的基础库,所以并不需要额外进行库函数的配置。程序主体可以分为5个部分,几个主要模块分别为init()、display()、reshape()、keyboard()、main()。
其中,init()主要对程序数据进行初始化,包括球体属性设置,渲染属性设置等。display()用于绘制图形,包括球体和地面,通过不断调用display()函数实现动态效果。reshape()函数则是对视口的设置,当窗口大小变化时,为了防止物体变形,这时要重设投影转换矩阵,设置视口转换矩阵,以及视图转换矩阵。keyboard()函数则是处理键盘输入,对图形的旋转、缩放进行控制:
a:左旋转 d:右旋转
s:前旋转 w:后旋转
e:放大 r:缩小
main()函数是程序的入口,调用其他方法实现程序功能。
五、程序运行结果截图
六、参考目录
- OpenGL编程指南
- 纹理原理部分由参考自百度
原创文章:引用表明出处