admin 管理员组文章数量: 1184232
2024年3月13日发(作者:用vb编写计时秒表的程序)
setGeometry( 0, 0, 640, 480 );
setCaption( "NeHe's Animated Blended Textures Tutorial" );
if ( fullscreen )
showFullScreen();
startTimer( 5 ); // startTimer( 5 )就是每5毫秒执行一次timerEvent()函数做定
时操作
}
我们需要在构造函数中给各个变量赋初值。
void NeHeWidget::loadGLTextures()
{
QImage tex, buf;
if ( !( "./data/" ) )
{
qWarning( "Could not read image file, using single-color instead." );
QImage dummy( 128, 128, 32 );
( Qt::() );
buf = dummy;
}
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[0] );
glBindTexture( GL_TEXTURE_2D, texture[0] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexImage2D( GL_TEXTURE_2D, 0, 3, (), (), 0,
GL_RGBA, GL_UNSIGNED_BYTE, () );
}
loadGLTextures()函数就是用来载入纹理的。
void NeHeWidget::initializeGL()
{
loadGLTextures();
glEnable( GL_TEXTURE_2D );
glShadeModel( GL_SMOOTH );
glClearColor( 0.0, 0.0, 0.0, 0.5 );
glClearDepth( 1.0 );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
glBlendFunc( GL_SRC_ALPHA, GL_ONE );
glEnable( GL_BLEND );
上述设置前面的各章都提到过了,就不再讲了。
for ( loop = 0; loop < num; loop++ )
{
star[loop].angle = 0.0;
star[loop].dist = ( float(loop)/num ) * 5.0;
star[loop].r = rand() % 256;
star[loop].g = rand() % 256;
star[loop].b = rand() % 256;
}
设置了每颗星星的起始角度、距离和颜色。您会注意到修改结构的属性有多容易。全
部50颗星星都会被循环设置。要改变star[1]的角度我们所要做的只是star[1].angle={某
个数值};就这么简单!
第loop颗星星离中心的距离是将loop的值除以星星的总颗数,然后乘上5.0。基本
上这样使得后一颗星星比前一颗星星离中心更远一点。这样当loop为50时(最后一颗星
星),loop除以num正好是1.0。之所以要乘以5.0是因为1.0*5.0就是5.0。5.0已经很
接近屏幕边缘。我不想星星飞出屏幕,5.0是最好的选择了。当然如果如果您将场景设置的
更深入屏幕里面的话,也许可以使用大于5.0的数值,但星星看起来就更小一些(都是透
视的缘故)。
您还会注意到每颗星星的颜色都是从0~255之间的一个随机数。也许您会奇怪为何
这里的颜色得取值范围不是OpenGL通常的0.0~1.0之间。这里我们使用的颜色设置函
数是glColor4ub,而不是以前的glColor4f。ub意味着参数是unsigned byte型的。一
个byte的取值范围是0~255。这里使用byte值取随机整数似乎要比取一个浮点的随机
数更容易一些。
}
void NeHeWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
清楚屏幕及深度缓存。
glBindTexture( GL_TEXTURE_2D, texture[0] );
选择纹理。
for ( loop = 0; loop < num; loop++ )
{
这段程序我们来循环绘制所有的星星。
glLoadIdentity();
绘制每颗星星之前,重置模型观察矩阵。
glTranslatef( 0.0, 0.0, zoom );
深入屏幕里面(使用“zoom”的值)。
glRotatef( tilt, 1.0, 0.0, 0.0 );
倾斜视角(使用“tilt”的值)。
glRotatef( star[loop].angle, 0.0, 1.0, 0.0 );
接下来的代码,我们来移动星星。星星开始时位于屏幕的中心。我们要做的第一件事
是把场景沿Y轴旋转。如果我们旋转90度的话,X轴不再是自左至右的了,他将由里向
外穿出屏幕。为了让大家更清楚些,举个例子。假想您站在房子中间。再设想您左侧的墙
上写着-x,前面的墙上写着-z,右面墙上就是+x咯,您身后的墙上则是+z。加入整个房子
向右转90度,但您没有动,那么前面的墙上将是-x而不再是-z了。所有其他的墙也都跟
着移动。-z出现在右侧,+z出现在左侧,+x出现在您背后。神经错乱了吧?通过旋转场
景,我们改变了x和z平面的方向。
glTranslatef( star[loop].dist, 0.0, 0.0 );
这代码沿x轴移动一个正值。通常x轴上的正值代表移向了屏幕的右侧(也就是通常
的x轴的正向),但这里由于我们绕y轴旋转了坐标系,x轴的正向可以是任意方向。如果
我们转180度的话,屏幕的左右侧就镜像反向了。因此,当我们沿x轴正向移动时,可能
向左,向右,向前或向后。
glRotatef( -star[loop].angle, 0.0, 1.0, 0.0 );
glRotatef( -tilt, 1.0, 0.0, 0.0 );
星星实际上是一个平面的纹理。现在您在屏幕中心画了个平面的四边形然后贴上纹理,
这看起来很不错。一切都如您所想的那样。但是当您当您沿着y轴转上个90度的话,纹
理在屏幕上就只剩右侧和左侧的两条边朝着您。看起来就是一条细线。这不是我们所想要
的。我们希望星星永远正面朝着我们,而不管屏幕如何旋转或倾斜。
我们通过在绘制星星之前,抵消对星星所作的任何旋转来实现这个愿望。您可以采用
逆序来抵消旋转。当我们倾斜屏幕时,我们实际上以当前角度旋转了星星。通过逆序,我
们又以当前角度“反旋转”星星。也就是以当前角度的负值来旋转星星。就是说,如果我
们将星星旋转了10度的话,又将其旋转-10度来使星星在那个轴上重新面对屏幕。上面的
第一行抵消了沿y轴的旋转。然后,我们还需要抵消掉沿x轴的屏幕倾斜。要做到这一点,
我们只需要将屏幕再旋转-tilt倾角。在抵消掉x和y轴的旋转后,星星又完全面对着我们
了。
if ( twinkle )
{
glColor4ub( star[(num-loop)-1].r,
star[(num-loop)-1].g,
star[(num-loop)-1].b, 255 );
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 0.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 0.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 0.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 0.0 );
glEnd();
}
如果twinkle为真,我们在屏幕上先画一次不旋转的星星:将星星总数(num)减去
当前的星星数(loop)再减去1,来提取每颗星星的不同颜色(这么做是因为循环范围从
0到num-1)。举例来说,结果为10的时候,我们就使用10号星星的颜色。这样相邻星
星的颜色总是不同的。这不是个好法子,但很有效。最后一个值是alpha通道分量。这个
值越小,这颗星星就越暗。
由于启用了twinkle,每颗星星最后会被绘制两遍。程序运行起来会慢一些,这要看
您的机器性能如何了。但两遍绘制的星星颜色相互融合,会产生很棒的效果。同时由于第
一遍的星星没有旋转,启用twinkle后的星星看起来有一种动画效果。(如果您这里看不懂
得话,就自己去看程序的运行效果吧。)
值得注意的是给纹理上色是件很容易的事。尽管纹理本身是黑白的,纹理将变成我们
在绘制它之前选定的任意颜色。此外,同样值得注意的是我们在这里使用的颜色值是byte
型的,而不是通常的浮点数。甚至alpha通道分量也是如此。
glRotatef( spin, 0.0, 0.0, 1.0 );
glColor4ub( star[loop].r, star[loop].g, star[loop].b, 255 );
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 0.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 0.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 0.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 0.0 );
glEnd();
上面是绘制第二遍的星星。唯一和前面的代码不同的是这一遍的星星肯定会被绘制,
并且这次的星星绕着z轴旋转。
spin += 0.01;
star[loop].angle += float(loop)/num;
star[loop].dist -= 0.01;
以上的代码代表星星的运动。我们增加spin的值来旋转所有的星星(公转)。然后,
将每颗星星的自转角度增加loop/num。这使离中心更远的星星转的更快。最后减少每颗
星星离屏幕中心的距离。这样看起来,星星们好像被不断地吸入屏幕的中心。
if ( star[loop].dist < 0.0 )
{
star[loop].dist += 5.0;
star[loop].r = rand() % 256;
star[loop].g = rand() % 256;
star[loop].b = rand() % 256;
}
}
检查星星是否已经碰到了屏幕中心。当星星碰到屏幕中心时,我们为它赋一个新颜色,
然后往外移5个单位,这颗星星将踏上它回归屏幕中心的旅程。
}
void NeHeWidget::timerEvent(QTimerEvent*)
{
updateGL();
}
这里就是定时操作函数timerEvent(),执行的操作就是updateGL(),就是刷新窗口了,
其实它也会调用paintGL(),所以就实现了每5毫秒刷新一次的动画效果。
void NeHeWidget::keyPressEvent( QKeyEvent *e )
{
switch ( e->key() )
{
case Qt::Key_T:
twinkle = !twinkle;
updateGL();
break;
按下了T键,就可以切换是否闪烁。
case Qt::Key_Up:
tilt -= 0.5;
updateGL();
break;
case Qt::Key_Down:
tilt += 0.5;
updateGL();
break;
case Qt::Key_Prior:
zoom -= 0.2;
updateGL();
break;
case Qt::Key_Next:
zoom += 0.2;
updateGL();
break;
case Qt::Key_F2:
fullscreen = !fullscreen;
if ( fullscreen )
{
showFullScreen();
}
else
{
showNormal();
setGeometry( 0, 0, 640, 480 );
}
update();
break;
case Qt::Key_Escape:
close();
}
}
这一课我尽我所能来解释如何加载一个灰阶位图纹理,(使用融合)去掉它的背景色后,
再给它上色,最后让它在三维场景中移动。我已经向您展示了如何创建漂亮的颜色与动画
效果。实现原理是在原始位图上再重叠一份位图拷贝。到现在为止,只要您很好的理解了
我所教您的一切,您应该已经能够毫无问题的制作您自己的3D Demo了。所有的基础知
识都已包括在内!
本课程的源代码。
旗的效果(波动纹理)
大家好!对那些想知道我在这里作了些什么的朋友,您可以先按文章的末尾所列出的
链接,下载我那毫无意义的Demo看看先!我是bosco,我将尽我所能教您来实现一个以
正弦波方式运动的图象。这一课基于NeHe的教程第六课,当然您至少也应该学会了一至
六课的知识。您需要下载源码压缩包,并将压缩包内带的data目录连其下的位图一起释放
至您的代码目录下。或者使用您自己的位图,当然它的尺寸必须适合OpenGL纹理的要求。
NeHeWidget类
(由nehewidget.h展开。)
class NeHeWidget : public QGLWidget
{
Q_OBJECT
public:
NeHeWidget( QWidget* parent = 0, const char* name = 0, bool fs = false );
~NeHeWidget();
protected:
void initializeGL();
void paintGL();
void resizeGL( int width, int height );
void keyPressEvent( QKeyEvent *e );
void loadGLTextures();
void timerEvent( QTimerEvent * );
这些函数前几课中都提到过了。
protected:
bool fullscreen;
GLfloat xRot, yRot, zRot;
GLfloat hold;
GLuint texture[1];
float points[45][45][3];
int wiggle_count;
};
我们将使用points数组来存放网格各顶点独立的(x,y,z)坐标。这里网格由45×45点
形成,换句话说也就是由44格×44格的小方格子依次组成了。wiggle_count用来指定纹
理波浪的运动速度,每3帧一次看起来很不错。变量hold将存放一个用来对旗形波浪进
行光滑的浮点数。
(由展开。)
#include
因为我们在程序中要使用到sin和cos两个三角函数,所以我们需要包含math.h这
个头文件。
NeHeWidget::NeHeWidget( QWidget* parent, const char* name, bool fs )
: QGLWidget( parent, name )
{
xRot = yRot = zRot = 0.0;
hold = 0.0;
wiggle_count = 0;
fullscreen = fs;
setGeometry( 0, 0, 640, 480 );
setCaption( "bosco & NeHe's Waving Texture Tutorial" );
if ( fullscreen )
showFullScreen();
startTimer( 5 );
}
我们需要在构造函数中给各个变量赋初值。startTimer()函数我们在第九课中已经讲过
了。
void NeHeWidget::loadGLTextures()
{
QImage tex, buf;
if ( !( "./data/" ) )
{
qWarning( "Could not read image file, using single-color instead." );
QImage dummy( 128, 128, 32 );
( Qt::() );
buf = dummy;
}
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[0] );
glBindTexture( GL_TEXTURE_2D, texture[0] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexImage2D( GL_TEXTURE_2D, 0, 3, (), (), 0,
GL_RGBA, GL_UNSIGNED_BYTE, () );
}
loadGLTextures()函数就是用来载入纹理的。
void NeHeWidget::initializeGL()
{
loadGLTextures();
glEnable( GL_TEXTURE_2D );
glShadeModel( GL_SMOOTH );
glClearColor( 0.0, 0.0, 0.0, 0.5 );
glClearDepth( 1.0 );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
glPolygonMode( GL_BACK, GL_FILL );
glPolygonMode( GL_FRONT, GL_LINE );
上面的代码指定使用完全填充模式来填充多边形区域的背面(或者叫做后表面吧)。相
反,多边形的正面(前表面)则使用轮廓线填充了。这些方式完全取决于您的个人喜好。
并且与多边形的方位或者顶点的方向有关。详情请参考Red Book。这里我顺便推销一本
推动我学习OpenGL的好书-Addison-Wesley出版的《Programmer's Guide to
OpenGL》。个人以为这是学习OpenGL的无价之宝。
for ( int x = 0; x < 45; x++ )
{
for ( int y = 0; y < 45; y++ )
{
points[x][y][0] = float( ( x/5.0 ) - 4.5 );
points[x][y][1] = float( ( y/5.0 ) - 4.5 );
points[x][y][2] = float( sin( ( ( ( x/5.0 ) * 40.0 )/360.0 ) * 3.141592654 * 2.0 ) );
}
}
这里感谢Graham Gibbons关于使用整数循环变量消除波浪间的脉冲锯齿的建议。
上面的两个循环初始化网格上的点。使用整数循环可以消除由于浮点运算取整造成的
脉冲锯齿的出现。我们将x和y变量都除以5,再减去4.5。这样使得我们的波浪可以“居
中”(这样计算所得结果将落在区间[-4.5,4.5]之间)。
点[x][y][2]最后的值就是一个sin函数计算的结果。sin()函数需要一个弧度参变量。将
float_x乘以40.0,得到角度值。然后除以360.0再乘以PI,乘以2.0,就转换为弧度了。
}
void NeHeWidget::paintGL()
{
int x, y;
float float_x, float_y, float_xb, float_yb;
x、y是循环变量,float_x、float_y、float_xb、float_yb是用来将旗形的波浪分割成
很小的四边形。
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
glTranslatef( 0.0, 0.0, -12.0 );
glRotatef( xRot, 1.0, 0.0, 0.0 );
glRotatef( yRot, 0.0, 1.0, 0.0 );
glRotatef( zRot, 0.0, 0.0, 1.0 );
glBindTexture( GL_TEXTURE_2D, texture[0] );
glBegin( GL_QUADS );
开始绘制四边形。
for ( x = 0; x < 44; x++ )
{
沿X平面0-44循环(45点)
for ( y = 0; y < 44; y++ )
{
沿Y平面0-44循环(45点)
float_x = float(x)/44.0;
float_y = float(y)/44.0;
float_xb = float(x+1)/44.0;
float_yb = float(y+1)/44.0;
上面我们使用4个变量来存放纹理坐标。每个多边形(网格之间的四边形)分别映射
了纹理的1/44 x 1/44部分。循环首先确定左下顶点的值,然后我们据此得到其他三点的
值。
glTexCoord2f( float_x, float_y );
glVertex3f( points[x][y][0], points[x][y][1], points[x][y][2] );
glTexCoord2f( float_x, float_yb );
glVertex3f( points[x][y+1][0], points[x][y+1][1], points[x][y+1][2] );
glTexCoord2f( float_xb, float_yb );
glVertex3f( points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2] );
glTexCoord2f( float_xb, float_y );
glVertex3f( points[x+1][y][0], points[x+1][y][1], points[x+1][y][2] );
上面四个坐标分别为左下、左上、右上、右下。
上面几行使用glTexCoord2f()和 glVertex3f()载入数据。提醒一点:四边形是逆时针
绘制的。这就是说,您开始所见到的表面是背面。后表面完全填充了,前表面由线条组成。
如果您按顺时针顺序绘制的话,您初始时见到的可能是前表面。也就是说您将看到网
格型的纹理效果而不是完全填充的。
}
}
glEnd();
四边形绘制结束。
if ( wiggle_count == 2 )
{
for ( y = 0; y < 45; y++ )
{
hold = points[0][y][2];
for ( x = 0; x < 44; x++ )
{
points[x][y][2] = points[x+1][y][2];
}
points[44][y][2] = hold;
}
wiggle_count = 0;
}
wiggle_count++;
上面所作的事情是先存储每一行的第一个值,然后将波浪左移一下,是图象产生波浪。
存储的数值挪到末端以产生一个永无尽头的波浪纹理效果。然后重置计数器
wiggle_count以保持动画的进行。
上面的代码由NeHe(2000年2月)修改过,以消除波浪间出现的细小锯齿。
xRot += 0.3;
yRot += 0.2;
zRot += 0.4;
标准的NeHe旋转增量:)。
}
void NeHeWidget::timerEvent(QTimerEvent*)
{
updateGL();
}
void NeHeWidget::keyPressEvent( QKeyEvent *e )
{
switch ( e->key() )
{
case Qt::Key_F2:
fullscreen = !fullscreen;
if ( fullscreen )
{
showFullScreen();
}
else
{
showNormal();
setGeometry( 0, 0, 640, 480 );
}
update();
break;
case Qt::Key_Escape:
close();
}
}
现在编译并运行程序,您将看到一个漂亮的位图波浪。除了嘘声一片之外,我不敢确
信大家的反应。但我希望大家能从这一课中学到点什么。如果您有任何问题或者需要澄清
的地方,请随便联络我。感谢大家!
本课程的源代码。
显示列表
这次我将教你如何使用显示列表,显示列表将加快程序的速度,而且可以减少代码的
长度。
当你在制作游戏里的小行星场景时,每一层上至少需要两个行星,你可以用OpenGL
中的多边形来构造每一个行星。聪明点的做法是做一个循环,每个循环画出行星的一个面,
最终你用几十条语句画出了一个行星。每次把行星画到屏幕上都是很困难的。当你面临更
复杂的物体时你就会明白了。
那么,解决的办法是什么呢?用显示列表,你只需要一次性建立物体,你可以贴图,
用颜色,想怎么弄就怎么弄。给显示列表一个名字,比如给小行星的显示列表命名为
“asteroid”。现在,任何时候我想在屏幕上画出行星,我只需要调用glCallList(asteroid)。
之前做好的小行星就会立刻显示在屏幕上了。因为小行星已经在显示列表里建造好了,
OpenGL不会再计算如何构造它。它已经在内存中建造好了。这将大大降低CPU的使用,
让你的程序跑的更快。
那么,开始学习咯。我称这个DEMO为Q-Bert显示列表。最终这个DEMO将在屏
幕上画出15个立方体。每个立方体都由一个盒子和一个顶构成,顶部是一个单独的显示
列表,盒子没有顶。
这一课是建立在第六课的基础上的,我将重写大部分的代码,这样容易看懂。下面的
这些代码在所有的课程中差不多都用到了。
NeHeWidget类
(由nehewidget.h展开。)
class NeHeWidget : public QGLWidget
{
Q_OBJECT
public:
NeHeWidget( QWidget* parent = 0, const char* name = 0, bool fs = false );
~NeHeWidget();
protected:
void initializeGL();
void paintGL();
void resizeGL( int width, int height );
void keyPressEvent( QKeyEvent *e );
void loadGLTextures();
void buildLists();
这个是建立显示列表的函数。
protected:
bool fullscreen;
GLfloat xRot, yRot, zRot;
GLuint box, top;
这里是两个用来存放显示列表的指针。
GLuint xLoop, yLoop;
这里是两个表示立方体位置的变量。
GLuint texture[1];
};
(由展开。)
static GLfloat boxcol[5][3] =
{
{ 1.0, 0.0, 0.0 },
{ 1.0, 0.5, 0.0 },
{ 1.0, 1.0, 0.0 },
{ 0.0, 1.0, 0.0 },
{ 0.0, 1.0, 1.0 }
};
static GLfloat topcol[5][3] =
{
{ 0.5, 0.0, 0.0 },
{ 0.5, 0.25, 0.0 },
{ 0.5, 0.5, 0.0 },
{ 0.0, 0.5, 0.0 },
{ 0.0, 0.5, 0.5 }
};
这里是两个颜色数组。
NeHeWidget::NeHeWidget( QWidget* parent, const char* name, bool fs )
: QGLWidget( parent, name )
{
xRot = yRot = zRot = 0.0;
box = top = 0;
xLoop = yLoop = 0;
fullscreen = fs;
setGeometry( 0, 0, 640, 480 );
setCaption( "NeHe's Display List Tutorial" );
if ( fullscreen )
showFullScreen();
}
我们需要在构造函数中给各个变量赋初值。
void NeHeWidget::loadGLTextures()
{
QImage tex, buf;
if ( !( "./data/" ) )
{
qWarning( "Could not read image file, using single-color instead." );
QImage dummy( 128, 128, 32 );
( Qt::() );
buf = dummy;
}
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[0] );
glBindTexture( GL_TEXTURE_2D, texture[0] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexImage2D( GL_TEXTURE_2D, 0, 3, (), (), 0,
GL_RGBA, GL_UNSIGNED_BYTE, () );
}
loadGLTextures()函数就是用来载入纹理的。
贴图纹理的代码和之前教程里的代码是一样的。我们需要一个可以贴在立方体上的纹
理。我决定使用mipmapping处理让纹理看上去光滑,因为我讨厌看见像素点。纹理的文
件名是“”,存放在data目录下。
void NeHeWidget::buildLists()
{
box = glGenLists( 2 );
开始的时候我们告诉OpenGL我们要建立两个显示列表。glGenLists(2)建立了两个显
示列表的空间,并返回第一个显示列表的指针。“box”指向第一个显示列表,任何时候
调用“box”第一个显示列表就会显示出来。
现在开始构造第一个显示列表。我们已经申请了两个显示列表的空间了,并且有box
指针指向第一个显示列表。所以现在我们应该告诉OpenGL要建立什么类型的显示列表。
glNewList( box, GL_COMPILE );
我们用glNewList()命令来做这个事情。你一定注意到了box是第一个参数,这表示
OpenGL将把列表存储到box所指向的内存空间。第二个参数GL_COMPILE告诉OpenGL
我们想预先在内存中构造这个列表,这样每次画的时候就不必重新计算怎么构造物体了。
GL_COMPILE类似于编程。在你写程序的时候,把它装载到编译器里,你每次运行程
序都需要重新编译。而如果它已经编译成了.exe文件,那么每次你只需要点击那个.exe文
件就可以运行它了,不需要编译。当OpenGL编译过显示列表后,就不需要再每次显示的
时候重新编译它了。这就是为什么用显示列表可以加快速度。
你可以在glNewList()和glEngList()中间加上任何你想加上的代码。可以设置颜色,
贴图等等。唯一不能加进去的代码就是会改变显示列表的代码。显示列表一旦建立,你就
不能改变它。
比如你想加上glColor3ub( rand()%255, rand()%255, rand()%255 ),使得每一次画
物体时都会有不同的颜色。但因为显示列表只会建立一次,所以每次画物体的时候颜色都
不会改变。物体将会保持第一次建立显示列表时的颜色。 如果你想改变显示列表的颜色,
你只有在调用显示列表之前改变颜色。后面将详细解释这一点。
glBegin( GL_QUADS );
这部分的代码画出一个没有顶部的盒子,它不会出现在屏幕上,只会存储在显示列表
里。
glNormal3f( 0.0, -1.0, 0.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glNormal3f( 0.0, 0.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glNormal3f( 0.0, 0.0, -1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glNormal3f( 1.0, 0.0, 0.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glNormal3f( -1.0, 0.0, 0.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glEnd();
glEndList();
用glEngList()命令,我们告诉OpenGL我们已经完成了一个显示列表。在glNewList()
和glEngList()之间的任何东西就是显示列表的一部分。
top = box + 1;
现在我们来建立第二个显示列表。在上一个显示列表的指针上加1,就得到了第二个
显示列表的指针。第二个显示列表的指针命名为“top”。
glNewList( top, GL_COMPILE );
glBegin( GL_QUADS );
这部分代码画出盒子的顶部。
glNormal3f( 0.0, 1.0, 0.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glEnd();
glEndList();
然后告诉OpenGL第二个显示列表建立完毕。
}
void NeHeWidget::initializeGL()
{
loadGLTextures();
buildLists();
请注意代码的顺序,先读入纹理,然后建立显示列表,这样当我们建立显示列表的时
候就可以将纹理贴到立方体上了。
glEnable( GL_TEXTURE_2D );
glShadeModel( GL_SMOOTH );
glClearColor( 0.0, 0.0, 0.0, 0.5 );
glClearDepth( 1.0 );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glEnable( GL_LIGHT0 );
glEnable( GL_LIGHTING );
glEnable( GL_COLOR_MATERIAL );
上面的三行使灯光有效。Light0一般来说是在显卡中预先定义过的,如果Light0不
工作,把下面那行注释掉好了。
最后一行的GL_COLOR_MATERIAL使我们可以用颜色来贴纹理。如果没有这行代码,
纹理将始终保持原来的颜色,glColor3f( r, g, b )就没有用了。总之这行代码是很有用的。
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
最后,设置投影校正。
}
现在来看绘画的代码。对数学我从来都是很头大的,没有sin,没有cos,但仍然看起
来很奇怪(相信读者不会觉得头大)。首先,按惯例,清除屏幕和深度缓冲。
然后捆绑纹理到立方体上(我知道捆绑这个词不太专业,但是……)。可以将这行放在
显示列表里,但放在外边,就可以在任何时候修改它。
void NeHeWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glBindTexture( GL_TEXTURE_2D, texture[0] );
现在到了真正有趣的地方了。用一个循环,循环变量用于改变Y轴位置,在Y轴上画
5个立方体,所以用从1到5的循环。
for ( yLoop = 1; yLoop < 6; yLoop++ )
{
另外用一个循环,循环变量用于改变X轴位置。每行上的立方体数目取决于行数,所
以循环方式如下。
for ( xLoop = 0; xLoop < yLoop; xLoop++ )
{
下边的代码是移动和旋转当前坐标系到需要画出立方体的位置。(原文有很罗嗦的一大
段,相信大家的数学功底都不错,就不翻译了)
glLoadIdentity();
glTranslatef( 1.4 + (float(xLoop) * 2.8) - (float(yLoop) * 1.4),
( (6.0 - (float(yLoop)) ) * 2.4 ) - 7.0, -20.0 );
glRotatef( 45.0 - (2.0 * yLoop) + xRot, 1.0, 0.0, 0.0 );
glRotatef( 45.0 + yRot, 0.0, 1.0, 0.0 );
然后在正式画盒子之前设置颜色。每个盒子用不同的颜色。
glColor3fv( boxcol[yLoop-1] );
好了,颜色设置好了。现在需要做的就是画出盒子。不用写出画多边形的代码,只需
要用glCallList(box)命令调用显示列表。盒子将会用glColor3fv()所设置的颜色画出来。
glCallList( box );
然后用另外的颜色画顶部。搞定。
glColor3fv( topcol[yLoop-1] );
glCallList( top );
}
}
}
void NeHeWidget::keyPressEvent( QKeyEvent *e )
{
switch ( e->key() )
{
case Qt::Key_Up:
xRot -= 0.2;
updateGL();
break;
case Qt::Key_Down:
xRot += 0.2;
updateGL();
break;
case Qt::Key_Left:
yRot -= 0.2;
updateGL();
break;
case Qt::Key_Right:
yRot += 0.2;
updateGL();
break;
case Qt::Key_F2:
fullscreen = !fullscreen;
if ( fullscreen )
{
showFullScreen();
}
else
{
showNormal();
setGeometry( 0, 0, 640, 480 );
}
update();
break;
case Qt::Key_Escape:
close();
}
}
上面就是键盘控制,用上下左右键来控制立方体的运动。
本课程的源代码。
看起来很棒的雾
难道你不想把“雾”加入到你的OpenGL程序中吗?那么在这课里我将要为您展现如
何实现这项功能。这是我第一次写教程,而且相对来说我也是OpenGL/C++程序设计新
手,所以如果您发现有什么错误的话,请让我知道。这课的代码是基于第七课的。
NeHeWidget类
(由nehewidget.h展开。)
class NeHeWidget : public QGLWidget
{
Q_OBJECT
public:
NeHeWidget( QWidget* parent = 0, const char* name = 0, bool fs = false );
~NeHeWidget();
protected:
void initializeGL();
void paintGL();
void resizeGL( int width, int height );
void keyPressEvent( QKeyEvent *e );
void loadGLTextures();
protected:
bool fullscreen;
GLfloat xRot, yRot, zRot;
GLfloat zoom;
GLfloat xSpeed, ySpeed;
GLuint texture[3];
GLuint filter;
bool light;
GLuint fogFilter;
};
变量fogfilter,将被用做记录您所选择的雾的类型的索引。
(由展开。)
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 };
GLuint fogMode[3] = { GL_EXP, GL_EXP2, GL_LINEAR };
GLfloat fogColor[4] = { 0.5, 0.5, 0.5, 1.0 };
数据设定
我们将要设定我们用来保存关于雾的信息的所有变量。变量fogMode,用来保存3
种有关雾的类型:GL_EXP,GL_EXP2,GL_LINEAR。稍后我会解释这三种类型间的差别。
这个变量将在代码的开头声明。变量fogColor会保存任何您想要的雾的颜色。
NeHeWidget::NeHeWidget( QWidget* parent, const char* name, bool fs )
: QGLWidget( parent, name )
{
xRot = yRot = zRot = 0.0;
zoom = -5.0;
xSpeed = ySpeed = 0.0;
filter = 0;
light = false;
fogFilter = 0;
fullscreen = fs;
setGeometry( 0, 0, 640, 480 );
setCaption( "Chris Aliotta & NeHe's Fog Tutorial" );
if ( fullscreen )
showFullScreen();
}
我们需要在构造函数中给各个变量赋初值。
void NeHeWidget::loadGLTextures()
{
QImage tex, buf;
if ( !( "./data/" ) )
{
qWarning( "Could not read image file, using single-color instead." );
QImage dummy( 128, 128, 32 );
( Qt::() );
buf = dummy;
}
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 3, &texture[0] );
glBindTexture( GL_TEXTURE_2D, texture[0] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexImage2D( GL_TEXTURE_2D, 0, 3, (), (), 0,
GL_RGBA, GL_UNSIGNED_BYTE, () );
glBindTexture( GL_TEXTURE_2D, texture[1] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexImage2D( GL_TEXTURE_2D, 0, 3, (), (), 0,
GL_RGBA, GL_UNSIGNED_BYTE, () );
glBindTexture( GL_TEXTURE_2D, texture[2] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGB, (), (),
GL_RGBA, GL_UNSIGNED_BYTE, () );
}
loadGLTextures()函数就是用来载入纹理的。
void NeHeWidget::initializeGL()
{
loadGLTextures();
glEnable( GL_TEXTURE_2D );
glShadeModel( GL_SMOOTH );
glClearColor( 0.5, 0.5, 0.5, 1.0 );
场景绘制设定
上面我们进行初始化OpenGL了。glClearColor()将会稍做改变以用来将屏幕颜色清
除为雾的颜色以获得更好的视觉效果。这里并没有复杂的代码用来进行雾的操作,而且您
会发现,这将是非常简单的。
glClearDepth( 1.0 );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
glLightfv( GL_LIGHT1, GL_AMBIENT, lightAmbient );
glLightfv( GL_LIGHT1, GL_DIFFUSE, lightDiffuse );
glLightfv( GL_LIGHT1, GL_POSITION, lightPosition );
glEnable( GL_LIGHT1 );
glFogi( GL_FOG_MODE, fogMode[fogFilter] );
确定了雾的类型。开始的时候我们声明了数组fogMode,它保存了GL_EXP,
GL_EXP2,GL_LINEAR。在我们使用这些值之前,让我稍做解释:
GL_EXP:简单渲染在屏幕上显示的雾的模式。它无法给予我们非常漂亮的雾的效果,
但是却可以在古老的电脑上工作的很好。
GL_EXP2:比1提高了一点,将渲染全屏幕的雾,然而她会给予场景更深的效果。
GL_LINEAR:这是最好的雾的渲染模式,对象在雾中消隐的很好。
glFogfv( GL_FOG_COLOR, fogColor );
设定了雾的颜色,开始的时候我们把它设定为(0.5,0.5,0.5,1.0),使用变量fogcolor
将给予我们漂亮的灰色。
glFogf( GL_FOG_DENSITY, 0.35 );
确定了雾的密度,增大这个数值雾将会变的更浓,减小它雾将会变的更淡。
glHint( GL_FOG_HINT, GL_DONT_CARE );
确定了雾的渲染方式,我使用GL_DONT_CARE是因为我并不关心建议值。然而这里
有一个用来解释关于这个项的不同值之间的区别:
GK_DONT_CARE:让OPENGL自己来确定雾的渲染方式,每顶点或是每像素。
GL_NICEST:对每一像素进行雾的渲染,它看起来是极棒的。
GL_FASTEST:对每一顶点进行雾的渲染,它速度较快,但是不够美丽。
glFogf( GL_FOG_START, 1.0 );
确定了雾的开始初离屏幕有多近。你可以将这个值改变为任意你想要的值,这个值描
述了那个你想要使雾开始的位置。下一行与上行相似,它告诉OpenGL雾能离开屏幕有多
远。
glFogf( GL_FOG_END, 5.0 );
glEnable( GL_FOG );
glEnable(GL_FOG)解释起来非常容易,它告诉OpenGL开始进行雾的计算。
}
void NeHeWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
glTranslatef( 0.0, 0.0, zoom );
glRotatef( xRot, 1.0, 0.0, 0.0 );
glRotatef( yRot, 0.0, 1.0, 0.0 );
glBindTexture( GL_TEXTURE_2D, texture[filter] );
glBegin( GL_QUADS );
glNormal3f( 0.0, 0.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glNormal3f( 0.0, 0.0, -1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glNormal3f( 0.0, 1.0, 0.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glNormal3f( 0.0, -1.0, 0.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glNormal3f( 1.0, 0.0, 0.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glNormal3f( -1.0, 0.0, 0.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glEnd();
xRot += xSpeed;
yRot += ySpeed;
}
void NeHeWidget::keyPressEvent( QKeyEvent *e )
{
switch ( e->key() )
{
case Qt::Key_L:
light = !light;
if ( !light )
{
glDisable( GL_LIGHTING );
}
else
{
glEnable( GL_LIGHTING );
}
updateGL();
break;
case Qt::Key_F:
filter += 1;;
if ( filter > 2 )
{
filter = 0;
}
updateGL();
break;
case Qt::Key_G:
fogFilter += 1;;
if ( fogFilter > 2 )
{
fogFilter = 0;
}
glFogi( GL_FOG_MODE, fogMode[fogFilter] );
updateGL();
break;
按下了G键,就可以变换一下所使用的雾的类型。
case Qt::Key_Prior:
zoom -= 0.2;
updateGL();
break;
case Qt::Key_Next:
zoom += 0.2;
updateGL();
break;
case Qt::Key_Up:
xSpeed -= 0.01;
updateGL();
break;
case Qt::Key_Down:
xSpeed += 0.01;
updateGL();
break;
case Qt::Key_Right:
ySpeed += 0.01;
updateGL();
break;
case Qt::Key_Left:
ySpeed -= 0.01;
updateGL();
break;
case Qt::Key_F2:
fullscreen = !fullscreen;
if ( fullscreen )
{
showFullScreen();
}
else
{
showNormal();
setGeometry( 0, 0, 640, 480 );
}
update();
break;
case Qt::Key_Escape:
close();
}
}
上面就是键盘控制,用上下左右键来控制立方体的运动。
这就是全部了!我们结束了我们的课程,现在你已经在你的OPENGL程序中有了雾。
我敢说这是决不费事的。
本课程的源代码。
版权声明:本文标题:Qt+OpenGL中文教程 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1710278624a565802.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论