QUAKE系列引擎以及基于QUAKE扩展引擎的源代码全面分析(三) --bsp文件格式分析1
一:一些废话
好久没更新了,一方面是年底了,对于做销售的人来说,利用这段时间出出差,拜访拜访经销商以及KA客户,目的是确定明年的销售指标,晕。另一个更重要的原因是竟然把密码忘记了,没办法进入我的博客。前天整理东西时候竟然发现写密码的那张纸了,内心狂喜,哈哈!!
本来想接上次的,写一些关于渲染器方面的东西,但是因为整个渲染器是依赖与BSP进行操作的,而且QUAKE中的碰撞检测也是依赖与BSP树的,因此先写一些关于BSP树方面的基础东西,以利于大家有个比较具体的印象,希望能够写的比较通俗易懂吧。
事实上,前天我写了将近500字的BSP编译器的分析的文章,发现好象如果直接写编译器这个核心东西,可能需要一些关于QUAKE的BSP的相关理论的和基础的东西,特别是QQ上有个朋友和我说,他研究QUAKE2的渲染器代码已经很久了,但是有些函数看了半年还是看不懂,哈哈,其实这和我以前的感觉一样。为什么呢,因为实在网络资料很少,如果你不从Q3MAP这个源代码以及关卡编辑器产生的结果数据和GAME.DLL模块中以SP_开头的函数进行分析的话,BSP永远都是一知半解的,那是因为不知道BSP生成的原理,所以很多东西都看不懂。所以决定了,先从结果推导BSP的编译原理,当然我想这是一个非常大的代码分析,基本上最起码可以写15000字以上的文章了,呵呵,反正现在有的是时间,就慢慢写吧
二:分析生成BSP后的文件结构:
BSP事实上分为三个部分,第一部分是关卡编辑器生成.map的文件格式(Q3RADIANT),第二部分是通过Q3MAP将.map的文件格式编译成.BSP格式,对于BSP文件而言,我们可以将BSP格式的文件数据分成两个大类,即用于渲染用的数据和用于碰撞检测的数据(QUAKE3里面称为CLIPMAP),至于编译过程就是一个流水线式的操作,要进行多次步骤产生结果. 第三部分是操作BSP,关于BSP的操作,以后我慢慢来写,事实上是非常非常重要的和好玩的东东.
在这里我只想简单说一下为什么BSP的文件格式里面包含渲染数据和物理碰撞数据,那是因为QUAKE3的渲染部分和物理碰撞部分是分离的,这样的好处是渲染部分是客户端进行调用的,服务器端不需要用到渲染模块,然而碰撞检测却是服务器端和客户端都要用到的,所以分离以后就具有很大的灵活性. 事实上服务器是上帝,定义一切规则和进行物理动力学的计算,而客户端使用碰撞检测是为了进行同步服务器,进行客户端预测使用的,这是一个网络端编程的概念,以后进行C/S架构分析再说吧
三: BSP文件结构代码
typedef struct {
int fileofs, filelen;
} lump_t;
typedef struct {
int ident;
int version;
lump_t lumps[HEADER_LUMPS];
} dheader_t;//
typedef struct {
char shader[MAX_QPATH];
int surfaceFlags;//绝对经典的东西,还是和q3map一起说比较有趣,
int contentFlags;//绝对经典的东西,还是和q3map一起说比较有趣
} dshader_t; // lump1
// planes x^1 is allways the opposite of plane x
typedef struct {
float normal[3];
float dist;
} dplane_t; // lump2
typedef struct {
int planeNum;
int children[2]; // negative numbers are -(leafs+1), not nodes
int mins[3]; // for frustom culling
int maxs[3];
} dnode_t; // lump3
typedef struct {
int cluster; // -1 = opaque cluster (do I still store these?)
int area;
int mins[3]; // for frustum culling
int maxs[3];
int firstLeafSurface;
int numLeafSurfaces;
//用于碰撞检测,不用于渲染模块
int firstLeafBrush;
int numLeafBrushes;
} dleaf_t; // lump4
int leafsurfaces; // lump5
int leafbrushes; //lump6
typedef struct {
float mins[3], maxs[3];
int firstSurface, numSurfaces;
//下面的变量用于碰撞检测用
int firstBrush, numBrushes;
} dmodel_t;// lump7
typedef struct {
int firstSide;
int numSides;
int shaderNum; // the shader that determines the contents flags
} dbrush_t;// lump8
typedef struct {
int planeNum; // positive plane side faces out of the leaf
int shaderNum;
} dbrushside_t;// lump9
typedef struct {
vec3_t xyz;
float st[2];
float lightmap[2];
vec3_t normal;
byte color[4];
} drawVert_t;// lump10
int drawIndexes; // lump11
typedef struct {
char shader[MAX_QPATH];
int brushNum;
int visibleSide; // the brush side that ray tests need to clip against (-1 == none)
} dfog_t;// lump12
//对表面类型进行总结,具体见下面
typedef enum {
MST_BAD,
MST_PLANAR,//很重要的,说明该表面是一个世界的静态表面,例如墙面,地板等,可以通
//过brushside计算出来
MST_PATCH,//二次贝塞尔表面,要进行相应三角型化,要求速度的话,可以使用前向差分
//算法,二次贝塞尔使用9个控制点插值计算
MST_TRIANGLE_SOUP,//用于BMODEL的表面,可以进行三角形扇或带化或顶点索引三角形
//如果要了解具体算法,可以参考一些计算几何的算法,如果有足够
//深厚的功力,建议参考nvstriper相关代码,还有关于计算几何或
//拓拔方面的知识,网络上有一个很好的库ttl,里面有篇实现的论
//文,关于gmap概念以及使用半边结构进行各种拓拔查找以及修改,
//绝对经典的东西
MST_FLARE //实际上就是公告版,因该都会使用吧
} mapSurfaceType_t;
typedef struct {
int shaderNum;//索引指向shaderlump
int fogNum;//索引指向foglump
int surfaceType;// mapSurfaceType_t,具体说明见上
int firstVert;//索引指向drawVert_tlump
int numVerts;
int firstIndex;//索引指向顶点索引lump
int numIndexes;
//下面一些变量和静态lightmap相关,事实上现在的图形硬件足够快,静态光照图相关算
//法已有没落的趋势,事实上现在比较先进的引擎都是全动态光照,通过BSP进行场景管理
//可以非常高效的实现,使渲染效果大幅度提高。这部分是我最感兴趣的部分,以后有机会
//可以探讨一下,但是必须要对BSP相关操作有非常的了解才可以深入
int lightmapNum;
int lightmapX, lightmapY;
int lightmapWidth, lightmapHeight;
vec3_t lightmapOrigin;
vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds
//下面两个变量用于贝塞尔曲面
int patchWidth;
int patchHeight;
} dsurface_t;// lump13
byte lightBytes; // lump14
byte lightgridData;// lump15
byte visBytes;// lump16
这里我列出 bsp 文件格式的各个 lump ,除了 entity 这个比较特别的 lump ,这个留到 q3map 再说,是比较特别一个东东。还有就是具体表面, bmodel 以及著名的 brush/side 等之间的关系,以及 shader 各个元素还是下次再写把,发现写东西还真是很费脑子的拉,今天就先到这里了 .