OpenGL: 透明 不透明 混合【3D游戏引擎设计】

源颜色:(Rs, Gs, Bs, As)

目标颜色:(Rd, Gd, Bd, Ad)

最终混合结果的颜色:(Rf, Gf, Bf, Af)

此处讨论的所有颜色通道的值都是在[0, 1]范围之间。材质颜色和纹理图片的颜色都具有alpha通道。

 

一,使用alpha通道融合,最经典的用法就是作为混合因子,如果alpha = 1 完全不透明;alpha = 0完全透明。仅仅混合RGB通道的公式如下:

(Rf, Gf, Bf)

 = (1 - As) * (Rs, Gs, Bs) + As * (Rd, Gd, Bd)

 = ( (1 - As) * Rs + As * Rd, (1 - As) * Gs + As * Gd, (1 - As) * Bs + As * Bd )

每个颜色成分都要进行代数操作。假设你已经将目标颜色绘制到了帧缓存中,此时帧缓存中的颜色变成了目标颜色,下一次将要绘制的颜色是源颜色,源颜色的alpha值被用来混合源颜色和当前帧缓存中的内容。

 

二,有时候也可能将目标颜色绘制到屏幕缓存区中,然后将其和源颜色混合,然后使用屏幕缓存区中的颜色和帧缓存区中的当前内容混合。这种情形下,我们也必须记录屏幕缓存中的颜色的alpha值,我们需要alpha值混合的公式:

As = (1 - As) * As + As * Ad

 

将四个通道组合到一个公式下,最经典的alpha混合方程为:

(Rf, Gf, Bf, Af)

 = ( (1 - As) * Rs + As * Rd, (1 - As) * Gs + As * Gd, (1 - As) * Bs + As * Bd,  (1 - As) * As + As * Ad )

 

如果混合结果的颜色又作为另外一个混合操作的目标颜色,那么:

(Rd, Gd, Bd, Ad) = (Rf, Gf, Bf, Af)

 

图形API更加一般的颜色混合,不管颜色来自于顶点的属性,还是纹理图片,一般的公式是:

( Rf, Gf, Bf, Af)

= ( mr*Rs  + nr*Rd,  mg*Gs  + ng*Gd,  mb*Bs  + nb*Bd, ma*As  + na*Ad)

或者写成矩阵的形式:

 

 

混合因子为(mr, mg, mb, ma (nr, ng, nb, na) ,脚注为他们影响的颜色通道。这些系数全部处于[0,1]之间。在图形API中源混合因子和目标混合因子都是由枚举定义指定。例如OpenGL中的混合因子枚举:

GL_DST_ALPHA             ( Ad , Ad , Ad , Ad )

GL_DST_COLOR             ( Rd , Gd , Bd , Ad )

GL_ONE                   (1,1,1,1)

GL_ONE_MINUS_DST_ALPHA   (1,1,1,1) - (Ad,Ad,Ad,Ad)

GL_ONE_MINUS_DST_COLOR   (1,1,1,1) - (Rd,Gd,Bd,Ad)

GL_ONE_MINUS_SRC_ALPHA   (1,1,1,1) - (As,As,As,As)

GL_SRC_ALPHA             ( As , As , As , As )

GL_SRC_ALPHA_SATURATE    (f,f,f,1) : f = min(As,1-Ad)

GL_ZERO                  ( 0 , 0 , 0 , 0 )

 

下面是一个有趣的例子,比较几种混合的模式。

1,两种纹理相乘

源颜色的混合因子为目标颜色,目标颜色的混合因子为0.也就是:

相乘的颜色通道都是处于[0,1]之间,乘积也是在[0,1]之间,因此不需要截断。这种混合用于dark maps,此时源纹理代表一种光源。之所以使用dark是因为乘积能降低最终混合颜色的强度,相对于目标颜色。

 

2,两种纹理相加

颜色值相加有可能导致结果超出[0,1],所以最终颜色值将被截断到[0,1]区间。这种混合用于light maps,此时源纹理也代表一种光源。最终颜色的强度增强,也表现的更加饱和。

 

3,一种折中的做法是soft addition(柔求和),公式如下:

源混合因子为ONE_MINUS_DST_COLOR,目标混合因子为ONE

这种方法不会导致过饱和,该方法是目标颜色和源颜色的一部分相加,如果目标颜色非常亮(alpha值接近1),那么源颜色的混合系数非常小,因此源颜色不会导致结果过白。同理如果目标颜色过黑(alpha值接近0),那么目标颜色对最终结果的贡献很小,而源颜色的系数很大,因此源颜色主导,而最后的结果是增白的黑暗区域。

 

三,混合的思想也包括alpha测试

所谓alpha测试就是当且仅当源混合因子和特定的参数值相当的时候才将RGBA源颜色和RGBA目标颜色混合。Alpha测试的伪代码如下:

source = (Rs, Gs, Bs, As);
destination = (Rd, Gd, Bd, Ad);
reference = Aref;
if ( ComparesFavorably(As, Aref) )
{
    result = BlendTogether(source, destination);
}

ComparesFavorably(x, y)是一个标准的比较函数:

x<y, x<=y,x>y,x>=y,x=y,x!=y.

此外还有两个函数:alwaysnever。前者是始终进行混合,它是alpha混合系统的默认值,后者就是禁止混合。

 

正确地绘制一系列物体,包括半透明的物体,规则是先绘制不透明的物体,然后按视线方向从后向前,绘制半透明物体。游戏程序员一直想采取捷径以获得一种快速的系统,或者是避免一些复杂操作的系统。对于这个例子,捷径是跳过排序步骤使用alpha测试。对象列表被渲染两次,两次中都开启深度测试才能正确排序对象。两次中都开启alpha测试。第一次遍历,测试函数设置为允许和任何alpha等于1的颜色混合;即,不透明的对象被绘制,半透明的对象不会。第二次遍历,测试函数设置为允许和任何alpha不等于1的颜色混合,这次半透明的对象被绘制,不透明的对象不会。

起初,该系统有问题(忽略了从后向前排序),开启深度缓存,记得你有能力控制读写操作。第一遍遍历中绘制不透明的对象,读写深度缓存保证最终渲染结果的正确性。在绘制一个像素到到帧缓存中之前,先要读取相应位置的深度数组。如果当前的像素通过了深度测试,像素将被写到帧缓存中去。结果,为了更新这个像素的深度值,深度缓存必须被写操作。如果当前的像素没有通过深度测试,该像素就不会被绘制,深度缓存也不会更新。第二次遍历中半透明的物体被绘制。这些对象不是从后到前排序的,有可能两个半透明对象是从前到后绘制,结果是先绘制的对象比后绘制的对象更加接近观察者,你能看穿第一个对象因为它是半透明的,因此你将会看到后绘制的对象就在它后面。为了确保这种情况的发生,必须在第二次遍历中禁止掉深度缓存的写操作。如果你不这样做,你可以考虑一下会发生什么情况。第一个对象被绘制,深度缓存被写成对应这个对象的深度值,当你试图绘制第二个对象的时候,它的深度值比第一个对象的深度值,因此深度测试失败,第二个对象不会被绘制出来,尽管第二个对象按理应该透过第一个对象可见的。禁止深度缓存的写操作将会阻止这种错误的发生,这个过程的伪代码如下:

//initialization
ObjectList objects = <objects to draw, some opaque, some transparent>;
EnableDepthRead();
SetDepthCompare(less_than_or_equal);
EnableAlphaBlend(SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
EnableAlphaTesting(1); //alpha reference = 1
//first pass
SetAlphaTestCompare(equal);
EnableDepthWrite();
Draw(objects);
//second pass
SetAlphaTestCompare(not_equal);
DisnableDepthWrite();
Draw(objects);


Alpha测试的另外一个应用是绘制纹理的alpha值非01的对象。一个经典的例子是给一个物体贴花。贴花集合体是一个有一个相关纹理的矩形,纹理图片上有个艺术作品,而且这个贴花并没有完全覆盖所有的图片的像素,没有覆盖的区域将是透明的。即,如果贴花被绘制到另外一个对象上面,你看到了图片中的贴花,同时你也看到了其他的对象。为了实现这种效果,图片中,艺术品绘制区域的alpha值被设置为1,其他区域alpha0。当绘制这种对象时,alpha参考值被设为0.5(可以是不等于01的任何值),测试函数被设为“greate_than”,当贴花纹理被绘制到对象时候,只有alpha值等于1(0.5)的部分被绘制,alpha值等于0(0.5)的部分将不会被绘制。正因为他们不会被绘制,所以对深度缓存没有影响,因此没有必要使用上个例子中讨论的两次遍历技术。

http://blog.csdn.net/ryfdizuo/article/details/5035371

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值