下文帮助你来理解OpenGL模板测试

以cocos中的ClippingNode为例子

比如我希望透过一个圆形显示一个正方形

圆形只作为参考,不绘制

正方形只绘制在圆内的部分,圆外的不绘制

 

这种裁剪使用的方法是OpenGL模板测试

我们可以把正方形称为内容,把圆形称为遮罩

模板是一个8位无符号整数即0~255(00000000~11111111)

如何根据遮罩裁剪内容很容易理解

但理解实现过程----模板测试,就没那么容易了

下面我们对于ClippingNode最简单的使用(非嵌套模式)

详细分析一下源代码(请按照5-1,5-2,5-3,5-4,5-5顺序阅读)

代码版本cocos2d-x3.5

转载请注明原文地址http://www.cnblogs.com/billyrun/articles/6122611.html

参考文献

http://www.blogjava.net/qileilove/archive/2014/01/23/409269.html

http://blog.csdn.net/sydnash/article/details/46754727

https://www.opengl.org/sdk/docs/man2/xhtml/glStencilMaskSeparate.xml

 

/*
 * Copyright (c) 2012      Pierre-David Bélanger
 * Copyright (c) 2012      cocos2d-x.org
 * Copyright (c) 2013-2014 Chukong Technologies Inc.
 *
 * cocos2d-x: http://www.cocos2d-x.org
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include "2d/CCClippingNode.h"
#include "2d/CCDrawingPrimitives.h"
#include "renderer/CCGLProgramCache.h"
#include "renderer/ccGLStateCache.h"
#include "renderer/CCRenderer.h"
#include "base/CCDirector.h"


NS_CC_BEGIN

static GLint g_sStencilBits = -1;
// store the current stencil layer (position in the stencil buffer),
// this will allow nesting up to n ClippingNode,
// where n is the number of bits of the stencil buffer.
static GLint s_layer = -1;

static void setProgram(Node *n, GLProgram *p)
{
    n->setGLProgram(p);
    
    auto& children = n->getChildren();
    for(const auto &child : children) {
        setProgram(child, p);
    }
}

ClippingNode::ClippingNode()
: _stencil(nullptr)
, _alphaThreshold(0.0f)
, _inverted(false)
, _currentStencilEnabled(GL_FALSE)
, _currentStencilWriteMask(~0)
, _currentStencilFunc(GL_ALWAYS)
, _currentStencilRef(0)
, _currentStencilValueMask(~0)
, _currentStencilFail(GL_KEEP)
, _currentStencilPassDepthFail(GL_KEEP)
, _currentStencilPassDepthPass(GL_KEEP)
, _currentDepthWriteMask(GL_TRUE)
,  _currentAlphaTestEnabled(GL_FALSE)
, _currentAlphaTestFunc(GL_ALWAYS)
, _currentAlphaTestRef(1)
{

}

ClippingNode::~ClippingNode()
{
    if (_stencil)
    {
        _stencil->stopAllActions();
        _stencil->release();
    }
}

ClippingNode* ClippingNode::create()
{
    ClippingNode *ret = new (std::nothrow) ClippingNode();
    if (ret && ret->init())
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    
    return ret;
}

ClippingNode* ClippingNode::create(Node *pStencil)
{
    ClippingNode *ret = new (std::nothrow) ClippingNode();
    if (ret && ret->init(pStencil))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    
    return ret;
}

bool ClippingNode::init()
{
    return init(nullptr);
}

bool ClippingNode::init(Node *stencil)
{
    CC_SAFE_RELEASE(_stencil);
    _stencil = stencil;
    CC_SAFE_RETAIN(_stencil);
    
    _alphaThreshold = 1;
    _inverted = false;
    // get (only once) the number of bits of the stencil buffer
    static bool once = true;
    if (once)
    {
        glGetIntegerv(GL_STENCIL_BITS, &g_sStencilBits);
        if (g_sStencilBits <= 0)
        {
            CCLOG("Stencil buffer is not enabled.");
        }
        once = false;
    }
    
    return true;
}

void ClippingNode::onEnter()
{
#if CC_ENABLE_SCRIPT_BINDING
    if (_scriptType == kScriptTypeJavascript)
    {
        if (ScriptEngineManager::sendNodeEventToJSExtended(this, kNodeOnEnter))
            return;
    }
#endif
    
    Node::onEnter();
    
    if (_stencil != nullptr)
    {
        _stencil->onEnter();
    }
    else
    {
        CCLOG("ClippingNode warning: _stencil is nil.");
    }
}

void ClippingNode::onEnterTransitionDidFinish()
{
    Node::onEnterTransitionDidFinish();
    
    if (_stencil != nullptr)
    {
        _stencil->onEnterTransitionDidFinish();
    }
}

void ClippingNode::onExitTransitionDidStart()
{
    if (_stencil != nullptr)
    {
        _stencil->onExitTransitionDidStart();
    }
   
    Node::onExitTransitionDidStart();
}

void ClippingNode::onExit()
{
    if (_stencil != nullptr)
    {
        _stencil->onExit();
    }
    
    Node::onExit();
}

void ClippingNode::drawFullScreenQuadClearStencil()
{
    Director* director = Director::getInstance();
    CCASSERT(nullptr != director, "Director is null when seting matrix stack");
    
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    director->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    
    Vec2 vertices[] = {
        Vec2(-1, -1),
        Vec2(1, -1),
        Vec2(1, 1),
        Vec2(-1, 1)
    };
    
    auto glProgram = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_U_COLOR);
    
    int colorLocation = glProgram->getUniformLocation("u_color");
    CHECK_GL_ERROR_DEBUG();
    
    Color4F color(1, 1, 1, 1);
    
    glProgram->use();
    glProgram->setUniformsForBuiltins();
    glProgram->setUniformLocationWith4fv(colorLocation, (GLfloat*) &color.r, 1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION );
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    
    CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4);
    
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}

void ClippingNode::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    if (!_visible || !hasContent())
        return;
    
    uint32_t flags = processParentFlags(parentTransform, parentFlags);

    // IMPORTANT:
    // To ease the migration to v3.0, we still support the Mat4 stack,
    // but it is deprecated and your code should not rely on it
    Director* director = Director::getInstance();
    CCASSERT(nullptr != director, "Director is null when seting matrix stack");
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);

    //Add group command
        
    _groupCommand.init(_globalZOrder);
    renderer->addCommand(&_groupCommand);

    renderer->pushGroup(_groupCommand.getRenderQueueID());

    _beforeVisitCmd.init(_globalZOrder);
    //5-1 执行onBeforeVisit 初始化模板缓冲区
    _beforeVisitCmd.func = CC_CALLBACK_0(ClippingNode::onBeforeVisit, this);
    renderer->addCommand(&_beforeVisitCmd);
    if (_alphaThreshold < 1)
    {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
#else
        // since glAlphaTest do not exists in OES, use a shader that writes
        // pixel only if greater than an alpha threshold
        GLProgram *program = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE_ALPHA_TEST_NO_MV);
        GLint alphaValueLocation = glGetUniformLocation(program->getProgram(), GLProgram::UNIFORM_NAME_ALPHA_TEST_VALUE);
        // set our alphaThreshold
        program->use();
        program->setUniformLocationWith1f(alphaValueLocation, _alphaThreshold);
        // we need to recursively apply this shader to all the nodes in the stencil node
        // FIXME: we should have a way to apply shader to all nodes without having to do this
        setProgram(_stencil, program);
        
#endif

    }
    //5-2 绘制遮罩图形,得到需要的模板缓冲区
    // 假设遮罩图形_stencil是一个Δ三角形
    // 以正模式为例,_stencil->visit执行后,模板缓冲区值如下
    /*
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 1 0 0 0 0
    0 0 1 1 1 0 0 0
    0 1 1 1 1 1 0 0
    1 1 1 1 1 1 1 0
    */
    _stencil->visit(renderer, _modelViewTransform, flags);

    _afterDrawStencilCmd.init(_globalZOrder);
    //5-3 onAfterDrawStencil 遮罩图形绘制后,重社模板测试函数 
    _afterDrawStencilCmd.func = CC_CALLBACK_0(ClippingNode::onAfterDrawStencil, this);
    renderer->addCommand(&_afterDrawStencilCmd);

    int i = 0;
    bool visibleByCamera = isVisitableByVisitingCamera();
    
    //5-4 
    // 模板缓冲区和模板测试函数已经设置好了
    // 绘制内容(子节点)
    if(!_children.empty())
    {
        sortAllChildren();
        // draw children zOrder < 0
        for( ; i < _children.size(); i++ )
        {
            auto node = _children.at(i);
            
            if ( node && node->getLocalZOrder() < 0 )
                node->visit(renderer, _modelViewTransform, flags);
            else
                break;
        }
        // self draw
        if (visibleByCamera)
            this->draw(renderer, _modelViewTransform, flags);
        
        for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
            (*it)->visit(renderer, _modelViewTransform, flags);
    }
    else if (visibleByCamera)
    {
        this->draw(renderer, _modelViewTransform, flags);
    }

    _afterVisitCmd.init(_globalZOrder);
    //5-5 结束绘制 openGL恢复原值
    _afterVisitCmd.func = CC_CALLBACK_0(ClippingNode::onAfterVisit, this);
    renderer->addCommand(&_afterVisitCmd);

    renderer->popGroup();
    
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}

void ClippingNode::setCameraMask(unsigned short mask, bool applyChildren)
{
    Node::setCameraMask(mask, applyChildren);
    
    if (_stencil)
        _stencil->setCameraMask(mask, applyChildren);
}

Node* ClippingNode::getStencil() const
{
    return _stencil;
}

void ClippingNode::setStencil(Node *stencil)
{
    CC_SAFE_RETAIN(stencil);
    CC_SAFE_RELEASE(_stencil);
    _stencil = stencil;
}

bool ClippingNode::hasContent() const
{
    return _children.size() > 0;
}

GLfloat ClippingNode::getAlphaThreshold() const
{
    return _alphaThreshold;
}

void ClippingNode::setAlphaThreshold(GLfloat alphaThreshold)
{
    _alphaThreshold = alphaThreshold;
}

bool ClippingNode::isInverted() const
{
    return _inverted;
}

void ClippingNode::setInverted(bool inverted)
{
    _inverted = inverted;
}

void ClippingNode::onBeforeVisit()
{
    ///////////////////////////////////
    // INIT

    //5-1-1
    // ClippingNode未嵌套时 s_layer值为0
    // 简单起见,下文的注释讨论不嵌套的情况
    // increment the current layer
    s_layer++;

    //5-1-2
    //ClippingNode未嵌套时 mask_layer值为1 (00000001)
    //_mask_layer_le 与 mask_layer相等
    //模板值格式为8位 即0~255 0x00~0xff
    // [模板缓冲区与颜色缓冲区类似,即每一个像素都有一个颜色值/模板值]
    //若开启模板测试,每个像素的模板值就会派上用场,决定这个片段绘制or丢弃

    // mask of the current layer (ie: for layer 3: 00000100)
    GLint mask_layer = 0x1 << s_layer;
    // mask of all layers less than the current (ie: for layer 3: 00000011)
    GLint mask_layer_l = mask_layer - 1;
    // mask of all layers less than or equal to the current (ie: for layer 3: 00000111)
    _mask_layer_le = mask_layer | mask_layer_l;

    //5-1-3
    // manually save the stencil state
    // 保存原有模板状态 在本节点绘制完成后恢复
    // 这种思路体现了OpenGL流水线工作方式
    // 虽然可以渲染多个图片,用多个模板,但颜色缓冲区/模板缓冲区只有一份,渲染一次改变一次

    _currentStencilEnabled = glIsEnabled(GL_STENCIL_TEST);
    glGetIntegerv(GL_STENCIL_WRITEMASK, (GLint *)&_currentStencilWriteMask);
    glGetIntegerv(GL_STENCIL_FUNC, (GLint *)&_currentStencilFunc);
    glGetIntegerv(GL_STENCIL_REF, &_currentStencilRef);
    glGetIntegerv(GL_STENCIL_VALUE_MASK, (GLint *)&_currentStencilValueMask);
    glGetIntegerv(GL_STENCIL_FAIL, (GLint *)&_currentStencilFail);
    glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, (GLint *)&_currentStencilPassDepthFail);
    glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, (GLint *)&_currentStencilPassDepthPass);

    // enable stencil use
    glEnable(GL_STENCIL_TEST);
    // check for OpenGL error while enabling stencil test
    CHECK_GL_ERROR_DEBUG();

    //5-1-4
    // mask_layer值为1(00000001)
    // 只有1的位可以写入,其他的位不能改变(嵌套时限制可写的位数)// https://www.opengl.org/sdk/docs/man2/xhtml/glStencilMaskSeparate.xml
    
    // all bits on the stencil buffer are readonly, except the current layer bit,
    // this means that operation like glClear or glStencilOp will be masked with this value
    glStencilMask(mask_layer);

    // manually save the depth test state

    glGetBooleanv(GL_DEPTH_WRITEMASK, &_currentDepthWriteMask);

    // disable depth test while drawing the stencil
    //glDisable(GL_DEPTH_TEST);
    // disable update to the depth buffer while drawing the stencil,
    // as the stencil is not meant to be rendered in the real scene,
    // it should never prevent something else to be drawn,
    // only disabling depth buffer update should do
    glDepthMask(GL_FALSE);

    ///////////////////////////////////
    // CLEAR STENCIL BUFFER
    
    //5-1-5
    // glStencilFunc(GL_NEVER, mask_layer, mask_layer);
    // GL_NEVER 表示[绘制命令一律不绘制到屏幕],若这里是其他值如GL_EQUAL
    // 则后两个参数会决定是否绘制到屏幕
    // eg.
    //GL_EQUAL
    //    Passes if (ref & mask) = (stencil & mask).
    // ref 和 mask 就是2,3参数,这里我们传的一样,&操作之后不变
    // stencil指模板缓冲区中每个像素点上的当前模板值
    // 模板缓冲区由 图形,glStencilFunc测试结果,glStencilOp中的参数共同决定
    
    //5-1-6
    // glStencilOp(!_inverted ? GL_ZERO : GL_REPLACE, GL_KEEP, GL_KEEP);
    // 此函数用来改写模板缓冲区(中每个像素点的模板值)
    // 三个参数分别表示
    // fail模板测试未通过时该如何变化;zfail表示模板测试通过,但深度测试未通过时该如何变化;zpass表示模板测试和深度测试或者未执行深度测试均通过时该如何变化
    // 这里后两个参数都是GL_KEEP表示不改变模板缓冲区
    // 而第一个参数,
    // 在正模式下取GL_ZERO,表示"绘制命令试图绘制的区域(的每个像素位置),若不能通过测试,模板缓冲值写为0"
    // 在反模式下取GL_REPLACE,用ref的值1来replace,表示"绘制命令试图绘制的区域(的每个像素位置),若不能通过测试,模板换充值写为1"
    // 结合了上文之后我们知道
    // 1.glStencilFunc中GL_NEVER表示试图绘制的内容都不能通过测试
    // 2.glStencilMask(mask_layer)限制了只能写1位(最低位),这里具体的写入应该是有限制的,写为0/1都是允许的

    // manually clear the stencil buffer by drawing a fullscreen rectangle on it
    // setup the stencil test func like this:
    // for each pixel in the fullscreen rectangle
    //     never draw it into the frame buffer
    //     if not in inverted mode: set the current layer value to 0 in the stencil buffer
    //     if in inverted mode: set the current layer value to 1 in the stencil buffer
    glStencilFunc(GL_NEVER, mask_layer, mask_layer);
    glStencilOp(!_inverted ? GL_ZERO : GL_REPLACE, GL_KEEP, GL_KEEP);

    //5-1-7
    // 设置完成后下面开始绘制,这里绘制了一个全屏矩形
    // 因此对于全屏所有像素位置,都不能通过测试
    // 模板缓冲区全部写入0(正模式)或1(反模式)
    // 以正模式为例,假设屏幕像素为8*6
    // drawFullScreenQuadClearStencil执行后,模板缓冲区值如下
    /*
        0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 0
    */
    // draw a fullscreen solid rectangle to clear the stencil buffer
    //ccDrawSolidRect(Vec2::ZERO, ccpFromSize([[Director sharedDirector] winSize]), Color4F(1, 1, 1, 1));
    drawFullScreenQuadClearStencil();

    ///////////////////////////////////
    // DRAW CLIPPING STENCIL
    
    //5-1-8
    // 上面可以理解为配置遮罩图形前的模板缓冲区初始化
    // 在本函数结束后会执行模板绘制 5-2 绘制模板_stencil->visit
    // 在这里改变模板函数
    // glStencilFunc(GL_NEVER, mask_layer, mask_layer);
    // 这条语句不变,因为遮罩和全屏矩形一样,其本身不在屏幕绘制
    // glStencilOp(!_inverted ? GL_REPLACE : GL_ZERO, GL_KEEP, GL_KEEP);
    // 正模式GL_REPLACE/反模式GL_ZERO
    // 接下来遮罩图形的绘制命令执行后
    // 其图形相对应的模板缓冲区值变为1/0(见5.2图解)

    // setup the stencil test func like this:
    // for each pixel in the stencil node
    //     never draw it into the frame buffer
    //     if not in inverted mode: set the current layer value to 1 in the stencil buffer
    //     if in inverted mode: set the current layer value to 0 in the stencil buffer
    glStencilFunc(GL_NEVER, mask_layer, mask_layer);
    glStencilOp(!_inverted ? GL_REPLACE : GL_ZERO, GL_KEEP, GL_KEEP);

    // enable alpha test only if the alpha threshold < 1,
    // indeed if alpha threshold == 1, every pixel will be drawn anyways
    if (_alphaThreshold < 1) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
        // manually save the alpha test state
        _currentAlphaTestEnabled = glIsEnabled(GL_ALPHA_TEST);
        glGetIntegerv(GL_ALPHA_TEST_FUNC, (GLint *)&_currentAlphaTestFunc);
        glGetFloatv(GL_ALPHA_TEST_REF, &_currentAlphaTestRef);
        // enable alpha testing
        glEnable(GL_ALPHA_TEST);
        // check for OpenGL error while enabling alpha test
        CHECK_GL_ERROR_DEBUG();
        
        //5-1-9
        // 若用户在cocos中设置了遮罩图片的alpha过滤值
        // 只令遮罩图片中的不透明部分起遮罩效果,设置了_alphaThreshold
        // openGL的做法是在绘制遮罩图片前开启alpha测试
        // 遮罩图片中alpha值不足的片段直接被丢弃
        // 只绘制通过alpha测试的片段
        // 目前已经设置了许多地方,这是由openGL面向过程的特性决定的
        // 也正因如此,最后一步结束绘制后恢复原值十分必要!

        // pixel will be drawn only if greater than an alpha threshold
        glAlphaFunc(GL_GREATER, _alphaThreshold);
#else
        
#endif
    }

    //Draw _stencil
}

void ClippingNode::onAfterDrawStencil()
{
    // restore alpha test state
    if (_alphaThreshold < 1)
    {
        //5.3.1
        // 如果5-1-9那里设置了alpha测试(为了绘制遮罩改变模板缓冲区)
        // 那么这里显然应该关闭alpha测试,尝试绘制全部的子节点而不是根据alpha值丢弃一部分
        // 再次体现openGL面向过程

#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
        // manually restore the alpha test state
        glAlphaFunc(_currentAlphaTestFunc, _currentAlphaTestRef);
        if (!_currentAlphaTestEnabled)
        {
            glDisable(GL_ALPHA_TEST);
        }
#else
// FIXME: we need to find a way to restore the shaders of the stencil node and its childs
#endif
    }

    // restore the depth test state
    glDepthMask(_currentDepthWriteMask);
    //if (currentDepthTestEnabled) {
    //    glEnable(GL_DEPTH_TEST);
    //}

    ///////////////////////////////////
    // DRAW CONTENT
    //5.3.2
    // 这里第三次改变模板函数
    // glStencilFunc(GL_EQUAL, _mask_layer_le, _mask_layer_le);
    // 首先在非嵌套情况下_mask_layer_le与mask_layer相等都是1(00000001)
    // GL_EQUAL表示
    // 接下来的内容(5-4) 通过模板测试的部分绘制在屏幕上,其余丢弃
    // 再看一遍怎样算通过
    //GL_EQUAL
    //    Passes if (ref & mask) = (stencil & mask).
    // 左边==1 特好理解1(00000001)&1(00000001)==1
    // 右边stencil表示模板缓存区中某个位置的模板值
    // 再放出5-2状态图
    /*
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 1 0 0 0 0
    0 0 1 1 1 0 0 0
    0 1 1 1 1 1 0 0
    1 1 1 1 1 1 1 0
    */
    // 一目了然!
    // 值为0的位置stencil & mask == 0 不等于左边1 丢弃
    // 值为1的位置stencil & mask == 1 通过测试 绘制到屏幕
    // 1的区域代表了遮罩图片的区域
    // 正模式下,子节点只有落在区域内的部分绘制,反之丢弃
    // 反模式下,子节点只有落在区域外的部分绘制

    //5.3.3
    // glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    // 模板缓冲已经设置好了,因此无论是否通过测试/绘制子节点
    // 都不在改变模板缓冲区

    // setup the stencil test func like this:
    // for each pixel of this node and its childs
    //     if all layers less than or equals to the current are set to 1 in the stencil buffer
    //         draw the pixel and keep the current layer in the stencil buffer
    //     else
    //         do not draw the pixel but keep the current layer in the stencil buffer
    glStencilFunc(GL_EQUAL, _mask_layer_le, _mask_layer_le);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    // draw (according to the stencil test func) this node and its childs
}


void ClippingNode::onAfterVisit()
{
    ///////////////////////////////////
    // CLEANUP

    // manually restore the stencil state
    glStencilFunc(_currentStencilFunc, _currentStencilRef, _currentStencilValueMask);
    glStencilOp(_currentStencilFail, _currentStencilPassDepthFail, _currentStencilPassDepthPass);
    glStencilMask(_currentStencilWriteMask);
    if (!_currentStencilEnabled)
    {
        glDisable(GL_STENCIL_TEST);
    }

    // we are done using this layer, decrement
    s_layer--;
}

NS_CC_END