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__
View Code
#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;
}
View Code
#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__
View Code
#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);
}
View Code