下文帮助你来理解OpenGL模板测试
以cocos中的ClippingNode为例子
比如我希望透过一个圆形显示一个正方形
圆形只作为参考,不绘制
正方形只绘制在圆内的部分,圆外的不绘制
这种裁剪使用的方法是OpenGL模板测试
我们可以把正方形称为内容,把圆形称为遮罩
模板是一个8位无符号整数即0~255(00000000~11111111)
如何根据遮罩裁剪内容很容易理解
但理解实现过程----模板测试,就没那么容易了
下面我们对于ClippingNode最简单的使用(非嵌套模式)
详细分析一下源代码(请按照5-1,5-2,5-3,5-4,5-5顺序阅读)
代码版本cocos2d-x3.5
转载请注明原文地址http://www.cnblogs.com/billyrun/articles/6122611.html
参考文献
http://www.blogjava.net/qileilove/archive/2014/01/23/409269.html
http://blog.csdn.net/sydnash/article/details/46754727
https://www.opengl.org/sdk/docs/man2/xhtml/glStencilMaskSeparate.xml
/* * 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 "2d/CCDrawingPrimitives.h" #include "renderer/CCGLProgramCache.h" #include "renderer/ccGLStateCache.h" #include "renderer/CCRenderer.h" #include "base/CCDirector.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 (std::nothrow) ClippingNode(); if (ret && ret->init()) { ret->autorelease(); } else { CC_SAFE_DELETE(ret); } return ret; } ClippingNode* ClippingNode::create(Node *pStencil) { ClippingNode *ret = new (std::nothrow) 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; _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); 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"); 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); Vec2 vertices[] = { Vec2(-1, -1), Vec2(1, -1), Vec2(1, 1), Vec2(-1, 1) }; auto glProgram = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_U_COLOR); int colorLocation = glProgram->getUniformLocation("u_color"); CHECK_GL_ERROR_DEBUG(); Color4F color(1, 1, 1, 1); glProgram->use(); glProgram->setUniformsForBuiltins(); glProgram->setUniformLocationWith4fv(colorLocation, (GLfloat*) &color.r, 1); glBindBuffer(GL_ARRAY_BUFFER, 0); GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION ); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4); 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 || !hasContent()) 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); //5-1 执行onBeforeVisit 初始化模板缓冲区 _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_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 } //5-2 绘制遮罩图形,得到需要的模板缓冲区 // 假设遮罩图形_stencil是一个Δ三角形 // 以正模式为例,_stencil->visit执行后,模板缓冲区值如下 /* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0 */ _stencil->visit(renderer, _modelViewTransform, flags); _afterDrawStencilCmd.init(_globalZOrder); //5-3 onAfterDrawStencil 遮罩图形绘制后,重社模板测试函数 _afterDrawStencilCmd.func = CC_CALLBACK_0(ClippingNode::onAfterDrawStencil, this); renderer->addCommand(&_afterDrawStencilCmd); int i = 0; bool visibleByCamera = isVisitableByVisitingCamera(); //5-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); //5-5 结束绘制 openGL恢复原值 _afterVisitCmd.func = CC_CALLBACK_0(ClippingNode::onAfterVisit, this); renderer->addCommand(&_afterVisitCmd); renderer->popGroup(); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } void ClippingNode::setCameraMask(unsigned short mask, bool applyChildren) { Node::setCameraMask(mask, applyChildren); if (_stencil) _stencil->setCameraMask(mask, applyChildren); } Node* ClippingNode::getStencil() const { return _stencil; } void ClippingNode::setStencil(Node *stencil) { CC_SAFE_RETAIN(stencil); CC_SAFE_RELEASE(_stencil); _stencil = stencil; } bool ClippingNode::hasContent() const { return _children.size() > 0; } 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 //5-1-1 // ClippingNode未嵌套时 s_layer值为0 // 简单起见,下文的注释讨论不嵌套的情况 // increment the current layer s_layer++; //5-1-2 //ClippingNode未嵌套时 mask_layer值为1 (00000001) //_mask_layer_le 与 mask_layer相等 //模板值格式为8位 即0~255 0x00~0xff // [模板缓冲区与颜色缓冲区类似,即每一个像素都有一个颜色值/模板值] //若开启模板测试,每个像素的模板值就会派上用场,决定这个片段绘制or丢弃 // 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; //5-1-3 // manually save the stencil state // 保存原有模板状态 在本节点绘制完成后恢复 // 这种思路体现了OpenGL流水线工作方式 // 虽然可以渲染多个图片,用多个模板,但颜色缓冲区/模板缓冲区只有一份,渲染一次改变一次 _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(); //5-1-4 // mask_layer值为1(00000001) // 只有1的位可以写入,其他的位不能改变(嵌套时限制可写的位数)// https://www.opengl.org/sdk/docs/man2/xhtml/glStencilMaskSeparate.xml // 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 //5-1-5 // glStencilFunc(GL_NEVER, mask_layer, mask_layer); // GL_NEVER 表示[绘制命令一律不绘制到屏幕],若这里是其他值如GL_EQUAL // 则后两个参数会决定是否绘制到屏幕 // eg. //GL_EQUAL // Passes if (ref & mask) = (stencil & mask). // ref 和 mask 就是2,3参数,这里我们传的一样,&操作之后不变 // stencil指模板缓冲区中每个像素点上的当前模板值 // 模板缓冲区由 图形,glStencilFunc测试结果,glStencilOp中的参数共同决定 //5-1-6 // glStencilOp(!_inverted ? GL_ZERO : GL_REPLACE, GL_KEEP, GL_KEEP); // 此函数用来改写模板缓冲区(中每个像素点的模板值) // 三个参数分别表示 // fail模板测试未通过时该如何变化;zfail表示模板测试通过,但深度测试未通过时该如何变化;zpass表示模板测试和深度测试或者未执行深度测试均通过时该如何变化 // 这里后两个参数都是GL_KEEP表示不改变模板缓冲区 // 而第一个参数, // 在正模式下取GL_ZERO,表示"绘制命令试图绘制的区域(的每个像素位置),若不能通过测试,模板缓冲值写为0" // 在反模式下取GL_REPLACE,用ref的值1来replace,表示"绘制命令试图绘制的区域(的每个像素位置),若不能通过测试,模板换充值写为1" // 结合了上文之后我们知道 // 1.glStencilFunc中GL_NEVER表示试图绘制的内容都不能通过测试 // 2.glStencilMask(mask_layer)限制了只能写1位(最低位),这里具体的写入应该是有限制的,写为0/1都是允许的 // 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); //5-1-7 // 设置完成后下面开始绘制,这里绘制了一个全屏矩形 // 因此对于全屏所有像素位置,都不能通过测试 // 模板缓冲区全部写入0(正模式)或1(反模式) // 以正模式为例,假设屏幕像素为8*6 // drawFullScreenQuadClearStencil执行后,模板缓冲区值如下 /* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 */ // 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 //5-1-8 // 上面可以理解为配置遮罩图形前的模板缓冲区初始化 // 在本函数结束后会执行模板绘制 5-2 绘制模板_stencil->visit // 在这里改变模板函数 // glStencilFunc(GL_NEVER, mask_layer, mask_layer); // 这条语句不变,因为遮罩和全屏矩形一样,其本身不在屏幕绘制 // glStencilOp(!_inverted ? GL_REPLACE : GL_ZERO, GL_KEEP, GL_KEEP); // 正模式GL_REPLACE/反模式GL_ZERO // 接下来遮罩图形的绘制命令执行后 // 其图形相对应的模板缓冲区值变为1/0(见5.2图解) // 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(); //5-1-9 // 若用户在cocos中设置了遮罩图片的alpha过滤值 // 只令遮罩图片中的不透明部分起遮罩效果,设置了_alphaThreshold // openGL的做法是在绘制遮罩图片前开启alpha测试 // 遮罩图片中alpha值不足的片段直接被丢弃 // 只绘制通过alpha测试的片段 // 目前已经设置了许多地方,这是由openGL面向过程的特性决定的 // 也正因如此,最后一步结束绘制后恢复原值十分必要! // 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) { //5.3.1 // 如果5-1-9那里设置了alpha测试(为了绘制遮罩改变模板缓冲区) // 那么这里显然应该关闭alpha测试,尝试绘制全部的子节点而不是根据alpha值丢弃一部分 // 再次体现openGL面向过程 #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 //5.3.2 // 这里第三次改变模板函数 // glStencilFunc(GL_EQUAL, _mask_layer_le, _mask_layer_le); // 首先在非嵌套情况下_mask_layer_le与mask_layer相等都是1(00000001) // GL_EQUAL表示 // 接下来的内容(5-4) 通过模板测试的部分绘制在屏幕上,其余丢弃 // 再看一遍怎样算通过 //GL_EQUAL // Passes if (ref & mask) = (stencil & mask). // 左边==1 特好理解1(00000001)&1(00000001)==1 // 右边stencil表示模板缓存区中某个位置的模板值 // 再放出5-2状态图 /* 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0 */ // 一目了然! // 值为0的位置stencil & mask == 0 不等于左边1 丢弃 // 值为1的位置stencil & mask == 1 通过测试 绘制到屏幕 // 1的区域代表了遮罩图片的区域 // 正模式下,子节点只有落在区域内的部分绘制,反之丢弃 // 反模式下,子节点只有落在区域外的部分绘制 //5.3.3 // glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // 模板缓冲已经设置好了,因此无论是否通过测试/绘制子节点 // 都不在改变模板缓冲区 // 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 } 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
QQ:393422044
Email:fkrfkrfkr@163.com