为cocos2d 的精灵修改色相(Hue)的问题困扰了我很久,网上一直没有找到比较靠谱的解决方案。
前段时间在https://github.com/alex314/CCSpriteWithHue发现国外开发者在 cocos2d-iPhone 2.x版本上面实现了对精灵的色相修改,最近有时间在cocos2d-x 3.x 上重新实现了一下。
注意 :Debug模式下,需要注释掉 CCGLProgramState.cpp apply() 方法中 switch语句default分支下的断言
前段时间在https://github.com/alex314/CCSpriteWithHue发现国外开发者在 cocos2d-iPhone 2.x版本上面实现了对精灵的色相修改,最近有时间在cocos2d-x 3.x 上重新实现了一下。
实现的效果如下:
原图:
色相修改:
关键代码如下:
CCSpriteWithHue.h
//
// CCSpriteWithHue.h
// SnippetsProject
//
// Created by Lawis on 15/1/20.
//
//
#ifndef __SnippetsProject__CCSpriteWithHue__
#define __SnippetsProject__CCSpriteWithHue__
#include "cocos2d.h"
USING_NS_CC;
class CCSpriteWithHue :public Sprite {
private://variable
GLint m_hueLocation;
GLint m_alphaLocation;
protected://variable
public://variable
CC_SYNTHESIZE_READONLY(GLfloat, m_hue, Hue);
private://method
protected://method
virtual void setupDefaultSettings();
virtual void getUniformLocations();
virtual void updateColorMatrix();
virtual void updateAlpha();
virtual GLfloat alpha();
virtual void updateColor();
virtual bool initWithTexture(Texture2D *texture, const Rect& rect, bool rotated);
virtual void initShader();
public://method
CCSpriteWithHue();
virtual ~CCSpriteWithHue();
static CCSpriteWithHue* create(const std::string& filename);
virtual void setHue(GLfloat _hue);
};
#endif /* defined(__SnippetsProject__CCSpriteWithHue__) */
CCSpriteWithHue.cpp
//
// CCSpriteWithHue.cpp
// SnippetsProject
//
// Created by Lawis on 15/1/20.
//
//
#include "CCSpriteWithHue.h"
void xRotateMat(float mat[3][3], float rs, float rc);
void yRotateMat(float mat[3][3], float rs, float rc);
void zRotateMat(float mat[3][3], float rs, float rc);
void matrixMult(float a[3][3], float b[3][3], float c[3][3]);
void hueMatrix(GLfloat mat[3][3], float angle);
void premultiplyAlpha(GLfloat mat[3][3], float alpha);
CCSpriteWithHue::CCSpriteWithHue()
{
}
CCSpriteWithHue::~CCSpriteWithHue()
{
}
CCSpriteWithHue* CCSpriteWithHue::create(const std::string& filename)
{
CCSpriteWithHue *sprite = new (std::nothrow) CCSpriteWithHue();
if (sprite && sprite->initWithFile(filename))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
bool CCSpriteWithHue::initWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
{
if (!Sprite::initWithTexture(texture, rect, rotated)) {
return false;
}
this->setupDefaultSettings();
this->initShader();
return true;
}
void CCSpriteWithHue::setupDefaultSettings()
{
this->m_hue = 0.0;
}
void CCSpriteWithHue::initShader()
{
GLProgram * p = new GLProgram();
this->setGLProgram(p);
p->release();
p->initWithFilenames("hueChange.vsh", "hueChange.fsh");
p->link();
p->updateUniforms();
this->getUniformLocations();
this->updateColor();
}
void CCSpriteWithHue::getUniformLocations()
{
m_hueLocation = glGetUniformLocation(this->getGLProgram()->getProgram(), "u_hue");
m_alphaLocation = glGetUniformLocation(this->getGLProgram()->getProgram(), "u_alpha");
}
void CCSpriteWithHue::updateColorMatrix()
{
this->getGLProgram()->use();
GLfloat mat[3][3];
memset(mat, 0, sizeof(GLfloat)*9);
hueMatrix(mat, m_hue);
premultiplyAlpha(mat, this->alpha());
glUniformMatrix3fv(m_hueLocation, 1, GL_FALSE, (GLfloat *)&mat);
}
void CCSpriteWithHue::updateAlpha()
{
this->getGLProgram()->use();
glUniform1f(m_alphaLocation, this->alpha());
}
GLfloat CCSpriteWithHue::alpha()
{
return _displayedOpacity / 255.0f;
}
void CCSpriteWithHue::setHue(GLfloat _hue)
{
m_hue = _hue;
this->updateColorMatrix();
}
void CCSpriteWithHue::updateColor()
{
Sprite::updateColor();
this->updateColorMatrix();
this->updateAlpha();
}
#pragma mark -
void xRotateMat(float mat[3][3], float rs, float rc)
{
mat[0][0] = 1.0;
mat[0][1] = 0.0;
mat[0][2] = 0.0;
mat[1][0] = 0.0;
mat[1][1] = rc;
mat[1][2] = rs;
mat[2][0] = 0.0;
mat[2][1] = -rs;
mat[2][2] = rc;
}
void yRotateMat(float mat[3][3], float rs, float rc)
{
mat[0][0] = rc;
mat[0][1] = 0.0;
mat[0][2] = -rs;
mat[1][0] = 0.0;
mat[1][1] = 1.0;
mat[1][2] = 0.0;
mat[2][0] = rs;
mat[2][1] = 0.0;
mat[2][2] = rc;
}
void zRotateMat(float mat[3][3], float rs, float rc)
{
mat[0][0] = rc;
mat[0][1] = rs;
mat[0][2] = 0.0;
mat[1][0] = -rs;
mat[1][1] = rc;
mat[1][2] = 0.0;
mat[2][0] = 0.0;
mat[2][1] = 0.0;
mat[2][2] = 1.0;
}
void matrixMult(float a[3][3], float b[3][3], float c[3][3])
{
int x, y;
float temp[3][3];
for(y=0; y<3; y++) {
for(x=0; x<3; x++) {
temp[y][x] = b[y][0] * a[0][x] + b[y][1] * a[1][x] + b[y][2] * a[2][x];
}
}
for(y=0; y<3; y++) {
for(x=0; x<3; x++) {
c[y][x] = temp[y][x];
}
}
}
void hueMatrix(GLfloat mat[3][3], float angle)
{
#define SQRT_2 sqrt(2.0)
#define SQRT_3 sqrt(3.0)
float mag, rot[3][3];
float xrs, xrc;
float yrs, yrc;
float zrs, zrc;
// Rotate the grey vector into positive Z
mag = SQRT_2;
xrs = 1.0/mag;
xrc = 1.0/mag;
xRotateMat(mat, xrs, xrc);
mag = SQRT_3;
yrs = -1.0/mag;
yrc = SQRT_2/mag;
yRotateMat(rot, yrs, yrc);
matrixMult(rot, mat, mat);
// Rotate the hue
zrs = sin(angle);
zrc = cos(angle);
zRotateMat(rot, zrs, zrc);
matrixMult(rot, mat, mat);
// Rotate the grey vector back into place
yRotateMat(rot, -yrs, yrc);
matrixMult(rot, mat, mat);
xRotateMat(rot, -xrs, xrc);
matrixMult(rot, mat, mat);
}
void premultiplyAlpha(GLfloat mat[3][3], float alpha)
{
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
mat[i][j] *= alpha;
}
}
}
hueChange.fsh
varying vec2 v_texCoord;
uniform mat3 u_hue;
uniform float u_alpha;
void main()
{
vec4 pixColor = texture2D(CC_Texture0, v_texCoord);
vec3 rgbColor ;
rgbColor = u_hue * pixColor.rgb;
gl_FragColor = vec4(rgbColor.r,rgbColor.g,rgbColor.b, pixColor.a * u_alpha);
}
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
varying vec2 v_texCoord;
void main()
{
gl_Position = CC_PMatrix * a_position;
v_texCoord = a_texCoord;
}
注意 :Debug模式下,需要注释掉 CCGLProgramState.cpp apply() 方法中 switch语句default分支下的断言
调用方法如下:
CSpriteWithHue *sprite = CCSpriteWithHue::create("HelloWorld.png");
sprite->setHue(4.6);//值在 0 ~ 2 Pi 之间