• cocos源码分析--ClippingNode绘图原理


    在OpenGL 绘制过程中,与帧缓冲有关的是模版,深度测试,混合操作。模版测试使应用程序可以定义一个遮罩,在遮罩内的片段将被保留或者丢弃,在遮罩外的片段操作行为则相反。深度测试用来剔除那些被场景遮挡的片段,但是对于透明物体可能有问题,需要注意绘制顺序,本文主要结合ClippingNode分析下模版测试。

    具体关于模版测试的文章有很多,这里就不再重复了,直接开始分析cocos中模版测试的应用。

    ClippingNode是一个Node元素,它接受另外一个Node来作为模版,这个模版用来影响ClippingNode本身子元素的绘制:只绘制模版以内的部分或者只绘制模版以外的部分,模版本事并不会绘制。

    ClippingNode使用GroupCommand来进行绘制,因为他要对所有子元素的绘制设置一些片段测试,用于实现模版的遮罩。比如如下代码:

    //模板
        Sprite* stencil = Sprite::create("switch-mask.png");
    
        ClippingNode* c_node = ClippingNode::create();
        c_node->setStencil(stencil);
        c_node->setInverted(false);
        c_node->setAlphaThreshold(0.0f);
        Sprite* sp = Sprite::create("scrollviewbg.png");
        c_node->addChild(sp);
        c_node->setPosition(100,100);

    图片分别为:

    switch-mask:,scrollviewbg:,switch-mask2:

    运行结果为:

    实现了裁剪。

    现在看一下源码,是如何做到的

    /*
     * Copyright (c) 2012      Pierre-David Bélanger
     * Copyright (c) 2012      cocos2d-x.org
     * Copyright (c) 2013-2014 Chukong Technologies Inc.
     *
     * 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.
     *
     */
    
    #ifndef __MISCNODE_CCCLIPPING_NODE_H__
    #define __MISCNODE_CCCLIPPING_NODE_H__
    
    #include "2d/CCNode.h"
    #include "CCGL.h"
    #include "renderer/CCGroupCommand.h"
    #include "renderer/CCCustomCommand.h"
    
    NS_CC_BEGIN
    
    /** ClippingNode is a subclass of Node.
     It draws its content (childs) clipped using a stencil.
     The stencil is an other Node that will not be drawn.
     The clipping is done using the alpha part of the stencil (adjusted with an alphaThreshold).
     */
    class CC_DLL ClippingNode : public Node
    {
    public:
        /** Creates and initializes a clipping node without a stencil.
         */
        static ClippingNode* create();
        
        /** Creates and initializes a clipping node with an other node as its stencil.
         The stencil node will be retained.
         */
        static ClippingNode* create(Node *stencil);
    
        /** The Node to use as a stencil to do the clipping.
         The stencil node will be retained.
         This default to nil.
         */
        Node* getStencil() const;
        void setStencil(Node *stencil);
        
        /** The alpha threshold.
         The content is drawn only where the stencil have pixel with alpha greater than the alphaThreshold.
         Should be a float between 0 and 1.
         This default to 1 (so alpha test is disabled).
         */
        /*
         仅在模板具有alpha大于alphaThreshold的像素的情况下绘制内容。
               应该介于0和1之间的浮点数。
               默认为1(因此禁用alpha测试)。
         */
        /*
         可以对ClippingNode设置一个alpha比较值(alphaThreshold)用来选择模版Node上只有那些alpha值大于该比较直的片段才会构成模版遮罩的一部分,在移动平台此参数会被忽略
         */
        GLfloat getAlphaThreshold() const;
        void setAlphaThreshold(GLfloat alphaThreshold);
        
        /** Inverted. If this is set to true,
         the stencil is inverted, so the content is drawn where the stencil is NOT drawn.
         This default to false.
         */
        /*
         模板是倒置的,因此在未绘制模板的地方绘制内容。
               默认为false。
         */
        bool isInverted() const;
        void setInverted(bool inverted);
    
        // Overrides
        /**
         * @js NA
         * @lua NA
         */
        virtual void onEnter() override;
        /**
         * @js NA
         * @lua NA
         */
        virtual void onEnterTransitionDidFinish() override;
        /**
         * @js NA
         * @lua NA
         */
        virtual void onExitTransitionDidStart() override;
        /**
         * @js NA
         * @lua NA
         */
        virtual void onExit() override;
        virtual void visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) override;
        
    CC_CONSTRUCTOR_ACCESS:
        ClippingNode();
        
        /**
         * @js NA
         * @lua NA
         */
        virtual ~ClippingNode();
    
        /** Initializes a clipping node without a stencil.
         */
        virtual bool init();
        
        /** Initializes a clipping node with an other node as its stencil.
         The stencil node will be retained, and its parent will be set to this clipping node.
         */
        virtual bool init(Node *stencil);
    
    protected:
        /**draw fullscreen quad to clear stencil bits
        */
        void drawFullScreenQuadClearStencil();
    
        Node* _stencil;
        GLfloat _alphaThreshold;
        bool    _inverted;
    
        //renderData and callback
        void onBeforeVisit();
        void onAfterDrawStencil();
        void onAfterVisit();
    
        GLboolean _currentStencilEnabled;
        GLuint _currentStencilWriteMask;
        GLenum _currentStencilFunc;
        GLint _currentStencilRef;
        GLuint _currentStencilValueMask;
        GLenum _currentStencilFail;//模版测试失败后的处理
        GLenum _currentStencilPassDepthFail;//模版测试通过,深度测试失败后的处理
        GLenum _currentStencilPassDepthPass;//深度测试通过之后的处理
        GLboolean _currentDepthWriteMask;
    
        GLboolean _currentAlphaTestEnabled;
        GLenum _currentAlphaTestFunc;
        GLclampf _currentAlphaTestRef;
    
        GLint _mask_layer_le;
        
        GroupCommand _groupCommand;
        //onBeforeVisit用于生成一个GroupCommand并创建一个RenderQueue,并设置一些状态,然后绘制模版元素
        CustomCommand _beforeVisitCmd;
        //onAfterDrawStencil用于绘制ClippingNode自身的子元素,这些元素的RenderCommand会被夹到第一步创建的RenderQueue中,
        //并被执行模版测试
        CustomCommand _afterDrawStencilCmd;
        //onAfterVisit用来恢复GroupCommand对OpenGL状态的修改
        CustomCommand _afterVisitCmd;
    
    private:
        CC_DISALLOW_COPY_AND_ASSIGN(ClippingNode);
    };
    
    NS_CC_END
    
    #endif // __MISCNODE_CCCLIPPING_NODE_H__
    /*
     * 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 "renderer/CCGLProgram.h"
    #include "renderer/CCGLProgramCache.h"
    #include "2d/CCDrawingPrimitives.h"
    #include "base/CCDirector.h"
    
    #include "renderer/CCRenderer.h"
    #include "renderer/CCGroupCommand.h"
    #include "renderer/CCCustomCommand.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 ClippingNode();
        if (ret && ret->init())
        {
            ret->autorelease();
        }
        else
        {
            CC_SAFE_DELETE(ret);
        }
        
        return ret;
    }
    
    ClippingNode* ClippingNode::create(Node *pStencil)
    {
        ClippingNode *ret = new 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;//默认为1,禁用aphal测试
        _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);//获取opengl中的位平面数
            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");
        //此时最顶出的modeviewMatirx是单位向量,也就是说刷新全屏幕的模版值
        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);
        
        
        DrawPrimitives::drawSolidRect(Vec2(-1,-1), Vec2(1,1), Color4F(1, 1, 1, 1));
        
        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)
            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);
        _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_WINDOWS || 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
            // XXX: we should have a way to apply shader to all nodes without having to do this
            setProgram(_stencil, program);
            
    #endif
    
        }
        //访问模版对象 所有的draw都会吧绘制命令加到ClippingNode的Group中
        _stencil->visit(renderer, _modelViewTransform, flags);
    
        _afterDrawStencilCmd.init(_globalZOrder);
        _afterDrawStencilCmd.func = CC_CALLBACK_0(ClippingNode::onAfterDrawStencil, this);
        renderer->addCommand(&_afterDrawStencilCmd);
    
        int i = 0;
        
        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
            this->draw(renderer, _modelViewTransform, flags);
            
            for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
                (*it)->visit(renderer, _modelViewTransform, flags);
        }
        else
        {
            this->draw(renderer, _modelViewTransform, flags);
        }
    
        _afterVisitCmd.init(_globalZOrder);
        _afterVisitCmd.func = CC_CALLBACK_0(ClippingNode::onAfterVisit, this);
        renderer->addCommand(&_afterVisitCmd);
    
        renderer->popGroup();
        
        director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    }
    
    Node* ClippingNode::getStencil() const
    {
        return _stencil;
    }
    
    void ClippingNode::setStencil(Node *stencil)
    {
        CC_SAFE_RETAIN(stencil);
        CC_SAFE_RELEASE(_stencil);
        _stencil = stencil;
    }
    
    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
        auto fff=this;
        // increment the current layer
        s_layer++;
        //printf("呵呵呵呵呵呵呵=%d
    ",s_layer);
        // 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;
    
        // manually save the stencil state
    
        _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();
    
        // 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
        //模板缓冲区上的所有位都是只读的,当前层位除外,
        //这意味着像glClear或glStencilOp这样的操作将被这个值掩盖
        
        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
        //在绘制模板时禁用深度测试
        // glDisable(GL_DEPTH_TEST);
        //在绘制模板时禁用对深度缓冲区的更新,
        //因为模板不是要在真实场景中渲染,
        //它永远不应该阻止其他东西被绘制,
        //只应禁用深度缓冲区更新
        glDepthMask(GL_FALSE);
    
        ///////////////////////////////////
        // CLEAR STENCIL BUFFER
    
        // 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
        /*
         //通过在其上绘制全屏矩形来手动清除模板缓冲区
              //像这样设置模板测试功能:
              //对于全屏矩形中的每个像素
              //永远不要将它绘制到帧缓冲区中
              //如果不处于反转模式:在模板缓冲区中将当前图层值设置为0
              //如果处于反转模式:在模板缓冲区中将当前图层值设置为1
         */
        glStencilFunc(GL_NEVER, mask_layer, mask_layer);
        glStencilOp(!_inverted ? GL_ZERO : GL_REPLACE, GL_KEEP, GL_KEEP);
    
        // draw a fullscreen solid rectangle to clear the stencil buffer
        //ccDrawSolidRect(Vec2::ZERO, ccpFromSize([[Director sharedDirector] winSize]), Color4F(1, 1, 1, 1));
        /*
         这里使用了绘制一个全屏矩形的方式来设置模版缓冲区,但实际上可以直接通过glClear(GL_DEPTH_BUFFER_BIT)来清理
         
         */
        drawFullScreenQuadClearStencil();
    
        ///////////////////////////////////
        // DRAW CLIPPING STENCIL
    
        // 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
        /*
         //像这样设置模板测试功能:
              //对于模板节点中的每个像素
              //永远不要将它绘制到帧缓冲区中
              //如果不处于反转模式:在模板缓冲区中将当前图层值设置为1
              //如果处于反转模式:在模板缓冲区中将当前图层值设置为0
         */
        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_WINDOWS || 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();
            // 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)
        {
    #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WINDOWS || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
            // manually restore the alpha test state
            glAlphaFunc(_currentAlphaTestFunc, _currentAlphaTestRef);
            if (!_currentAlphaTestEnabled)
            {
                glDisable(GL_ALPHA_TEST);
            }
    #else
    // XXX: 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
    
        // 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
        //像这样设置模板测试功能:
        //对于此节点及其子节点的每个像素
        //如果在模板缓冲区中所有小于或等于当前的图层都设置为1
        //绘制像素并将当前图层保留在模板缓冲区中
        //别的
        //不要绘制像素,而是将当前图层保留在模板缓冲区中
        glStencilFunc(GL_EQUAL, _mask_layer_le, _mask_layer_le);//11必须都满足才行,2个模版实现了11
        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

    代码量不大,原理其实是挺简单的:

    我已经在关键的地方给了注释,下面说一下几个关键点:

    1 绘制过程:

     首先,onBeforeVisit用于生成一个GroupCommand并创建一个RenderQueue,并设置一些状态;然后绘制模版元素,在模版生成之后,onAfterDrawStencil用于绘制ClippingNode自身的子元素,这些元素的RenderCommand会被加入到第一步创建的RenderQueue中,并被执行模版测试;最后onAfterVisit用来恢复GroupCommand对OpenGL状态的修改

    2 尝试把事例代码改为如下:

    Sprite* stencil = Sprite::create("switch-mask.png");
    
        ClippingNode* c_node = ClippingNode::create();
        c_node->setStencil(stencil);
        c_node->setInverted(false);
        c_node->setAlphaThreshold(1.0f);
        Sprite* sp = Sprite::create("scrollviewbg.png");
        c_node->addChild(sp);
        c_node->setPosition(100,100);

    运行结果如下:

     看出来和之前的是不一样的,就因为设置了AlphaThreshold,代码位置:

    这个alphaThreshold 用来选择模版Node上只有那些alpha值大约该比较值的片段才会构成模版遮罩的一部分,但是这主要依靠Alpha测试来实现,在移动端OpenGL ES中是没有alpha测试的,这里在移动平台中是在片段着色器中实现了一个类似的功能。

    通过看上面的代码,如果这个值小于1,就会给ClippingNode切换着色器管理类,换成了ccShader_PositionTextureColorAlphaTest.frag 片段着色器,如下:

    const char* ccPositionTextureColorAlphaTest_frag = STRINGIFY(
    
    
    #ifdef GL_ES
    
    precision lowp float;
    
    #endif
    
    
    varying vec4 v_fragmentColor;
    varying vec2 v_texCoord;
    uniform float CC_alpha_value;
    
    void main()
    {
        vec4 texColor = texture2D(CC_Texture0, v_texCoord);
    
    
    // mimic: glAlphaFunc(GL_GREATER)
    
    // pass if ( incoming_pixel >= CC_alpha_value ) => fail if incoming_pixel < CC_alpha_value
    
    
        if ( texColor.a <= CC_alpha_value )
            discard;
    
        gl_FragColor = texColor * v_fragmentColor;
    }
    );

    通过标红代码可以看到,此时的CC_aipha_value为0,遮罩按钮图片的四边透明的部分的alpha=0,所以绘制行discard,所以当设置alphaThreshold=0的时候,是圆角的遮罩,反之,透明的部分也会当作遮罩,从而导致遮罩为矩形。

     3 关于ClippingNode的嵌套使用,具体怎么嵌套呢,或许可以有2种方式,如下:

    方式一:

     //模板
        Sprite* stencil = Sprite::create("switch-mask.png");
    
        ClippingNode* c_node = ClippingNode::create();
        c_node->setStencil(stencil);
        c_node->setInverted(false);
        c_node->setAlphaThreshold(1.0f);
        Sprite* sp = Sprite::create("scrollviewbg.png");
        c_node->addChild(sp);
        
    
    
        Sprite* stencil2 = Sprite::create("switch-mask2.png");
         //Sprite* stencil2 = Sprite::create("scrollviewbg.png");
    
        ClippingNode* c_node2 = ClippingNode::create();
        c_node2->setStencil(stencil2);
        //c_node2->setStencil(c_node);
        c_node2->setInverted(false);
        c_node2->setAlphaThreshold(0.0f);
        c_node2->addChild(c_node);
        //c_node2->addChild(stencil2);
    
    
        c_node2->setPosition(100,100);
        c_node2->setTag(10);
       
        scene->addChild(c_node2);

    运行如下:

    方式二:

      //模板
        Sprite* stencil = Sprite::create("switch-mask.png");
    
        ClippingNode* c_node = ClippingNode::create();
        c_node->setStencil(stencil);
        c_node->setInverted(false);
        c_node->setAlphaThreshold(1.0f);
        Sprite* sp = Sprite::create("scrollviewbg.png");
        c_node->addChild(sp);
        
    
    
        Sprite* stencil2 = Sprite::create("switch-mask2.png");
         //Sprite* stencil2 = Sprite::create("scrollviewbg.png");
    
        ClippingNode* c_node2 = ClippingNode::create();
       // c_node2->setStencil(stencil2);
        c_node2->setStencil(c_node);
        c_node2->setInverted(false);
        c_node2->setAlphaThreshold(0.0f);
        //c_node2->addChild(c_node);
        c_node2->addChild(stencil2);
    
    
        c_node2->setPosition(100,100);
        c_node2->setTag(10);
       
        scene->addChild(c_node2);

    运行结果:

    什么都没有,和第一段代码的区别已经标红。显然第一种嵌套用法是正确的,第二段是无效的,为什么无效呢,请看我举的小例子分析,地址:

    https://www.cnblogs.com/xiaonanxia/p/9414193.html

    下面主要分析一下正确的那段代码:

    代码的大意就是通过switch-mask和switch-mask2 两个遮罩来裁剪 scrollviewbg.png。请看ClippingNode源码,如下:

    void ClippingNode::onBeforeVisit()
    {
        ///////////////////////////////////
        // INIT
        auto fff=this;
        // increment the current layer
        s_layer++;
        //printf("呵呵呵呵呵呵呵=%d
    ",s_layer);
        // 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;
    。。。

    主要实现用的是位移操作。

    为了支持多个ClippingNode的嵌套,它用了一个静态的s_layer变量来保存嵌套的ClippingNode的数量,然后使用这个数量来对0x1执行一次左移操作生成模版值。

    由此可见,如果当前模版缓冲区每像素的位平面为8,则ClippingNode最多支持8个ClippingNode嵌套

    具体这个嵌套的过程,我们可以通过调试Render类的visitRenderQueue,里面会运行两个GroupCommand,第一个是对应c_node2的,第二个是对应c_node,数组分布情况为如图所示:

    关键的方法是在onBeforeVisit和onAfterDrawstencil,因为s_layer是一个静态变量,所以在执行两次onBeforeVisit的时候,会叠加,等执行完第二次onBeforeVisit之后

     _mask_layer_le已经变为二进制 11 也就是说,绘制的时候,模版值必须为11才能通过,在第一次onBeforeVisit的时候,模版缓冲区中的已经绘制的点的值为01,第二次执行完onBeforeVisit的时候,模版缓冲区的已经绘制的点的值为有的为11,有的为10,2个遮罩的相交部分为11,只有相交的部分才能绘制出来,所以最后得到了一个‘小矩形’。

    以上就是cocos中ClippingNode应用模版测试实现的基本原理

  • 相关阅读:
    PHP实现微信小程序人脸识别刷脸登录功能
    thinkphp3.2.3中设置路由,优化url
    ThinkPHP URL 路由简介
    在 Linux 下搭建 Git 服务器
    图解 Android 广播机制 狼人:
    手机系统竞争背后的利益竞逐 狼人:
    你必须知道的Windows Phone 7开发 狼人:
    展望Android之前世今生 狼人:
    在美做开发多年,写给国内iPhone开发新手 狼人:
    NDK入门项目实战 狼人:
  • 原文地址:https://www.cnblogs.com/xiaonanxia/p/9415100.html
Copyright © 2020-2023  润新知