【1. 概览】 Ogre支持Quake3的bsp格式。相关的代码在“Plugin_BSPSceneManager”工程中。主要的类有以下几个: Class BspSceneNode: BspSceneNode是SceneNode的派生类,是专门提供给BSPSceneManager使用的。主要是提供针对于BSP tree的可见性判断。这个类并不是BSP tree的node,BSP tree中的node使用BspNode。BspSceneNode会放入BSP tree的leaf节点中。由于SceneNode使用包裹盒的方法,不可分割,所以一个BspSceneNode可能放入多个Bsp tree的leaf节点中。 从类的定义看,BspSceneNode并没有额外的保存什么数据。重写的几个虚函数主要是用来通知BspSceneMapager, BspSceneNode::_update()会调用BspSceneManager::_notifyObjectMoved(),detach objcect会调用BspSceneManager::_notifyObjectDetached()。 Class BspSceneManager: 粗略的看BspSceneManager与OctreeSceneManager类似。首先保存了一个BspLevel的指针,然后使用一个 walkTree()函数,用来遍历tree结构。由于Quake使用BSP leaf tree,所以多了一个processVisibleLeaf()函数。另外一个明显的不同是有一个renderStaticGeometry()函数, “Renders the static level geometry tagged in walkTree”。此函数渲染“mMatFaceGroupMap”中的所有数据。BSP一个好处是不透明面可以front-back的顺序来渲染,而透明面back-front来渲染,OGRE是如何将此特性保存到MaterialFaceGroupMap的呢? Class BspLevel: 这是一个核心的class。他存储了BSP的所有数据,关键的数据有: <!--[if !supportLists]-->1. <!--[endif]-->“BspNode* mRootNode;”――BSP tree的根节点 <!--[if !supportLists]-->2. <!--[endif]-->“VertexData* mVertexData;”――整个level的所有顶点; <!--[if !supportLists]-->3. <!--[endif]-->“StaticFaceGroup* mFaceGroups;”――faces <!--[if !supportLists]-->4. <!--[endif]-->“BspNode::Brush *mBrushes;”――用来做碰撞检测的Brush,是QuakeBSP除了渲染以外的另外一个精华!Brush的名字有点怪,其实就是一个convex volume,可以减少CD的运算量。 <!--[if !supportLists]-->5. <!--[endif]-->“VisData mVisData;”――PVS数据,是又一个Quake中的精华!当初Carmark在设计Quake的时候还使用软件渲染,hiden surface removal和减少over draw是最另他头痛的问题。BSP的思想应该是他从网上看来的,不过PVS应该是他所创。PVS大大减少了over draw。(见《Michael Abrash's Graphics Programming Black Book》) <!--[if !supportLists]-->6. <!--[endif]-->“PatchMap mPatches;”――Quake3支持贝赛尔曲面 关键的函数: <!--[if !supportLists]-->1. <!--[endif]-->bool isLeafVisible(const BspNode* from, const BspNode* to) const;使用PVS来检测可见性。 <!--[if !supportLists]-->2. <!--[endif]-->void _notifyObjectMoved(const MovableObject* mov, const Vector3& pos); void _notifyObjectDetached(const MovableObject* mov); à void tagNodesWithMovable(BspNode* node, const MovableObject* mov, const Vector3& pos); 把MovableObject(注意:不是SceneNode)挂到BSP的leaves上。 Class BspNode: 这是Bsp中的另外一个重要的类了。Node和Leaf都使用这个类。 重要数据: <!--[if !supportLists]-->1. <!--[endif]-->Plane mSplitPlane; BspNode* mFront; BspNode* mBack; 分割平面和前后节点; <!--[if !supportLists]-->2. <!--[endif]-->int mVisCluster; 每个cluster占pvs的一个bit,这是为了减少pvs占用的内存。 <!--[if !supportLists]-->3. <!--[endif]-->int mNumFaceGroups; int mFaceGroupStart; 用来找到BspLevel中哪些face group是属于我这个leaf的,这样做也是为了优化存储; <!--[if !supportLists]-->4. <!--[endif]-->IntersectingObjectSet mMovables; 和本节点相交的movable对象 <!--[if !supportLists]-->5. <!--[endif]-->NodeBrushList mSolidBrushes; 本节点包含的brush。 另外剩下的OgreQuake3Level.h、OgreQuake3Shader.h、OgreQuake3ShaderManager.h、 OgreQuake3Type.h主要是为了把Quake3格式的bsp,shader信息读入,并转换成Ogre本地的bsp定义以及 Material。现在quake3的源码已经公开(非常感谢id software以及carmark),可以结合quake3的源码来看。 【2. Quake3 bsp的加载】 以Demo_BSP为例,首先需要修改“quake3settings.cfg”,两个参数,“Pak0Location”是pk包的路径(是一个zip文件),“Map”为想要加载的地图。 OGRE使用BspLevel来存储Bsp场景信息,这个类是与文件格式无关的。所以需要另外一个类来把Quake3的bsp文件读入。 Quake3Level的读盘的主要由两个函数完成: 1、“void Quake3Level::loadHeaderFromStream()”。调用的流程是: BspApplication::loadResources() à ResourceGroupManager::loadResourceGroup()【A】 à BspSceneManager::estimateWorldGeometry() à BspLevel::calculateLoadingStages() àQuake3Level::loadHeaderFromStream() Quake3 BSP的文件格式很简单明了,前面是一个文件头,后面是几个数据块。文件头主要存储了几个lump,包含数据块的起始位置和大小,通过lump,可以直接seek到对于的数据块。 此函数在加载了文件头之后,调用了Quake3Level:: initialiseCounts ()函数,主要是计算了每个lump包含的对象的个数,例如face,vertex,bursh等等。 2、第二个函数“void Quake3Level::loadFromStream()”。调用的过程是: ResourceGroupManager::loadResourceGroup()【A】 àBspSceneManager::setWorldGeometry() àBspResourceManager::load() àResourceManager::load() àResource::load() àBspLevel::loadImpl() àQuake3Level::loadFromStream() 在这地方OGRE延续了他罗嗦的风格,BspSceneManager要通过BspResourceManager来加载场景,BspLevel实现为一种 Resource,BspResourceManager通过标准的ResourceManager――》Resource来找到BspLevel,然后调用其加载函数。 此函数首先构造了一个“MemoryDataStream”对象,在 MemoryDataStream的构造函数中把文件数据全部读入其缓冲中(Quake也是这样干的),然后调用“void Quake3Level::initialisePointers(void)”函数,得到所有lump索引的对象的指针。 Quake3Level 把文件读入并明确了所有数据的指针之后,在void BspLevel::loadImpl()中调用“BspLevel::loadQuake3Level()”函数讲Quake3level中的数据拷贝到自己的数据对象中。主要执行了以下几个操作: <!--[if !supportLists]-->1. <!--[endif]-->“BspLevel::loadEntities()”,这个lump存的是一个字符串,用来描述一些游戏信息,Ogre的这个函数只读取了Player start信息(位置和角度)。 <!--[if !supportLists]-->2. <!--[endif]-->“Quake3Level:: extractLightmaps()”。Quake3 BSP的每个light map都是128×128大,此函数将Light map lump中的数据逐个调用“TextureManager::loadImage()”创建成Texture对象(class D3D9Texture for D3D9 RenderSystem)。 <!--[if !supportLists]-->3. <!--[endif]-->创建VertexData: [Create vertex declaration] OGRE BspLevel使用的顶点格式为:Position3,Normal3,Diffuse,uv0,uv1; [Build initial patches] 调用BspLevel::initQuake3Patches()。此函数遍历Quake3Level中的所有faces,对于每个face type为“BSP_FACETYPE_PATCH”的face,创建一个PatchSurface对象,并调用PatchSurface:: defineSurface()函数进行,然后保存到BspLevel:: mPatches数组中。此函数还计算了BspLevel:: mPatchVertexCount和BspLevel:: mPatchIndexCount; [硬件顶点缓冲] 调用HardwareBufferManager创建HardwareVertexBuffer对象;使用“BspLevel:: quakeVertexToBspVertex()”函数把q3 bsp顶点格式转换为Ogre BSPLevel的顶点格式。然后绑定到BspLevel::mVertexData; <!--[if !supportLists]-->4. <!--[endif]-->创建Faces:创建BspLevel:: mLeafFaceGroups数组;创建BspLevel:: mFaceGroups数组,此数组的数据在后面一步中填充;创建indexbuffer,并将Quake3Level::mElements拷贝进来; <!--[if !supportLists]-->5. <!--[endif]-->Create materials for shaders:对于Quake3Level::mFaces每一个bsp_face_t,找到它索引的Quake3Shader,并创建 Material,如果没有找到Quake3Shader的话则使用shader name去查找贴图文件; 在此循环中还进行了“Copy face data”的操作,填充BspLevel:: mFaceGroups中的数据; <!--[if !supportLists]-->6. <!--[endif]-->Nodes:创建BspLevel:: mRootNode数组,并将数据拷贝进来。 <!--[if !supportLists]-->7. <!--[endif]-->Brushes:将数据拷贝到BspLevel:: mBrushes中; <!--[if !supportLists]-->8. <!--[endif]-->Leaves:设置每个leaf节点的数据,主要包括包裹盒,mFaceGroupStart,mNumFaceGroups,mVisCluster,mSolidBrushes。参见BspNode类; <!--[if !supportLists]-->9. <!--[endif]-->Vis data:将数据拷贝到BspLevel:: mVisData中。 Quake3 BSP的load流程基本上就是这些了。 【3. Bsp tree scene的渲染】 仍然以Demo_BSP为例来分析。渲染的核心操作流程从SceneManager::_renderScene()开始(参见“Ogre学习笔记(3):Mesh的渲染流程”),接下来还有SceneManager:: _updateSceneGraph(),SceneManager::prepareRenderQueue(),BspSceneManager没有重写这几个函数。不过,有一点需要注意,SceneManager:: _updateSceneGraph()调用了BspSceneNode::_update()与OctreeSceneManager类似的,如果有必要的话,会调用BspSceneManager:: _notifyObjectMoved()--》BspLevel:: _notifyObjectMoved(),将SceneNode中的MovableObject attach到正确的leaf node中。 接下来是BspSceneManager:: findVisibleObjects(),这是一个从SceneManager重写的函数。顺理成章的,这个函数调用了 BspSceneManager::walkTree()。在这个函数中,首先找到了camera所在的leaf node(通过BspLevel::findleaf()函数);然后遍历BspLevel中的每个leaf node,先使用PVS检测可见性(通过BspLevel::isLeafVisible()函数),如果可见再使用camera――bounding box检测,如果还是可见的,则对此leaf node调用BspSceneManager::processVisibleLeaf()函数。此函数主要执行两个操作,一个是把World Geometry加入到渲染数据表中(mFaceGroupSet和mMatFaceGroupMap),另外一个是把与此leaf node相交的MoveableObject加到渲染队列中(mMovablesForRendering)。一件比较有疑问的事情是,walkTree 是循环遍历所有leaf node,而没有按照BSP tree递归遍历,这大大削弱了BSP的提前排序的优势。 然后是BspSceneManager重写了另外一个重要的函数_renderVisibleObjects()。此函数包含两个操作,一个是 renderStaticGeometry(),另外一个是调用父类的SceneManager::_renderVisibleObjects()。前者循环遍历mMatFaceGroupMap,然后调用RenderSystem::_rendr();后者已经在“Ogre学习笔记(3):Mesh的渲染流程”中详细分析过了。 |