登录社区:用户名: 密码: 忘记密码 网页功能:加入收藏 设为首页 网站搜索  

文档

下载

图书

论坛

安全

源码

硬件

游戏
首页 信息 空间 VB VC Delphi Java Flash 补丁 控件 安全 黑客 电子书 笔记本 手机 MP3 杀毒 QQ群 产品库 分类信息 编程网站
  立华软件园 - 安全技术中心 - 技术文档 - 入门基础 技术文章 | 相关下载 | 电子图书 | 攻防录像 | 安全网站 | 在线论坛 | QQ群组 | 搜索   
 安全技术技术文档
  · 安全配制
  · 工具介绍
  · 黑客教学
  · 防火墙
  · 漏洞分析
  · 破解专题
  · 黑客编程
  · 入侵检测
 安全技术工具下载
  · 扫描工具
  · 攻击程序
  · 后门木马
  · 拒绝服务
  · 口令破解
  · 代理程序
  · 防火墙
  · 加密解密
  · 入侵检测
  · 攻防演示
 安全技术论坛
  · 安全配制
  · 工具介绍
  · 防火墙
  · 黑客入侵
  · 漏洞检测
  · 破解方法
 其他安全技术资源
  · 攻防演示动画
  · 电子图书
  · QQ群组讨论区
  · 其他网站资源
最新招聘信息

游戏编程起源(初学者)Ⅹ
发表日期:2007-01-17作者:[转贴] 出处:  

☆ 锁定表面

没什么令人意外的东东,我们将使用的函数是IDirectDrawSurface7::Lock()。让我们仔细看看它:

HRESULT Lock(
    LPRECT lpDestRect,
    LPDDSURFACEDESC lpDDSurfaceDesc,
    DWORD dwFlags,
    HANDLE hEvent
);


一定要检测函数的调用是否成功,否则可能会有大麻烦的:如果锁定失败,而返回的指针指向了一个不正确的内存区域,你若操控该区域,很有可能导致系统的混乱。函数的参数有以下这些组成:
LPRECT lpDestRect:是一个指向RECT结构的指针,它描述了将要被直接访问的表面上的矩形区。该参数被设置为NULL,以锁定整个表面。
LPDDSURFACEDESC2 lpDDSurfaceDesc:是DDSURFACEDESC2类型的结构变量的地址,它由直接访问表面内存所必需的全部信息进行填充。在该结构中返回的信息表面的基地址、间距和象素格式。
DWORD dwFlags:好像没有几个DirectX函数没有这个东东的。下面列出几个最有用的标志常量:
◎ DDLOCK_READONLY:被锁定的表面为只读。(当然就不能写入了)
◎ DDLOCK_SURFACEMEMORYPTR:表面返回一个指向锁定矩形左上角坐标的有效指针;如果没有指定矩形,那么返回表面左上角的坐标。此项为默认且无需显式的输入。
◎ DDLOCK_WAIT:如果其它线程或进程正在进行位转换操作,不能锁定表面内存,则一直等到该表面可以锁定为止或返回错误信息。
◎ DDLOCK_WRITEONLY:被锁定表面为可写。(当然就不能读取了)
由于我们使用锁定去操控象素,你将总会用到DDLOCK_SURFACEMEMORYPTR。即使我们目前还没有学习位块操作,但使用DDLOCK_WAIT总是一个好主意。
HANDLE hEvent:没用的东东,设置为NULL好了。

一旦我们锁定了表面,我们需要查看一下DDSURFACEDESC2结构来获取一些表面信息。我们以前介绍过这个结构,但在这里,针对现在的课题,我们只需要它的两个成员。由于它们都很重要,我就再重复一遍:
LONG lPitch:这个lPitch成员表示每个显示行的字节数,也就是行间距。例如,对于640×480×16模式,每一行有640个象素,每一个象素需要两个字节存放颜色信息,所以行间距应该为1280个字节,对不对?Well,对于一些显示卡,它的长度大于1280,每行上多于的内存不存放任何的图象数据,但你必须让它存在,因为这种显示卡在某种显示模式下不能创建线性内存模式。的确,这种显示卡的比例很小,但你需要考虑到它。
LPVOID lpSurface:这是指向内存中表面的指针。不管你使用何种显示模式,DirectDraw都创建一个线性地址模式,使你能够操控表面上的象素。

这个lpSurface指针是很容易理解的,而行间距是一个需要记住的重要值,因为你将必须使用它去计算特殊象素的偏移量。
我们过一会儿在细说,因为有一件事我们现在必须知道,当对锁定的表面操作完成后,你需要释放这个锁定表面,这个函数IDirectDrawSurface7::Unlock()的原形为:

HRESULT Unlock(LPRECT lpRect);

参数同你传递给Lock()函数的要保持一致。都准备好了,让我们画一些象素吧!

☆ 绘制象素

首先是确定从Lock()函数得到的指针类型。逻辑上,我们希望指针的大小同象素的大小要保持一致。所以我们为8-bit色彩深度分配了UCHAR*类型,USHORT*是16-bit的,UINT*是32-bit的。但是24-bit怎么办呢?因为没有与之相对应的数据类型,我们还是使用UCHAR*类型,但具体操作有一些不同。
我们也应该把lPitch成员转换成与指针相同的单位。记得吗,当我们第一次从DDSURFACEDESC2结构得到lPitch时,它是以字节为单位。对于16-bit模式,我们应该把它除以2以适应USHORT,对于32-bit我们应该把它除以4以适应UINT。
在我们进行第二步前先看看实例代码。假设我们在32-bit模式锁定主表面来绘制象素。以下是代码:

// declare and initialize structure
DDSURFACEDESC2 ddsd;
INIT_DXSTRUCT(ddsd);

// lock the surface
lpddsPrimary->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);

// now convert the pointer and the pitch
UINT* buffer = (UINT*)ddsd.lpSurface;
UINT nPitch = ddsd.lPitch >> 2;

现在让我先一步告诉你象素绘制函数,然后我再解释:

inline void PlotPixel32(int x, int y, UINT color, UINT *buffer, int nPitch)
{
    buffer[y*nPitch + x] = color;
}

All right,让我分别解说。首先,你可能已经注意到了我把它声明为一个inline函数,目的是消除传递所有参数时的辅助操作,例如每次我们想要做些简单的事情(如绘制一个象素)。在函数里,仅用了一行就定位了我们要绘制的点和设置了该点的颜色。注意,颜色仅仅是一个值,不是由红、绿、蓝分别组成的,所以我们需要使用宏RGB_32BIT()来设置这个颜色值。
公式用来定位要绘制象素的具体位置——y*nPitch + x 。nPitch表示行间距,被y乘后就得到了正确的行数,再加上x,就得到了正确的位置。这就是你需要知道的,很简单吧!让我再告诉你在8-bit和16-bit模式下绘制象素的函数,它们都十分相象:

inline void PlotPixel8(int x, int y, UCHAR color, UCHAR* buffer, int byPitch)
{
    buffer[y*byPitch + x] = color;
}

inline void PlotPixel16(int x, int y, USHORT color, USHORT* buffer, int nPitch)
{
    buffer[y*nPitch + x] = color;
}

几个函数间唯一不同的就是参数数据类型的不同。应该还记得对于8-bit色彩深度,间距是以字节表示,对于16-bit,间距是以USHORT类型表示。现在,只剩下一个模式没有说了,就是24-bit模式。由于没有相应的数据类型,我们需要分别传递红、绿、蓝三个值,函数看起来应该如下:

inline void PlotPixel24(int x, int y, UCHAR r, UCHAR g, UCHAR b, UCHAR* buffer, int byPitch)
{
    int index = y*byPitch + x*3;

    buffer[index] = r;
    buffer[index+1] = g;
    buffer[index+2] = b;
}

如你所看到的,它将工作慢一些,因为它多了一次乘法运算,并且有三次内存写操作。你可以用其它方法替换x*3加快一些速度,如(x+x+x)或者(x<<1)+x,但是不会有太大效果的。当然,她还没有到应该放弃的地步。现在你就明白了为什么说24-bit色彩深度有点儿讨厌了吧!

☆ 关注速度

你应该采取一些行动使程序尽可能会的运行。首先,锁定一个表面并不是最快的,所以你要试图锁定表面上你要操作的最小矩形区域。对于很多操作,包括很简单的绘制象素演示程序,你都应该锁定最小的矩形区域。
第二,让我们就640×480×16模式来说,间距总是1280个字节,你应该试图考虑有没有更好的办法表述它。当然,1280个字节你是不能改变的,但我们可以使公式最优化,用位移来替代乘法是一贯的加速方法。我们先前的公式是这样的:
buffer[y*nPitch + x] = color;
如果我们知道nPitch将会是640(由于nPitch是USHORT类型,不是字节),我们就可以加速它(我们本来就知道它是640)。640不是一个理想的位移数字,但512是2的9次幂,128是2的7次幂,你猜到了吧,512+128=640。^_^ 很棒吧?我们就可以用下面这个更快的公式取代先前的公式:
buffer[(y<<9) + (y<<7) + x] = color;
多数的解决办法都是分解成2的几次幂,有的需要动一点儿脑筋,如800×600(512+256+32=800),小菜一碟哦!位移是我们应用的最快的运算符。
最后,如果你要使用两个函数—— 一个做乘法运算,一个做位移运算,要将比较判断放到循环的外部,不能象下面这样:

for (x=0; x<1000; x++)
{
    if (nPitch == 640)
        PlotPixelFast16();
    else
        PlotPixel16();
}

判断部分使你的优势殆尽,你应该这样做:

if (nPitch == 640)
{
    for (x=0; x<1000; x++)
        PlotPixelFast16( parameters );
}
else
{
    for (x=0; x<1000; x++)
        PlotPixel16( parameters );
}

有意义吧?无论何时用大的循环,都应该尽量把判断放到循环的外部,没有必要进行上千次同样的比较判断。同理,如果你要绘制象素,形成有规律的图案,如水平线或垂直线,甚至是斜线,你都没有必要每一次都重复确定象素的位置。看看下面的例子,画一条任意颜色的直线:

for (x=0; x<640; x++)
    PlotPixel16(x, 50, color, buffer, pitch);


函数每次都重复计算正确的行,你可以一次就把行指定好。下面是快一点儿的做法:

// get the address of the line
USHORT* temp = &buffer[50*pitch];

// plot the pixels
for (x=0; x<640; x++)
{
    *temp = color;
    temp++;
}

你可能认为节省这么一点点时间意义不大,但当你进行千万次的循环时,意义就很大了。游戏程序员总是想办法提高游戏的速度的。
看看以前的文章,我们已经进行了好长时间的铺垫了。现在,我们知道了怎样绘制象素了,让我们看看能用现在学到的做些什么。

☆ 淡出操作

在游戏中最常用到的屏幕操作就是淡出成黑色,或者从黑色淡入。两种方式是同样的机理:你简单画出你的图象,然后申请一些屏幕转换来改变图象的亮度。对于淡出,你减少亮度从100%——0%;对于淡入,你增加亮度从0%——100%。如果你工作在调色板模式,这很容易做到,你只要改变你的调色板的颜色就可以了。如果你工作在RGB模式下,你得考虑一些其它方法。
现在,我将说一说屏幕淡入、淡出相对好一些的方法。你可以使用Direct3D,它支持α混合,先设定每一帧的纹理,然后设置透明层;或者,更容易的方法,你可以使用DirectDraw的color/gamma控制。但是,如果你仅仅希望屏幕的一部分进行淡入或淡出的操作,或者淡入或淡出一种非黑色的颜色,而且你又不是一个Direct3D的高手——我本人就不是!——那么具体做法的手册就在你眼前。现在,你所需要做的最基本的就是读取每一个你需要控制的象素,然后把它分解成红色、绿色和蓝色,然后你把三个值分别乘以要淡出或淡入的级别,再合成RGB值,把新的颜色值写回缓冲区。听起来很复杂?别害怕,没有想象的那么坏。看看下面这段演示代码,它演示了屏幕左上角200×200区域的淡出效果,是16-bit色彩深度和565格式:

void ApplyFade16_565(float pct, USHORT* buffer, int pitch)
{
    int x, y;
    UCHAR r, g, b;
    USHORT color;

    for (y=0; y<200; y++)
    {
        for (x=0; x<200; x++)
        {
            // first, get the pixel
            color = buffer[y*pitch + x];

            // now extract red, green, and blue
            r = (color & 0xF800) >> 11;
            g = (color & 0x0730) >> 5;
            b = (color & 0x001F);

            // apply the fade
            r = (UCHAR)((float)r * pct);
            g = (UCHAR)((float)g * pct);
            b = (UCHAR)((float)b * pct);

            // write the new color back to the buffer
            buffer[y*pitch + x] = RGB_16BIT565(r, g, b);
        }
    }
}

现在,这个函数有很多不稳妥的地方。首先,计算象素的位置公式不但包含在循环中,而且还出现了两次!你可以在整个程序中只计算它一次,但现在的代码计算了它80000次!^_^ 下面是你应该做的:在函数的开始部分,你应该声明一个USHORT*的变量,让它等于buffer(如USHORT* temp = buffer;)。在内部循环里,增加一个指针使其能得到下一个象素;在外部循环,增加一行(temp+=jump;),使其能转入下一行。下面是修改后的代码:

void ApplyFade16_565(float pct, USHORT* buffer, int pitch)
{
    int x, y;
    UCHAR r, g, b;
    USHORT color;
    USHORT* temp = buffer;
    int jump = pitch - 200;

    for (y=0; y<200; y++)
    {
        for (x=0; x<200; x++, temp++) // move pointer to next pixel each time
        {
            // first, get the pixel
            color = *temp;

            // now extract red, green, and blue
            r = (color & 0xF800) >> 11;
            g = (color & 0x0730) >> 5;
            b = (color & 0x001F);

            // apply the fade
            r = (UCHAR)((float)r * pct);
            g = (UCHAR)((float)g * pct);
            b = (UCHAR)((float)b * pct);

            // write the new color back to the buffer
            *temp = RGB_16BIT565(r, g, b);
        }

        // move pointer to beginning of next line
        temp+=jump;
    }
}

这就好一些了吧!jump值是USHORT类型,是表示从200个象素宽的末尾(200个象素没有占满一行)到下一行开始的值。尽管如此,对于浮点运算和提取/还原颜色计算并没有提高速度。应该有办法的,看看这个:

USHORT clut[65536][20];

如果你要求一个DOS程序员把这么大的数组放入他的程序中,他可能痛苦的会哭出声来,甚至当场昏死过去,起码也要加速自然死亡。但在Windows中,如果你需要这样做,不会遇到什么麻烦的。因为你拥有整个系统的可利用内存。如果把整个的内循环替换成下面这一行,是不是很美妙的一件事呢?

*temp = clut[*temp][index];

这样做,又快了一些!^_^ 你可以传递一个0——100间的整数来替代浮点数传递给函数。如果为100,就不需要淡出的操作了,所以就返回“什么事儿也不用做”;如果为0,就用ZeroMemory()函数处理所有的工作好了。另外,把传递的数除以5,作为数组的第二个下标。
如果你对于我知道查询表的尺寸感到好奇,我就告诉你好了,65536是2的16次幂,所以在16-bit模式下,就有65536种颜色。既然我们的颜色值是无符号的值,它们的范围从0——65535,那么我们就用20作为淡出的增量值好了,反正考虑到相关的内存,我觉得挺合适的。
对于24-bit和32-bit模式,你显然不能直接使用颜色查询表,因为数组太巨大了,所以你只有使用三个小一点的数组:

UCHAR red[256];
UCHAR green[256];
UCHAR blue[256];

然后,每当你读取一个象素,就把它分解出的颜色值放入相应的数组,使其形成自己的查询表,经过变化,再组合到一起,得到RGB色彩值。有很多办法可以实现程序的优化,最好的办法是根据你的目的不断地测试哪一种是最适合你的程序的,然后总结经验,记住它。我下面将简单的介绍一下你可能用得着的其它的转换。

☆ 透明操作

把一个透明的图象覆盖在非透明的图象上,你就不能使用颜色查询表了,因为它总共需要有65536个查询表,一台普通的电脑就需要8.6GB的内存来处理这个庞然大物。^_^ 所以你不得不计算每一个象素。我将给你一个基本的思路。假设你要用图象A覆盖图象B,图象A的透明百分比为pct,这是一个0——1之间的浮点数,当为0时是完全不可见的,当为1时是完全可见的。那么,让我们把图象A的象素称作pixelA,相对应,图象B的象素称作pixelB。你将应用下面这个公式:

color = (pixelA * pct) + (pixelB * (1-pct));

基本上,这是一个两个象素颜色的平均值。所以,你实际上看到每个象素有6个浮点乘法运算。你可以用一些小型的查询表降低你的工作量。你真的应该试一试!
你或许想做的另一件事情是建立一个部分透明的纯色窗口。如果你看过了我做的一个RPG游戏的DEMO——地球人(http://www.aeon-software.com/framesok/tn_demo1.html )你或许明白我的意思。那种效果用一个颜色查询表完全可以达到。因为对于“地球人”,我只需要为屏幕上可能出现的颜色提供蓝色。实际上,我就是用查询表完成的。我将告诉你我实际的意思:

void Init_CLUT(void)
{
    int x, y, bright;
   
UCHAR r, g, b;

    // calculate textbox transparency CLUT
    for (x=0; x<65536; x++)
    {
        // transform RGB data
        if (color_depth == 15)
        {
            r = (UCHAR)((x & 0x7C00) >> 10);
            g = (UCHAR)((x & 0x03E0) >> 5);
            b = (UCHAR)(x & 0x001F);
        }
        else // color_depth must be 16
        {
            r = (UCHAR)((x & 0xF800) >> 11);
            g = (UCHAR)((x & 0x07E0) >> 6); // shifting 6 bits instead of 5 to put green
            b = (UCHAR)(x & 0x001F); // on a 0-31 scale instead of 0-63
        }

        // find brightness as a weighted average
        y = (int)r + (int)g + (int)b;
        bright = (int)((float)r * ((float)r/(float)y) + (float)g * ((float)g/(float)y) + (float)b * ((float)b/(float)y) + .5f);

        // write CLUT entry as 1 + one half of brightness
        clut[x] = (USHORT)(1 + (bright>>1));
    }
}

这段代码来源于“地球人”,用查询表创建了一个文本框。为了安全起见,随处都使用了类型修饰。这段代码还能再快一些,但我没有很认真的优化,因为我只在游戏的最开始的部分调用了它一次。首先,红、绿、蓝的亮度值被提取出来,由于是16-bit模式,注意我们用了一个color_depth变量检测了显示卡是555还是565格式。然后,用下面公式计算了象素的亮度:

y = r + g + b;
brightness = r*(r/y) + g*(g/y) + b*(b/y);

这是一个理想的平均值。我不能确定是否颜色亮度值这样得到就正确,但它看起来符合逻辑,并且实际效果很好。在公式的最后我加了一个.5,因为当你把浮点数变为整数时,小数部分被去掉,加上.5使其凑整。最后,我把亮度除以2再加上1,这样不会使文本框太亮,加1使文本框不会全黑。由于16-bit模式的低位是蓝色,我可以只把颜色设置为蓝色,就不用宏了。理解了吗?最后,结束之前,我给你演示怎样创建文本框:

int Text_Box(USHORT *ptr, int pitch, LPRECT box)
{
   
int x, y, jump;
    RECT ibox;

    // leave room for the border
    SetRect(&ibox, box->left+3, box->top+3, box->right-3, box->bottom-3);

    // update surface pointer and jump distance
    ptr += (ibox.top * pitch + ibox.left);
    jump = pitch - (ibox.right - ibox.left);

    // use CLUT to apply transparency
    for (y=ibox.top; y<ibox.bottom; y++)
    {
        for (x=ibox.left; x<ibox.right; x++, ptr++)
            *ptr = clut[*ptr];
        ptr += jump;
    }
    return(TRUE);
}

这就是一个查询表,看起来更象淡出操作的代码了,就是查询表的控制值与前面的不一样了。这里用一个计算代替了20。^_^ 顺便说一下,对于查询表的一个声明,象下面这个:

USHORT clut[65536];

使用它,你可以做出更有趣的效果。
在你自己动手前,请先看看针对本章编写的例程代码,它可以从http://www.aeon-software.com/downloads/pixels.zip 下载。你应该试着改动它一下,用象素填满屏幕,然后在上面绘制一个透明的文本框。

☆ 总结

本章是为以象素为基础的图形服务的。下一章,我们将学习位图的知识。不管你信不信,使用位图要比象素简单多了,以后你就知道了。下一章将是学习DirectX基础知识的最后一章,在此之后,我们将编写一个RPG游戏。细节到时候你就知道了。

作者的邮箱:ironblayde@aeon-software.com 要用英文给作者发信哦!
作者的ICQ:53210499
【傻马乱踢:howsee@163.com】
待续。。。。。。

我来说两句】 【发送给朋友】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 游戏编程起源(初学者)Ⅹ

 ■ [欢迎对本文发表评论]
用  户:  匿名发出:
您要为您所发的言论的后果负责,故请各位遵纪守法并注意语言文明。

最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放 / 友情链接  
Copyright ©2001-2006 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.00197