cocos2d-x v3.3 BlendProtocol and BlendFunc

cocos2d-x的颜色混合机制依靠OpenGL ES的glBlendFunc()实现,而BlendProtocol和BlendFunc就是对glBlendFunc()的一层封装。

颜色混合机制,顾名思义就是两种或多种颜色叠加在一起时的显示方案。比如可以一种颜色覆盖另一种颜色,可以两种或多种颜色均匀融合,也可以不均匀的融合。这就好像你在向红色颜料中加入蓝色颜料调制紫色的时候,随着蓝色颜料加入量的增多,调制出的颜色依次为:红紫色、紫红色、紫色、紫蓝色、蓝紫色。

cocos2d-x绘制图片有个顺序(zOrder),就好像上面颜料的例子,你得先挤好红色颜料,然后再挤出适量的蓝色颜料进行混合。什么?双管齐下!计算机会say:"NO, NO, NO."的。先画的图片(存储于frame buffer中的图片)的颜色被称为

“目标颜色”,后画的图片(即将要画的图片)的颜色被称为
“源颜色”。当两种颜色叠加时,OpenGL ES会把源颜色和目标颜色各自取出,并乘以一个系数(源颜色乘以的系数称为
“源因子”,目标颜色乘以的系数称为
“目标因子”),然后相加,这样就得到了新的颜色,而这个源因子和目标因子就是由颜色混合机制决定的。

具体这个叠加颜色是怎么计算出来的,请参见:
http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBlendFunc.xml

然而我觉得这个讲得更通俗易懂些:
http://shahdza.blog.51cto.com/2410787/1547633

BlendProtocol与BlendFunc并无直接继承关系,BlendFunc仅作为对于BlendProtocol的具体配置以结构体的形式出现。而BlendProtocol由于与底层的OpenGL ES关系密切,所以其上也没有父类,它仅作为一个框架类出现,规定了其子类必须实现的东西。

BlendProtocol:

1、成员方法:

(1) virtual void setBlendFunc(const BlendFunc &blendFunc) = 0;

设置使用哪种颜色混合机制。此处为纯虚函数,规定了由子类实现,且必须实现。

(2) virtual const BlendFunc &getBlendFunc() const = 0;

获取当前使用的颜色混合机制。此处为纯虚函数,规定了由子类实现,且必须实现。

BlendFunc:

1、成员变量:

public:
// 源因子。
GLenum src;
// 目标因子。
GLenum dst;
// 源颜色使用全部,目标颜色不使用,进行颜色混合(源颜色覆盖目标颜色),ccTypes.cpp中定义为{GL_ONE, GL_ZERO}。
static const BlendFunc DISABLE;
// 源颜色使用全部,目标颜色使用1-源颜色透明度的值,进行颜色混合,ccTypes.cpp中定义为{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}。
static const BlendFunc ALPHA_PREMULTIPLIED;
// 源颜色使用自身透明度的值,目标颜色使用1-源颜色透明度的值,进行颜色混合,ccTypes.cpp中定义为{GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}。
static const BlendFunc ALPHA_NON_PREMULTIPLIED;
// 源颜色使用自身透明度的值,目标颜色使用全部,ccTypes.cpp中定义为{GL_SRC_ALPHA, GL_ONE}。
static const BlendFunc ADDITIVE;

2、成员方法:

(1) bool operator==(const BlendFunc &a) const
bool operator!=(const BlendFunc &a) const
bool operator<(const BlendFunc &a) const

重载了一些算术符,无他。

实现源码:

bool operator==(const BlendFunc &a) const
{
    return src == a.src && dst == a.dst;
}

bool operator!=(const BlendFunc &a) const
{
    return src != a.src || dst != a.dst;
}

bool operator<(const BlendFunc &a) const
{
    return src < a.src || (src == a.src && dst < a.dst);
}


接下来用一个简单的实例来总结颜色混和机制的使用。

实现源码:

    auto box_red = LayerColor::create(Color4B(255, 0, 0, 255));
    box_red->setAnchorPoint(Vec2(0.5,0.5));
    box_red->setContentSize(Size(300, 300));
    box_red->ignoreAnchorPointForPosition(false);
    box_red->setPosition(visibleSize.width / 5 * 2, visibleSize.height / 5 * 2);
    this->addChild(box_red);

    auto box_blue = LayerColor::create(Color4B(0, 0, 255, 255));
    box_blue->setAnchorPoint(Vec2(0.5,0.5));
    box_blue->setContentSize(Size(300, 300));
    box_blue->ignoreAnchorPointForPosition(false);
    box_blue->setPosition(visibleSize.width / 5 * 3, visibleSize.height / 5 * 3);
    this->addChild(box_blue);

    auto box_green = LayerColor::create(Color4B(0, 255, 0, 255));
    box_green->setAnchorPoint(Vec2(0.5,0.5));
    box_green->setContentSize(Size(300, 300));
    box_green->ignoreAnchorPointForPosition(false);
    box_green->setPosition(visibleSize.width / 5 * 4, visibleSize.height / 5 * 4);
    this->addChild(box_green);

    BlendFunc cbl_blue_red = BlendFunc::DISABLE;    // 让蓝色覆盖红色。
    box_blue->setBlendFunc(cbl_blue_red);
    // no effect ↓
    BlendFunc cbl_red = BlendFunc::ADDITIVE;    // 这段代码虽然写在后面,但仍不会影响上面设置好的蓝色与红色的混合模式,关键点总结中会说明具体原因。
    box_red->setBlendFunc(cbl_red);
    // no effect ↑
    BlendFunc cbl_green_blue = BlendFunc::ADDITIVE;    // 让蓝色和绿色混合。
    box_green->setBlendFunc(cbl_green_blue);

以上的实例通过LayerColor分别画了3个颜色不同的正方形,分别让他们部分重合,并设置各自的颜色混合模式。

来看看效果:
<img title = 'QQ浏览器截屏未命名.png' src='http://cdn.cocimg.com/bbs/attachment/Fid_41/41_410339_0cf6bac63e183f9.png' > 

关键点总结:

♂ LayerColor继承自BlendProtocol,它实现了BlendProtocol所规定的setBlendFunc()和getBlendFunc()。

/// blendFunc getter
const BlendFunc &LayerColor::getBlendFunc() const
{
return _blendFunc; // LayerColor::_blendFunc
}

/// blendFunc setter
void LayerColor::setBlendFunc(const BlendFunc &var)
{
_blendFunc = var; // LayerColor::_blendFunc
}

很简单,就是设置变量的值和读取变量的值。之后引擎在渲染界面的过程中,就会使用到LayerColor::_blendFunc。

CCLayer.cpp line 593 LayerColor::onDraw()
|-- CCLayer.cpp line 613 GL::blendFunc( _blendFunc.src, _blendFunc.dst ); // LayerColor::_blendFunc
|-- ccGLStateCache.cpp line 120 blendFunc()
|-- ccGLStateCache.cpp line 130 SetBlending( sfactor, dfactor );
|-- ccGLStateCache.cpp line 107 SetBlending()

static void SetBlending(GLenum sfactor, GLenum dfactor)
{
if (sfactor == GL_ONE && dfactor == GL_ZERO)
{
glDisable(GL_BLEND);
}
else
{
glEnable(GL_BLEND);
glBlendFunc(sfactor, dfactor); // 调用了OpenGL ES的glBlendFunc()。
}
}

♂ 只有对源颜色设置颜色混合模式才能真正起作用,这就是例子中对红色的box设置blendFunc()不起作用的原因。
当红色box被绘制时,红色box作为源颜色,而frame buffer中没有目标颜色,所以红色box的blendFunc()不起任何作用。
当蓝色box被绘制时,frame buffer中的红色box作为目标颜色,所以蓝色box的blendFunc()发挥了作用。
绿色box与蓝色box的混合同理。