因为我负责游戏的场景管理部分,最近也在狂补场景管理方面的知识,主要是参考DOMM Q3和Renderware的代码,至于GAMEBRYO的场景管理实在不够使,就给了个纯粹的场景树,和半条命2和unreal我看最近我是没时间看了.
室外场景管理基本敲定了,跟FARCRY一样,我在网上看了HL2场景管理的方式,确实很特别,可惜我还没到哪个水准,也没是了,就用GEOMIPMAP吧,剩下要做的就是一些关键加速算法的实现,比如遮挡剔除,比较难搞.自动水面和海浪生成可以用现成的,估计要做简单的CSG编辑功能.
最近关注的主要是室内场景管理,参考了Q3MAP的代码,BSP PORTAL PVS,在Q3中BSP主要是用CSG生成的面所分割的,完成空间分割后再将BRUSH几何体分类到各个小空间中去,MESH 也是.主要是还是PORTAL自动生成的问题, Automatic Portal Generation比较容易懂,我先看了下这篇文章,还找了个中文的PDF,建议看英文,比较好懂, 我把读的过程写下来加深自己的印象.
文章中有个例子,第一次我没仔细看,搞得我后面很郁闷,我先把这个例子提一下,它假设已经有个棵BSP书,只有2级,root和2个叶接点 GeneratePortal(BspNode0,NULL) BspNode0为根接点,请注意了第2个参数SuperPlane 第一次传进去的是NULL,SuperPlane 就是用来保存Portal类的.这里BspNode0不是叶接点,而且Superplane也为NULL,算法中会为BspNode0生成一个superplane,如何生成这个superplane呢?这里有个关键的概念,因为这个superplane中的portal是连接BspNode0空间中的2个凸空间的,(BSP就是一次将空间分为2部分),所以这个portal的大小最大不能超过BspNode0空间的大小,固要用BspNode0的父接点中的分割平面来分割这个superplane.
pBspNode_->m_bPushed = TRUE;
CPortal* pPortal = new CPortal;
CSuperPlane* pNewSuperPlane = new CSuperPlane;
m_ListOfSuperPlanes.Add(pNewSuperPlane);
//***********************************************************
他省略了一些代码:在BspNode0分割面的位置上,生成一个足够大的矩形(也可以是其他形状),这个矩形必须延伸到整个BSP树空间之外,用BspNode0所有父接点上的分割面分割这个大矩形,为的是将这个大矩形限制在BspNode0子空间内,防止这个portal多边型干扰到其他子空间,明白?
pPortal中已经有个巨大的矩形了,并且已经加入到了pNewSuperPlane中,pNewSuperPlane又加到了m_ListOfSuperPlanes中
//***************************************************************
CbspNode* pTempNode,pPrevNode
PPrevNode = pBspNode_;
for (pTempNode = pBspNode_->m_pParent;pTempNode!= NULL;
pTempNode = pTempNode->m_pParent)
{
if (pTempPortal is spanning the dividing plane of pTempNode)
{
//*************************************************************************************
COME ON! 分析一下这下面的代码: 很显然用父接点中的空间分割平面分割BspNode0的superplane的巨大矩形,问题是之后那段if代码是干嘛的呢?判断当前接点是父接点的前面还是后面,如果是前面则分割后portal的后部分就可以扔掉了,也就是说这段代码是保留BspNode0的大矩形上属于本子空间的部分,很饶口....
//**************************************************************************************
ClipPortal(pTempNode->m_DividingPlane,pPortal,*pTemp1,*pTemp2);
if (pPrevNode == pTempNode->m_pLeftNode)
*pPortal = *pTemp1;
else
*pPortal = *pTemp2;
delete pTemp1;
delete pTemp2;
}
pPrevNode = pTempNode;
}
pNewSuperPlane->m_ListOfPortals.Add(pPortal);
//****************************************************************************
计算分割后的portal中多变形的发线,根据法线方向设置其连接的2个空间编号
//*******************************************************************************
第 2步 GeneratePortal(BspNode1,SuperPlane)
BspNode1(带几何信息的叶接点) superplane就是第一步产生的切割portal
//用所有三角形依次切割superplane中所有portal的多边形
for (each triangle T in pBspNode_)
{
iCount = pSuperPlane_->m_ListOfPortals.GetCount();
for (each portal P in pSuperPlane_->m_ListOfPortals)
{
iCount--;
if (iCount<0) break;
//please keep in mind,this process is
//to trace the validation of each portal
int *pInt;
if (P->m_iLeftNode == pBspNode->m_iTag)
pInt = &P->m_iLeftNode;
else if (P->m_iRightNode == pBspNode->m_iTag)
pInt = &P->m_iRightNode;
else continue; // P has no relationship with this pBspNode_,so just ignore P
if (TriangleIntersectPolygon(T,P->m_polygon))
{
pTemp1 = new CPortal;
pTemp2 = new CPortal;
pTemp1.m_bCulled = FALSE;
pTemp2.m_bCulled = TRUE;
ClipPortal(T->m_Plane, P,*pTemp1,*pTemp2);
if (!pTemp1->Reasonable()|| !pTemp2->Reasonable())
{
//Abort this operation
delete pTemp1;
delete pTemp2;
continue;
}
pSuperPlane_->m_ListOfPortals.Add(pTemp1);
pSuperPlane_->m_ListOfPortals.Add(pTemp2);
pSuperPlane_->m_ListOfPortals.Remove(P);
}
}
}
//OK,now we can delete those Culled portals in this leaf!
for (each portal P in pSuperPlane_->m_ListOfPortals)
{
if ((P->m_iLeftNode!=pBspNode_->m_iTag)&&
(P->m_iRightNode!=pBspNode_->m_iTag))
continue;
if (P->m_bCulled)
pSuperPlane_->Remove(P);
}
//我觉得这部分很简单就是判断一个三角是否与portal中的多边形是否相交,然后用三角形所在的平面对多边形进行切割,需要主要的是这里的相交判断需要有一点点容差处理.后面我会讲到
我觉得最难理解的还是第3种情况GeneratePortal(BspNodeX,SuperPlane)
BspNodeX非叶接点, superplane从父接点传下来的.
if (pSuperPlane)
{
iCount = pSuperPlane_->m_ListOfPortals.GetCount();
for (each portal P in pSuperPlane_->m_ListOfPortals)
{
iCount--;
if (iCount<0) break;
//************************************************************
代码需要跟踪这个portal到底连接到了哪2个空间
//************************************************************
int *pInt;//判断本接点在父接点传下来的portal的哪边?
if (P->m_iLeftNode == pBspNode->m_iTag)
pInt = &P->m_iLeftNode;//如果在前边,则只需要修改portal的前接点
else if (P->m_iRightNode == pBspNode->m_iTag)
pInt = &P->m_iRightNode;
else continue;// P has no relationship with this pBspNode_,so just ignore P
//用这个接点的分割平面测试父接点传下来的portal多边形的方位
switch (CalculateSide(pBspNode_->m_DividingPlane,&P->m_Polygon))
{
//很显然,如果这个portal多变形在接点分割面的前边,则这个portal连接到了这个接点的前子空间
case Polygon is on positive side of the dividing plane:
*pInt = pBspNode_->m_pLeftNode->m_iTag;
break;
case Polygon is on negative side of the dividing plane:
*pInt = pBspNode_->m_pRightNode->m_iTag;
break;
case Polygon is spanning the dividing plane:
//这个最麻烦,要切割这个portal
pTemp1 = new CPortal;
pTemp2 = new CPortal;
pTemp1->m_bCulled = P->m_bCulled;
pTemp2->m_bCulled = P->m_bCulled;
//将这2个新portal的左接点设置好,右保持和老portal 一样不变
if (*pInt == pPortal->m_iLeftNode)
{
This means we only need to update the left connection information:
pTemp1->m_iLeftNode = pBspNode_->m_pLeftNode->m_iTag;
pTemp2->m_iLeftNode = pBspNode_->m_pRightNode->m_iTag;
pTemp1->m_iRightNode = pBspNode_->m_pRightNode->m_iTag;
pTemp2->m_iRightNode = pBspNode_->m_pRightNode->m_iTag;
}
else
{//右的情况
This means we only need to update the right connection information:
pTemp1->m_iRightNode = pBspNode_->m_pLeftNode->m_iTag;
pTemp2->m_iRightNode = pBspNode_->m_pRightNode->m_iTag;
pTemp1->m_iLeftNode = pBspNode_->m_pLeftNode->m_iTag;
pTemp2->m_iLeftNode = pBspNode_->m_pLeftNode->m_iTag;
}
ClipPortal(pBspNode_->m_DividingPlane,P,*pTemp1,*pTemp2);
pSuperPlane_->m_ListOfPortals.Add(pTemp1);
pSuperPlane_->m_ListOfPortals.Add(pTemp2);
pSuperPlane_->m_ListOfPortals.Remove(P);
break;
case Polygon is coincide with the dividing plane:
//居然与父分割面共面,你BSP树形成的不合理呀,什么都不干
break;
}
}
GeneratePortal(pBspNode_->m_pLeftNode,pSuperPlane_);
GeneratePortal(pBspNode_->m_pRightNode,pSuperPlane_);
}
Q3的portal生成与这个差不多,而且没有做叶接点几何体切分,过2天把Q3代码分析温习温习,88