在Cocos2d-x v3.x 中实现带颜色滤镜的Sprite

2015年03月23日 13:22 0 点赞 0 评论 更新于 2025-11-21 18:16

一、目的

在使用Cocos2d-x进行项目开发时,常常会遇到对图片进行变色处理的需求,其中最常见的是将图片变灰,例如让按钮变灰以表示其当前处于不可点击状态。然而,Cocos2d-x的Sprite类本身并未提供变灰的支持。因此,我们需要手动扩展实现一个具备变色功能的Sprite类,这里将其命名为FilterSprite。FilterSprite扩展了Sprite的功能,能够方便地对图片进行颜色变换。

二、原理

对图片进行颜色变换,本质上是对图片上的每个像素进行处理。为了实现这一功能,我们需要创建一个新的片段着色器(fragmentShader)。这个新的着色器相较于Sprite原有的fragmentShader,多了一个颜色变换矩阵。在处理过程中,着色器会将图片上的每个像素与该颜色变换矩阵相乘,从而输出新的像素值。

以下是具体的着色器代码:

#ifdef GL_ES
precision mediump float;
#endif

uniform sampler2D u_texture;
varying vec2 v_texCoord;
varying vec4 v_fragmentColor;
uniform mat4 fiterMat;

void main(void)
{
vec4 value = v_fragmentColor * texture2D(u_texture, v_texCoord);
gl_FragColor = fiterMat * value;
};

从上述代码可以看出,filterMat 即为颜色变换矩阵,它在原来像素输出前对像素进行了处理,具体方式是与待输出像素相乘。由于该着色器处于OpenGL层级,若要将其应用到Cocos2d-x引擎中,我们需要实现Cocos2d-x的FilterSprite类。

三、实现

在实现FilterSprite类时,需要注意以下几个要点:

  • GLProgram与GLProgramState的关联:在Cocos2d-x引擎中,一个着色器对应一个GLProgram对象。因此,这个带颜色滤镜的着色器(称为filterShader)对应一个GLProgram对象(称为filterProgram)。在实际使用时,我们使用对GLProgram进行了封装的GLProgramState对象(称为filterProgramState)。在FilterSprite对象中,需要将其 _glProgramState 设置为filterProgramState对象。在源码中,我们在 FilterSpriteinitWithTexture 方法中完成filterShader和filterProgram的关联。
  • 滤镜传递:在渲染时,需要将滤镜传递给着色器程序。在源码中,这一操作是在 onDraw 回调函数中完成的,具体代码如下:
    glProgramState->setUniformMat4("fiterMat", m_uSpriteFilter);
    

四、使用

FilterSprite的使用非常简单,只需要设置一个颜色矩阵即可。例如,如果要将图片变灰,可设置一个灰度矩阵;若要恢复图片原貌,则设置一个单位矩阵。以下是具体的使用示例:

Sprite *_sprite1;
_sprite1 = FilterSprite::create("Images/background3.png");

GLfloat filterMat[16] = {
0.3f,  0.3f,  0.3f,  0.0f,
0.59f, 0.59f, 0.59f, 0.59f,
0.11f, 0.11f, 0.11f, 0.0f,
0.0f,  0.0f,  0.0f,  1.0f
};

dynamic_cast<FilterSprite*>(_sprite1)->setFilterMat(filterMat);

五、源码

FilterSprite.h

/****************************************************************************
FilterSprite.h
Created by LiaoYanXuan on 14-10-21.
****************************************************************************/

#ifndef __FilterSprite_h
#define __FilterSprite_h

#include "cocos2d.h"
USING_NS_CC;

class FilterSprite : public Sprite {
public:
FilterSprite();
virtual ~FilterSprite();

static FilterSprite* create();
static FilterSprite* create(const std::string& filename);
static FilterSprite* create(const std::string& filename, const Rect& rect);
static FilterSprite* createWithTexture(Texture2D *pTexture);
static FilterSprite* createWithTexture(Texture2D *pTexture, const Rect& rect, bool rotated = false);
static FilterSprite* createWithSpriteFrame(SpriteFrame *pSpriteFrame);
static FilterSprite* createWithSpriteFrameName(const std::string& spriteFrameName);

bool initWithTexture(Texture2D* pTexture, const Rect& tRect);
virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;
void onDraw(const Mat4 &transform, uint32_t flags);
void setFilterMat(cocos2d::Mat4 matrixArray);
// to-do 提供一个设置滤镜的方法

protected:
CustomCommand _customCommand;

private:
cocos2d::Mat4 m_uSpriteFilter;
};

#endif

FilterSprite.cpp

/****************************************************************************
FilterSprite.cpp
Created by LiaoYanXuan on 14-10-21.
****************************************************************************/

#include "FilterSprite.h"

FilterSprite::FilterSprite(void)
{
m_uSpriteFilter = Mat4::IDENTITY;
}

FilterSprite::~FilterSprite()
{
}

FilterSprite* FilterSprite::create()
{
FilterSprite *sprite = new (std::nothrow) FilterSprite();
if (sprite && sprite->init())
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}

FilterSprite* FilterSprite::create(const std::string& filename)
{
FilterSprite *sprite = new (std::nothrow) FilterSprite();
if (sprite && sprite->initWithFile(filename))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}

FilterSprite* FilterSprite::create(const std::string& filename, const Rect& rect)
{
FilterSprite *sprite = new (std::nothrow) FilterSprite();
if (sprite && sprite->initWithFile(filename, rect))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}

FilterSprite* FilterSprite::createWithTexture(Texture2D *pTexture)
{
FilterSprite *sprite = new (std::nothrow) FilterSprite();
Rect rect = Rect::ZERO;
rect.size = pTexture->getContentSize();
if (sprite && sprite->initWithTexture(pTexture, rect))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}

FilterSprite* FilterSprite::createWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
{
FilterSprite *sprite = new (std::nothrow) FilterSprite();
if (sprite && sprite->initWithTexture(texture, rect))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}

FilterSprite* FilterSprite::createWithSpriteFrame(SpriteFrame *spriteFrame)
{
FilterSprite *sprite = new (std::nothrow) FilterSprite();
if (sprite && spriteFrame && sprite->initWithSpriteFrame(spriteFrame))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}

FilterSprite* FilterSprite::createWithSpriteFrameName(const std::string& spriteFrameName)
{
SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(spriteFrameName);
#if COCOS2D_DEBUG > 0
char msg[256] = {0};
sprintf(msg, "Invalid spriteFrameName: %s", spriteFrameName.c_str());
CCASSERT(frame != nullptr, msg);
#endif
return createWithSpriteFrame(frame);
}

bool FilterSprite::initWithTexture(Texture2D* pTexture, const Rect& tRect) {
do {
CC_BREAK_IF(!Sprite::initWithTexture(pTexture, tRect));

GLchar* pszFragSource =
"#ifdef GL_ES \n"
"precision mediump float; \n"
"#endif \n"
"uniform sampler2D u_texture; \n"
"varying vec2 v_texCoord; \n"
"varying vec4 v_fragmentColor; \n"
"uniform mat4 fiterMat; \n"
"void main(void) \n"
"{ \n"
"vec4 value = v_fragmentColor * texture2D(u_texture, v_texCoord); \n"
"gl_FragColor = fiterMat * value; \n"
"}";

auto glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_vert, pszFragSource);
auto glprogramstate = GLProgramState::getOrCreateWithGLProgram(glprogram);
setGLProgramState(glprogramstate);

CHECK_GL_ERROR_DEBUG();
return true;
} while (0);
return false;
}

void FilterSprite::setFilterMat(cocos2d::Mat4 matrixArray)
{
m_uSpriteFilter = matrixArray;
}

void FilterSprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
_customCommand.init(_globalZOrder);
_customCommand.func = CC_CALLBACK_0(FilterSprite::onDraw, this, transform, flags);
renderer->addCommand(&_customCommand);
}

void FilterSprite::onDraw(const Mat4 &transform, uint32_t flags)
{
auto glProgramState = getGLProgramState();
glProgramState->setUniformMat4("fiterMat", m_uSpriteFilter);
glProgramState->apply(transform);

GL::blendFunc(_blendFunc.src, _blendFunc.dst);
GL::bindTexture2D(_texture->getName());
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

#define kQuadSize sizeof(_quad.bl)
size_t offset = (size_t)&_quad;

// vertex
int diff = offsetof(V3F_C4B_T2F, vertices);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));

// 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));

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

CHECK_GL_ERROR_DEBUG();
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4);
}

通过以上步骤,我们就可以在Cocos2d-x v3.x中实现带颜色滤镜的Sprite了。

作者信息

menghao

menghao

共发布了 3994 篇文章