2009年4月10日星期五

OpenGL (1) 入门

前几天跟着师兄做东西,“被迫”学习了一下OpenGl,这几天又要开始忙着作业啊,考试啊,什么的,赶紧抓个空把学到的东西记录下来,不然一个不小心就“丢”了。

首先讨论一下开发环境配置问题,需要一些DLL、头文件、LIB文件,这些东西网上应该都有:

1) GLU.DLL GLUT.DLLGLUT32.DLL

2) GL.HGLAUX.HGLEXT.HGLU.HGLUT.HWGLEXT.H

3) GLAUX.LIBGLU32.LIBGLUT32.LIBOPENGL32.LIB

由于OpenGL是跨平台的,因此上面的是Win平台下需要的东西。其实GLUT是一个工具包,是Nate Robin开发的,他的主页上有非常详细的教程。GLUTWin的窗口、事件相关的API进行了封装,至少对于初学的我来说用起来十分舒服,而且它也被大家广泛使用的。

当然,我更喜欢原滋原味的东西,因此还是要看看Nehe的教程的。

不说废话了,就以我目前做的一个小例子来说说OpenGL的基本框架吧,当然这里面没有用到光照、雾、纹理等高级的东西,不过有涉及到拾取。

VS2008新建一个控制台程序,源代码如下:

#include

#include

#include

#include

注意这里只需要有GL/GLUT.H就够了,因为其内部包含了对OPENGL的头文件的正确引用。

#define BLACK_F 0.0, 0.0, 0.0

#define RED_F 1.0, 0.0, 0.0

#define GREEN_F 0.0, 1.0, 0.0

#define YELLOW_F 1.0, 1.0, 0.0

#define BLUE_F 0.0, 0.0, 1.0

#define WHITE_F 1.0, 1.0, 1.0

定义了浮点数形式的RGB数值,只是为了编程方便~ *_F”这种表示方法是模仿OpenGL的,因为OpenGL里的很多函数是根据参数的数据类型来命名最后一个的譬如glColor3f(),glColor3d(),分别表示参数为GLfloatGLdouble类型,但是其功能是一模一样的。

#define CUBE_NAME 1

#define PI 3.1415926

#define CUBE_SIZE 2.0

#define HEIGHT 600

#define WIDTH 800

#define SELECT_BUFF_SIZE 512

GLint wndWidth, wndHeight;

全局的变量用来记录当前的窗口宽和高

GLfloat angle = 0.0;

//

// Rotate Axis

//

GLfloat axis[ 3 ] = { 0.0, 0.0, 0.0 };

//

// Mouse location

//

GLfloat lastPos[ 3 ], currentPos[ 3 ];

//

// Camera Properties

//

GLfloat eye[3] = { 0.0, 0.0, 4.0 };

GLfloat at[3] = { 0.0, 0.0, 0.0 };

GLfloat up[3] = { 0.0, 1.0, 0.0 };

GLfloat lastMatrix[ 16 ] =

{1.0, 0.0, 0.0, 0.0,

0.0, 1.0, 0.0, 0.0,

0.0, 0.0, 1.0, 0.0,

0.0, 0.0, 0.0, 1.0};

GLfloat selectCopy[ 16 ] =

{1.0, 0.0, 0.0, 0.0,

0.0, 1.0, 0.0, 0.0,

0.0, 0.0, 1.0, 0.0,

0.0, 0.0, 0.0, 1.0};

unsigned char btnState;

GLboolean isSelected;

//

// Project a 2D mouse coordinates to a 3D sphere

//

int ProjectSphere( GLint x, GLint y, GLint r, GLfloat v[ 3 ] )

{

GLfloat z;

v[0] = ( GLfloat ) x * 2.0 - ( GLfloat ) r;

v[1] = ( GLfloat ) r - ( GLfloat ) y * 2.0;

z = r * r - v[0] * v[0] - v[1] * v[1];

if (z <>

{

return 0;

}

v[2] = sqrt( z );

//

// Normalise

//

v[0] /= ( GLfloat ) r;

v[1] /= ( GLfloat ) r;

v[2] /= ( GLfloat ) r;

return !0;

}

这个函数是将二维的鼠标坐标映射到一个单位球面上,通过它可以实现鼠标控制摄像机。

void Init( void )

{

glClearColor( WHITE_F, 0.0 );

glShadeModel( GL_FLAT );

//glEnable( GL_DEPTH_TEST );

}

做一些初始化的操作glClearColor()设置了清除颜色缓冲区时所试用的颜色,glShadeModel()则指明了着色方案,你只有两种选择GL_FLAT 或者是GL_SMOOTH,默认是后者

void Display( void )

{

glClear( GL_COLOR_BUFFER_BIT );

glClear( GL_DEPTH_BUFFER_BIT );

清除颜色缓冲区和深度缓冲区

glColor3f( RED_F );

设置了我们画东西需要用什么颜色来画

glMatrixMode( GL_MODELVIEW );

调整模型视图矩阵,这里需要特殊说明一下:

OpenGL在完成绘制前一般有几个步骤:

1) 调整投影矩阵, 简洁来说就是把照相机放好,放到哪个位置,朝哪个方向。

2) 调整模型视图矩阵,对物体进行的一系列变换,譬如我们要让它旋转、平移等。

3) 调整视口, 摄像机看到的东西要映射到屏幕上,需要调整映射到哪里的问题。

4) 绘制

glLoadIdentity();

//

// Apply the new transform with the old one

//

glRotatef( angle, axis[ 0 ], axis[ 1 ], axis[ 2 ] );

glMultMatrixf( lastMatrix );

首先进行了旋转,然后乘上了之前的变换矩阵,然而由于矩阵乘法顺序的问题,实际上我们等于先做了lastMatrix的变换,然后才有旋转的效果。

//

// Save as last Matrix

//

glGetFloatv(GL_MODELVIEW_MATRIX, lastMatrix);

glutSolidCube( CUBE_SIZE );

绘制函数,这里我们只是调用了一个GLUT的一个工具函数,画了一个实心的立方体,其实很多时候要绘制什么需要我们自己定义的,调用glDraw***一类的函数,然后放到glBegin()和glEnd()之间就可以了。。。

glutSwapBuffers();

为了实现旋转效果的平滑过渡,我们用了双缓冲,在后面的初始化里有这样的设置,当然你也可以设置成单缓冲,那么这里一定要有glFlush()

}

以上就是画好我们想要的东西,然后把这个函数设置为回调函数就可以了。那么对于用户输入的响应或者一些变换需要在下面实现:

void Reshape( GLsizei w, GLsizei h )

{

glViewport( 0, 0, w, h );

glMatrixMode( GL_PROJECTION );

glLoadIdentity();

gluPerspective( 60.0, ( GLfloat ) w / ( GLfloat ) h, 0.1, 8.0 );

gluLookAt( eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2] );

对投影矩阵进行变换,之所以没有把它放到Display函数里,是因为当我们改变窗口大小的时候,会导致投影矩阵做相应的变换,同样视口也一样

//

// New window size

//

wndHeight = h;

wndWidth = w;

}

GLboolean JudgeSelect( GLint x, GLint y )

{

GLuint selectBUf[ SELECT_BUFF_SIZE ];

GLint hitNum;

GLint viewPort[ 4 ];

//

// Judge if select the cube

//

glGetIntegerv( GL_VIEWPORT, viewPort );

glSelectBuffer( SELECT_BUFF_SIZE ,selectBUf );

glRenderMode( GL_SELECT );

glInitNames();

glPushName( 0 );

glMatrixMode( GL_PROJECTION );

glPushMatrix();

glLoadIdentity();

//

// Set a small area to be hitted

//

gluPickMatrix( ( GLdouble ) x, ( GLdouble ) ( viewPort[ 3 ] - y ), 5.0, 5.0, viewPort );

gluPerspective( 60.0, ( GLfloat ) wndWidth / ( GLfloat ) wndHeight, 0.1, 8.0 );

gluLookAt( eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2] );

glMatrixMode( GL_MODELVIEW );

glPushMatrix();

memcpy( selectCopy, lastMatrix, 16 * sizeof( lastMatrix[ 0 ] ) );

glLoadIdentity();

//

// Apply the new transform with the old one

//

glRotatef( angle, axis[ 0 ], axis[ 1 ], axis[ 2 ] );

glMultMatrixf( lastMatrix );

//

// Save as last Matrix

//

glGetFloatv(GL_MODELVIEW_MATRIX, lastMatrix);

glLoadName( CUBE_NAME );

glutSolidCube( CUBE_SIZE );

glPopMatrix();

memcpy( lastMatrix, selectCopy, 16 * sizeof( lastMatrix[ 0 ] ) );

glMatrixMode( GL_PROJECTION );

glPopMatrix();

glFlush( );

hitNum = glRenderMode( GL_RENDER );

if( hitNum <= 0 ) {

return FALSE;

} else {

return TRUE;

}

}

这一部分是用来实现选择的,即如果用户没有选择立方体,那么拖动鼠标就不会旋转,选择的机制这里先略过,因为我还没有彻底搞懂。。。,从结果上看,就是hitNum来记录点击的次数。

void Mouse( GLint button, GLint state, GLint x, GLint y)

{

//

// Record mouse state

//

btnState = (unsigned char) button;

btnState <<= 4;

btnState |= (unsigned char)state;

switch ( button )

{

case GLUT_LEFT_BUTTON:

//

// Initial the lastPos

//

ProjectSphere( x, y, wndHeight, lastPos );

isSelected = JudgeSelect( x, y );

break;

}

}

这个函数也是一个回调函数,用来处理鼠标事件,当点击鼠标之后我们就要准备进行旋转了,但是也不一定,isSelected会告诉我们是否点到了立方体上,是否需要旋转。

void Motion( GLint x, GLint y )

{

GLfloat d, dx, dy, dz;

if( ! isSelected ) {

return;

}

if ( ( ( btnState >> 4 ) & 0x0F) == GLUT_LEFT_BUTTON )

{

if( ! ProjectSphere( x, y, wndHeight, currentPos ) )

{

return;

}

dx = currentPos[ 0 ] - lastPos[ 0 ];

dy = currentPos[ 1 ] - lastPos[ 1 ];

dz = currentPos[ 2 ] - lastPos[ 2 ];

//

// If mouse has moved

//

if (dx || dy || dz)

{

//

// Moving angle

//

d = sqrt( dx * dx + dy * dy + dz * dz );

angle= d * 180.0;

//

// The nomal vector of the move plane

//

axis[0] = lastPos[1] * currentPos[2] - lastPos[2] * currentPos[1];

axis[1] = lastPos[2] * currentPos[0] - lastPos[0] * currentPos[2];

axis[2] = lastPos[0] * currentPos[1] - lastPos[1] * currentPos[0];

//

// Record the old coordinates

//

lastPos[0] = currentPos[0];

lastPos[1] = currentPos[1];

lastPos[2] = currentPos[2];

}

glutPostRedisplay();

}

}

鼠标移动的回调函数,这里我们实现了旋转需要的数学运算,最关键的是记得最后要有glutPostRedisplay()这个函数,它告诉窗体要重绘。

GLint main( GLint argc, char** argv )

{

glutInit( &argc, argv );

glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );/* | GLUT_DEPTH );*/

glutInitWindowSize( WIDTH, HEIGHT );

glutInitWindowPosition( 100, 100 );

glutCreateWindow( "BLIZZARD" );

同样是一些初始化的工作,可以都一起扔到Init()函数里面。。。

Init();

glutDisplayFunc( Display );

glutReshapeFunc( Reshape );

glutMouseFunc( Mouse );

glutMotionFunc( Motion );

glutMainLoop();

这里就是对上面所有的回调函数进行绑定。GlutMainLoop则进入消息循环,里面会有一些默认的处理机制,譬如点窗口的X,就会关掉窗口等。。。

return 0;

}

使用glut工具包,确实非常方便,简化了和系统相关的编程部分。。。另外尽量使用GL开头的数据类型,这个是跨平台的问题了。。。

没有评论:

发表评论