编程初学者:游戏编程起源(初学者)Ⅹ



☆ 锁定表面

没什么令人意外东东我们将使用是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-bitUINT*是32-bit但是24-bit如何办呢?没有和的相对应数据类型我们还是使用UCHAR*类型但具体操作有些区别
我们也应该把lPitch成员转换成和指针相同单位记得吗当我们第次从DDSURFACEDESC2结构得到lPitch时它是以字节为单位对于16-bit模式我们应该把它除以2以适应USHORT对于32-bit我们应该把它除以4以适应UINT
在我们进行第 2步前先看看例子代码假设我们在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 poer and the pitch
UINT* buffer = (UINT*)ddsd.lpSurface;
UINT nPitch = ddsd.lPitch >> 2;

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

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

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

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



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

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

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

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

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

☆ 关注速度

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

for (x=0; x<1000; x)
{
(nPitch 640)
PlotPixelFast16;

PlotPixel16;
}

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

(nPitch 640)
{
for (x=0; x<1000; x)
PlotPixelFast16( parameters );
}

{
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它支持α混合先设定每纹理然后设置透明层;或者更容易思路方法你可以使用DirectDrawcolor/gamma控制但是如果你仅仅希望屏幕部分进行淡入或淡出操作或者淡入或淡出种非黑色颜色而且你又不是个Direct3D高手——我本人就不是!——那么具体做法手册就在你眼前现在你所需要做最基本就是读取每个你需要控制象素然后把它分解成红色、绿色和蓝色然后你把 3个值分别乘以要淡出或淡入级别再合成RGB值把新颜色值写回缓冲区听起来很复杂?别害怕没有想象那么坏看看下面这段演示代码它演示了屏幕左上角200×200区域淡出效果是16-bit色彩深度和565格式:

void ApplyFade16_565(float pct, USHORT* buffer, pitch)
{
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 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, pitch)
{


x, y;
UCHAR r, g, b;
USHORT color;
USHORT* temp = buffer;
jump = pitch - 200;

for (y=0; y<200; y)
{
for (x=0; x<200; x, temp) // move poer 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 color back to the buffer
*temp = RGB_16BIT565(r, g, b);
}

// move poer to beginning of next line
tempjump;
}
}

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

USHORT clut[65536][20];

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

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

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

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)
{
x, y, bright;
UCHAR r, g, b;

// calculate textbox transparency CLUT
for (x=0; x<65536; x)
{
// transform RGB data
(color_depth 15)
{
r = (UCHAR)((x & 0x7C00) >> 10);
g = (UCHAR)((x & 0x03E0) >> 5);
b = (UCHAR)(x & 0x001F);
}
// color_depth must be 16
{
r = (UCHAR)((x & 0xF800) >> 11);
g = (UCHAR)((x & 0x07E0) >> 6); // shting 6 bits instead of 5 to put green
b = (UCHAR)(x & 0x001F); // _disibledevent=>}

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



// write CLUT entry as 1 + _disibledevent=>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模式低位是蓝色我可以只把颜色设置为蓝色就不用宏了理解了吗?最后结束的前我给你演示怎样创建文本框:

Text_Box(USHORT *ptr, pitch, LPRECT box)
{
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 poer 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;
}
(TRUE);
}

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

USHORT clut[65536];

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

☆ 整理总结

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

作者邮箱:[email protected] 要用英文给作者发信哦!
作者ICQ:53210499
【傻马乱踢:[email protected]
待续

Tags:  网络游戏的起源 网络游戏起源 游戏的起源 编程初学者

延伸阅读

最新评论

发表评论