在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
[cc language=”c++”]
/******************************************************************
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
[/cc]

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

然后是实现部分 CCSpriteWidthFilter.cpp

[cc lang=”c++”]
#include “CCSpriteWidthFilter.h”
#include “ccMacros.h”

void CCSpriteWidthFilter::draw()
{
CCObject* obj;
CCARRAY_FOREACH(m_filters,obj)
{
CCFilter* filter = static_cast(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(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(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(obj);
addFilter->initSprite(this);
}
m_filters->addObjectsFromArray(filters);
}

CCArray* CCSpriteWidthFilter::getFilters()
{
return m_filters;
}
[/cc]

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

[cc lang=”c++”]
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();
[/cc]

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

之后是 CCFilter.h

[cc lang=”c++”]
#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
[/cc]

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

CCFilter.cpp
[cc lang=”c++”]
#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)
{

}
[/cc]

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

使用方法很简单,像这样
[cc lang=”c++”]
CCGodRayFilter* filter = CCGodRayFilter::create();
CCSpriteWidthFilter* sprite = CCSpriteWidthFilter::create(“HelloWorld.png”);
sprite->setFilter(filter);
//sprite->setAnchorPoint(ccp(0,0));
[/cc]

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

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

发表评论

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