
大家好!对那些想知道我在这里作了些什么的朋友,您可以先按文章的末尾所列出的链接,下载我那毫无意义的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();
}
}
现在编译并运行程序,您将看到一个漂亮的位图波浪。除了嘘声一片之外,我不敢确信大家的反应。但我希望大家能从这一课中学到点什么。如果您有任何问题或者需要澄清的地方,请随便联络我。感谢大家!
本课程的源代码。