Cocos2D-X shader(四) 利用shader改变图片色相(Hue)

时间:2021-09-08 04:00:07

背景

美术给出一套资源后,可以通过改变图片色相,复用同一套资源产生出多套资源的效果:

Cocos2D-X shader(四) 利用shader改变图片色相(Hue)

Cocos2D-X shader(四) 利用shader改变图片色相(Hue)Cocos2D-X shader(四) 利用shader改变图片色相(Hue)

上图中蓝色是原始图片,利用代码改变图片色相后,可以产生效果差异明显的资源出来。像一些传统的游戏,如星际争霸等,都是通过这种技术实现了同一兵种,不同颜色种族的特效。

实现理论原理

看上去非常神奇的转换,实际上是利用了HSV格式图像处理的技术:

传统RGB模型:RGB是一种加色模式 将不同比例的RED/GREEN/BLUE混合在一起得到新的颜色 
Cocos2D-X shader(四) 利用shader改变图片色相(Hue)

HSV(HSB)模型:通过色相/饱和度/亮度来得到颜色 
H(hue):色相 表示颜色的类型 值域[0,360] 
S(Saturation):饱和度 从灰度到纯色 值域[0,1] 
V(Value or Brightness):亮度 从黑色到特定饱和度的颜色 值域[0,1]

HSV模型图 
Cocos2D-X shader(四) 利用shader改变图片色相(Hue)

RGB到HSV的转换公式 
Cocos2D-X shader(四) 利用shader改变图片色相(Hue)

HSV到RGB的转换公式 
Cocos2D-X shader(四) 利用shader改变图片色相(Hue)

公式可以参考 
http://baike.baidu.com/subview/541362/8445478.htm?fr=aladdin

普通代码实现

利用上述转换公式,实现代码如下(透明像素不处理):

<span style="font-family:SimSun;font-size:14px;">Texture2D* HelloWorld::initTextureWithImage(Image *image, float _fhue)
{

unsigned char* tempData = NULL;
bool hasAlpha = image->hasAlpha();
Size imageSize = Size((float)(image->getWidth()), (float)(image->getHeight()));
Texture2D::PixelFormat pixelFormat;

unsigned int width = image->getWidth();
unsigned int height = image->getHeight();

// Repack the pixel data into the right format
unsigned int length = width * height;
unsigned int newDataLen = 0;

// Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRRRRGGGGGGGGBBBBBBBB"
float _f = _fhue / 60; //节省运算
if (hasAlpha)
{
// compute pixel format
pixelFormat = Texture2D::PixelFormat::RGBA8888;
tempData = new unsigned char[length * 4];
newDataLen = length * 4;
unsigned char *outPixel8 = tempData;
unsigned int* inPixel32 = (unsigned int*)image->getData();
for (unsigned int i = 0; i < length; ++i, ++inPixel32)
{
unsigned char* _colRGB = outPixel8;
*outPixel8++ = (*inPixel32 >> 0) & 0xFF; // R
*outPixel8++ = (*inPixel32 >> 8) & 0xFF; // G
*outPixel8++ = (*inPixel32 >> 16) & 0xFF; // B
*outPixel8 = (*inPixel32 >> 24) & 0xFF; // A
//透明图层不做处理
if (*outPixel8++)
{
unsigned char _r = *_colRGB;
unsigned char _g = *(_colRGB + 1);
unsigned char _b = *(_colRGB + 2);
unsigned char min = (_r < _g) ? _r : _g;
min = (min < _b) ? min : _b;

unsigned char max = (_r > _g) ? _r : _g;
max = (max > _b) ? max : _b;

unsigned char temp = (max - min);
float hsbH = 0; //temp

if (temp)
{
if (max == _r) {
if (_g >= _b)
{
hsbH = (float)(_g - _b) / (float)temp;
}
else
{
hsbH = ((float)(_g - _b) / (float)temp) + 6;
}
}
else if (max == _g) {
hsbH = ((float)(_b - _r) / (float)temp) + 2;
}
else if (max == _b) {
hsbH = ((float)(_r - _g) / (float)temp) + 4;
}
}
else
{
hsbH = 0;
}

hsbH += _f;
if (hsbH < 0)
hsbH += 6;
else if (hsbH > 6)
hsbH -= 6;

char i = (int)hsbH;
hsbH = hsbH - i;
switch (i) {
case 6:
case 0:
(*_colRGB++) = max;
(*_colRGB++) = min + (int)(hsbH*temp);
(*_colRGB++) = min;
break;
case 1:
(*_colRGB++) = max - (int)(hsbH*temp);
(*_colRGB++) = max;
(*_colRGB++) = min;
break;
case 2:
(*_colRGB++) = min;
(*_colRGB++) = max;
(*_colRGB++) = min + (int)(hsbH*temp);
break;
case 3:
(*_colRGB++) = min;
(*_colRGB++) = max - (int)(hsbH*temp);
(*_colRGB++) = max;
break;
case 4:
(*_colRGB++) = min + (int)(hsbH*temp);
(*_colRGB++) = min;
(*_colRGB++) = max;
break;
case 5:
(*_colRGB++) = max;
(*_colRGB++) = min;
(*_colRGB++) = max - (int)(hsbH*temp);
break;
default:
break;
}
}
}
}
else
{
pixelFormat = Texture2D::PixelFormat::RGB888;
tempData = new unsigned char[length * 3];
newDataLen = length * 3;
unsigned char *out3 = image->getData();
unsigned char *outPixel8 = tempData;
for (unsigned int i = 0; i < length; ++i)
{
unsigned char _r = *out3++;
unsigned char _g = *out3++;
unsigned char _b = *out3++;
//changeHSLForRgb(255, 0, 0);
// unsigned char *_nowRGB = changeHSLForRgb(_r,_g,_b,_fhue);
// _r = *_nowRGB++;
// _g = *_nowRGB++;
// _b = *_nowRGB++;
*outPixel8++ = _r; // R
*outPixel8++ = _g; // G
*outPixel8++ = _b; // B
}
}

Texture2D* _text2d = new Texture2D();
_text2d->initWithData(tempData, newDataLen, pixelFormat, width, height, imageSize);


delete[] tempData;
//_text2d->_hasPremultipliedAlpha = image->hasPremultipliedAlpha();
return _text2d;
}</span>

利用Shader实现

Shader可以利用GPU提升渲染效率:

colorHSL.fsh

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 v_texCoord;
uniform sampler2D CC_Texture0;
uniform float u_dH;
uniform float u_dS;
uniform float u_dL;

void main() {

vec4 texColor=texture2D(CC_Texture0, v_texCoord);
float r=texColor.r;
float g=texColor.g;
float b=texColor.b;
float a=texColor.a;
//convert rgb to hsl
float h;
float s;
float l;
{
float max=max(max(r,g),b);
float min=min(min(r,g),b);
//----h
if(max==min){

h=0.0;
}else if(max==r&&g>=b){
h=60.0*(g-b)/(max-min)+0.0;
}else if(max==r&&g<b){
h=60.0*(g-b)/(max-min)+360.0;
}else if(max==g){
h=60.0*(b-r)/(max-min)+120.0;
}else if(max==b){
h=60.0*(r-g)/(max-min)+240.0;
}
//----l
l=0.5*(max+min);
//----s
if(l==0.0||max==min){
s=0.0;
}else if(0.0<=l&&l<=0.5){
s=(max-min)/(2.0*l);
}else if(l>0.5){
s=(max-min)/(2.0-2.0*l);
}
}
//(h,s,l)+(dH,dS,dL) -> (h,s,l)
h=h+u_dH;
s=min(1.0,max(0.0,s+u_dS));
l=l+u_dL;
//convert (h,s,l) to rgb and got final color
vec4 finalColor;
{
float q;
if(l<0.5){
q=l*(1.0+s);
}else if(l>=0.5){
q=l+s-l*s;
}
float p=2.0*l-q;
float hk=h/360.0;
float t[3];
t[0]=hk+1.0/3.0;t[1]=hk;t[2]=hk-1.0/3.0;
for(int i=0;i<3;i++){
if(t[i]<0.0)t[i]+=1.0;
if(t[i]>1.0)t[i]-=1.0;
}//got t[i]
float c[3];
for(int i=0;i<3;i++){
if(t[i]<1.0/6.0){
c[i]=p+((q-p)*6.0*t[i]);
}else if(1.0/6.0<=t[i]&&t[i]<0.5){
c[i]=q;
}else if(0.5<=t[i]&&t[i]<2.0/3.0){
c[i]=p+((q-p)*6.0*(2.0/3.0-t[i]));
}else{
c[i]=p;
}
}
finalColor=vec4(c[0],c[1],c[2],a);
}

finalColor+=vec4(u_dL,u_dL,u_dL,0.0);

gl_FragColor=finalColor;

}

以下适用COCOS2.2版本

.H中增加以下代码

void setHSLMode();
void setHSL(float h , float s, float l);
void updateHSL();

float m_dH;
float m_dS;
float m_dL;

GLuint m_dHlocation;
GLuint m_dSlocation;
GLuint m_dLlocation;


具体实现

void GameColorSprite::setHSLMode(){

ccBlendFunc blendFunc={GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA};
this->setBlendFunc(blendFunc);

GLchar * fragSource = (GLchar*) CCString::createWithContentsOfFile(CCFileUtils::sharedFileUtils()->fullPathForFilename("colorHSL.fsh").c_str())->getCString();

CGLProgramWithUnifos* pProgram = new CGLProgramWithUnifos();
pProgram->initWithVertexShaderByteArray(ccPositionTextureColor_vert, fragSource);
this->setShaderProgram(pProgram);
pProgram->release();

CHECK_GL_ERROR_DEBUG();

this->getShaderProgram()->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position);
this->getShaderProgram()->addAttribute(kCCAttributeNameColor, kCCVertexAttrib_Color);
this->getShaderProgram()->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords);
CHECK_GL_ERROR_DEBUG();

this->getShaderProgram()->link();
CHECK_GL_ERROR_DEBUG();

this->getShaderProgram()->updateUniforms();
CHECK_GL_ERROR_DEBUG();

m_dHlocation = glGetUniformLocation(getShaderProgram()->getProgram(), "u_dH");
m_dSlocation = glGetUniformLocation(getShaderProgram()->getProgram(), "u_dS");
m_dLlocation = glGetUniformLocation(getShaderProgram()->getProgram(), "u_dL");

updateHSL();
}

void GameColorSprite::setHSL(float h , float s, float l){
m_dH = h;
m_dS = s;
m_dL = l;
updateHSL();
}

void GameColorSprite::updateHSL(){
glUniform1f(m_dHlocation,m_dH);
glUniform1f(m_dSlocation,m_dS);
glUniform1f(m_dLlocation,m_dL);
}