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); //1renderer->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 thresholdGLProgram *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 alphaThresholdprogram->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 thissetProgram(_stencil, program);#endif}_stencil->visit(renderer, _modelViewTransform, flags); //2_afterDrawStencilCmd.init(_globalZOrder);_afterDrawStencilCmd.func = CC_CALLBACK_0(ClippingNode::onAfterDrawStencil, this); //3renderer->addCommand(&_afterDrawStencilCmd);int i = 0;bool visibleByCamera = isVisitableByVisitingCamera();//4if(!_children.empty()){sortAllChildren();// draw children zOrder < 0for( ; i < _children.size(); i++ ){auto node = _children.at(i);if ( node && node->getLocalZOrder() < 0 )node->visit(renderer, _modelViewTransform, flags);elsebreak;}// self drawif (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); //5renderer->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 layers_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 useglEnable(GL_STENCIL_TEST);// check for OpenGL error while enabling stencil testCHECK_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 valueglStencilMask(mask_layer); //设置当前使用的参见缓冲区id// manually save the depth test stateglGetBooleanv(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 doglDepthMask(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 bufferglStencilFunc(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,否则设置为0glStencilFunc(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 anywaysif (_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 testingglEnable(GL_ALPHA_TEST);// check for OpenGL error while enabling alpha testCHECK_GL_ERROR_DEBUG();// pixel will be drawn only if greater than an alpha thresholdglAlphaFunc(GL_GREATER, _alphaThreshold);#else#endif}//Draw _stencil 从上面的注释可知,在这个函数执行完毕之后,缓冲区已经被设置如下情况:

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

如果取反,则全为mask_layer

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

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

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

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

当你见过了世界上最美丽的风景,

cocos2dx clippingNode的实现原理

相关文章:

你感兴趣的文章:

标签云: