游戏引擎中最终要的部分——世界场景,直接影响了引擎的结构。通过学习Quake3和Quake4(原理和Doom3几乎相同)的场景渲染方式,我们也来为自己的引擎添加这一模块。
Quake3和Quake4在这一方面有不同之处。可能是由于卡马克感觉到BSP的局限性,在Quake4中,没有直接采用编译好了的BSP文件,而是直接用了.map文件,把BSP树(在Quake4中也许还是这中场景管理树)的创建放在了游戏引擎运行时,这种方法的具体细节还无法得知,在Quake4的SDK中也只能找到.map文件的信息,所以,我们的重点放在Quake3的场景管理中。
这篇文章的目的,是简单的介绍BSP在Quake中的实现,以及更重要的,我们怎样实现。这篇文章是我原创的,但是用到了几篇英文的文章,你可以在结尾查看,所以,我将不使用“原创”来标识这篇文章,我想用“整理”,毕竟这是我现在正在做的工作。可惜没有“整理”这个选项,所以,如果我引用了英文的文章,我就用“翻译”,用了中文的文章,我就用“转载”(我并不关心其他的问题,我的文章,主要目的是总结,还有分享)。
先来说明一下Quake中的地图格式,.map文件是用ASCII码记录的,你可以通过文本编辑器查看所有的信息,还有就是.bsp文件,是用bsp编译器对.map编译生成,你可以查看相应的命令行参数,这个里面有很多的讲究。有一点需要注意,在Quake3的游戏资源中,我们看不到.map文件,只有.bsp文件;而Quake4的游戏资源中,只有.map文件。所以,这里的重点在与讲解Quake3中的.bsp文件及其渲染方式,而对于.map文件,注意了,用的是Quake4的格式。
先来看一下Quake4中的.map格式,因为这种格式比较直观(简单):
brushside: 记录的是一个平面和对应的纹理(包括纹理坐标信息);
brush: 由多个brushsdie组成,brush是quake中比较“出名”的一个名词,你可以在很多的地图编辑器中看到这个名词;brush不仅可以表示几何信息(可以渲染出来)而且,还是天然的包围体,在游戏引擎中,会大量的应用这一性质。
patch: 这是quake中的曲面,也包含了对应的纹理和纹理坐标,在渲染之前,需要采样获得一个多边形列表。
entity: 实体,也就是地图中的“东东”,由多个brush和patch组成。
就这么简单几个东西,因为.map的唯一用途就是记载世界的几何信息。
下面是引擎中Q4File.h文件。
// file: Q4File.h
#ifndef __INCLUDE_Q4FILE_H__
#define __INCLUDE_Q4FILE_H__
#include <PlutoMain/PEPreCompiled.h>
#include <EngineLib/EngineLib.h>
using namespace EL;
namespace PE
{
class _PE_Export Q4MapBrushSide
{
public:
Q4MapBrushSide();
virtual ~Q4MapBrushSide();
void SetPlane( const Plane &plane );
const Plane & GetPlane( void ) const;
void SetSkinStr( const std::string &skin );
const std::string & GetSkinStr( void ) const;
void SetTexMatrix( const Vector3 mat[2] );
void GetTexMatrix( const Vector3 &mat1, const Vector3 &mat2 ) const;
protected:
std::string m_skin;
Plane m_plane;
Vector3 m_texMat[2];
};
class _PE_Export Q4MapPrimitive
{
public:
enum { TYPE_NONE = -1, TYPE_BRUSH, TYPE_PATCH };
Q4MapPrimitive() { m_type = TYPE_NONE; }
virtual ~Q4MapPrimitive() { }
protected:
int m_type;
};
class _PE_Export Q4MapBrush : public Q4MapPrimitive
{
public:
Q4MapBrush();
virtual ~Q4MapBrush();
int GetNumSides( void );
void AddSide( Q4MapBrushSide *pSide );
Q4MapBrushSide * GetSideAt( const int index );
protected:
std::list<Q4MapBrushSide*> m_sides;
};
typedef struct _Q4Vertex
{
float x, y, z;
float u, v;
} Q4Vertex;
class _PE_Export Q4MapPatch : public Q4MapPrimitive
{
public:
Q4MapPatch();
virtual ~Q4MapPatch();
void SetSkinStr( const std::string &skin );
const std::string & GetSkinStr( void ) const;
void SetControl( const Q4Vertex &v, const int index );
const Q4Vertex & GetControlAt( const int index ) const;
protected:
Q4Vertex m_controls[9];
std::string m_skin;
};
class _PE_Export Q4MapEntity
{
typedef std::list<Q4MapPrimitive*> PrimitiveList;
public:
Q4MapEntity();
virtual ~Q4MapEntity();
size_t GetNumPrimitives( void );
void AddPrimitive( Q4Primitive *pPrim );
Q4MapPrimitive * GetPrimitiveAt( const int index );
void SetName( const std::string &name );
const std::string & GetName( void ) const;
void SetClass( const std::string &name );
const std::string & GetClass( void ) const;
protected:
PrimitiveList m_primitives;
std::string m_strName;
std::string m_strClass;
};
class _PE_Export Q4MapFile
{
typedef std::list<Q4MapEntity*> EntityList;
public:
Q4MapFile();
virtual ~Q4MapFile();
bool ParseFile( const char *fileName );
bool WriteFile( const char *fileName );
void AddEntity( Q4MapEntity *pEntity );
Q4MapEntity * GetEntityAt( const int index );
Q4MapEntity * GetEntityByName( const std::string &name );
void DeleteEntity( Q4MapEntity *pEntity );
void DeleteEntity( const int index );
void DeleteEntity( const std::string &name );
void DeleteEntityClass( const std::string &name );
void DeleteAllEntities( void );
bool HasPrimitiveData( void );
bool ParseEntity( Q4MapEntity *pEntity );
bool ParseBrushSide( Q4MapBrushSide *pBrushSide );
bool ParseBrush( Q4MapBrush *pBrush );
bool ParsePatch( Q4MapPatch *pPatch );