效果图:
主要类有加载3DS模型类:C3DS;雪花粒子效果类:CSnow;加载纹理类:CWenLi以及字体类:CFont;
主要用到了纹理,光照材质,雾,粒子系统,3DS模型设计与加载等技术。
纹理主要代码如下:
FILE *file=fopen(name,"r");
if(!file)
{
::MessageBox(NULL,"Open file failed!","Load texture error!",MB_OK);
return FALSE;
}
fclose(file);
AUX_RGBImageRec* pImage[1];
memset(pImage,0,sizeof(void*)*1);
pImage[0]=auxDIBImageLoad(name);
if(m_First)
{
glGenTextures(WENLIMAXNUMBER,&m_pMem[0]);
m_First=false;
}
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
//glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
//glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
glBindTexture(GL_TEXTURE_2D,m_pMem[m_Num]);
glTexImage2D(GL_TEXTURE_2D,
0,
3,
pImage[0]->sizeX,
pImage[0]->sizeY,
0,
GL_RGB,
GL_UNSIGNED_BYTE,
pImage[0]->data);
free(pImage[0]->data);
free(pImage[0]);
光照材质雾主要代码如下:
/*******************设置灯光********************/
float ambient[]={1.0,1.0,1.0};
float position[]={-20.0,20.0,-50};
float direction[]={0.0,-0.5,-1.0};
glLightfv(GL_LIGHT0,GL_SPECULAR,ambient);
glLightfv(GL_LIGHT0,GL_POSITION,position);
glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,direction);
glLightf(GL_LIGHT0,GL_SPOT_EXPONENT,2.0);
glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,30.0);
//glLightf(GL_LIGHT0,GL_LINEAR_ATTENUATION,0.5);
/**********************设置材质*****************/
float mat_diffuse[]={1.0,0.5,0.5};
glMaterialfv(GL_LIGHT0,GL_DIFFUSE,mat_diffuse);
glMaterialf(GL_LIGHT0,GL_SHININESS,10.0);
/**********************设置雾效果*****************/
float fog_color[]={0.5,0.5,0.5};
glFogf(GL_FOG_MODE,GL_LINEAR);
glFogf(GL_FOG_START,0.0);
glFogf(GL_FOG_END,300);
glFogfv(GL_FOG_COLOR,fog_color);
创建好后不要忘记启用它们:
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_FOG);
粒子系统使由多个粒子组成的,初始化是为每个例子赋予生命,坐标,速度,加速度等变量,主要代码如下:
typedef struct
{
float x,y,z;
}Vertex,*pVertex;
typedef struct
{
Vertex position;
Vertex speed;
Vertex a;//acceleration int three directions
float lifetime;
float size;
Vertex color;
float dec;//the decreasing speed of the particle
bool alive;
float alpha;
}Particle,*pParticle;
class CSnow
{
public:
Particle m_Pat[MAXNUMBER];
public:
void Display();
void Update();
void Init();
CSnow();
~CSnow();
bool InitSingleParticle(int number);
};
bool CSnow::InitSingleParticle(int number)
{
m_Pat[number].a.x=m_Pat[number].a.y=m_Pat[number].a.z=0.0;
m_Pat[number].alive=true;
m_Pat[number].alpha=1.0;
m_Pat[number].color.x=m_Pat[number].color.y=m_Pat[number].color.z=1.0;
m_Pat[number].dec=0.0;
m_Pat[number].lifetime=100.0;
m_Pat[number].position.x=GROUND_MAX_WIDTH/2.0-rand()%GROUND_MAX_WIDTH;
m_Pat[number].position.y=300;
m_Pat[number].position.z=-50.0-rand()%(GROUND_MAX_LENGTH-50);
m_Pat[number].size=0.4;
m_Pat[number].speed.x=0.5-rand()%10/10;
m_Pat[number].speed.z=0.5-rand()%10/10;
m_Pat[number].speed.y=-1*rand()%4-2.0;
return true;
}
3DS模型加载比较复杂,需要先自己用3DsMax制作房子,树等模型并贴好贴图,然后在用递归读取的方法将3DS文件中模型顶点,纹理等信息读入到程序中,在用OpenGL渲染管线(Pipline)渲染出来,其递归代码如下:
void CLoad3DS::ReadNextChunk(t3DModel *pModel, tChunk *pPreChunk)
{ t3DObject newObject = {0}; // 用来添加到对象链表
tMatInfo newTexture = {0}; // 用来添加到材质链表
unsigned int version = 0; // 保存文件版本
int buffer[50000] = {0}; // 用来跳过不需要的数据
m_CurrentChunk = new tChunk; // 为新的块分配空间
// 下面每读一个新块,都要判断一下块的ID,如果该块是需要的读入的,则继续进行
// 如果是不需要读入的块,则略过
// 继续读入子块,直到达到预定的长度
while (pPreChunk->bytesRead < pPreChunk->length)
{ // 读入下一个块
ReadChunk(m_CurrentChunk);
// 判断块的ID号
switch (m_CurrentChunk->ID)
{
case VERSION: // 文件版本号
// 在该块中有一个无符号短整型数保存了文件的版本
// 读入文件的版本号,并将字节数添加到bytesRead变量中
m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
// 如果文件版本号大于3,给出一个警告信息
if (version > 0x03)
MessageBox(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK);
break;
case OBJECTINFO: // 网格版本信息
// 读入下一个块
ReadChunk(m_TempChunk);
// 获得网格的版本号
m_TempChunk->bytesRead += fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);
// 增加读入的字节数
m_CurrentChunk->bytesRead += m_TempChunk->bytesRead;
// 进入下一个块
ReadNextChunk(pModel, m_CurrentChunk);
break;
case MATERIAL: // 材质信息
// 材质的数目递增
pModel->numOfMaterials++;
// 在纹理链表中添加一个空白纹理结构
pModel->pMaterials.push_back(newTexture);
// 进入材质装入函数
ReadNextMatChunk(pModel, m_CurrentChunk);
break;
case OBJECT: // 对象的名称
// 该块是对象信息块的头部,保存了对象了名称
// 对象数递增
pModel->numOfObjects++;
// 添加一个新的tObject节点到对象链表中
pModel->pObject.push_back(newObject);
// 初始化对象和它的所有数据成员
memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3DObject));
// 获得并保存对象的名称,然后增加读入的字节数
m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName);
// 进入其余的对象信息的读入
ReadNextObjChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk);
break;
case EDITKEYFRAME:
// 跳过关键帧块的读入,增加需要读入的字节数
m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
default:
// 跳过所有忽略的块的内容的读入,增加需要读入的字节数
m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
break;
}
// 增加从最后块读入的字节数
pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
}
// 释放当前块的内存空间
delete m_CurrentChunk;
m_CurrentChunk = pPreChunk;
}
有了以上知识基础后就可以开发游戏场景程序了^_^,当然,制作一个完整的游戏还有许多技术要用到,比如模型骨骼动画,碰撞检测,视觉漫游,图像读取等高级技术,大家一起努力学习吧。