cocos2dx clippingNode的实现原理

cocos2dx clippingNode的实现原理,第1张

概述           clippingNode是利用opengl的裁剪缓冲区实现的,因为最近有使用这个功能需要,顺便把这部分实现看看了看。    opengl的裁剪主要有以下几个步骤: 1、开启裁剪缓冲区 2、设置裁剪缓冲区中的mask。 3、正常绘制图形,这个时候会根据裁剪缓冲区的值和设置好的比较函数进行计算,根据通过与否选择是否会知道framebuffer 4、绘制完成之后关闭裁剪缓冲区 这几

clipPingNode是利用opengl的裁剪缓冲区实现的,因为最近有使用这个功能需要,顺便把这部分实现看看了看。

opengl的裁剪主要有以下几个步骤:

1、开启裁剪缓冲区

2、设置裁剪缓冲区中的mask。

3、正常绘制图形,这个时候会根据裁剪缓冲区的值和设置好的比较函数进行计算,根据通过与否选择是否会知道framebuffer

4、绘制完成之后关闭裁剪缓冲区

这几个步骤在cocos2dx的clipPingNode中体现在以下的这段代码中:

<pre name="code" >    _groupCommand.init(_globalZOrder);    renderer->addCommand(&_groupCommand);    renderer->pushGroup(_groupCommand.getRenderQueueID());    _beforeVisitCmd.init(_globalZOrder);    _beforeVisitCmd.func = CC_CALLBACK_0(ClipPingNode::onBeforeVisit,this);   //1    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    }    _stencil->visit(renderer,_modelVIEwtransform,flags);   //2    _afterDrawStencilCmd.init(_globalZOrder);    _afterDrawStencilCmd.func = CC_CALLBACK_0(ClipPingNode::onAfterDrawStencil,this);  //3    renderer->addCommand(&_afterDrawStencilCmd);    int i = 0;    bool visibleByCamera = isVisitableByVisitingCamera();    //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,flags);            else                break;        }        // self draw        if (visibleByCamera)            this->draw(renderer,flags);                for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)            (*it)->visit(renderer,flags);    }    else if (visibleByCamera)    {        this->draw(renderer,flags);    }    _afterVisitCmd.init(_globalZOrder);    _afterVisitCmd.func = CC_CALLBACK_0(ClipPingNode::onAfterVisit,this);   //5    renderer->addCommand(&_afterVisitCmd);


    在这段代码中一共有5步:  

1、onBeforeVisit函数:在这个函数中启动了裁剪缓冲去,并且设置好缓冲区比较函数。

2、绘制裁剪图形,这里使用图形的Alpha来确定什么区域可以绘制什么区域不绘制,通过设置的比较函数来设置裁剪缓冲区的值。

3、裁剪缓冲设置完毕之后,重新设置裁剪参数(比较函数和ref、mask等)。

4、绘制图形,通过测试的将会知道frambuffer。

5、关闭裁剪缓冲区。上面的第1、2步骤一起完成了裁剪缓冲去mask的设置工作。

3、4步完成了裁剪工作。

5步清楚了裁剪参数配置,关闭裁剪缓冲区,恢复普通绘制。

下面具体看看每一步都是怎么工作的:

第一步:为了方便我直接在代码中进行注释来说明。(中文注释是我自己的理解)

  <pre name="code" >///////////////////////////////////    // INIT    // increment the current layer    s_layer++;    //这个参数是为了可以在clipPingNode中嵌入clipPingNode使用的,但是通过分析我发现这里有一点点小BUG。    // mask of the current layer (IE: for layer 3: 00000100)    Glint mask_layer = 0x1 << s_layer;    //这个值作为裁剪缓冲区中的mask。    // 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);   //设置当前使用的参见缓冲区ID    // 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));    drawFullScreenQuadClearStencil();  //这里进行一次绘制,因为上面将测试函数设置为了GL_NEVER,表示永远不会通过测试,所以这次绘制不会绘制到裁剪缓冲区,通过上面的glStencilOp函数可知如果不取反则此时缓冲区全为0,否则全为mask_layer。        ///////////////////////////////////    // 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	//这里再次设置比较函数和 *** 作:如果不取反,则缓冲区会被设置为mask_layer,否则设置为0    glStencilFunc(GL_NEVER,mask_layer);    glStencilOp(!_inverted ? GL_REPLACE : GL_ZERO,GL_KEEP);   //因为我们是使用图片的Alpha进行裁剪,所以对于非gles平台,直接使用Alphatest功能:	//对于通过Alpha测试的区域,如果取反,则设置为mask_layer,否则设置0.          //如果是opengles平台,因为不支持Alphatest,所以使用了一个自定义的shader来实现Alphatest,这个shader很简单,就是如果Alpha通过则绘制,如果不通过,直接discard。  opengles设置shader的代码在上面的一段代码中,就是修改node的shaderprogram。    // 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


     从上面的注释可知,在这个函数执行完毕之后,缓冲区已经被设置如下情况:  

如果不取反,则缓冲现在全为0

如果取反,则全为mask_layer

并且,设置了新的比较函数和Alphatest,如果接下来进行绘制 *** 作,则通过Alphatest部分的缓冲区会被重新设置:

如果不取反,则通过Alphatest部分的缓冲区为mask_layer

如果取反,则通过Alphatest部分的缓冲区为0

第二步:正如上面所说,对裁剪图片进行了绘制,这时缓冲的裁剪mask已经设置完毕。

第三步:重新设置裁剪函数和 *** 作,对node中的子节点进行裁剪:下面看看第三步的参数设置:

    glDepthMask(_currentDepthWriteMask);   <span >	</span>//恢复之前的深度检测设置    <span >	</span>//设置比较函数为GL_EQUAL,这个参数的意义为:mask_layer_le & mask_layer_le == mask & mask_layer_le则通过测试,否则不通过     //mask指的是裁剪缓冲区中对应位置对应的值。     //通过上面的代码中的mask_leyer_le的计算和裁剪缓冲区的设置可知:此时缓冲区中的maks要么是0要么是mask_layer     //果layer大于0,则mask_layer & mask_layer_le = mask_layer     //而mask_layer_le & mask_layer_le = mask_layer_le且mask_layer != mask_layer_le,也就是说如果clipPingNode有嵌套,那么第二层嵌套开始所有node都会被裁剪掉,和本义不符,其实这里只需要继续设置mask_layer就可以了,因为每一层都有自己的裁剪缓冲区。这里不知道我的理解是否正确,求指教。</span>    glStencilFunc(GL_EQUAL,_mask_layer_le,_mask_layer_le);    glStencilOp(GL_KEEP,GL_KEEP);  这个函数的意义是不管通过或者不通过裁剪测试,都保证裁剪缓冲区不再被修改。


第四步:则是绘制children,在绘制的时候根据上面提到的裁剪测试方式进行裁剪;

如果不取反则通过Alphatest部分能通过测试,即裁剪图像透明度大于Alpha部分被绘制

如果取反,则没有通过Alphatest部分通过测试,即裁剪图像透明度小于Alpha部分被绘制。

第五步:恢复保存的裁剪参数(选择上一层的裁剪缓冲区或者关闭裁剪缓冲区,设置上一层的裁减函数配置)

以上就是clipPingnode做的事情,下面来详细说明上面用到的两个函数:

一、glStencilFunc(func,ref,mask) 该函数用于设置裁剪缓冲区的比较函数,每一个函数对应的比较方法如下:

GL_NEVER
Always fails.

GL_LESS
Passes if ( ref & mask ) < ( stencil & mask ).

GL_LEQUAL
Passes if ( ref & mask ) <= ( stencil & mask ).

GL_GREATER
Passes if ( ref & mask ) > ( stencil & mask ).

GL_GEQUAL
Passes if ( ref & mask ) >= ( stencil & mask ).

GL_EQUAL
Passes if ( ref & mask ) = ( stencil & mask ).

GL_NOTEQUAL
Passes if ( ref & mask ) != ( stencil & mask ).

GL_ALWAYS
Always passes.

二、glStencilOp(sfail,dpfail,dppass)

这三个参数用于分别在不同情况下对裁剪缓冲区的 *** 作

第一参数表示在裁剪测试失败的情况下所做的 *** 作

第二参数表示在裁剪测试通过,深度测试失败的时候的 *** 作

第三个参数表示在裁剪和深度测试都通过的时候的 *** 作,

*** 作方法有如下几种:

GL_KEEP Keeps the current value. GL_ZERO Sets the stencil buffer value to 0. GL_REPLACE Sets the stencil buffer value to ref,as specifIEd by glStencilFunc. GL_INCR Increments the current stencil buffer value. Clamps to the maximum representable unsigned value. GL_INCR_WRAP Increments the current stencil buffer value. Wraps stencil buffer value to zero when incrementing the maximum representable unsigned value. GL_DECR Decrements the current stencil buffer value. Clamps to 0. GL_DECR_WRAP Decrements the current stencil buffer value. Wraps stencil buffer value to the maximum representable unsigned value when decrementing a stencil buffer value of zero. GL_INVERT Bitwise inverts the current stencil buffer value.

总结

以上是内存溢出为你收集整理的cocos2dx clippingNode的实现原理全部内容,希望文章能够帮你解决cocos2dx clippingNode的实现原理所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1063094.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存