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呢?

 

转载于:https://www.cnblogs.com/wbaoqing/p/4447473.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值