大家好!对那些想知道我在这里作了些什么的朋友,您可以先按文章的末尾所列出的链接,下载我那毫无意义的Demo看看先!我是bosco,我将尽我所能教您来实现一个以正弦波方式运动的图象。这一课基于NeHe的教程第六课,当然您至少也应该学会了一至六课的知识。您需要下载源码压缩包,并将压缩包内带的data目录连其下的位图一起释放至您的代码目录下。或者使用您自己的位图,当然它的尺寸必须适合OpenGL纹理的要求。
(由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将存放一个用来对旗形波浪进行光滑的浮点数。
(由nehewidget.cpp展开。)
#include <math.h>
因为我们在程序中要使用到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 ( !buf.load( "./data/Tim.bmp" ) ) { qWarning( "Could not read image file, using single-color instead." ); QImage dummy( 128, 128, 32 ); dummy.fill( Qt::green.rgb() ); 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, tex.width(), tex.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() ); }
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(); } }
现在编译并运行程序,您将看到一个漂亮的位图波浪。除了嘘声一片之外,我不敢确信大家的反应。但我希望大家能从这一课中学到点什么。如果您有任何问题或者需要澄清的地方,请随便联络我。感谢大家!
本课程的源代码。