站内搜索:     
站点首页破釜学院编程开发其他相关 → 游戏开发新手入门之调色板和像素
正在加载相关信息.....
Web 站内搜索
游戏开发新手入门之调色板和像素
】【打印】【加入收藏】【关闭收藏到新浪ViVi】【收藏到365KEY】 浏览字号:
日期:2006-08-09 人气: 出处:天极

  淡出操作

   在游戏中最常用到的屏幕操作就是淡出成黑色,或者从黑色淡入。两种方式是同样的机理:你简单画出你的图象,然后申请一些屏幕转换来改变图象的亮度。对于淡出,你减少亮度从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个浮点乘法运算。你可以用一些小型的查询表降低你的工作量。你真的应该试一试!

   你或许想做的另一件事情是建立一个部分透明的纯色窗口。那种效果用一个颜色查询表完全可以达到。因为对于“地球人”,我只需要为屏幕上可能出现的颜色提供蓝色。实际上,我就是用查询表完成的。我将告诉你我实际的意思:
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];
  总结

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

>>>> 进入论坛交流 <<<<