遊戲開發學堂:2D模式下的alpha混合(轉)

post0發表於2007-08-12
遊戲開發學堂:2D模式下的alpha混合(轉)[@more@]

  alpha混合是一種常見的顏色處理,是把源點的顏色值和目標點的顏色值按照一定的演算法進行運算,得到一個透明的效果.

  

  alpha混合的基本公式:

  

  result = ALPHA * srcPixel + ( 1 - ALPHA ) * destPixel

  其中:

  ALPHA:0到1之間的一個數,表示混合時的透明程度,為0的時候結果就是目標點的原值,為1的時候是源點的原值

  srcPixel:源點顏色值

  destPixel:目標點顏色值

  result:結果,將會賦給目標點

  

  這個是最基本的公式,但是我們在寫程式的時候可以做一些最佳化.公式裡面有兩個乘號,乘法消耗的時鐘週期要多得多,我們要想方法去掉它.做一個變換.

  

  alpha混合的改進公式1:

  result = ALPHA * ( srcPixel - destPixel ) + destPixel

  

  然後我們也看到ALPHA是一個浮點數,我們可以把它轉換成整數,因為一種顏色最多佔8bit,所以ALPHA的值也最多到256,那我們把ALPHA的值先乘上256,然後運算的時候再除以256就得到下面的公式:

  

  alpha混合的改進公式2:

  result = ALPHA * ( srcPixel - destPixel )/256 + destPixel

  其中:

  ALPHA是一個0到256的數

  

  有了這個公式,我們的第一份code也就出來了.

  

  alpha混合程式碼1:

  //16位565格式

  i = height;?// 混合矩形高度

  do

  {

  j = width; ?// 混合矩形寬度

  do

  {

  sTemp = *((WORD*)lpSour);// 源點顏色值,16位--WORD型別,(lpSour和lpDest都是BYTE*)

  if ( sTemp != sColorKey ) ? // 不是colorkey

  {

  dTemp = *((WORD*)lpDest); // 目標點

  

  sb = sTemp & 0x1f; ? // 藍色分量

  db = dTemp & 0x1f;

  

  sg = (sTemp >> 5) & 0x3f; // 綠色分量

  dg = (dTemp >> 5) & 0x3f;

  

  sr = (sTemp >> 11) & 0x1f;// 紅色分量

  dr = (dTemp >> 11) & 0x1f;

  

  blue = (ALPHA * (sb - db) >> 8) + db;// 按照改進公式2運算三個分量

  green = (ALPHA * (sg - dg) >> 8) + dg;

  red = (ALPHA * (sr - dr) >> 8) + dr;

  *((WORD*)lpDest) = blue | (green << 5) | (red << 11); // 565格式生成結果並賦給目標點

  }

  lpDest += 2; // 下一個點,16位佔2個位元組

  lpSour += 2;

  }while ( --j > 0 );

  lpDest += dPadding; // 下一行

  lpSour += sPadding;

  }while (--i > 0);

  

  到此alpha混合也就差不多了,但是不得不說說MMX版本的alpha混合.上面的方式進行alpha混合的時候效率不高,而MMX使用64位的MMX暫存器,一次操作8(byte)/4(word)/2(dword)個資料單元,也就是所謂的"單指令多資料SIMD結構".這樣我們就可以一次處理幾個點.下面的程式碼是寫給16位565模式的.

  因為是16位模式,那麼我們可以一次處理4個點,但是因為編譯器不會主動的生成MMX程式碼,所以我們使用inline asm的方式進行.因為我們的點是定義成無符號的word型,不能出現負數,所以我們要修改我們的alpha混合公式:

  

  alpha混合mmx版公式:

  result = ( ALPHA * ( ( srcPixel+ 64 ) - destPixel) ) / 256 + destPixel- (ALPHA / 4)

  

  這個公式是這樣來的:因為16位模式下每個點的分量最多6位,換成10進位制也就是63,那麼我們在srcPixel上面加上64然後再減去destPixel,就不會出現負數了.加了之後當然要減回來,那這個公式就是這樣的:

  

  result = ( ALPHA * ( ( srcPixel+ 64 -64) - destPixel) ) / 256 + destPixel

  

  然後把紫色的-64提出來也就得到了上面的公式了.

  

  好了我們可以動手寫code了,看起來的code應該如下:

  

  alpha混合code2:

  

  MASKRED = 0xF800F800F800F800;?// 三種顏色的掩碼的64位擴充套件

  MASKGREEN = 0x07E007E007E007E0;

  MASKBLUE = 0x001F001F001F001F;

  

  __int64 ALPHA64, COLORKEY64, ALPHABY4;

  __int64 MASKRED, MASKGREEN, MASKBLUE;

  __int64 ADD64 = 0x0040004000400040;

  

  _asm

  {

  movd mm2,ALPHA // ALPHA值放入mm2

  punpcklwd mm2,mm2 // mm2 -> 0000 0000 00aa 00aa

  punpckldq mm2,mm2 // mm2 - 00aa 00aa 00aa 00aa,生成alpha的64位擴充套件

  movq ALPHA64,mm2 ?// 結果放入到 ALPHA64

  

  psrlw mm2, 2 ?// 每個ALPHA 除以4

  movq ALPHABY4,mm2 // 放到 ALPHABY4

  

  movd mm4,ColorKey // ColorKey -> mm4

  punpcklwd mm4,mm4 // mm4 -> 0000 0000 cccc cccc

  punpckldq mm4,mm4 // mm4 -> cccc cccc cccc cccc,生成Colorkey的64位擴充套件

  movq COLORKEY64,mm4 ; // 結果放到 COLORKEY64

  }

  

  i = height;

  do

  {

  j = width/4;

  

  // 兩行寫在一起表示假設他們同時在uv管道里面執行

  

  _asm

  {

  push edi ? // 儲存edi和esi

  push esi

  

  mov edi,lpDest // edi指向dest緩衝區

  mov esi,lpSour; // esi指向sour緩衝區

  

  SPAN_RUN_565:

  movq mm7,[edi] // dest的8 bytes 到 mm7--4個dest點到mm7

  movq mm6,[esi] // sour的8 bytes 到 mm6--4個sour點到mm6

  

  movq mm2,ALPHA64 ? // ALPHA64 -> mm2

  movq mm0,mm7 ? // 紅色 - 複製 mm7 到 mm0,目標點

  

  pand mm0,MASKRED ? // 紅色 - 與上紅色掩碼 -> [0r00 0r00 0r00 0r00],目標點

  movq mm1,mm6 ? // 紅色 - 複製 mm6 到 mm1,源點

  

  pand mm1,MASKRED ? // 紅色 - 與上紅色掩碼 -> [0r00 0r00 0r00 0r00],源點

  psrlw mm0,11 ? // 紅色 - 右移 11 位 -> [000r 000r 000r 000r],目標點

  

  movq mm5,mm7 ? // 綠色 - 複製 mm7 到 mm5,目標點

  psrlw mm1,11 ? // 紅色 - 右移 11 位 -> [000r 000r 000r 000r],源點

  

  paddw mm1, ADD64// 紅色 - 源點加上64

  

  movq mm3,mm6 ? // 綠色 - 複製 mm6 到 mm3,源點

  psubsw mm1,mm0 // 紅色 - 減去目標點

  

  pand mm5,MASKGREEN // 綠色 - 與上綠色掩碼 -> [00g0 00g0 00g0 00g0],目標點

  pmullw mm1,mm2 // 紅色 - 乘以ALPHA

  

  pand mm3,MASKGREEN // 綠色 - 與上綠色掩碼 -> [00g0 00g0 00g0 00g0],源點

  psrlw mm5,5 // 紅色 - 右移 5 位 -> [000g 000g 000g 000g],目標點

  

  psrlw mm3,5 // 紅色 - 右移 5 位 -> [000g 000g 000g 000g],源點

  nop // 空操作 - 指令配對

  

  

  paddw mm3, ADD64// 綠色 - 源點加上64

  

  psrlw mm1,8 // 紅色 - 除以 256 (右移8位)

  psubsw mm3,mm5 // 綠色 - 減去目標點

  

  pmullw mm3,mm2 // 綠色 - 乘以ALPHA

  paddw mm1,mm0 ?// 紅色 - 加上目標點

  

  psubw mm1, ALPHABY4 // 紅色 - 減去alpha的四分之一

  

  psllw mm1,11 ? // 紅色 - 左移11位,還原[0r00 0r00 0r00 0r00]

  movq mm0,mm7 ? // 藍色 - 複製 mm7 到 mm0,目標點

  

  pand mm0, MASKBLUE // 藍色 - 與上藍色掩碼 -> [000b 000b 000b 000b],目標點

  psrlw mm3,8 // 綠色 - 除以 256 (右移8位)

  

  paddw mm3,mm5 ?// 綠色 - 加上目標點

  movq mm4, mm6 ?// 藍色 - 複製 mm6 到 mm4,源點

  

  psubw mm3, ALPHABY4 // 綠色 - 減去alpha的四分之一

  

  pand mm4, MASKBLUE?// 藍色 - 與上藍色掩碼 -> [000b 000b 000b 000b],源點

  psllw mm3,5 // 綠色 - 左移5位,還原[00g0 00g0 00g0 00g0]

  

  paddw mm4, ADD64// 藍色 - 加上64

  

  psubsw mm4,mm0 // 藍色 - 減去目標點

  por mm1,mm3 // 組合紅色和綠色分量

  

  pmullw mm4,mm2 // 藍色 - 乘以alpha

  movq mm3,COLORKEY64 // COLORKEY64 -> mm3

  

  psrlw mm4,8 // 藍色 - 除以256(右移8位)

  pcmpeqw mm3,mm6 // 比較 colorKey 和原來的sour,如果相等,則相應的mm3的位為1

  

  paddw mm4,mm0 ?// 藍色 - 加上目標點

  movq mm5,mm3 ? // mm3 -> mm5

  

  psubw mm4, ALPHABY4 // 藍色 - 減去alpha的四分之一

  

  por mm1,mm4 // 合成3種顏色

  pand mm5,mm7 ? // mm5與上目標點 - 取出源點是colorkey的點的目標點的顏色值

  

  pandn mm3,mm1 ?// mm3取反然後與上mm1,mm1放的是運算結果,這樣就把源點是colorkey的點的結果清0

  

  por mm3,mm5 // mm3或上mm5,一個是源點是colorkey的點的目標點一個是源點不是colorkey的結果,兩個 ? ? ? ? ? ? ? ? ? ? ? ? // 對立位都是0,這樣就組合乘一個結果點

  

  movq [edi],mm3 ? ? ? // 最終結果放回目標點

  

  add edi,8 ?// 下一個目標點,一次64 bits = 8 bytes

  mov eax,dword ptr [j] ?// j -> eax

  

  add esi,8 ?// 下一個源點

  sub eax,1 ?// j--

  

  mov dword ptr [lpDest], edi // 儲存lpDest

  mov dword ptr [j],eax ?// 賦回 j

  

  mov dword ptr [lpSour], esi // 儲存lpSour

  cmp eax,0 ?// 這一行是不是處理完了

  

  jg SPAN_RUN_565 // 下一個點

  

  emms ? // 清除mmx暫存器

  

  pop es

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-952249/,如需轉載,請註明出處,否則將追究法律責任。

相關文章