在OpenGL 绘制过程中,与帧缓冲有关的是模版,深度测试,混合操作。模版测试使应用程序可以定义一个遮罩,在遮罩内的片段将被保留或者丢弃,在遮罩外的片段操作行为则相反。深度测试用来剔除那些被场景遮挡的片段,但是对于透明物体可能有问题,需要注意绘制顺序,本文主要结合ClippingNode分析下模版测试。
具体关于模版测试的文章有很多,这里就不再重复了,直接开始分析cocos中模版测试的应用。
ClippingNode是一个Node元素,它接受另外一个Node来作为模版,这个模版用来影响ClippingNode本身子元素的绘制:只绘制模版以内的部分或者只绘制模版以外的部分,模版本事并不会绘制。
ClippingNode使用GroupCommand来进行绘制,因为他要对所有子元素的绘制设置一些片段测试,用于实现模版的遮罩。比如如下代码:
//模板 Sprite* stencil = Sprite::create("switch-mask.png"); ClippingNode* c_node = ClippingNode::create(); c_node->setStencil(stencil); c_node->setInverted(false); c_node->setAlphaThreshold(0.0f); Sprite* sp = Sprite::create("scrollviewbg.png"); c_node->addChild(sp); c_node->setPosition(100,100);
图片分别为:
switch-mask:,scrollviewbg:,switch-mask2:
运行结果为:
实现了裁剪。
现在看一下源码,是如何做到的
/* * Copyright (c) 2012 Pierre-David Bélanger * Copyright (c) 2012 cocos2d-x.org * Copyright (c) 2013-2014 Chukong Technologies Inc. * * http://www.cocos2d-x.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #ifndef __MISCNODE_CCCLIPPING_NODE_H__ #define __MISCNODE_CCCLIPPING_NODE_H__ #include "2d/CCNode.h" #include "CCGL.h" #include "renderer/CCGroupCommand.h" #include "renderer/CCCustomCommand.h" NS_CC_BEGIN /** ClippingNode is a subclass of Node. It draws its content (childs) clipped using a stencil. The stencil is an other Node that will not be drawn. The clipping is done using the alpha part of the stencil (adjusted with an alphaThreshold). */ class CC_DLL ClippingNode : public Node { public: /** Creates and initializes a clipping node without a stencil. */ static ClippingNode* create(); /** Creates and initializes a clipping node with an other node as its stencil. The stencil node will be retained. */ static ClippingNode* create(Node *stencil); /** The Node to use as a stencil to do the clipping. The stencil node will be retained. This default to nil. */ Node* getStencil() const; void setStencil(Node *stencil); /** The alpha threshold. The content is drawn only where the stencil have pixel with alpha greater than the alphaThreshold. Should be a float between 0 and 1. This default to 1 (so alpha test is disabled). */ /* 仅在模板具有alpha大于alphaThreshold的像素的情况下绘制内容。 应该介于0和1之间的浮点数。 默认为1(因此禁用alpha测试)。 */ /* 可以对ClippingNode设置一个alpha比较值(alphaThreshold)用来选择模版Node上只有那些alpha值大于该比较直的片段才会构成模版遮罩的一部分,在移动平台此参数会被忽略 */ GLfloat getAlphaThreshold() const; void setAlphaThreshold(GLfloat alphaThreshold); /** Inverted. If this is set to true, the stencil is inverted, so the content is drawn where the stencil is NOT drawn. This default to false. */ /* 模板是倒置的,因此在未绘制模板的地方绘制内容。 默认为false。 */ bool isInverted() const; void setInverted(bool inverted); // Overrides /** * @js NA * @lua NA */ virtual void onEnter() override; /** * @js NA * @lua NA */ virtual void onEnterTransitionDidFinish() override; /** * @js NA * @lua NA */ virtual void onExitTransitionDidStart() override; /** * @js NA * @lua NA */ virtual void onExit() override; virtual void visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) override; CC_CONSTRUCTOR_ACCESS: ClippingNode(); /** * @js NA * @lua NA */ virtual ~ClippingNode(); /** Initializes a clipping node without a stencil. */ virtual bool init(); /** Initializes a clipping node with an other node as its stencil. The stencil node will be retained, and its parent will be set to this clipping node. */ virtual bool init(Node *stencil); protected: /**draw fullscreen quad to clear stencil bits */ void drawFullScreenQuadClearStencil(); Node* _stencil; GLfloat _alphaThreshold; bool _inverted; //renderData and callback void onBeforeVisit(); void onAfterDrawStencil(); void onAfterVisit(); GLboolean _currentStencilEnabled; GLuint _currentStencilWriteMask; GLenum _currentStencilFunc; GLint _currentStencilRef; GLuint _currentStencilValueMask; GLenum _currentStencilFail;//模版测试失败后的处理 GLenum _currentStencilPassDepthFail;//模版测试通过,深度测试失败后的处理 GLenum _currentStencilPassDepthPass;//深度测试通过之后的处理 GLboolean _currentDepthWriteMask; GLboolean _currentAlphaTestEnabled; GLenum _currentAlphaTestFunc; GLclampf _currentAlphaTestRef; GLint _mask_layer_le; GroupCommand _groupCommand; //onBeforeVisit用于生成一个GroupCommand并创建一个RenderQueue,并设置一些状态,然后绘制模版元素 CustomCommand _beforeVisitCmd; //onAfterDrawStencil用于绘制ClippingNode自身的子元素,这些元素的RenderCommand会被夹到第一步创建的RenderQueue中, //并被执行模版测试 CustomCommand _afterDrawStencilCmd; //onAfterVisit用来恢复GroupCommand对OpenGL状态的修改 CustomCommand _afterVisitCmd; private: CC_DISALLOW_COPY_AND_ASSIGN(ClippingNode); }; NS_CC_END #endif // __MISCNODE_CCCLIPPING_NODE_H__
/* * Copyright (c) 2012 Pierre-David Bélanger * Copyright (c) 2012 cocos2d-x.org * Copyright (c) 2013-2014 Chukong Technologies Inc. * * cocos2d-x: http://www.cocos2d-x.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "2d/CCClippingNode.h" #include "renderer/CCGLProgram.h" #include "renderer/CCGLProgramCache.h" #include "2d/CCDrawingPrimitives.h" #include "base/CCDirector.h" #include "renderer/CCRenderer.h" #include "renderer/CCGroupCommand.h" #include "renderer/CCCustomCommand.h" NS_CC_BEGIN static GLint g_sStencilBits = -1; // store the current stencil layer (position in the stencil buffer), // this will allow nesting up to n ClippingNode, // where n is the number of bits of the stencil buffer. static GLint s_layer = -1; static void setProgram(Node *n, GLProgram *p) { n->setGLProgram(p); auto& children = n->getChildren(); for(const auto &child : children) { setProgram(child, p); } } ClippingNode::ClippingNode() : _stencil(nullptr) , _alphaThreshold(0.0f) , _inverted(false) , _currentStencilEnabled(GL_FALSE) , _currentStencilWriteMask(~0) , _currentStencilFunc(GL_ALWAYS) , _currentStencilRef(0) , _currentStencilValueMask(~0) , _currentStencilFail(GL_KEEP) , _currentStencilPassDepthFail(GL_KEEP) , _currentStencilPassDepthPass(GL_KEEP) , _currentDepthWriteMask(GL_TRUE) , _currentAlphaTestEnabled(GL_FALSE) , _currentAlphaTestFunc(GL_ALWAYS) , _currentAlphaTestRef(1) { } ClippingNode::~ClippingNode() { if (_stencil) { _stencil->stopAllActions(); _stencil->release(); } } ClippingNode* ClippingNode::create() { ClippingNode *ret = new ClippingNode(); if (ret && ret->init()) { ret->autorelease(); } else { CC_SAFE_DELETE(ret); } return ret; } ClippingNode* ClippingNode::create(Node *pStencil) { ClippingNode *ret = new ClippingNode(); if (ret && ret->init(pStencil)) { ret->autorelease(); } else { CC_SAFE_DELETE(ret); } return ret; } bool ClippingNode::init() { return init(nullptr); } bool ClippingNode::init(Node *stencil) { CC_SAFE_RELEASE(_stencil); _stencil = stencil; CC_SAFE_RETAIN(_stencil); _alphaThreshold = 1;//默认为1,禁用aphal测试 _inverted = false; // get (only once) the number of bits of the stencil buffer static bool once = true; if (once) { glGetIntegerv(GL_STENCIL_BITS, &g_sStencilBits);//获取opengl中的位平面数 if (g_sStencilBits <= 0) { CCLOG("Stencil buffer is not enabled."); } once = false; } return true; } void ClippingNode::onEnter() { #if CC_ENABLE_SCRIPT_BINDING if (_scriptType == kScriptTypeJavascript) { if (ScriptEngineManager::sendNodeEventToJSExtended(this, kNodeOnEnter)) return; } #endif Node::onEnter(); if (_stencil != nullptr) { _stencil->onEnter(); } else { CCLOG("ClippingNode warning: _stencil is nil."); } } void ClippingNode::onEnterTransitionDidFinish() { Node::onEnterTransitionDidFinish(); if (_stencil != nullptr) { _stencil->onEnterTransitionDidFinish(); } } void ClippingNode::onExitTransitionDidStart() { if (_stencil != nullptr) { _stencil->onExitTransitionDidStart(); } Node::onExitTransitionDidStart(); } void ClippingNode::onExit() { if (_stencil != nullptr) { _stencil->onExit(); } Node::onExit(); } void ClippingNode::drawFullScreenQuadClearStencil() { Director* director = Director::getInstance(); CCASSERT(nullptr != director, "Director is null when seting matrix stack"); //此时最顶出的modeviewMatirx是单位向量,也就是说刷新全屏幕的模版值 director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); DrawPrimitives::drawSolidRect(Vec2(-1,-1), Vec2(1,1), Color4F(1, 1, 1, 1)); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } void ClippingNode::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) { if(!_visible) return; uint32_t flags = processParentFlags(parentTransform, parentFlags); // IMPORTANT: // To ease the migration to v3.0, we still support the Mat4 stack, // but it is deprecated and your code should not rely on it Director* director = Director::getInstance(); CCASSERT(nullptr != director, "Director is null when seting matrix stack"); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); //Add group command _groupCommand.init(_globalZOrder); renderer->addCommand(&_groupCommand); renderer->pushGroup(_groupCommand.getRenderQueueID()); _beforeVisitCmd.init(_globalZOrder); _beforeVisitCmd.func = CC_CALLBACK_0(ClippingNode::onBeforeVisit, this); renderer->addCommand(&_beforeVisitCmd); if (_alphaThreshold < 1)// { #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WINDOWS || 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 // XXX: we should have a way to apply shader to all nodes without having to do this setProgram(_stencil, program); #endif } //访问模版对象 所有的draw都会吧绘制命令加到ClippingNode的Group中 _stencil->visit(renderer, _modelViewTransform, flags); _afterDrawStencilCmd.init(_globalZOrder); _afterDrawStencilCmd.func = CC_CALLBACK_0(ClippingNode::onAfterDrawStencil, this); renderer->addCommand(&_afterDrawStencilCmd); int i = 0; 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 this->draw(renderer, _modelViewTransform, flags); for(auto it=_children.cbegin()+i; it != _children.cend(); ++it) (*it)->visit(renderer, _modelViewTransform, flags); } else { this->draw(renderer, _modelViewTransform, flags); } _afterVisitCmd.init(_globalZOrder); _afterVisitCmd.func = CC_CALLBACK_0(ClippingNode::onAfterVisit, this); renderer->addCommand(&_afterVisitCmd); renderer->popGroup(); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } Node* ClippingNode::getStencil() const { return _stencil; } void ClippingNode::setStencil(Node *stencil) { CC_SAFE_RETAIN(stencil); CC_SAFE_RELEASE(_stencil); _stencil = stencil; } GLfloat ClippingNode::getAlphaThreshold() const { return _alphaThreshold; } void ClippingNode::setAlphaThreshold(GLfloat alphaThreshold) { _alphaThreshold = alphaThreshold; } bool ClippingNode::isInverted() const { return _inverted; } void ClippingNode::setInverted(bool inverted) { _inverted = inverted; } void ClippingNode::onBeforeVisit() { /////////////////////////////////// // INIT auto fff=this; // increment the current layer s_layer++; //printf("呵呵呵呵呵呵呵=%d ",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 //模板缓冲区上的所有位都是只读的,当前层位除外, //这意味着像glClear或glStencilOp这样的操作将被这个值掩盖 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 //在绘制模板时禁用深度测试 // glDisable(GL_DEPTH_TEST); //在绘制模板时禁用对深度缓冲区的更新, //因为模板不是要在真实场景中渲染, //它永远不应该阻止其他东西被绘制, //只应禁用深度缓冲区更新 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 /* //通过在其上绘制全屏矩形来手动清除模板缓冲区 //像这样设置模板测试功能: //对于全屏矩形中的每个像素 //永远不要将它绘制到帧缓冲区中 //如果不处于反转模式:在模板缓冲区中将当前图层值设置为0 //如果处于反转模式:在模板缓冲区中将当前图层值设置为1 */ 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)); /* 这里使用了绘制一个全屏矩形的方式来设置模版缓冲区,但实际上可以直接通过glClear(GL_DEPTH_BUFFER_BIT)来清理 */ 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 /* //像这样设置模板测试功能: //对于模板节点中的每个像素 //永远不要将它绘制到帧缓冲区中 //如果不处于反转模式:在模板缓冲区中将当前图层值设置为1 //如果处于反转模式:在模板缓冲区中将当前图层值设置为0 */ 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_WINDOWS || 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 } void ClippingNode::onAfterDrawStencil() { // restore alpha test state if (_alphaThreshold < 1) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WINDOWS || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) // manually restore the alpha test state glAlphaFunc(_currentAlphaTestFunc, _currentAlphaTestRef); if (!_currentAlphaTestEnabled) { glDisable(GL_ALPHA_TEST); } #else // XXX: 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 //像这样设置模板测试功能: //对于此节点及其子节点的每个像素 //如果在模板缓冲区中所有小于或等于当前的图层都设置为1 //绘制像素并将当前图层保留在模板缓冲区中 //别的 //不要绘制像素,而是将当前图层保留在模板缓冲区中 glStencilFunc(GL_EQUAL, _mask_layer_le, _mask_layer_le);//11必须都满足才行,2个模版实现了11 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // draw (according to the stencil test func) this node and its childs } void ClippingNode::onAfterVisit() { /////////////////////////////////// // CLEANUP // manually restore the stencil state glStencilFunc(_currentStencilFunc, _currentStencilRef, _currentStencilValueMask); glStencilOp(_currentStencilFail, _currentStencilPassDepthFail, _currentStencilPassDepthPass); glStencilMask(_currentStencilWriteMask); if (!_currentStencilEnabled) { glDisable(GL_STENCIL_TEST); } // we are done using this layer, decrement s_layer--; } NS_CC_END
代码量不大,原理其实是挺简单的:
我已经在关键的地方给了注释,下面说一下几个关键点:
1 绘制过程:
首先,onBeforeVisit用于生成一个GroupCommand并创建一个RenderQueue,并设置一些状态;然后绘制模版元素,在模版生成之后,onAfterDrawStencil用于绘制ClippingNode自身的子元素,这些元素的RenderCommand会被加入到第一步创建的RenderQueue中,并被执行模版测试;最后onAfterVisit用来恢复GroupCommand对OpenGL状态的修改
2 尝试把事例代码改为如下:
Sprite* stencil = Sprite::create("switch-mask.png"); ClippingNode* c_node = ClippingNode::create(); c_node->setStencil(stencil); c_node->setInverted(false); c_node->setAlphaThreshold(1.0f); Sprite* sp = Sprite::create("scrollviewbg.png"); c_node->addChild(sp); c_node->setPosition(100,100);
运行结果如下:
看出来和之前的是不一样的,就因为设置了AlphaThreshold,代码位置:
这个alphaThreshold 用来选择模版Node上只有那些alpha值大约该比较值的片段才会构成模版遮罩的一部分,但是这主要依靠Alpha测试来实现,在移动端OpenGL ES中是没有alpha测试的,这里在移动平台中是在片段着色器中实现了一个类似的功能。
通过看上面的代码,如果这个值小于1,就会给ClippingNode切换着色器管理类,换成了ccShader_PositionTextureColorAlphaTest.frag 片段着色器,如下:
const char* ccPositionTextureColorAlphaTest_frag = STRINGIFY( #ifdef GL_ES precision lowp float; #endif varying vec4 v_fragmentColor; varying vec2 v_texCoord; uniform float CC_alpha_value; void main() { vec4 texColor = texture2D(CC_Texture0, v_texCoord); // mimic: glAlphaFunc(GL_GREATER) // pass if ( incoming_pixel >= CC_alpha_value ) => fail if incoming_pixel < CC_alpha_value if ( texColor.a <= CC_alpha_value ) discard; gl_FragColor = texColor * v_fragmentColor; } );
通过标红代码可以看到,此时的CC_aipha_value为0,遮罩按钮图片的四边透明的部分的alpha=0,所以绘制行discard,所以当设置alphaThreshold=0的时候,是圆角的遮罩,反之,透明的部分也会当作遮罩,从而导致遮罩为矩形。
3 关于ClippingNode的嵌套使用,具体怎么嵌套呢,或许可以有2种方式,如下:
方式一:
//模板 Sprite* stencil = Sprite::create("switch-mask.png"); ClippingNode* c_node = ClippingNode::create(); c_node->setStencil(stencil); c_node->setInverted(false); c_node->setAlphaThreshold(1.0f); Sprite* sp = Sprite::create("scrollviewbg.png"); c_node->addChild(sp); Sprite* stencil2 = Sprite::create("switch-mask2.png"); //Sprite* stencil2 = Sprite::create("scrollviewbg.png"); ClippingNode* c_node2 = ClippingNode::create(); c_node2->setStencil(stencil2); //c_node2->setStencil(c_node); c_node2->setInverted(false); c_node2->setAlphaThreshold(0.0f); c_node2->addChild(c_node); //c_node2->addChild(stencil2); c_node2->setPosition(100,100); c_node2->setTag(10); scene->addChild(c_node2);
运行如下:
方式二:
//模板 Sprite* stencil = Sprite::create("switch-mask.png"); ClippingNode* c_node = ClippingNode::create(); c_node->setStencil(stencil); c_node->setInverted(false); c_node->setAlphaThreshold(1.0f); Sprite* sp = Sprite::create("scrollviewbg.png"); c_node->addChild(sp); Sprite* stencil2 = Sprite::create("switch-mask2.png"); //Sprite* stencil2 = Sprite::create("scrollviewbg.png"); ClippingNode* c_node2 = ClippingNode::create(); // c_node2->setStencil(stencil2); c_node2->setStencil(c_node); c_node2->setInverted(false); c_node2->setAlphaThreshold(0.0f); //c_node2->addChild(c_node); c_node2->addChild(stencil2); c_node2->setPosition(100,100); c_node2->setTag(10); scene->addChild(c_node2);
运行结果:
什么都没有,和第一段代码的区别已经标红。显然第一种嵌套用法是正确的,第二段是无效的,为什么无效呢,请看我举的小例子分析,地址:
https://www.cnblogs.com/xiaonanxia/p/9414193.html
下面主要分析一下正确的那段代码:
代码的大意就是通过switch-mask和switch-mask2 两个遮罩来裁剪 scrollviewbg.png。请看ClippingNode源码,如下:
void ClippingNode::onBeforeVisit() { /////////////////////////////////// // INIT auto fff=this; // increment the current layer s_layer++; //printf("呵呵呵呵呵呵呵=%d ",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; 。。。
主要实现用的是位移操作。
为了支持多个ClippingNode的嵌套,它用了一个静态的s_layer变量来保存嵌套的ClippingNode的数量,然后使用这个数量来对0x1执行一次左移操作生成模版值。
由此可见,如果当前模版缓冲区每像素的位平面为8,则ClippingNode最多支持8个ClippingNode嵌套
具体这个嵌套的过程,我们可以通过调试Render类的visitRenderQueue,里面会运行两个GroupCommand,第一个是对应c_node2的,第二个是对应c_node,数组分布情况为如图所示:
关键的方法是在onBeforeVisit和onAfterDrawstencil,因为s_layer是一个静态变量,所以在执行两次onBeforeVisit的时候,会叠加,等执行完第二次onBeforeVisit之后
_mask_layer_le已经变为二进制 11 也就是说,绘制的时候,模版值必须为11才能通过,在第一次onBeforeVisit的时候,模版缓冲区中的已经绘制的点的值为01,第二次执行完onBeforeVisit的时候,模版缓冲区的已经绘制的点的值为有的为11,有的为10,2个遮罩的相交部分为11,只有相交的部分才能绘制出来,所以最后得到了一个‘小矩形’。
以上就是cocos中ClippingNode应用模版测试实现的基本原理