Alpha混合

<转贴1>

alpha混合技术对熟悉游戏的人来说不会陌生,这种技术在如今的游戏特效里已经被用烂了。3D的游戏就不说了,2D的游戏里,这种技术也是满眼皆是。
  alpha混合听上去很神秘,实际非常简单,其作用就是要实现一种半透明效果。假设一种不透明东西的颜色是A,另一种透明的东西的颜色是B,那么透过B去看A,看上去的颜色C就是B和A的混合颜色,可以用这个式子来近似,设B物体的透明度为alpha(取值为0-1,0为完全透明,1为完全不透明)

R(C)=alpha*R(B)+(1-alpha)*R(A)
G(C)=alpha*G(B)+(1-alpha)*G(A)
B(C)=alpha*B(B)+(1-alpha)*B(A)

  R(x)、G(x)、B(x)分别指颜色x的RGB分量。看起来这个东西这么简单,可是用它实现的效果绝对不简单,应用alpha混合技术,可以实现出最眩目的火光、烟雾、阴影、动态光源等等一切你可以想象的出来的半透明效果。

  火光、烟雾的效果是事先做好一个火或雾的图和一个alpha通道图(用过Photoshop的人都该知道什么是alpha通道),画上去的时候每点每点计算,得到的就是火光掩映的效果。雾化效果在3D里还需要模糊一下,在这里就免了,本来alpha混合就有不小的计算量了,算法再不优化再加上模糊或其它的一些什么原因,那么你就是在看幻灯片了。(关于优化,网上见仁见智,我再找时候再讲)。

  动态光源,听起来高深的一塌。那我先讲一下阴影,这个就简单了,以往的游戏也有阴影(象《仙剑》),不过我们把它升一下级,从不透明变成半透明而已。就是把一个影子图放在地表上面作alpha混合(而且可以简化,因为影子的alpha值可以是一定的,这样就可以大幅提高计算速度)就OK了。

  该讲动态光源了。我们把没有光源的地方想象成一张黑幕蒙在屏幕上,没光也就什么都看不到。那么我们就加上一个光源,相当于在黑幕上挖了一个洞,这个洞的大小就是被照亮的范围,现在我们可以看到下面的东西了。但现在这个效果说是光源,倒不如说是个窗户,要显得象光源,就要让光源的中心最亮,逐渐向四周暗下去,最后到什么都看不见,这才象个光源。具体实现就是alpha混合啦,蒙版的颜色是黑,中心alpha值为0,完全透明,到光源的尽头alpha值为1,完全不透明,成果就是这个样子,象这么回事吧!光源做好了,动态的光源就是实时生成一个动态的alpha蒙版,然后盖上去就行了。

  不难吧!游戏里(其实也不只游戏,好多算法也是这样)的一些技术听起来很玄,说通了也就是那么回事,只不过不是一下子就能想到就是了。

  Diablo里面就大量应用了alpha混合技术(至少我看上去象),那些眩目的魔法产生出来的半透明效果,还有乱飞的火球照亮迷宫,每个火球也就是个小的光源,一堆光源产生出来的蒙版(就是对应的alpha相加,超过255就截断)再蒙上去。(真正的光源应该是这样的:当alpha值超过255时,alpha=alpha-255,alpha是一个Byte时也就是回绕,同时该点蒙版的色彩变为白色,这才是对的,不过简单起见,还是原来那样就可以了)。


<转贴2>

256色视频模式由于采用了调色板,在显存里存放的像素值实际上是是调色板的索引号,256色的BMP文件也与之类似,其数据域里存放的也是调色板的索引号,这种情况下给我们的Alpha混客带来极大的不方便。


    在实际的应用中,通常是选定一个固定的调色板,然后在这个调色板下进行绘图操作。这是因为一副图像在不同的调色板下有不同的显示效果,两个采用不同调色板的位图在一般情况下是不能同屏显示的。所以通常的情况是选择一个通用的调色板,在把各个位图都抖动处理为使用这个通用调色板的位图,从而解决了同屏显示的问题。这样看来,如何选择一个通用的调色板就是关键了。

 

    大家都知道VGA/SVGA的调色板寄存器通常为6 bits,每个寄存器存放一个颜色分量,一个RGB颜色向量就需要三个寄存器来存放,因此最多能表示 2^6*2^6*2^6=256k 种颜色,这个颜色范围是很大的。而VGA/SVGA只有256组调色板寄存器,,因此要在(0,0,0)-(63,63,63)这样一个向量空间中,精选出256个颜色向量。这有点和线性代数中求向量空间的基(极大无关组)类似。我们的任务也类似于要在(0,0,0)-(63,63,63)这个颜色空间中找出一个基。简单的说就是要找出256个颜色向量,组成一个颜色调色板,并且要使这256个颜色均匀分布于(0,0,0)-(63,63,63)这个空间中,并且还要保证其独立性。详细的做法这里不再探讨,而只给出一个比较通用的调色板。
    R(i)=(i/32%8)*9;
    G(i)=(i/4%8)*9;
    B(i)=(i%4)*21;
    其中i为寄存器组号,R(i)、G(i)、B(i)分别为该寄存器的RGB颜色分量值,这是一个从i到R(i)、G(i)、B(i)的变换式。
    由此可以写出其逆变换式:
    i=R/9*32+G/9*4+B/21;

 

    做一下优化,可以不做乘法和除法运算,得到如下式子:
    R(i)=(((i>>5)%8)<<3)+((i>>5)%8);
    G(i)=(((i>>2)4%8)<<3)+((i>>2)4%8);
    B(i)=((i%4)<<4)+((i%4)<<2)+(i%4);

 

    i=((R/9)<<5)+((G/9)<<2)+B/21;

 

    根据这两个变换式我们可以写出两个宏,用于求取对应(R,G,B)的颜色号i,和颜色号i对应的(R,G,B)值。
    #define RGB(r,g,b)  ((((r)/9)<<5)+(((g)/9)<<2)+(b)/21)
    #define ARGB(r,g,b) RGB(r+4,g+4,b+10)
    #define GETRGB(i,pr,pg,pb) {*(pr)=(((i>>5)%8)<<3)+((i>>5)%8);*(pg)=(((i>>2)4%8)<<3)+((i>>2)4%8);(*pb)=((i%4)<<4)+((i%4)<<2)+(i%4);}

 

    其中ARGB(Adjusted RGB)宏是对RGB宏的矫正,因为RGB宏存在误差。

 

    这样我们就建立起了i与(R,G,B)的对应关系,这我为我们的Alpha混合铺平了道路。

 

    现在再谈谈Alpha混合。Alpha混合指的是给定两个点P1、P2,其RGB颜色分量分别为(r1,g1,b1)和(r2,g2,b2),假定P1位于P2的后面,P2的透明度为a(0%<a<100%),要求我没透过点P2看到P1的颜色值是多少。假定该值为P3(r3,g3,b3),其计算公式如下:
    r3=(1-a)*r2+a*r1;
    g3=(1-a)*g2+a*g1;
    b3=(1-a)*b2+a*b1;
    这就是通常所说的Alpha混合。

 

    优化一下得到:
    r3=r2+a*(r1-r2);
    g3=g2+a*(g1-g2);
    b3=b2+a*(b1-b2);
    少做了一次乘法运算。但由于a为浮点数,运算起来仍然很慢,所以一般不采用上面的公式,而采用整数级的Alpha混合,如下:
    r2=r2+n*(r1-r2)/256;
    g2=g2+n*(g1-g2)/256;
    b2=b2+n*(b1-b2)/256;
    以上为256级Alpha混合公式,由于VGA/SVGA调色板寄存器为6bits,所以做256色的Alpha混合意义不大。

 

    而采用一下的64级Alpha混合公式:
    r2=r2+n*(r1-r2)/64;
    g2=g2+n*(g1-g2)/64;
    b2=b2+n*(b1-b2)/64;

 

    进一步优化为L:
    r2=r2+(n*(r1-r2)>>6);
    g2=g2+(n*(g1-g2)>>6);
    b2=b2+(n*(b1-b2)>>6);

 

    仅做了一次乘法运算,这样程序应该能跑得飞快了。

 

    下面给出混合一个点的Alpha算法:
    int Alpha(int p1,int p2,int n)
    {
        int c1[3];
        int c2[3];
        int c3[3];

 

        GETRGB(p1,c1,c1+1,c1+2);
        GETRGB(p2,c2,c2+1,c2+2);

 

        c3[0]=c2[0]+(n*(c1[0]-c2[0])>>6);
        c3[1]=c2[1]+(n*(c1[1]-c2[1])>>6);
        c3[2]=c2[2]+(n*(c1[2]-c2[2])>>6);

 

        return ARGB(c3[0],c3[1],c3[2]);
    }

 

    对半透明混合,可有如下更快的公式:
    r2=r2+((r1-r2)>>1);
    g2=g2+((g1-g2)>>1);
    b2=b2+((b1-b2)>>1);
    这个公式没有乘法和除法,半透明在游戏中运用也很广。

 

    以下是半透明的Alpha混合:
    int Alpha(int p1,int p2,int n)
    {
        int c1[3];
        int c2[3];
        int c3[3];

 

        GETRGB(p1,c1,c1+1,c1+2);
        GETRGB(p2,c2,c2+1,c2+2);

 

        c3[0]=c2[0]+((c1[0]-c2[0])>>1);
        c3[1]=c2[1]+((c1[1]-c2[1])>>1);
        c3[2]=c2[2]+((c1[2]-c2[2])>>1);

 

        return ARGB(c3[0],c3[1],c3[2]);
    }

 

    对于n级Alpha混合中的乘法运算,我们也有办法进一步优化,可以采用移位乘法的技术来实现快速的乘法运算,但性能提升不大,有兴趣的朋友可以自己查阅相关资料,这里不再详述。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mator/archive/2007/01/23/1491549.aspx

 

16位Alpha混合的MMX优化

 

最近,我开始学习汇编程序,用来优化我的引擎,断断续续的学习了20多天,也有了一些心得体会,希望和他家一起分享:) 游戏中图形操作就是吃系统资源老虎,大量的cpu时钟都被他吃掉了,为了加速我的游戏引擎的图形处理速度,我对一些图形操作进行了汇编优化,并使用了威力惊人的MMX指令。为什么说威力惊人呢?看下去就明白了。

  MMX指令适合用来做大量的串操作,在MMX处理器中,提供了8个64位的寄存器,并且,MMX指令能够使用数据组的操作。所以,用来处理逐个点操作的图形处理最合适不过了。下面,我就用我的Alpha混合的代码来说明一下,MMX的强大处理能力:) 以前,我在主页上面写过一篇16位Alpha操作的文章,那个时候,我不会汇编,用C写的例子程序,在一个912X720的区域内Alpha混合的时候,FPS值只有3-4,实在是没有实用价值,现在,我手握MMX这个利剑,经过一番努力后,FPS值直线飙升,达到了14,整整有4倍的擦别啊!如果在640X480下面,全屏幕的alpha也能达到20多帧! 为什么有这么大的性能提升呢?开始我说过,mmx寄存器是64位的,分别是mm0--mm7一共八个。MMX的指令可以让mmx寄存器之间的运算按照byte,word,dword,qd分组运算。这就是问题的关键!让我们看看,我们的16位Alpha混合是一个点占用2个byte,在一个MMX寄存器中间,我们可以容纳4个这样的点,所以一次混合计算,将能够让四个点混合,这就是4倍速度提升的原因!!! 好,让我们看看实际的代码: 其中wdDest,wdRes分别是目的和源图形数据指针,__Depth使Alpha混合深度,值为0x0001000100010001 * nDepth,nMMXCount是我用来纪录一行进行多少次MMX操作的,Not_MMX_Point是拷贝矩形宽度除4的余数,因为一旦出现宽度不是4字节对齐,余下的多余点就必须按照普通的汇编代码处理了,这里,我值不过是留下了接口,没有去实现,因为寄存器不够用,需要push和pop。__Mask64为__int64类型的掩码,值为0x0001000100010001 * (short)m_nColorKey,__Mask是用来过滤多余颜色的奄码0x001f001f001f001f(因为图形为555格式)。 大家注意,每次都是用movq来移动4个点到mmx寄存器,然后实用word对齐分组计算的。 这里要说明,使用了mmx后,原来透明色是可以不处理的,现在必须处理,为了让透明部分不影响目的数据,我是用了一点小技巧,具体做法看我的代码注释。这可是我自己想出来的哦:) 要看完整的代码,请下载我的完整的JAppLib。

__asm
{
    //pusha;
    movq mm6,__Depth;
    mov eax,dword ptr wdDest;
    mov ebx,dword ptr wdRes;
    mov cx,nUseH;//纪录操作矩形高

Add_Next_Row:
    cmp cx,0;
    je All_End;
    xor dx,dx;

Next_MMX_Point:
    cmp dx,nMMXCount;
    je Not_MMX_Point;
    movq mm0,[eax];
    movq mm1,[ebx];
    movq mm7,mm1;
    pcmpeqw mm7,__Mask64;//这里用来处理透明色的,我的思路就是透明色就用目的颜色填充源
    psubusw mm1,mm7; //这样,透明部分目的和源一样,不管怎么去alpha,只要公式是正确的,
    pand mm7,mm0; //他们混合后的颜色就是不会变,add color特效也是同样的道理
    por mm1,mm7; //

    movq mm2,mm0;//g
    psrlw mm2,5;
    pand mm2,__Mask;
    movq mm3,mm1;
    psrlw mm3,5;
    pand mm3,__Mask;

    movq mm4,mm0;//r //wdDR=(((wdDR-wdRR)*nDepth+(wdRR<<5))>>5);
    psrlw mm4,10;
    pand mm4,__Mask;
    movq mm5,mm1;
    psrlw mm5,10;
    pand mm5,__Mask;

    //psllw mm0,1;//b
    pand mm0,__Mask;
    //psllw mm1,1;
    pand mm1,__Mask;

    psubsw mm0,mm1;
    pmullw mm0,mm6;
    psllw mm1,5;
    paddsw mm0,mm1;
    psrlw mm0,5;
    //psrlw mm0,5;
    //paddusw mm0,mm1;

    psubsw mm2,mm3;
    pmullw mm2,mm6;
    psllw mm3,5;
    paddsw mm2,mm3;
    psrlw mm2,5;
    //psrlw mm2,5;
    //paddusw mm2,mm3;

    psubsw mm4,mm5;
    pmullw mm4,mm6;
    psllw mm5,5;
    paddsw mm4,mm5;
    psrlw mm4,5;
    //psrlw mm4,5;
    //paddusw mm4,mm5;

    //psllw mm0,10;
    psllw mm2,5;
    psllw mm4,10;

    por mm0,mm2;
    por mm0,mm4;

    movq [eax],mm0;

    add eax,8;
    add ebx,8;
    inc dx;
    jmp Next_MMX_Point;

Not_MMX_Point:
    xor dx,dx;

Not_MMX_Next:
    cmp dx,nNotMMX;
    je Row_End;
    sub eax,2;
    sub ebx,2;
    inc dx;
    jmp Not_MMX_Next;

Row_End:
    sub eax,nUnUsed2;
    sub ebx,nUnUsed1;
    dec cx;
    jmp Add_Next_Row;
    //loop Add_Next_Row;

All_End:
    //popa;
    emms;
}

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Garfield/archive/2005/02/12/286135.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值