ClippingNode是cocos里面很常用的功能
通过OpenGL实现了画面剪裁效果
设置了蒙版Stencil之后
程序会裁剪掉Stencil区域以外的部分
若设置setAlphaThreshold(alpha)
则会在原有的区域裁剪基础上
比较Stencil图像中的每个像素alpha值与给定alpha值大小关系
来进一步裁剪
详细的讲解可以参考http://blog.csdn.net/sydnash/article/details/46754727
ClippingNode优点在于通用性,设置好了Stencil之后
可以随意向ClippingNode里面addChild
child可以是Sprite也可以是Armature骨骼动画
然而缺点在于,只有剪和不剪两种状态,没法实现半透明裁剪的效果
与之相对应的,游戏中常常有这样的需求:
需要按蒙版图的alpha值强弱来绘制显示元素
alpha值为0和1时,与传统剪裁做法一致,0时不绘制显示元素,1时绘制显示元素
alpha值介于0,1之间时,按alpha值的强弱来绘制显示元素
下面我们就来实现这个功能,做一个ClippingSprite
原理依然是多重纹理
转载请注明原址http://www.cnblogs.com/billyrun/articles/5588451.html
1.继承Sprite
第一步是继承Sprite
并且增加一个属性
cocos2d::Texture2D* _uvTexture;
父类属性_texture我们用来做蒙版
_uvTexture表示需要经过蒙版处理的显示元素
然后是新增方法来设置_uvTexture,并且完成相应的初始化操作
//设置显示元素
bool setContent(const std::string& filename);
bool ClippingSprite::setContent(const std::string& filename) { CCASSERT(filename.size()>0, "Invalid filename for sprite"); Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename); bool result = texture != nullptr; if (texture) { __setUVTexture(texture); //记录下Sprite与uv纹理的尺寸比例 _uvScale.x = getContentSize().width / texture->getContentSize().width; _uvScale.y = getContentSize().height / texture->getContentSize().height; //若是修改了shader中的代码 //C++代码也需要修改后重新编译一次才能生效 auto program = new GLProgram(); program->initWithFilenames("clippingSprite.vert", "clippingSprite.frag"); program->link(); //set uniform locations program->updateUniforms(); this->setGLProgram(program); //记录shader中uniform的loc _uvScaleLoc = glGetUniformLocation(program->getProgram(), "u_uvScale"); _uvVelocityLoc = glGetUniformLocation(program->getProgram(), "u_vVelocity"); _uvRepeatLoc = glGetUniformLocation(program->getProgram(), "u_iRepeat"); } return result; }
2.渲染流程设计
Sprite使用的是QuadCommand
QuadCommand适合于对四边形的通用渲染
ClippingSprite需要做一些个性化的处理
所以改为使用CustomCommand做自定义渲染处理
为此我们要重写draw函数
virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;
再新增一个渲染函数
void onDraw(const Mat4 &transform, uint32_t flags);
void ClippingSprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if (_uvTexture) { //重写draw函数 使用customCommand渲染命令 _customCommand.init(_globalZOrder); _customCommand.func = CC_CALLBACK_0(ClippingSprite::onDraw, this, transform, flags); renderer->addCommand(&_customCommand); } else { //do nothing } } void ClippingSprite::onDraw(const Mat4 &transform, uint32_t flags) { auto glProgramState = getGLProgramState(); //转换图片的4个顶点 //对应quadCommand中的Renderer::fillQuads操作 //若不进行如下变换,Node使用的pos,scale都表现不出来 transform.transformPoint(_quad.tl.vertices, &_verticesTransformed[0]); transform.transformPoint(_quad.bl.vertices, &_verticesTransformed[1]); transform.transformPoint(_quad.tr.vertices, &_verticesTransformed[2]); transform.transformPoint(_quad.br.vertices, &_verticesTransformed[3]); glProgramState->apply(transform); GL::blendFunc(_blendFunc.src, _blendFunc.dst); //分别取本身纹理和uv纹理绑定至默认的0,1纹理单元中 //这两个纹理单元在shader中对应CC_Texture0/CC_Texture1 GL::bindTexture2D(_texture->getName()); GL::bindTexture2DN(1, _uvTexture->getName()); GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX); #define kQuadSize sizeof(_quad.bl) size_t offset = (size_t)&_quad; // 对于vertex绑定我们变换过的顶点信息 // 对于texCoods和color绑定_quad中的信息 // vertex int diff = offsetof(V3F_C4B_T2F, vertices); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 0, _verticesTransformed); // texCoods diff = offsetof(V3F_C4B_T2F, texCoords); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff)); // color diff = offsetof(V3F_C4B_T2F, colors); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff)); // 设置我们的自定义变量 // 位置很关键 // 提前只能取位置loc // 赋值需要在这里 // uvScale glUniform2f(_uvScaleLoc, _uvScale.x, _uvScale.y); glUniform1i(_uvRepeatLoc, (int)_uvRepeat); _uvPosition = _uvPosition + _uvVelocity; glUniform2f(_uvVelocityLoc, _uvPosition.x, _uvPosition.y); //绘制三角形 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); CHECK_GL_ERROR_DEBUG(); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4); }
在具体的绘制逻辑onDraw中
GL::bindTexture2D(_texture->getName());
GL::bindTexture2DN(1, _uvTexture->getName());
分别绑定了蒙版纹理和显示元素的纹理
并且将_uvRepeat属性传入shader
显示元素是不会拉伸的
若显示元素size大于蒙版,则大于的部分都会剪掉,不显示
若显示元素size小于蒙版,
若repeat为真,则以平铺的方式铺满蒙版size
若repeat为假,那么只显示单位大小,空出的部分绘制无色
这么做的设计思路是让ClippingNode做两件事
1.常规的半透裁剪,蒙版与显示元素同样size,依照美术设计的蒙版来透出显示元素,repeat为假(由于蒙版与显示元素size相同,repeat已经失去了意义)
2.动态透明动画效果,显示元素size小于蒙版,并在蒙版范围内平铺,并按照蒙版的alpha值来决定不同位置的透明度,repeat为真
后文会具体给出效果
3.Shader
attribute vec4 a_position; attribute vec2 a_texCoord; attribute vec4 a_color; varying vec4 v_fragmentColor; varying vec2 v_texCoord; void main() { gl_Position = CC_PMatrix * a_position; v_fragmentColor = a_color; v_texCoord = a_texCoord; }
varying vec4 v_fragmentColor; varying vec2 v_texCoord; uniform vec2 u_uvScale; uniform vec2 u_vVelocity; uniform int u_iRepeat; void main() { vec2 uv_Coord = v_texCoord * u_uvScale; vec4 color0 = texture2D(CC_Texture0, v_texCoord); vec4 color1 = texture2D(CC_Texture1, uv_Coord + u_vVelocity); if(u_iRepeat == 0 && (uv_Coord.x > 1.0 || uv_Coord.y > 1.0)) { gl_FragColor = vec4(0.0,0.0,0.0,0.0); } else { gl_FragColor = color0.a * color1; } }
顶点shader没什么好说的,和Sprite一样
片段shader主要做了如下操作
采样得到蒙版颜色color0与显示元素的颜色color1
蒙版颜色只是做计算用,color1才是我们要显示颜色
首先当用户设置repeat时
直接拿蒙版的alpha值与color1乘
相当于整体减弱了color1的颜色,因为color0.a是一个介于0~1的值(OpenGL对每个颜色分量的表示都是0~1)
这里我做过一个尝试,只用color.a去乘color1.a来得到新的alpha值
color1.rgb不做处理保持原值
然后效果并不好,没有那种透明之后颜色淡化弱化的效果
在if语句中我们绘制颜色gl_FragColor = vec4(0.0,0.0,0.0,0.0);
相当于绘制了无色或者没有绘制,达到裁剪的效果
4.效果与总结
图一是我们使用的蒙版,圆的外部是全透明的,圆的内部不同位置透明图有所不同
图二是repeat = false的效果
我们使用了helloworld图片,由于helloworld图片比蒙版小,因此看起来有点诡异(应该找一下同样大小的图片!)
helloworld图片按其本身的size显示了一部分,左上角对应球的左上角(全透明)因此没有显示
其余的地方按相应的透明度显示
图三是repeat = true的效果
使用了雨滴图片来做repeat(雨滴透明部分必须是无色的,不能是黑底)
可以很清晰的看到,雨滴在对应蒙版不同位置的显示
为了更加直观,我们这一次在底层垫了一张JanisJoplin的图片
如果底是一张大地图,上面有一片海
那么只要绘制相应的模板,和波光纹理
就可以实现一些非常酷炫的动态效果
波光纹理的强弱,移动速度 都是十分容易控制的
ClippingSprite实现了ClippingNode无法做到的半透效果
然而局限性也很明显
只能针对一个个Sprite来处理
无法像ClippingNode那样设置好了蒙版之后随意addChild
技术是创意的一部分
同样创意也是对技术的补充!
源码如下

#ifndef __CLIPPING_TEXT_H__ #define __CLIPPING_TEXT_H__ #include "cocos2d.h" class ClippingTest : public cocos2d::Layer { public: // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); // implement the "static create()" method manually CREATE_FUNC(ClippingTest); ClippingTest(); }; #endif // __CLIPPING_TEXT_H__

#include "ClippingTest.h" #include "ClippingSprite.h" USING_NS_CC; Scene* ClippingTest::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autorelease object auto layer = ClippingTest::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } ClippingTest::ClippingTest() {} bool ClippingTest::init() { ////////////////////////////// // 1. super init first if (!Layer::init()) { return false; } auto visibleSize = Director::getInstance()->getVisibleSize(); auto background = Sprite::create("test.jpg"); background->setPosition(Vec2(visibleSize / 2)); addChild(background); ClippingSprite * sprite = ClippingSprite::create("ball.png"); sprite->setPosition(Vec2(visibleSize / 2)); addChild(sprite); //sprite->setContent("HelloWorld.png"); sprite->setContent("rain.png"); sprite->setUvRepeat(true); sprite->setUvVelocity(Vec2(0.0f, -0.01f)); return true; }

#ifndef __ClippingSprite_H__ #define __ClippingSprite_H__ #include "cocos2d.h" USING_NS_CC; class ClippingSprite : public cocos2d::Sprite { public: static ClippingSprite* create(const std::string& filename); ClippingSprite(); virtual ~ClippingSprite(); //重写draw virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override; void onDraw(const Mat4 &transform, uint32_t flags); //设置显示元素 bool setContent(const std::string& filename); void setUvRepeat(bool val); protected: void __setUVTexture(cocos2d::Texture2D *texture); cocos2d::Texture2D* _uvTexture; CustomCommand _customCommand; Vec3 _verticesTransformed[4]; //uvTexture与Sprite自身纹理的宽高比例,uv纹理是否拉伸 Vec2 _uvScale; GLuint _uvScaleLoc; //uv纹理移动速度,坐标偏移量 CC_SYNTHESIZE(Vec2, _uvVelocity, UvVelocity); CC_SYNTHESIZE_READONLY(Vec2, _uvPosition, UvPosition); GLuint _uvVelocityLoc; //repeat标志 GLuint _uvRepeatLoc; CC_SYNTHESIZE_READONLY(bool, _uvRepeat, UvRepeat); }; #endif // __ClippingSprite_H__

#include "ClippingSprite.h" USING_NS_CC; ClippingSprite* ClippingSprite::create(const std::string& filename) { ClippingSprite *sprite = new (std::nothrow) ClippingSprite(); if (sprite && sprite->initWithFile(filename)) { sprite->autorelease(); return sprite; } CC_SAFE_DELETE(sprite); return nullptr; } ClippingSprite::ClippingSprite() :_uvTexture(nullptr) , _uvVelocity(0.0f, 0.0f) , _uvPosition(0.0f, 0.0f) , _uvRepeat(false) {} ClippingSprite::~ClippingSprite() { CC_SAFE_RELEASE(_uvTexture); } void ClippingSprite::__setUVTexture(Texture2D *texture) { CC_SAFE_RETAIN(texture); CC_SAFE_RELEASE(_uvTexture); _uvTexture = texture; } void ClippingSprite::setUvRepeat(bool val) { _uvRepeat = val; //GL_LINEAR 避免放大后色块失真 //GL_REPEAT u/v超界时(0~1)repeat Texture2D::TexParams tRepeatParams; tRepeatParams.magFilter = GL_LINEAR; tRepeatParams.minFilter = GL_LINEAR; tRepeatParams.wrapS = val ? GL_REPEAT : GL_CLAMP_TO_EDGE; tRepeatParams.wrapT = val ? GL_REPEAT : GL_CLAMP_TO_EDGE; _uvTexture->setTexParameters(tRepeatParams); } bool ClippingSprite::setContent(const std::string& filename) { CCASSERT(filename.size()>0, "Invalid filename for sprite"); Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename); bool result = texture != nullptr; if (texture) { __setUVTexture(texture); //记录下Sprite与uv纹理的尺寸比例 _uvScale.x = getContentSize().width / texture->getContentSize().width; _uvScale.y = getContentSize().height / texture->getContentSize().height; //若是修改了shader中的代码 //C++代码也需要修改后重新编译一次才能生效 auto program = new GLProgram(); program->initWithFilenames("clippingSprite.vert", "clippingSprite.frag"); program->link(); //set uniform locations program->updateUniforms(); this->setGLProgram(program); //记录shader中uniform的loc _uvScaleLoc = glGetUniformLocation(program->getProgram(), "u_uvScale"); _uvVelocityLoc = glGetUniformLocation(program->getProgram(), "u_vVelocity"); _uvRepeatLoc = glGetUniformLocation(program->getProgram(), "u_iRepeat"); } return result; } void ClippingSprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { if (_uvTexture) { //重写draw函数 使用customCommand渲染命令 _customCommand.init(_globalZOrder); _customCommand.func = CC_CALLBACK_0(ClippingSprite::onDraw, this, transform, flags); renderer->addCommand(&_customCommand); } else { //do nothing } } void ClippingSprite::onDraw(const Mat4 &transform, uint32_t flags) { auto glProgramState = getGLProgramState(); //转换图片的4个顶点 //对应quadCommand中的Renderer::fillQuads操作 //若不进行如下变换,Node使用的pos,scale都表现不出来 transform.transformPoint(_quad.tl.vertices, &_verticesTransformed[0]); transform.transformPoint(_quad.bl.vertices, &_verticesTransformed[1]); transform.transformPoint(_quad.tr.vertices, &_verticesTransformed[2]); transform.transformPoint(_quad.br.vertices, &_verticesTransformed[3]); glProgramState->apply(transform); GL::blendFunc(_blendFunc.src, _blendFunc.dst); //分别取本身纹理和uv纹理绑定至默认的0,1纹理单元中 //这两个纹理单元在shader中对应CC_Texture0/CC_Texture1 GL::bindTexture2D(_texture->getName()); GL::bindTexture2DN(1, _uvTexture->getName()); GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX); #define kQuadSize sizeof(_quad.bl) size_t offset = (size_t)&_quad; // 对于vertex绑定我们变换过的顶点信息 // 对于texCoods和color绑定_quad中的信息 // vertex int diff = offsetof(V3F_C4B_T2F, vertices); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 0, _verticesTransformed); // texCoods diff = offsetof(V3F_C4B_T2F, texCoords); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff)); // color diff = offsetof(V3F_C4B_T2F, colors); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff)); // 设置我们的自定义变量 // 位置很关键 // 提前只能取位置loc // 赋值需要在这里 // uvScale glUniform2f(_uvScaleLoc, _uvScale.x, _uvScale.y); glUniform1i(_uvRepeatLoc, (int)_uvRepeat); _uvPosition = _uvPosition + _uvVelocity; glUniform2f(_uvVelocityLoc, _uvPosition.x, _uvPosition.y); //绘制三角形 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); CHECK_GL_ERROR_DEBUG(); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4); }