QUAKE系列引擎以及基于QUAKE扩展引擎的源代码分析

时间:2022-01-19 18:39:09

前言:

一:研究quake系列引擎断断续续也已经两年有余了,一直想写点什么,但是真的到了那一步,又发现其实很难下笔,原因有三:1 文笔不流畅,特别是技术性技巧的缺乏,概念难以精确定义,无法想内心的真实表达转换为文字,郁闷啊!!2 QUAKE系列引擎的庞大结构以及各个模块的协作关系,真的想写的时候不知道如何组织,导致思路极其紊乱。3 对于QUAKE系列的深奥部分,列如bsp的编译程序部分的源码还未完全理解,这是整个QUAKE引擎的核心部分,这是QUAKE系列引擎最核心的部分,可以说,整个QUAKE系列引擎就是围绕这张图推进的。虽然BSP的生成确实是一个非常非常难的难点,好在使用起BSP来相对比较简单,即使不知道如何生成,但至少可以灵活使用,伟大的慷慨的卡马克。

   二:QUAKE系列的引擎源码分析涉及到的引擎包括如下几个:

           1  QUAKE1 及其基于   QUAKE1框架的扩展引擎DARKPLACES

           2  QUAKE2 及其基于 QUAKE2 框架的扩展引擎 QFUSION

           3  QUAKE3及其基于 QUAKE3 框架的扩展引擎  XREAL/EVQ3 

   三:为什么要使用上述的扩展引擎?

1         QUAKE系列引擎毕竟时间久远,现代硬件技术的发展以及渲染方面的新概念,新技术的出现,一些扩展引擎更加贴近当今世界技术发展的潮流。例如这三个引擎都增加了GLSL进行相应的扩展。

2         这些扩展引擎都是非常著名的开源项目,资料比较多,而且都有相应使用这些扩展引擎作成的游戏,可以观看到游戏实时效果。

例如基于DARKPLACES引擎的游戏—NEXUIZ,渲染效果极其惊人啊!!!

    基于 QFUSION 引擎的游戏-----WARSOW

基于 XREAL/EVQ3引擎的游戏-----REVOLUTION

3         对上述三个扩展引擎的感受

A:DARKPLACES使用QUAKE1的框架结构,重写了全部的渲染模块,网络协议以及传输模块,支持DOOM3类型的光影特效,根据我的感觉,应该是渲染效果最好的开源的项目,使用到了相当多的特效(DOOM3光影特效=Stencil Shadow Volume, Per Pixel Lighting, NormalizationCubeMap, 2D+1D Attenuation Texturing, and Light Projection Filtering).该引擎可以使用著名的QUAKEC进行服务器端游戏逻辑的开发(相当于QUAKE3中的GAME.DLL模块),同时相对于QUAKE1的QUAKEC内置函数,该引擎扩展了相当多的服务器端QUAKEC函数.更漂亮的是他同时扩展了客户端逻辑部分的函数,通过这些函数可以编写客户端游戏逻辑(相当于QUAKE3中的CGAME.DLL模块,但不完全等同).事实上本人是非常喜欢QUAKEC的编码方式,简练而强大.

B:QFUSION引擎最大的特点是他的逻辑结构非常清晰,他所扩展的骨骼动画系统是其最大的特点.本人的感觉是极其漂亮(当然每个人的观点不同,这只是本人的观点罢了).他使用了skm骨骼动画文件格式.正是通过该引擎的骨骼动画系统,让本人完全掌握整个骨骼动画的精髓,真是收益非浅啊!!!而且整个游戏逻辑端非常清晰,在代码分析时候主要以该引擎作为原代码基础.

C:xreal/evq3整体框架与quake3变化不大,但是最重要的一点是,本人最喜欢的渲染器结构是quake3的render.dll,简直是太完美了,quake3的渲染器可以使用双处理器,为了支持双处理器的运作,quake3自己实现了一条渲染命令流水线,及其完美,收益非浅啊!!!!又要说一句非常重要的话了,伟大的慷慨的卡马克.而引xreal/evq3擎完美的保留了quake3的渲染器框架结构同时扩展了较多的功能,光影特效足够强大.增加了md5骨骼模型的渲染.在以后渲染器代码分析时候,主要以该扩展引擎为基础结合darkplaces引擎的相关技术.

    总体而言, quake系列引擎的整体架构非常漂亮,是学习的好材料,并且从quake1到quake3,整体逻辑端代码变化不大, 真正比较有突破性的是渲染引擎.本人在学习quake引擎过程中常常敬佩卡马克的想象力,api函数是如此之简洁,整体框架是如此之完美,面向对象的c写得如此之漂亮,通过quake引擎,可以学到游戏设计的各个概念以及各个模块,外部工具是如何完美的结合起来,这一点是非常非常重要的.

 

本来想直接进入quake源码分析,但发现如果没有好的写作框架,就凭QUAKE引擎这么大的代码群,真的很难写的,所以决定先搭一个分析框架,定义好各个章节,争取在本周内全部完成该工作,然后从下个礼拜开始就往分析框架里面填写内容了!哈哈!!
     声明:1。本人从未进入程序员行列,只是喜好才写这些文章的,所以在文章写作过程中,有任何技术性的错误,以及没有甬道正确的术语,请见谅(因为很多quake中的术语都是我自己定义的)
                2。在整体的分析过程中,并不一定按照目录所定义好的顺序来写的,想到什么就写什么,这就是博客的精神把!!整个目录框架是分析的思路,可能会改变。
                3。在目录各个章节都是主题的定义,我会在空闲时间慢慢的填进去的,希望能够最终坚持下来形成一整套关于quake系列比较系统的文挡。
                4。本人的目的是在中国形成一个比较活跃的以quake为基础的社群,希望更多的人了解quake的精神,我想在中国研究quake引擎的人应该很多,高手更是不少,但是在中国的网络上却看不到系统的quake源码分析,不知道为什么??所以由本人,一个不在程序员序列的图形学爱好者来稍微引导一下。由于本人不属于程序员,因此也更本没有所谓违反某个软件公司的知识产权,具有更大的灵活性,希望其他地方的各个quake高手进行完善与修整本人所写的东东。
             5。本人渴望与从事游戏行业的程序员进行交流。由于本人的生活圈子与程序员根本不搭界,所以没有机会和从事游戏设计的人员进行交流,很郁闷啊。很想了解一下现在的图形学在中国处于什么状态,各个游戏公司底层的引擎是自己开发的还是使用开源的或则是购买世界著名的游戏引擎。希望能有机会与各位交流,本人的qq号码是47178234,本人生活在上海,如果有上海的高手,我们可以多多交流,时而可以face to face的交流拉,以增加对程序员生活的真正了解!! 

目录:

 第一章:QUAKE引擎的整体框架结构:

         1.引擎和API的精确定义

         2.整个quake引擎是基于C/S模式

         3.各个模块间的关系图

         4.客户端如何与服务器端相连接(网络消息的传递与响应以及客户端数据库的产生)

         5.当客户端连接到服务器后如何进入游戏状态的流程(即玩家的产生)

         6.当客户端死亡后重生的流程

         7.简要说明进入游戏状态后一帧运行的流程,包括各个模块函数调用的示意图                  

 第二章:渲染器(refresh模块)

         1. 两个重要的由外部操作的结构(refEntity_t和refdef_t)以及这些结构各个值域的详细解释

         2.渲染器模块导出函数(API)的分类以及作用(以quake3-1.32b原代码为准)

            A:渲染数据资源管理函数集(12个函数,资源包括BSP世界数据,模型数据,shader数据,skin数据,vis数据以及字体)

            B:设置渲染命令流水线的函数集(4个函数)

            C:场景管理以及渲染的函数集(7个函数)

            D:其他函数集(6个函数)总计29个导出函数

         3.Quake3 渲染器的整体结构:

            A:QUAKE3渲染器是以OPENGL为基础并支持双处理器并行运算的

            B:QUAKE3渲染器在渲染过程中可以分为前端部分和后端部分(图解),他们是如何协调起来的。

            C: QUAKE3是如何支持双处理器并行进行渲染

         4.对quake3模型系统的扩展(使用MD5模型格式以及skm模型格式)

            A: 为什么不用MD3模型

            B:MD5&SKM骨骼模型的格式分析

            C:骨骼动画的原理以及应用

(1)       骨骼动画的分类(boneoffset类型和vertexoffset类型的详解以及各自的优缺点)

(2)       详细分析骨骼动画数学原理

(3)       在定义骨骼动画的时候需要详细考虑的一些问题以及目的,不同的目的会有不同的编码方式

(4)       重点分析SKM骨骼动画在warsow游戏中的运用以及编码方式

(5)       骨骼动画的CPU实现和GPU实现的优缺点分析以及如何平衡各自的优缺点

(6)       附我的计划:市面上公开格式的骨骼动画事实上在渲染原理上基本差别不是很大,因此在学习骨骼动画的过程中,感受很多啊,现在本人正在进行系统设计,根据骨骼动画的原理,参考相关资料,提炼出一条骨骼动画统一渲染流水线。完成后公开源代码。系统设计的要求是

(A)       可以直接并入quake3引擎的多核渲染流水线

(B)       使用CPU实现的,以SIMD为基础数学运算(因为CPU实现进行转化后可以直接获得顶点数据再进行阴影系统的绘制,而GPU数据的取回比较麻烦,再说本人也没有支持D3D10版本的GPU,无法使用新增加的stream output statge以及几何shader)

(C)       能够在运行过程中人工控制各个骨头的运动

(D)       使用统一的骨骼动画渲染流水线,使模型与数据相分离,并且将阴影系统并入该渲染流水线。

 

         5.BSP文件格式以及QUAKE3 SHADER文件格式

 

一:一些废话  
   
好久没更新了,一方面是年底了,对于做销售的人来说,利用这段时间出出差,拜访拜访经销商以及KA客户,目的是确定明年的销售指标,晕。另一个更重要的原因是竟然把密码忘记了,没办法进入我的博客。前天整理东西时候竟然发现写密码的那张纸了,内心狂喜,哈哈!!
    
本来想接上次的,写一些关于渲染器方面的东西,但是因为整个渲染器是依赖与BSP进行操作的,而且QUAKE中的碰撞检测也是依赖与BSP树的,因此先写一些关于BSP树方面的基础东西,以利于大家有个比较具体的印象,希望能够写的比较通俗易懂吧。
    
事实上,前天我写了将近500字的BSP编译器的分析的文章,发现好象如果直接写编译器这个核心东西,可能需要一些关于QUAKEBSP的相关理论的和基础的东西,特别是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各个元素还是下次再写把,发现写东西还真是很费脑子的拉,今天就先到这里了.