`

[转载]脏矩形技术

阅读更多
地址:
http://hi.baidu.com/zhuxixi527/blog/item/eba16af89f19b50ad8f9fd25.html
终极优化你的游戏 —— 使用脏矩形技术
作者:Kylinx


说明:本文由kylinx本人亲自撰写,欢迎各位游戏制作同仁转载和指点,但是任何人不得在本人许可之外以任何理由篡改,模糊本文。谢谢。联系方式:game-diy@163.com

很久以来由于工作上的繁忙没有写新东西了~hoho~
本文基于2D表现的游戏,在当今3D大行其道的时代,说2D是否显得格格不入?这个问题我不作讨论,因为本人从事的一直都是2D游戏的开发,所以如果你认为讨论2D技术是一个过时的东西就此打住。
废话不多说,下面进入正题。
优化一直是我在程序中追求的东西之一,想想让自己的游戏在一个古董机器能流畅的运行或者说在当今的机器上,CPU占用率和内存占用率都很低的情况。(毕竟我非常讨厌一个游戏独占了我所有的CPU资源)。
如果从图形接口上作优化,常用的就是使用3D加速和CPU的特殊指令(虽然说DirectDraw能够使用2D硬件加速,但大部分机器支持的仅仅是简单的加速,比如带ColorKey的支持,连一些稍微高级一点的东西,比如Alpha混合,带Alpha通道的纹理(表面)都不支持,需要自己写,优化起来还是使用CPU的特殊指令)。虽然说使用3D加速非常简单,但是它的缺点也非常明显:对硬件有苛刻的要求,如果仅仅是做2D游戏不推荐使用(新手作为练习写DEMO而使用倒还可以,我也这样使用过,呵呵)。使用特殊的CPU指令最常见的就是使用MMX指令了,现在想找到一块装了Windows95以上但不支持MMX的CPU都有难度 ~自己花了大半年的时间用MMX高速实现了D3D对2D贴图的各种特效(带通道或者不带通道的纹理,带BlendColor, 带缩放旋转,做加减法的Alpha混合之类的)之后,虽然发现可以不使用D3D的东西,但是如果画面的东西很多的话,在一些内存带宽不高的机器上的速度还是不够理想,所以还是需要更多的优化。这时候我想起了DirtyRect。
什么是脏矩形?简单的说,就是游戏每次画面的刷新只更新需要更新的那一块区域。Windows本身就是最好的例子。或者说Flash控件,也正是利用了脏矩形技术,所以他的效率才如此的高。传统的游戏循环如下:
   while( 游戏没有结束 )
{
   if( 有Windows消息 )
   {
    处理Windows消息
   }
   else if( 需要渲染 )
   {
    清除游戏屏幕的缓冲区
    把游戏中的物体画到缓冲区里面
    把缓冲区更新到游戏窗口上
    锁定游戏速度,Sleep一段时间,限制FPS
   }
}
从上面的伪代码可以看出,每次游戏都要做清除缓冲区-〉渲染游戏的物体-〉更新到窗口,而基本上我们写游戏至少要保证最低每秒钟要刷新24帧以上(一般都在30)。所以上面的代码每秒钟要至少24次以上,画面东西越多,耗费的CPU越多。
不过我们也可以自然的想到,每次那么多东西不一定都需要更新的,比如一个动画,一般都有一个延迟,比如间隔200毫秒更新一次,那么在这段时间是不需要重新画的,只有更新了帧以后,表示这个动画所在的范围已经“脏”了,需要重新画,这个时候才需要画这个动画。而这段时间之内我们可以节约大量的CPU时间,很自然,积少成多,总体下来这个数值是非常可观的。再举一个例子,一个静止的游戏物体,(比如一棵树)是永远都不需要更新的,除非这个树的位置或者他的属性发生了变化。这样下来我们首先想到的是,每次我们都省略清除后台缓冲这个步骤,这个非常重要,因为上一次画下来的东西都在这个缓冲区里面,如果清除之后就什么都没有啦~~
搞明白了这个原理以后,下面来看看具体实现过程中遇到的问题:
游戏中的物体不会是相互没有遮挡的,所以如果遇到遮挡的问题怎么办?
如果游戏中有100个物体,里面的物体相互遮挡关系总有一个顺序,为了简化问题,只考虑两个物体遮挡的情况,多个物体的遮挡可以根据这个来衍生。






考虑上图,物体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<Count;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<m_nCurrentDirtyRectCount; 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物体是采用了叠加混合到背景的算法的话,问题就出来了,重画的部分会变得非常亮。所以脏矩形的分割就显得非常重要,也就是说把这些脏矩形还要分割为互相独立的互不相交的矩形,至于分割算法嘛,嘿嘿,各位还是动一下脑筋思考思考吧:)这个也是最值得优化的地方了,哈哈。实在想不出的话,最简单的方法就是把彼此相交的脏矩形都做一个合并,得到更大的脏矩形,虽然没有相交的区域了,但是也许这个脏矩形会变得比较大了哦:)
最后,大家一定关心的是我会不会提供源代码,很抱歉的说,不能。我在我的引擎中实现的不是以简单的链表去做的,用的是一棵比较复杂的渲染树,牵扯到的东西就比较多了,所以不方便提供代码,不过可以给一个演示吧:)再说大家如果真的明白了我所说的,那就可以自己动手写一下嘛,不要怕失败/P^_^,

好啦,关于脏矩形的技术就介绍到这里啦,用好这个技术你会发现你的游戏会在配置极低的机器上也能运行如飞的:)这种技术如果能用在现在市面上的那么多的游戏中的话,就不必为一个小游戏就强占了您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,哈哈!

关于商业合作:
MSN:wzh1100@hotmail.com
Mail:game-diy@163.com

关于该引擎:演示用的这个引擎(代号ShinyFairy:闪灵,基于前期开发的GFX3.0系列,该系列已经成功运行在某商业游戏公司的休闲游戏系列),采用Kylinx历时2年多开发的具有自主知识产权的基于2D游戏的超级引擎,强大的数据加密,打包,图形接口同时提供d3d8版本和mmx版本,该演示使用的是mmx版本,适用于各种休闲游戏平台,或者大型2D RPG/MMORPG均可适用,有意者可联系我详谈。


例子下载:http://dev.gameres.com/Program/Visual/2D/DirtyRectDemo.rar

                Kylinx
             MSN:wzh1100@hotmail.com
               2005,10,30

TraceBack:http://dev.gameres.com/Program/Visual/2D/DirtyRect.mht
  • 大小: 2.2 KB
  • 大小: 9.8 KB
  • 大小: 8.1 KB
分享到:
评论

相关推荐

    脏矩形技术测试Demo

    脏矩形技术是一种在计算机图形学中用于优化2D图形更新的策略,特别是在游戏和GUI应用中。这种技术的核心思想是避免对整个屏幕或者不必要区域进行无谓的重绘,而是只更新屏幕上的“脏”或已改变的矩形区域。在标题...

    很不错的脏矩形技术学习资料

    脏矩形技术是一种在计算机图形学和游戏开发中常见的优化技术,主要应用于更新屏幕上的显示内容。这种技术的目的是减少不必要的重绘操作,提高效率,特别是在处理大量动态元素时。以下是对脏矩形技术的详细解释: 脏...

    copyPixel与脏矩形渲染的效率对比

    脏矩形渲染是一种更高级的优化技术,尤其适用于多窗口系统和动态界面。它基于这样一个观察:并非屏幕上的每个像素每次都需要更新。当内容发生变化时,只记录和更新实际改变的区域(即“脏矩形”)。然后,在下一次...

    脏矩形技术学习,附上测试Demo

    脏矩形技术是一种在计算机图形学中用于优化屏幕更新的技术,尤其在游戏开发和GUI设计中广泛应用。这种技术的核心思想是识别并仅重绘屏幕上发生改变的区域,而不是每次都需要刷新整个屏幕,从而大大提高了效率。 脏...

    dirtyrect.rar_DEMO_脏矩形

    脏矩形技术是一种在计算机图形学和用户界面设计中广泛使用的优化策略,它主要用于减少屏幕重绘的计算量,提高程序性能。这个"dirtyrect.rar_DEMO_脏矩形"压缩包包含了一个演示脏矩形技术的应用源代码,让我们深入...

    矩形顶管技术交流-西安轨道交通讲稿.pdf

    矩形顶管技术是一种地下工程施工方法,主要用于建立预制的矩形结构作为隧道或地下通道。根据给定文件内容,可以从以下知识点进行详细阐述: 1. 矩形顶管技术的应用现状: 矩形顶管技术主要应用于地下综合管廊、地铁...

    矩形矩形矩形矩形1.png

    矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形矩形...

    例说矩形PLC应用技术快速入门

    ### 例说矩形PLC应用技术快速入门 #### 矩形PLC基础知识与入门指南 在自动化领域,可编程逻辑控制器(Programmable Logic Controller,简称PLC)是核心组成部分之一,广泛应用于各种工业控制场景。对于初学者而言...

    C#橡皮筋技术画矩形和线

    在C#编程中,"橡皮筋"技术是一种常见的用户界面交互方式,它允许用户在屏幕上绘制临时形状,如矩形或线段,然后在松开鼠标按钮时完成绘制。这种技术通常用于图形编辑器、绘图软件或者地图应用中,提供给用户一个直观...

    matlab_minrect.zip_外接矩形_最小外接矩形_框出目标_矩形 目标_确定目标的最小外接矩形

    在计算机视觉和图像处理领域,计算目标的最小外接矩形是一项常见的任务。最小外接矩形是指能够完全覆盖一个不规则形状的目标区域的最小矩形,它在各种应用场景中都有重要作用,比如图像分析、物体识别和定位等。本文...

    找图像中最大的轮廓画外接矩形,计算矩形度

    本文将详细讲解如何在DOS(Disk Operating System)环境下找到图像中的最大轮廓,绘制其外接矩形,并计算矩形度。 首先,我们需要了解轮廓的基本概念。在图像处理中,轮廓是指图像中物体边缘的连续像素集合,它能够...

    flash判断旋转矩形是否相交

    通过掌握这些基本概念和技术,你可以在Flash环境中实现判断旋转矩形相交的功能。同时,深入学习和理解2D几何、矩阵变换和线段相交算法,将有助于提升你的编程技能和解决问题的能力。在《Flash学习笔记》中,你可以...

    C# pictureBox 绘制矩形框

    当你需要在图像上进行图形操作,如绘制矩形框、选中并调整框的大小时,可以通过重写`pictureBox`的相关事件来实现。下面我们将深入探讨如何在`pictureBox`上绘制矩形框,并支持选中及边框拉伸功能。 首先,你需要...

    wpf 绘制矩形 可动态调整矩形大小

    在本文中,我们将深入探讨如何在Windows Presentation Foundation (WPF) 中实现动态绘制矩形以及允许用户通过拖动矩形的四个角来调整其大小的功能。这个功能在各种图形编辑工具或界面设计应用中非常常见,它能提供...

    矩形和圆的位置

    本程序以Java语言为例,探讨了如何判断一个圆是否位于矩形内部。这涉及到坐标系中的几何运算和条件判断,是计算机图形学的基础知识。 首先,我们需要理解矩形和圆的基本属性。矩形是由四个顶点定义的四边形,其具有...

    opengl环境下,采用橡皮条技术绘制矩形

    本教程重点介绍如何在OpenGL环境中使用橡皮条技术来交互式地绘制矩形。 首先,橡皮条技术是一种在用户进行图形选择或编辑时提供反馈的常见方法。在OpenGL中,它涉及到在用户拖动鼠标或触摸屏时显示一个临时的形状,...

    302_规格划分矩形.cpp

    划分一个由64块小正方形组成的8*8的矩形: 将原矩形分成两个矩形,在分开后的两个矩形中任选一块重复这样的划分, 这样分了(n-1)次后,连同最后剩下的矩形共有n块矩形。 原矩形上每一格有一个分值, 一块矩形的...

    qt 画旋转矩形

    本主题将深入探讨如何使用Qt来绘制矩形,特别是如何实现旋转矩形的功能,以及如何将其应用于图像处理中的抠图场景。 首先,我们需要了解QPainter的基本用法。在Qt中,你需要创建一个QPainter对象,然后将其与一个...

    一个矩形类

    面向对象程序设计之矩形类和正方形类的...通过定义矩形类和正方形类,我们学习了面向对象程序设计的基本概念和技术,包括类的定义、继承、多态和接口的实现。这些技术是程序设计的基础,将在后续的学习中被反复应用。

    SVG制作圆角矩形代码

    总的来说,SVG制作圆角矩形的代码是利用SVG的`&lt;rect&gt;`标签结合`rx`和`ry`属性实现的,这一技术广泛应用于网页设计和其他需要矢量图形的领域。提供的`circlerect.svg`和`circlerect.html`文件为开发者提供了一个可以...

Global site tag (gtag.js) - Google Analytics