本次实验主要是学习下opengl中光照的使用方法,opengl中的光照分为环境光,漫射光,镜面光,反射光4种,这里主要是学习环境光和漫射光的设置,同时对比下opengl中支持的几种纹理滤波方式的效果,另外也可以加入色彩融合效果。
纹理滤波
在上篇文章OpenGL_Qt学习笔记之_05(纹理映射)中我们采用的是GL_LINEAR方式进行滤波的,opengl还支持GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR等几种方式,其中mipmap为计算量最大的方式,因为它保留了纹理的很多细节。
由于我们不想让纹理图片的像素限制为2的n次方,所以在创建纹理时我们可以使用gluBuild2DMipmaps()函数代替glTexImage2D(),而gluBuild2DMipmaps()又是以glu开头的。
其实我在opengl学习系列的第一篇文章中就发现了Qt新版本中不能使用opengl的glu开头的函数,文章为OpenGL_Qt学习笔记之_01(创建一个OpenGL窗口),因为那个时候初学,所以遇到编译找不到gluPerspective()标识符时,我直接把这个函数屏蔽掉了,反正当时也不理解该函数的功能。而在本人博客OpenGL_Qt学习笔记之_02(绘制简单平面几何图形)中也发现没有使用gluPerspective()函数时,画几何图形时,坐标和网上很多人的例子不同,我这里全是-1~+1之间的小数,因此这些函数的功能还是挺重要的,比不可少。
下面给出我在windows平台下的解决方法。
http://hi.baidu.com/%B3%A4%C6%BD%C5%A9%B7%F2/blog/item/573cb1f9dbf83a07d8f9fd53.html#0
过在它的方法步骤4中,在建立glut文件,是这样的:新建一个txt文件,里面输入代码:#include “glut.h”,然后把该txt文件的名字重命名为glut,注意这个地方不需要加后缀。另外需要特别注意的是,虽然我们把glut.h文件放入在qt目录下的\include\QtOpenGL下,且我们也在GLWidget.cpp中也加入了头文件#include
既然上面提到了gluPerspective()对结果的影响很大,那么就很有必要了解该函数。在NeHe等人的教程中,该函数使用其各个参数设置为如下:
gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0 );
该函数是设置场景的透视效果的,所谓透视,可以简单的理解为越远处的物体看起来越小。所以这里的参数1,2,3表示的是透视是按照基于窗口宽度和高度的45度视角来计算的,参数0.1和参数100是我们场景中所能绘制深度的起点和终点值。
光照
Opengl中使用光照时,首先定义了3个跟光滑有关的数组:
GLfloat lightAmbient[4] = { 0.5, 0.5, 0.5, 1.0 };
GLfloat lightDiffuse[4] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat lightPosition[4] = { 0.0, 0.0, 2.0, 1.0 };
LightAmbient数组用来设置环境光的参数,前面3个参数表示光的rgb分量为0.5,后面那个是alpha通道,alph为1表示不透明,所谓环境光就是指物体所在的环境中的光照,一般比较均匀,四面八方都有;lightDiffuse数组用来设置漫射光的参数,这里讲漫射光强度设置为最大,漫射光是指的特定位置发出的光;lightPosition就是该光源的位置。
上面数组只是这些光源的参数,如果我们要设置光源,则应该使用下面的代码:
/*opengl中支持8个光源,即GL_LIGHT0~GL_LIGHT7*/
glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient);//指定光源1的环境光参数
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);//指定光源1的漫射光参数
glLightfv(GL_LIGHT1, GL_POSITION, light_position);//指定光源1的位置
glEnable(GL_LIGHT1);//允许光源1的使用
glEnable(GL_LIGHTING);//我们还需要启动总光源开关
然后在对空间物体进行贴图时,需要指定物体表面的法向量,使用下面的函数
glNormal3f(x, y, z);
该函数指定是法线的方向为向量(x, y, z)方向,在使用光源时,且对空间物体进行纹理映射时,每个面都需要指定其法线的方向,否则会出现各种意外的结果。
在使用光照时,在前面课程基础上注意上面几点就Ok了。
色彩融合
色彩融合简单的说就是将2幅图片按照一定的透明度比例进行叠加(每个像素点都进行叠加),其公式如下所示:
(Rs As + Rd (1 - As), Gs As + Gd (1 - As), Bs As + Bd (1 - As), As As + Ad (1 - As))
其中Rs,Gs,Bs,As分别表示源目标图像的r,g,b,alpha分量值,Rd,Gd,Bd,Ad分别表示目标图像的r,g,b,alpha分量值。如果As等于0.5,则这个公式会生成透明/半透明的效果。
上面的公式表示各颜色通道的alpha相同,且源像素因子和目标像素因子之和为1.一般情况下,源像素因子和目标像素因子采用的是:
void WINAPI glBlendFunc(GLenum sfactor, GLenum dfactor);
参数1为源像素因子的取值,我在程序中用的GL_SRC_ALPHA表示采用alpha通道的取值;参数2表示目标像素因子的取值,程序中用的是GL_ONE,表示目标像素因子取值1.0.具体该函数的用法可以参考网友的这篇文章:http://www.cnblogs.com/yujunyong/archive/2011/04/13/2015467.html
实验说明
这次实验是将一个木箱纹理贴到一个立方体上,然后我们在空间屏幕正外方设置了一个光源,可以用键盘的L键来控制该光源的开启和关闭;用F键来旋转程序中用到的3种滤波方式,按下F键后,依次切换该3种方式;用PageUp键来使物体离观察者越来越远,相反,用PageDown来使物体离我们越来越近;使用向上光标键加快物体旋转的速度,向下光标键减小物体旋转的速度。
开发环境:windows+QtCreator2.5.1+Qt4.8.2
实验结果:
关闭灯光时效果:
开启灯光时效果:
三种纹理对比效果,纹理1:
纹理2:
纹理3:
将物体远离观察者效果:
开灯后无色彩融合效果:
开灯后有色彩融合效果:
实验主要部分代码及注释(附录有工程code下载链接地址):
#include "glwidget.h" #include "ui_glwidget.h" #include#include #include #include /*c++中可以在类的外部定义变量*/ GLfloat light_ambient[4]={0.5, 0.5, 0.5, 1.0}; GLfloat light_diffuse[4]={1.0, 1.0, 1.0, 1.0}; GLfloat light_position[4]={0.0, 0.0, 2.0, 0.0}; GLWidget::GLWidget(QGLWidget *parent) : QGLWidget(parent), ui(new Ui::GLWidget) { // setCaption("The Opengl for Qt Framework"); ui->setupUi(this); fullscreen = false; rotate_angle = 0.0; zoom = -5.0; rotate_speed = 3.0; filter = 0; light = false; blend = false; } //这是对虚函数,这里是重写该函数 void GLWidget::initializeGL() { setGeometry(300, 150, 500, 500);//设置窗口初始位置和大小 loadTextures(); glEnable(GL_TEXTURE_2D);//允许采用2D纹理技术 glShadeModel(GL_SMOOTH);//设置阴影平滑模式 glClearColor(0.0, 0.0, 0.0, 0);//改变窗口的背景颜色,不过我这里貌似设置后并没有什么效果 glClearDepth(1.0);//设置深度缓存 glEnable(GL_DEPTH_TEST);//允许深度测试 glDepthFunc(GL_LEQUAL);//设置深度测试类型 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//进行透视校正 /*opengl中支持8个光源,即GL_LIGHT0~GL_LIGHT7*/ glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient);//指定光源1的环境光参数 glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);//指定光源1的漫射光参数 glLightfv(GL_LIGHT1, GL_POSITION, light_position);//指定光源1的位置 glEnable(GL_LIGHT1);//允许光源1的使用 // glEnable(GL_LIGHTING);//我们还需要启动总光源开关,默认的时候不开,后面的L键来控制开启和关闭 glColor4f(1.0, 1.0, 1.0, 0.5);//后面的步骤都是以全亮绘制物体,并且50%的透明度 glBlendFunc(GL_SRC_ALPHA, GL_ONE); } void GLWidget::paintGL() { //glClear()函数在这里就是对initializeGL()函数中设置的颜色和缓存深度等起作用 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /*下面开始画立方体,并对其进行纹理映射*/ glLoadIdentity(); glTranslatef(0.0, 0.0, zoom); glRotatef(rotate_angle, -0.4f, 0.4f, -1.0f); glBindTexture(GL_TEXTURE_2D, texture[filter]);//这句代码一定要,因为在initializeGL()函数中已绑定一个固定的纹理目标了 glBegin(GL_QUADS); //上顶面 glNormal3f(0.0, 1.0, 0.0);//该函数指定是法线的方向为向量(x, y, z)方向,在使用光源时,且对空间物体进行纹理映射时, //每个面都需要指定其法线的方向,否则会出现各种意外的结果。 glTexCoord2f(0.0, 1.0);//将2D的纹理坐标映射到3D的空间物体表面上 glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, 1.0f, -1.0f); //下顶面 glNormal3f(0.0, -1.0, 0.0); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, -1.0f, -1.0f); //正前面 glNormal3f(0.0, 0.0, 1.0); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, 1.0f, 1.0f); //右侧面 glNormal3f(1.0, 0.0, 0.0); glTexCoord2f(0.0, 1.0); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(0.0, 0.0); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, -1.0f, -1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, 1.0f, -1.0f); //背后面 glNormal3f(0.0, 0.0, -1.0); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0, 0.0); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, -1.0f, -1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(-1.0f, -1.0f, -1.0f); //左侧面 glNormal3f(-1.0, 0.0, 0.0); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0, 0.0); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(-1.0f, 1.0f, 1.0f); glEnd(); rotate_angle += rotate_speed; } //该程序是设置opengl场景透视图,程序中至少被执行一次(程序启动时). void GLWidget::resizeGL(int width, int height) { if(0 == height) height = 1;//防止一条边为0 glViewport(0, 0, (GLint)width, (GLint)height);//重置当前视口,本身不是重置窗口的,只不过是这里被Qt给封装好了 glMatrixMode(GL_PROJECTION);//选择投影矩阵 glLoadIdentity();//重置选择好的投影矩阵 gluPerspective(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);//建立透视投影矩阵 glMatrixMode(GL_MODELVIEW);//以下2句和上面出现的解释一样 glLoadIdentity(); } void GLWidget::keyPressEvent(QKeyEvent *e) { switch(e->key()) { /*L键位开启光照的开关*/ case Qt::Key_L: light = !light; if(!light) glDisable(GL_LIGHTING); else glEnable(GL_LIGHTING); updateGL(); break; /*B键位选择是否采用色彩融合*/ case Qt::Key_B: blend = !blend; if(blend) { glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); } else { glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); } updateGL(); break; /*F键位选择纹理滤波的方式*/ case Qt::Key_F: filter += 1; if(filter > 2) filter = 0; updateGL(); break; /*PageUp键为将木箱移到屏幕内部方向*/ case Qt::Key_PageUp: zoom -= 0.2; updateGL(); break; /*PageDown键为将木箱移到屏幕外部方向*/ case Qt::Key_PageDown: zoom += 0.2; updateGL(); break; /*Up键为加快立方体旋转的速度*/ case Qt::Key_Up: rotate_speed += 1.0; updateGL(); break; /*Down键为减慢立方体旋转的速度*/ case Qt::Key_Down: rotate_speed -= 1.0; updateGL(); break; /*F1键为全屏和普通屏显示切换键*/ case Qt::Key_F1: fullscreen = !fullscreen; if(fullscreen) showFullScreen(); else { setGeometry(300, 150, 500, 500); showNormal(); } updateGL(); break; /*Ese为退出程序键*/ case Qt::Key_Escape: close(); } } /*装载纹理*/ void GLWidget::loadTextures() { QImage tex, buf; if(!buf.load("../opengl_qt_nehe_07/cat.jpg")) // if(!buf.load("../opengl_qt_nehe_07/crate.bmp")) { qWarning("Cannot open the image..."); QImage dummy(128, 128, QImage::Format_RGB32);//当没找到所需打开的图片时,创建一副128*128大小,深度为32位的位图 dummy.fill(Qt::green); buf = dummy; } tex = convertToGLFormat(buf);//将Qt图片的格式buf转换成opengl的图片格式tex glGenTextures(3, &texture[0]);//开辟3个纹理内存,索引指向texture[0] /*建立第一个纹理*/ glBindTexture(GL_TEXTURE_2D, texture[0]);//将创建的纹理内存指向的内容绑定到纹理对象GL_TEXTURE_2D上,经过这句代码后,以后对 //GL_TEXTURE_2D的操作的任何操作都同时对应与它所绑定的纹理对象 glTexImage2D(GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());//开始真正创建纹理数据 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//当所显示的纹理比加载进来的纹理小时,采用GL_NEAREST的方法来处理 //GL_NEAREST方式速度非常快,因为它不是真正的滤波,所以占用内存非常 // 小,速度就快了 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//当所显示的纹理比加载进来的纹理大时,采用GL_NEAREST的方法来处理 /*建立第二个纹理*/ glBindTexture(GL_TEXTURE_2D, texture[1]); glTexImage2D(GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//当所显示的纹理比加载进来的纹理小时,采用GL_LINEAR的方法来处理 //GL_LINEAR方式速度非常稍微慢些,但它做到了线性滤波 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); /*建立第三个纹理*/ glBindTexture(GL_TEXTURE_2D, texture[2]); gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex.width(), tex.height(), GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());//该函数对所加载的 //纹理像素没有要求是2的n次方,可以是任意的像素 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);//mipmap方式是当物体很远时,也能保留很好的细节,所以 //它的运算量很大,速度很慢,这里GL_LINEAR_MIPMAP_NEAREST是混合使用 } GLWidget::~GLWidget() { delete ui; }
总结:
本次实验对比了3中纹理滤波方式,学会了简单的光照的使用方法,并用键盘控制物体的某些特性。
参考资料:
http://nehe.gamedev.net/
http://www.owlei.com/DancingWind/
http://www.qiliang.net/old/nehe_qt/
附录:
实验工程code下载。