在Cocos2dx中,自由的更换滤镜

cocso2dxgodray

可以通过网盘下载gif看看动态效果
http://pan.baidu.com/s/1jGoRER0

也可以看看移植前的stage3D版本的效果
http://www.dreamfairy.cn/blog/index.php/2014/05/10/create-a-god-ray-effect-in-stage3d.html

又一个月没写博客了,先吐槽下,怎么广告这么多。。。 每天都要删,插件都顶不住了。
然后再吐槽下之前某网友好像是叫as3er的看了我的god ray教程,然后自己在away3d中实现了但是缺少了遮挡这个god ray中最体现效果的功能。 这先不说,这位同学看就看了,理解了就好了嘛,反而不满我不发源码~ 我也不知道该说啥 摊手. 国内的开源社区条件是一直很恶劣的,以前我也完整开源,现在只能有选择的开源了,我还没有那么伟大到不用考虑薪水问题,而无私贡献。之前廖成在 Particle 做的 幻影和热流效果就不开源,我做了详细的讲解但也不开源,没有必要跟他人起矛盾,但我觉得对于有经验的程序员这些讲解已经足够了。

回归正题,cocos2dx纯粹的代码堆积,毫无美感,这是用了一段时间的体会,API的不稳定也是大忌,有点赞同云风大叔的想法了。 最近要在 CCSprite 上做各种效果,以网络上的教程来说,只能创建多个 自定义CCSprite, 每个特定滤镜,最后不断切换。。。 对于从AS毕业的我来说,太坑了。

于是打算实现带滤镜接口的CCSprite,我没有一部分程序员的毛病,比如重复造轮子,触控的程序员估计大部分都有这毛病,看看cocos2dx中大量的重复功能函数库。 于是找到了 zrong 同学写的 CCFilteredSprite github 上在这里
https://github.com/zrong/cocos2d-x-filters 而且这货在 quick 和 cocos2dx 3.x 中貌似继承了。 由于我不想装win8, 所以用不了 3.x , 因为是开发弹幕游戏,担心lua顶不住,不能用quick. 又因为项目是用 cocos2dx 2.2.4 搞的,这个库不完全兼容,所以只能做修改或者重写了。

我试着重现AS3 的 多个滤镜,但是 cocos2dx 在 2.x 版本对于 renderTexture 的封装。。。无力吐槽, 要实现仅能大改了,于是放弃了这个想法,还是单个滤镜吧。

接下来上代码

核心类 只有2个 CCSpriteWithFilter.cpp 和 CCFilter.cpp
所有的滤镜都是继承自 CCFilter.cpp

那么从头开始

首先创建头文件 CCSpriteWithFilter.h

/******************************************************************
            Copyright (c) 2014, dreamfairy.cn
                   All rights reserved
         
    创建日期:  2014年6月26日   16时33分
    文件名称:  CCSpriteWidthFilter.h
    说    明:  
   
    当前版本:  1.00
    作    者:  苍白的茧
    概    述:      

*******************************************************************/

#ifndef __CCspriteWithFilter__
#define __CCspriteWithFilter__

#include "cocos2d.h"
#include "filter/CCFilter.h"

class CCFilter;

USING_NS_CC;

class CCSpriteWidthFilter : public CCSprite
{
public:
    CCSpriteWidthFilter();
    ~CCSpriteWidthFilter();

    static CCSpriteWidthFilter* create();
    static CCSpriteWidthFilter* create(const char* $pszFileName);
    static CCSpriteWidthFilter* create(const char* $pszFileName, const CCRect& $rect);

    static CCSpriteWidthFilter* createWithTexture(CCTexture2D* $pTexture);
    static CCSpriteWidthFilter* createWithTexture(CCTexture2D* $pTexture, const CCRect& rect);

    static CCSpriteWidthFilter* createWithSpriteFrame(CCSpriteFrame* $pSpriteFrame);

    static CCSpriteWidthFilter* createWithSpriteFrameName(const char* $pszSpriteFrameName);


    virtual void draw(void);
    virtual CCFilter* getFilter(unsigned int index = 0);
    virtual void setFilter(CCFilter* filter);

    virtual CCArray* getFilters();
    virtual void setFilters(CCArray* filters);

    virtual void clearFilter();

protected:
    virtual void drawFilter();
    virtual bool updateFilters();

    CCArray* m_filters;
};

#endif

本质上只是增加几个自定义方法 setFilter, setFilters, clearFilter, drawFiler 功能就是将滤镜丢进去,或者清除 没啥好讲解的

然后是实现部分 CCSpriteWidthFilter.cpp

#include "CCSpriteWidthFilter.h"
#include "ccMacros.h"


void CCSpriteWidthFilter::draw()
{
    CCObject* obj;
    CCARRAY_FOREACH(m_filters,obj)
    {
        CCFilter* filter = static_cast<CCFilter*>(obj);

        CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");

        CCAssert(!m_pobBatchNode, "If CCSprite is being rendered by CCSpriteBatchNode,      CCSprite#draw SHOULD NOT be called");

        this->setShaderProgram(filter->getProgram());

        do
        {
            ccGLEnable(m_eGLServerState);
            CCAssert(getShaderProgram(), "No shader program set for this node");
            {
                getShaderProgram()->use();
                getShaderProgram()->setUniformsForBuiltins();
            }
        } while (0);

        //useProgram后调用draw
        filter->draw();

        ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );

        ccGLBindTexture2D(m_pobTexture->getName());
        ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );

#define kQuadSize sizeof(m_sQuad.bl)
#ifdef EMSCRIPTEN
        long offset = 0;
        setGLBufferData(&m_sQuad, 4 * kQuadSize, 0);
#else
        long offset = (long)&m_sQuad;
#endif // EMSCRIPTEN

        // vertex
        int diff = offsetof( ccV3F_C4B_T2F, vertices);
        glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));

        // texCoods
        diff = offsetof( ccV3F_C4B_T2F, texCoords);
        glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));

        // color
        diff = offsetof( ccV3F_C4B_T2F, colors);
        glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));


        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        CHECK_GL_ERROR_DEBUG();


#if CC_SPRITE_DEBUG_DRAW == 1
        // draw bounding box
        CCPoint vertices[4]={
            ccp(m_sQuad.tl.vertices.x,m_sQuad.tl.vertices.y),
            ccp(m_sQuad.bl.vertices.x,m_sQuad.bl.vertices.y),
            ccp(m_sQuad.br.vertices.x,m_sQuad.br.vertices.y),
            ccp(m_sQuad.tr.vertices.x,m_sQuad.tr.vertices.y),
        };
        ccDrawPoly(vertices, 4, true);
#elif CC_SPRITE_DEBUG_DRAW == 2
        // draw texture box
        CCSize s = this->getTextureRect().size;
        CCPoint offsetPix = this->getOffsetPosition();
        CCPoint vertices[4] = {
            ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
            ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y+s.height)
        };
        ccDrawPoly(vertices, 4, true);
#endif // CC_SPRITE_DEBUG_DRAW

        CC_INCREMENT_GL_DRAWS(1);

        CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");
    }
}

void CCSpriteWidthFilter::drawFilter()
{
    if(m_filters && m_filters->count() == 1)
    {
        static_cast<CCFilter*>(m_filters->objectAtIndex(0))->draw();
    }
}

//以push的方式增加filter
void CCSpriteWidthFilter::setFilter( CCFilter* filter )
{
    if(m_filters->indexOfObject(filter) == UINT_MAX)
    {
        m_filters->addObject(filter);
        filter->initSprite(this);
    }
}

void CCSpriteWidthFilter::clearFilter()
{
    //todo
}

bool CCSpriteWidthFilter::updateFilters()
{
    return true;
}


CCFilter* CCSpriteWidthFilter::getFilter( unsigned int index /*= 0*/ )
{
    return static_cast<CCFilter*>(m_filters->objectAtIndex(index));
}


CCSpriteWidthFilter* CCSpriteWidthFilter::create()
{
    CCSpriteWidthFilter* sprite = new CCSpriteWidthFilter();
    if(sprite && sprite->init())
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

CCSpriteWidthFilter* CCSpriteWidthFilter::create(const char* $pszFileName)
{
    CCSpriteWidthFilter* sprite = new CCSpriteWidthFilter();
    if (sprite && sprite->initWithFile($pszFileName))
    {
        sprite->autorelease();
        return sprite;
    }else{
        CC_SAFE_DELETE(sprite);
        return NULL;
    }
}

CCSpriteWidthFilter* CCSpriteWidthFilter::create(const char* $pszFileName, const CCRect& $rect)
{
    CCSpriteWidthFilter *sprite = new CCSpriteWidthFilter();
    if (sprite && sprite->initWithFile($pszFileName, $rect))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

CCSpriteWidthFilter* CCSpriteWidthFilter::createWithTexture(CCTexture2D* $pTexture)
{
    CCSpriteWidthFilter *sprite = new CCSpriteWidthFilter();
    if (sprite && sprite->initWithTexture($pTexture))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

CCSpriteWidthFilter* CCSpriteWidthFilter::createWithTexture(CCTexture2D* $pTexture, const CCRect& rect)
{
    CCSpriteWidthFilter *sprite = new CCSpriteWidthFilter();
    if (sprite && sprite->initWithTexture($pTexture, rect))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

CCSpriteWidthFilter* CCSpriteWidthFilter::createWithSpriteFrame(CCSpriteFrame* $pSpriteFrame)
{
    CCSpriteWidthFilter *sprite = new CCSpriteWidthFilter();
    if ($pSpriteFrame && sprite)
    {
        if ($pSpriteFrame->isRotated())
        {
            CCSprite* __sp = CCSprite::createWithSpriteFrame($pSpriteFrame);
            CCSize __size = __sp->getContentSize();
            __sp->setAnchorPoint(ccp(0,0));
            __sp->setPosition(ccp(0,0));
            CCRenderTexture* __rTex = CCRenderTexture::create(__size.width, __size.height);
            __rTex->begin();
            __sp->visit();
            __rTex->end();
            CCTexture2D* __newTex = new CCTexture2D();
            __newTex->initWithImage(__rTex->newCCImage(true));
            __newTex->autorelease();
            sprite->initWithTexture(__newTex);
            //CCLOG("==== CCFilteredSprite::initWithTexture, rotated true texture wh(%f,%f)", __newTex->getContentSize().width, __newTex->getContentSize().height);
        }
        else
        {
            sprite->initWithSpriteFrame($pSpriteFrame);
        }
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

CCSpriteWidthFilter* CCSpriteWidthFilter::createWithSpriteFrameName(const char* $pszSpriteFrameName)
{
    CCSpriteFrame *pFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName($pszSpriteFrameName);

#if COCOS2D_DEBUG > 0
    char msg[256] = { 0 };
    sprintf(msg, "Invalid spriteFrameName: %s", $pszSpriteFrameName);
    CCAssert(pFrame != NULL, msg);
#endif

    return createWithSpriteFrame(pFrame);
}

CCSpriteWidthFilter::CCSpriteWidthFilter()
{
    m_filters = CCArray::create();
    m_filters->retain();
}

CCSpriteWidthFilter::~CCSpriteWidthFilter()
{
    m_filters->release();

    //CC_SAFE_DELETE(m_filters);
}

void CCSpriteWidthFilter::setFilters( CCArray* filters )
{
    CCObject* obj;
    CCARRAY_FOREACH(filters, obj)
    {
        CCFilter* addFilter = static_cast<CCFilter*>(obj);
        addFilter->initSprite(this);
    }
    m_filters->addObjectsFromArray(filters);
}

CCArray* CCSpriteWidthFilter::getFilters()
{
    return m_filters;
}

这里重点是 draw() 函数
与 GLProgram 通信(传递参数) 的前提是 GLProgram.use()
而 CCSprite 原本的 draw()函数中 用宏 CC_NODE_DRAW_SETUP 进行了封装,并使用了默认 GLProgram, 我对于我们的 SpriteWithFilter来说,其 GLProgram 位于滤镜中,因此 draw()函数要重写

this->setShaderProgram(filter->getProgram());

do
        {
            ccGLEnable(m_eGLServerState);
            CCAssert(getShaderProgram(), "No shader program set for this node");
            {
                getShaderProgram()->use();
                getShaderProgram()->setUniformsForBuiltins();
            }
        } while (0);

        //useProgram后调用draw
        filter->draw();

在手动切换到滤镜的program后,调用滤镜的draw ,给滤镜一个函数来传递滤镜需要的参数

之后是 CCFilter.h

#ifndef __CCFILTER__
#define __CCFILTER__

#include "cocos2d.h"
#include "CCGL.h"
#include "CCSpriteWidthFilter.h"

class CCSpriteWidthFilter;

USING_NS_CC;

class CCFilter : public CCObject
{
public:
    CCFilter();
    ~CCFilter();

    virtual void initSprite(CCSpriteWidthFilter* sprite);
    virtual void draw();
    CCGLProgram* getProgram();

    const char* shaderName;
protected:
    CCGLProgram* m_program;
    void initProgram();
    virtual CCGLProgram* loadShader();
    virtual void setAttributes(CCGLProgram* gl);
    virtual void setUniforms(CCGLProgram* gl);

    CCSpriteWidthFilter* m_sprite;
};
#endif

initSprite 让滤镜持有这个宿主对象,原本是打算扩展RTT的,后来放弃啦

CCFilter.cpp

#include "CCFilter.h"

CCFilter::CCFilter()
: shaderName(NULL),
m_program(NULL)
{

}

CCFilter::~CCFilter()
{

}

void CCFilter::initSprite(CCSpriteWidthFilter* sprite)
{

}

void CCFilter::draw()
{

}

CCGLProgram* CCFilter::getProgram()
{
    return m_program;
}

void CCFilter::initProgram()
{
    CCGLProgram* program = CCShaderCache::sharedShaderCache()->programForKey(this->shaderName);
    if(!program)
    {
        program = loadShader();

        this->setAttributes(program);
        CHECK_GL_ERROR_DEBUG();
        program->link();
        CHECK_GL_ERROR_DEBUG();
        program->updateUniforms();
        CHECK_GL_ERROR_DEBUG();
        CCShaderCache::sharedShaderCache()->addProgram(program, this->shaderName);
        program->release();
    }

    if(!m_program)
    {
        CCLOG("initProgram id:%d",program->getProgram());
        m_program = program;
        m_program->retain();
    }
}

CCGLProgram* CCFilter::loadShader()
{
    return NULL;
}

void CCFilter::setAttributes(CCGLProgram* gl)
{

}

void CCFilter::setUniforms(CCGLProgram* gl)
{

}

函数都留空,丢给子类去实现它

使用方法很简单,像这样

    CCGodRayFilter* filter = CCGodRayFilter::create();
    CCSpriteWidthFilter* sprite = CCSpriteWidthFilter::create("HelloWorld.png");
    sprite->setFilter(filter);
    //sprite->setAnchorPoint(ccp(0,0));

这个项目我会传到git上,目前只有几个预制滤镜
灰度,闪烁,颜色闪烁 和 上帝之光。 zrong 的 git 上有 饱和度,模糊等滤镜, 有空会考虑“山寨过来“, 其他滤镜在有空的时候会后续加入,比如之前写过的水波和阴影体还有HDR

https://github.com/dreamfairy/cocos2dx-2.x-filters/

《在Cocos2dx中,自由的更换滤镜》有2个想法

  1. 虽然我没有信仰,但是万事万物都是相对的,当你觉得你没有重复造轮子的时候,自己创造CCSpriteWithFilter的时候,相对于zong同学,或者用CCFiterSprite的程序员来说,CCSpriteWithFilter就是在重复造轮子,现在你该明白为什么触控有那么多重复函数了吧,其实你自己也是一个因(相对的,我也不是吐槽,其实你的有限度的开源,我也会参考,也会用)

  2. CCGodRayFilter* filter = CCGodRayFilter::create();
    CCSpriteWidthFilter* sprite = CCSpriteWidthFilter::create(“HelloWorld.png”);
    sprite->setFilter(filter);

    这个上帝之光的效果,只是颜色变灰 了,不知道是哪里设置有问题?

发表评论

电子邮件地址不会被公开。 必填项已用*标注