• cocos2d-x UILayout和ClipNode模板冲突


    现在手头上负责的一个项目,基于cocos2d-x做的一个手机游戏。

    里面有个背包界面。很早以前出现一个bug。

    外层界面一个ScrollView,就是滚动界面,底层基于两套机制实现裁剪的。

    1.基于模板缓冲机制做的。

    2.基于可视区域裁剪的。

    系统默认都是基于模板裁剪,最早以前我发现这个界面非常的卡顿,后面发现同事在里面的背包Item为了实现Mask用RTT(渲染到纹理)。

    每次进入界面都创建多个RT。这对性能影响是非常大的。后面我修改这段代码改成用 cocos2d-x提供一个类叫 ClipNode(也是基于模板缓冲+ALPHA_TEST做的)实现。

    但是放入之后,竟然出现上层模板缓冲失效的问题。

    先看代码,cocos2d-x怎么实现裁剪的。

    static GLint s_layer = -1;
     1 void Layout::onBeforeVisitStencil()
     2 {
     3     s_layer++;
     4     GLint mask_layer = 0x1 << s_layer;
     5     GLint mask_layer_l = mask_layer - 1;
     6     _mask_layer_le = mask_layer | mask_layer_l;
     7     _currentStencilEnabled = glIsEnabled(GL_STENCIL_TEST);
     8     glGetIntegerv(GL_STENCIL_WRITEMASK, (GLint *)&_currentStencilWriteMask);
     9     glGetIntegerv(GL_STENCIL_FUNC, (GLint *)&_currentStencilFunc);
    10     glGetIntegerv(GL_STENCIL_REF, &_currentStencilRef);
    11     glGetIntegerv(GL_STENCIL_VALUE_MASK, (GLint *)&_currentStencilValueMask);
    12     glGetIntegerv(GL_STENCIL_FAIL, (GLint *)&_currentStencilFail);
    13     glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, (GLint *)&_currentStencilPassDepthFail);
    14     glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, (GLint *)&_currentStencilPassDepthPass);
    15     
    16     glEnable(GL_STENCIL_TEST);
    17     CHECK_GL_ERROR_DEBUG();
    18     glStencilMask(mask_layer);
    19     glGetBooleanv(GL_DEPTH_WRITEMASK, &_currentDepthWriteMask);
    20     glDepthMask(GL_FALSE);
    21     glStencilFunc(GL_NEVER, mask_layer, mask_layer);
    22     glStencilOp(GL_ZERO, GL_KEEP, GL_KEEP);
    23 
    24     this->drawFullScreenQuadClearStencil();
    25     
    26     glStencilFunc(GL_NEVER, mask_layer, mask_layer);
    27     glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
    28 }
    View Code
    void Layout::onAfterDrawStencil()
    {
        glDepthMask(_currentDepthWriteMask);
        glStencilFunc(GL_EQUAL, _mask_layer_le, _mask_layer_le);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    }
    View Code
    void Layout::onAfterVisitStencil()
    {
        glStencilFunc(_currentStencilFunc, _currentStencilRef, _currentStencilValueMask);
        glStencilOp(_currentStencilFail, _currentStencilPassDepthFail, _currentStencilPassDepthPass);
        glStencilMask(_currentStencilWriteMask);
        if (!_currentStencilEnabled)
        {
            glDisable(GL_STENCIL_TEST);
        }
        s_layer--;
    }
    View Code

    UILayout和ClipNode实现模板裁剪的方式都是一样的,通过代码了解下。

    我这里用gDebugger来显示模板缓冲值,断点断在 glStencilFunc。

    首先是断在UILayout onBeforeVisitStencil函数

    glStencilFunc(GL_NEVER, mask_layer, mask_layer);

    glStencilOp(GL_ZERO, GL_KEEP, GL_KEEP);

    this->drawFullScreenQuadClearStencil();

    这三行的作用是把全屏幕的模板值设置成0,永远不通过,glStencilOp(GL_ZERO,打到清屏的作用。我们断点走一下就可以看到效果

    后台的值都是清空的,都是0。下一步就是渲染要遮罩的区域

    glStencilFunc(GL_NEVER, mask_layer, mask_layer);
    glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);

    测试函数也是这是成,永远不通过。但是失败操作设置成替换,GL_REPLACE,这样遮罩区域渲染就会在模板缓冲相同的区域值设置成1.我们来看下效果。

    断点走一步

    跟我们预想的一样,可以见区域的模板值设置成1,不可见区域设置成0,那么后面的渲染部分只要比对模板是否是1可以了。

    如果不是1就是不可见,如果是1就是可见。

    glStencilFunc(GL_EQUAL, _mask_layer_le, _mask_layer_le);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    所以的子节点都是这样渲染。等节点遍历完,

    void Layout::onAfterVisitStencil()
    {
        glStencilFunc(_currentStencilFunc, _currentStencilRef, _currentStencilValueMask);
        glStencilOp(_currentStencilFail, _currentStencilPassDepthFail, _currentStencilPassDepthPass);
        glStencilMask(_currentStencilWriteMask);
        if (!_currentStencilEnabled)
        {
            glDisable(GL_STENCIL_TEST);
        }
        s_layer--;
    }

    还原原来的值。

    代码里面实现这样,没错。如果一般情况下工作正常。

    我们可以看到Icon的边界被裁剪了。

    我前面说过了,里面有些Icon是需要裁剪的,用到ClipNode,但是ClipNode里面的代码实现跟UILayout一模一样。这样就造成一种情况。cocos2d-x是树形节点遍历的。渲染从后向前一层层画上去的,如果ScrollView里面放个ClipNode。会造成什么情况呢

    bool ClippingNode::isInverted() const
    {
        return _inverted;
    }
    
    void ClippingNode::setInverted(bool inverted)
    {
        _inverted = inverted;
    }
    
    void ClippingNode::onBeforeVisit()
    {
        ///////////////////////////////////
        // INIT
    
        // increment the current layer
        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
        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
    
        // 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);
    
        // 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
    
        // 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();
            // pixel will be drawn only if greater than an alpha threshold
            glAlphaFunc(GL_GREATER, _alphaThreshold);
    #else
            
    #endif
        }
    
        //Draw _stencil
    }
    View Code

    在这里面跟UILayout一样先是清理了,整个后台设置成0值,后面渲染一个Mask,往后台写个遮罩值。看模板缓冲的变化。

    看下清理代码的效果

    后面渲染一个遮罩的mask值到模板缓冲

    ClipNode里面遍历子节点跟UILayout一样,都是判断是否Mask为1,

    void ClippingNode::onAfterDrawStencil()
    {
        // restore alpha test state
        if (_alphaThreshold < 1)
        {
    #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
    
        // 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
    }
    View Code

    所以小Icon遮罩是正常的,因为模板测试区域是对了,但是当Icon渲染完成退出,状态还原到UILayout,去渲染兄弟节点的时候。状态还原了,但是模板缓冲可没法还原。

    所以只为了1的区域只有那么一小块。所以后面整个滚动区域裁剪就出错了。

    好了,说明就到这里,问题也找到了。怎么改呢?简单的做法,我前面提过了,因为UILayout裁剪有两种,一种是模板一种可视区域。

    改成可视区域裁剪,就没问题。

    最后买个关子问个问题。先说下Icon这个是三个Layer就是三层合在一起的,ClipNode是最上层。请问为什么第一个渲染都正常,就是红色框框那个,后面几个渲染底下外框都不见了呢。

    只剩下ClipNode呢?

  • 相关阅读:
    Week4_1Neural Networks Representation
    吴恩达逻辑回归题目详解
    丢弃正则化
    python随笔 join 字典,列表的清空 set集合 以及深浅拷贝(重点..难点)
    python全栈开发 随笔 'is' 和 == 的比较知识与区别 编码和解码的内容及转换
    python全栈 字典数据类型相关知识及操作
    Python全栈开发 列表, 元组 数据类型知识运用及操作 range知识
    python全栈 字符串,整数,bool 数据类型运用
    python全栈 流程控制;while 循环 格式化输出 运算符 及编码
    python全栈开发 什么是python python命名及循环
  • 原文地址:https://www.cnblogs.com/wbaoqing/p/4447473.html
Copyright © 2020-2023  润新知