cocos2dx clippingNode的实现原理

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

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

1、开启裁剪缓冲区

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

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

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

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

<pre name="code" class="cpp">    _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, _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);
    _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" class="cpp">///////////////////////////////////
    // 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, 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, mask_layer);
    glStencilOp(!_inverted ? GL_REPLACE : GL_ZERO, GL_KEEP, 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 style="white-space:pre">	</span>//恢复之前的深度检测设置
<span style="white-space:pre">	</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, 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.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-24 13:49:31

cocos2dx clippingNode的实现原理的相关文章

cocos2d-x触摸分发器原理

为了实现触摸事件,CCLayer已经封装好了简单的接口(继承了CCTouchDelegate类)来实现触摸事件的响应. 首先,触摸事件有两种:标准触摸代理和目标触摸代理.那么我们先看看如何开启这两种触摸代理. 1.标准触摸 在层初始化时调用setTouchEnable(true)方法即可实现标准触摸,实现处理事件回调函数,处理触摸事件即可. // optional virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent) virt

购买李宁Cocos2d-x套餐,送最新出的《Cocos2d-x游戏实战指南》签名书一本

活动时间:2016-10-18至2016-11-30 通过本套餐,可完全了解Cocos2d-x 3.x的相关技术,以及掌握C++语言,并具有一定的项目实战经验. Cocos2d-x游戏开发套餐:http://edu.51cto.com/pack/view/id-114.html <Cocos2d-x游戏实战指南>封面 本书月底出版,触控科技副总裁Jane.微软开放体验和合作事业部开发技术顾问梅颖广.51CTO学院运营总监曹亚莉.哈尔滨工业大学  王峥  联袂推荐 目录 第1章     初识CO

Quick-Cocos2d-x初学者游戏教程(二) -------------------- Quick内部的代码结构及相应的原理

Quick-Cocos2d-x初学者游戏教程(二) 上一章我们已经了解了Quick的一些基础知识,所以本章我们将开始深入到Quick内部,了解它内部的代码结构,同时在解析的过程中学到相应的原理,并学会如何修改.添加相应的代码文件,比如实现屏幕的分辨率适配. 前面我们创建了一个叫做parkour的游戏项目,其意思就是本人本来打算要做一个跑酷游戏的,但是因为这几天玩了一款叫做<el>的飞行游戏,非常有意境,并且几乎零差评,所以请允许我任性一下,善变的我不想做跑酷游戏了,而是想要挑战下这种类型的游戏

cocos基础教程(8)粒子效果

简介 粒子系统是指计算机图形学中模拟特定现象的技术,它在模仿自然现象.物理现象及空间扭曲上具备得天独厚的优势,为我们实现一些真实自然而又带有随机性的特效(如爆炸.烟花.水流)提供了方便. 粒子属性 一个强大的粒子系统它必然具备了多种多样的属性,这样才能配置出多样的粒子.下面就来看看粒子系统的主要属性吧. 主要属性: _duration 发射器生存时间,即它可以发射粒子的时间,注意这个时间和粒子生存时间不同.单位秒,-1表示永远:粒子发射结束后可点击工具栏的播放按钮再次发射 _emissionRa

(11)粒子系统

简介 粒子系统是指计算机图形学中模拟特定现象的技术,它在模仿自然现象.物理现象及空间扭曲上具备得天独厚的优势,为我们实现一些真实自然而又带有随机性的特效(如爆炸.烟花.水流)提供了方便.Cocos2d-x引擎中就为我们提供了强大的粒子系统,以下是粒子系统的继承关系图: 粒子属性 一个强大的粒子系统它必然具备了多种多样的属性,这样才能配置出多样的粒子.下面就来看看粒子系统的主要属性吧. 主要属性: _duration 发射器生存时间,即它可以发射粒子的时间,注意这个时间和粒子生存时间不同.单位秒,

OpenGL学习笔记——视图

顶点变换的步骤: 视图与模型变换一起组成了模型视图矩阵,这个矩阵作用于物体坐标,产生视觉坐标.紧接着,如果指定了其他的裁剪平面,用于从场景中删除某些物体或者提供物体的裁剪视图,这些裁剪平面会在这个时候生效.之后,OpenGL使用投影矩阵产生了裁剪坐标.这个变换定义了一个视景体,位于这个空间外的物体将会被裁剪掉.随后发生的是透视除法,它把坐标除以w,产生规范化的设备坐标.最后,经过变换的坐标经过视口变换成为窗口坐标.可以通过控制视口的大小对最终的图像进行放大.缩小和拉伸(cocos2dx的屏幕适配

Cocos2d视频教程收录

锋手游开发培训-Cocos2D-X-视频教程合集  (    )   1   千锋Cocos2D-X游戏视频教程-第01讲-Cocos2D-X介绍 2   千锋Cocos2D-X游戏视频教程-第02讲-Cocos2D-X游戏安装过程和创建项目 3   千锋Cocos2D-X游戏视频教程-第03讲-Cocos2D-X启动流程-OC部分 4   千锋Cocos2D-X游戏视频教程-第04讲-Cocos2D-X启动流程-EAGLView解释 5   千锋Cocos2D-X游戏视频教程-第05讲-Coc

cocos2dx[3.2](19)——裁剪节点ClippingNode

[唠叨] 学习cocos2dx 3.2确实比较吃力,因为网上关于最新版的v3.2的资料十分稀少,或者是讲解的确实不是很详细.大部分人都是根据官方文档照样画瓢,而对于有些比较抽象的概念及函数都是照着官方文档来讲解的.这样的结果,导致有些东西令我确实非常费解. 没有办法,只好自己来总结cocos2dx3.2,然后将个人的学习感悟分享给大家. PS:当然有些大牛写的文章还是很不错的. 有时候我们需要显示一张图片的部分区域,比如文字遮罩.图片遮罩... 本节要讲的ClippingNode的功能效果大致就

Cocos2d-x 3.2:通过ClippingNode实现一个功能完善的跑马灯公告(1)

Cocos2d-x 3.2:通过ClippingNode实现一个功能完善的跑马灯公告(1) 本文转载至深入理解Cocos2d-x 3.x:一步一步通过ClippingNode实现一个功能完善的跑马灯公告(1) 这篇文章主要是通过一步一步实现一个功能完善的跑马灯公告来展示ClippingNode的用法并且最终深入ClippingNode的源码,了解其实现原理. 首先,先介绍一下ClippingNode,ClippingNode也叫裁剪节点,能将一些内容通过使用模板裁剪出来显示在界面上,可以实现一些