首页 > Linux操作系统 > Linux操作系统 > 优化你的手机游戏:使用脏矩形技术
考虑上图,物体B遮挡了物体A, 也就是说渲染顺序是先画A再画B,这个顺序由各自定义,(我自己就喜欢用一棵渲染树来排序,当然如果你用连表或者其他数据结构来实现也没有问题。)如果物体A的整个区域都需要更新,那么对于B物体,需要更新的部分也就只有A与B的交集部分(图中的蓝色区域),在画B的时候,我们设置目标裁减区域(也就是屏幕缓冲的裁减区域)为这个交集部分,则B在渲染的时候,相当于整个缓冲区大小就只有蓝色区域那么大,那么裁减函数将会把B的数据区裁减到相应的位置(你实现的图形函数中不会没有做裁减的工作吧???如果没有实现,你就不用看了,直接return算了,不然下面的东西你肯定不明白我说什么)。怎么样,B物体相当于只画了蓝色区域这一部分的东西,比整个区域来说节约了不少时间吧?
不知道上面说的你明白了没有,如果没有明白请多看几遍,直到弄明白之后再往下看,不然千万不要往下看。
上面的例子大家肯定会问一个问题,我如何控制B只画蓝色区域的部分呢?这个问题我暂时不说,等到把所有的遮挡情况说完了再说。继续看另外的遮挡情况
上面6个物体A,B,C,D,E,X。X是我们的游戏背景颜色,假设画的顺序是EADCB,如果E需要重新画,那很显然,A,B,C,D不需要做什么
如果A,D都需要重新画,那显然A,D只需要各画一次。而B需要更新的,不是需要更新BD相交的区域,而是AB相交的大区域,也就是说小区域该忽略掉,如果B需要重新画,A,D,C需要重新画吗?也许有人会说,B画的次序是在最后的,所以前面的就不需要画了,对么?答案是错的,需要重新画,因为背景缓冲区我们一般情况下不去清除它,所以谈不上画的顺序了。也就是说,A与B相交的部分,A在下次画的时候也需要更新,D也同样(想通了吗?再举一个例子,如果B含有大量的透明色,如果B需要更新的话,那么B的区域首先要涂上X作为背景,不然B非透明色如果变成了透明色的话,那B在重新画的时候,由于透明色不需要画,那么B上一次留下来的颜色就残留在X上面,看起来当然不对啦,同理对于A,D也一样处理)。
上面的理论部分不知道听明白了没有,如果不明白的话自己花一点点时间去想象看。假如明白了的话,下面继续更加深入的问题。
从上面的理论解说部分可以看出,脏矩形的选取和优化是关键。怎样得到最优化的脏矩形表,就成为了这个技术优化的核心部分。
为了简单起见,这里使用的是一个链表来管理所有的渲染物体。
为了实现我们所设计的东西,我设计了一个非常简单的类:
class CRenderObject
{
public:
virtual ~CRenderObject(){}
virtual void OnRender( GraphicsDevice*pDevice ) = 0; //所有物体都在这里渲染
virtual void OnUpdate( float TimeStamp ) = 0;//物体更新,比如动画帧更新拉之类的,在这里面可以设置DirtyRect标志之类的
virtual bool IsDirty( ) = 0;//是否有脏矩形
virtual bool GetBoundsRect(RECT*pRect) =0;//得到该物体的范围
virtual int GetDirtyRects ( RECT*pRectBuffer ) = 0;//该物体的脏矩形个数,填充到pRectBuffer里面,返回填充了多少个
...其他函数
};
我们还需要一个简单的能管理脏矩形和渲染物体的类
class CRenderObjectManager
{
pulibc:
void RemoveRenderObject( CRenderObject*pObject );//删除一个渲染物体
void AddRenderObject( CRenderObject*pObject );//添加一个渲染物体
void Render( GraphicsDevice*pDevice );//渲染所有的物体
void Update( );//更新所有物体
.....其他函数
protected:
std::list< CRenderObject* > m_RenderObjects;
int m_nCurrentDirtyRectCount;//当前脏矩形数量
struct DirtyRect
{
RECT Range; //脏矩形范围
int AreaSize; //脏矩形大小,用来排序
};
BOOL m_bHoleDirty;//是否全部脏了
DirtyRect m_DirtyRects[128];//屏幕上最多的脏矩形数量,如果大于这个数量则认为屏幕所有范围都脏了
};
void CRenderObjectManager::Update()
{
m_bHoleDirty = false;
m_nCurrentDirtyRectCount = 0;
static RECT DirtyRectBuffer[128];
float TimeStamp = GetElapsedTime();
for(std::list< CRenderObject* >::iterator it = m_RenderObjects.begin();
it != m_RenderObjects.end(); it++)
{
CRenderObject*pObject = *it;
pObject->OnUpdate( TimeStamp );
if(m_bHoleDirty == false && pObject->IsDirty() )
{
int Count = pObject->GetDirtyRects(DirtyRectBuffer);
for( i =0; i
对于该物体的每一个脏矩形DirtyRectBuffer[i]
如果DirtyRectBuffer[i] 没有在任何一个已有的脏矩形范围内
那么把这个脏矩形根据从大到小的顺序添加到脏矩形范围内,否则忽略这个脏矩形
如果脏矩形数量已经大于设定的最大脏矩形范围,设置所有所有屏幕都脏了的标志,
}
}
}
如果屏幕所有都脏了,填充背景颜色
否则为每一个脏矩形填充背景颜色
}
void CRenderObjectManager::Render( GraphicsDevice* pGraphics)
{
for(std::list< CRenderObject* >::iterator it = m_RenderObjects.begin();
it != m_RenderObjects.end(); it++)
{
CRenderObject*pObject = *it;
if(如果屏幕都脏了的标志已经设定)
{
RECT rcBoundsRect = { 0, 0, 0, 0 };
if( pObject->GetBoundsRect( rcBoundsRect ) )
{
//设置屏幕裁减区域
pGraphics->SetClipper( &rcBoundsRect );
}
pObject->OnRender( pGraphics );
}
else
{
RECT rcBoundsRect = { 0, 0, 0, 0 };
if( pObject->GetBoundsRect( rcBoundsRect ) )
{
//如果该物体的范围与脏矩形缓冲区的任何一个脏矩形有交集的话
for( int i=0; i
RECT rcIntersect;
if( ::IntersectRect( &rcIntersect, &m_DirtyRects[i].Range, &rcBoundsRect ) )
{
//只画交集的部分
pGraphics-> SetClipper ( &m_DirtyRects[i].Range );
pObject->OnRender( pGraphics );
}
}
}
}
}
}
好了,核心代码的伪代码就在这里,不知道大家看明白没有,当然我在这里上面实现的这种方法有一个缺陷,最坏情况下一个也许会导致重新画很多次,如图的情况:
假设A是渲染物体,B,C,D,E是由大到小的脏矩形范围,那么很显然,重叠的部分就被反复画。。。这是在分割脏矩形导致的问题,这样画下来,如果A物体是采用了叠加混合到背景的算法的话,问题就出来了,重画的部分会变得非常亮。所以脏矩形的分割就显得非常重要,也就是说把这些脏矩形还要分割为互相独立的互不相交的矩形,至于分割算法嘛,嘿嘿,各位还是动一下脑筋思考思考吧:)这个也是最值得优化的地方了,哈哈。实在想不出的话,最简单的方法就是把彼此相交的脏矩形都做一个合并,得到更大的脏矩形,虽然没有相交的区域了,但是也许这个脏矩形会变得比较大了哦:)
最后,大家一定关心的是我会不会提供源代码,很抱歉的说,不能。我在我的引擎中实现的不是以简单的链表去做的,用的是一棵比较复杂的渲染树,牵扯到的东西就比较多了,所以不方便提供代码,不过可以给一个演示吧:)再说大家如果真的明白了我所说的,那就可以自己动手写一下嘛,不要怕失败/。
好啦,关于脏矩形的技术就介绍到这里啦,用好这个技术你会发现你的游戏会在配置极低的机器上也能运行如飞的:)这种技术如果能用在现在市面上的那么多的游戏中的话,就不必为一个小游戏就强占了您100%的CPU资源而烦恼拉:)
如果您有更好的方法或者指出其中的不完善的地方还请您不吝赐教,大家多多交流:)
关于测试的Demo
该Demo渲染部分由Kylinx花了近半年的时间,全部采用MMX写成,已经成功实现d3d中对2d纹理的操作,速度非常快
关于Settings.ini
EnableDirtyRect = 1 //是否允许脏矩形技术,0=关闭,1=开启
LockFPS = 1 //是否锁定FPS,0=关闭,1=开启
哈,这个Demo在不锁定FPS,脏技术开启的的情况下,我的Duron1.8G CPU,FPS达到 31500左右!(没错,是三万一千五百)这个数字吓人吧?如果脏技术未打开,只能在150左右,相差200倍阿!!!
如果LockFPS开启,在我机器上(512M DDR)跑30个DEMO,CPU占用还是为0,哈哈!
关于该引擎:演示用的这个引擎(代号ShinyFairy:闪灵,基于前期开发的GFX3.0系列,该系列已经成功运行在某商业游戏公司的休闲游戏系列),采用Kylinx历时2年多开发的具有自主知识产权的基于2D游戏的超级引擎,强大的数据加密,打包,图形接口同时提供d3d8版本和mmx版本,该演示使用的是mmx版本,适用于各种休闲游戏平台,或者大型2D RPG/MMORPG均可适用。
例子下载:http://dev.gameres.com/Program/Visual/2D/DirtyRectDemo.rar
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/9403012/viewspace-5562/,如需转载,请注明出处,否则将追究法律责任。