背景
美术给出一套资源后,可以通过改变图片色相,复用同一套资源产生出多套资源的效果:
上图中蓝色是原始图片,利用代码改变图片色相后,可以产生效果差异明显的资源出来。像一些传统的游戏,如星际争霸等,都是通过这种技术实现了同一兵种,不同颜色种族的特效。
实现理论原理
看上去非常神奇的转换,实际上是利用了HSV格式图像处理的技术:
传统RGB模型:RGB是一种加色模式 将不同比例的RED/GREEN/BLUE混合在一起得到新的颜色
HSV(HSB)模型:通过色相/饱和度/亮度来得到颜色
H(hue):色相 表示颜色的类型 值域[0,360]
S(Saturation):饱和度 从灰度到纯色 值域[0,1]
V(Value or Brightness):亮度 从黑色到特定饱和度的颜色 值域[0,1]
HSV模型图
RGB到HSV的转换公式
HSV到RGB的转换公式
公式可以参考
http://baike.baidu.com/subview/541362/8445478.htm?fr=aladdin
普通代码实现
利用上述转换公式,实现代码如下(透明像素不处理):
- <spanstyle="font-family:SimSun;font-size:14px;">Texture2D*HelloWorld::initTextureWithImage(Image*image,float_fhue)
- {
- unsignedchar*tempData=NULL;
- boolhasAlpha=image->hasAlpha();
- SizeimageSize=Size((float)(image->getWidth()),(float)(image->getHeight()));
- Texture2D::PixelFormatpixelFormat;
- unsignedintwidth=image->getWidth();
- unsignedintheight=image->getHeight();
- //Repackthepixeldataintotherightformat
- unsignedintlength=width*height;
- unsignedintnewDataLen=0;
- //Convert"RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA"to"RRRRRRRRGGGGGGGGBBBBBBBB"
- float_f=_fhue/60;//节省运算
- if(hasAlpha)
- {
- //computepixelformat
- pixelFormat=Texture2D::PixelFormat::RGBA8888;
- tempData=newunsignedchar[length*4];
- newDataLen=length*4;
- unsignedchar*outPixel8=tempData;
- unsignedint*inPixel32=(unsignedint*)image->getData();
- for(unsignedinti=0;i<length;++i,++inPixel32)
- {
- unsignedchar*_colRGB=outPixel8;
- *outPixel8++=(*inPixel32>>0)&0xFF;//R
- *outPixel8++=(*inPixel32>>8)&0xFF;//G
- *outPixel8++=(*inPixel32>>16)&0xFF;//B
- *outPixel8=(*inPixel32>>24)&0xFF;//A
- //透明图层不做处理
- if(*outPixel8++)
- {
- unsignedchar_r=*_colRGB;
- unsignedchar_g=*(_colRGB+1);
- unsignedchar_b=*(_colRGB+2);
- unsignedcharmin=(_r<_g)?_r:_g;
- min=(min<_b)?min:_b;
- unsignedcharmax=(_r>_g)?_r:_g;
- max=(max>_b)?max:_b;
- unsignedchartemp=(max-min);
- floathsbH=0;//temp
- if(temp)
- {
- if(max==_r){
- if(_g>=_b)
- {
- hsbH=(float)(_g-_b)/(float)temp;
- }
- else
- {
- hsbH=((float)(_g-_b)/(float)temp)+6;
- }
- }
- elseif(max==_g){
- hsbH=((float)(_b-_r)/(float)temp)+2;
- }
- elseif(max==_b){
- hsbH=((float)(_r-_g)/(float)temp)+4;
- }
- }
- else
- {
- hsbH=0;
- }
- hsbH+=_f;
- if(hsbH<0)
- hsbH+=6;
- elseif(hsbH>6)
- hsbH-=6;
- chari=(int)hsbH;
- hsbH=hsbH-i;
- switch(i){
- case6:
- case0:
- (*_colRGB++)=max;
- (*_colRGB++)=min+(int)(hsbH*temp);
- (*_colRGB++)=min;
- break;
- case1:
- (*_colRGB++)=max-(int)(hsbH*temp);
- (*_colRGB++)=max;
- (*_colRGB++)=min;
- break;
- case2:
- (*_colRGB++)=min;
- (*_colRGB++)=max;
- (*_colRGB++)=min+(int)(hsbH*temp);
- break;
- case3:
- (*_colRGB++)=min;
- (*_colRGB++)=max-(int)(hsbH*temp);
- (*_colRGB++)=max;
- break;
- case4:
- (*_colRGB++)=min+(int)(hsbH*temp);
- (*_colRGB++)=min;
- (*_colRGB++)=max;
- break;
- case5:
- (*_colRGB++)=max;
- (*_colRGB++)=min;
- (*_colRGB++)=max-(int)(hsbH*temp);
- break;
- default:
- break;
- }
- }
- }
- }
- else
- {
- pixelFormat=Texture2D::PixelFormat::RGB888;
- tempData=newunsignedchar[length*3];
- newDataLen=length*3;
- unsignedchar*out3=image->getData();
- unsignedchar*outPixel8=tempData;
- for(unsignedinti=0;i<length;++i)
- {
- unsignedchar_r=*out3++;
- unsignedchar_g=*out3++;
- unsignedchar_b=*out3++;
- //changeHSLForRgb(255,0);
- //unsignedchar*_nowRGB=changeHSLForRgb(_r,_g,_b,_fhue);
- //_r=*_nowRGB++;
- //_g=*_nowRGB++;
- //_b=*_nowRGB++;
- *outPixel8++=_r;//R
- *outPixel8++=_g;//G
- *outPixel8++=_b;//B
- }
- }
- Texture2D*_text2d=newTexture2D();
- _text2d->initWithData(tempData,newDataLen,pixelFormat,width,height,imageSize);
- delete[]tempData;
- //_text2d->_hasPremultipliedAlpha=image->hasPremultipliedAlpha();
- return_text2d;
- }</span>
利用Shader实现
Shader可以利用GPU提升渲染效率:
colorHSL.fsh
- #ifdefGL_ES
- precisionmediumpfloat;
- #endif
- varyingvec2v_texCoord;
- uniformsampler2DCC_Texture0;
- uniformfloatu_dH;
- uniformfloatu_dS;
- uniformfloatu_dL;
- voidmain(){
- vec4texColor=texture2D(CC_Texture0,v_texCoord);
- floatr=texColor.r;
- floatg=texColor.g;
- floatb=texColor.b;
- floata=texColor.a;
- //convertrgbtohsl
- floath;
- floats;
- floatl;
- {
- floatmax=max(max(r,g),b);
- floatmin=min(min(r,b);
- //----h
- if(max==min){
- h=0.0;
- }elseif(max==r&&g>=b){
- h=60.0*(g-b)/(max-min)+0.0;
- }elseif(max==r&&g<b){
- h=60.0*(g-b)/(max-min)+360.0;
- }elseif(max==g){
- h=60.0*(b-r)/(max-min)+120.0;
- }elseif(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;
- }elseif(0.0<=l&&l<=0.5){
- s=(max-min)/(2.0*l);
- }elseif(l>0.5){
- s=(max-min)/(2.0-2.0*l);
- }
- }
- //(h,s,l)+(dH,dS,dL)->(h,l)
- h=h+u_dH;
- s=min(1.0,max(0.0,s+u_dS));
- l=l+u_dL;
- //convert(h,l)torgbandgotfinalcolor
- vec4finalColor;
- {
- floatq;
- if(l<0.5){
- q=l*(1.0+s);
- }elseif(l>=0.5){
- q=l+s-l*s;
- }
- floatp=2.0*l-q;
- floathk=h/360.0;
- floatt[3];
- t[0]=hk+1.0/3.0;t[1]=hk;t[2]=hk-1.0/3.0;
- for(inti=0;i<3;i++){
- if(t[i]<0.0)t[i]+=1.0;
- if(t[i]>1.0)t[i]-=1.0;
- }//gott[i]
- floatc[3];
- for(inti=0;i<3;i++){
- if(t[i]<1.0/6.0){
- c[i]=p+((q-p)*6.0*t[i]);
- }elseif(1.0/6.0<=t[i]&&t[i]<0.5){
- c[i]=q;
- }elseif(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,0.0);
- gl_FragColor=finalColor;
- }
以下适用COCOS2.2版本
- voidsetHSLMode();
- voidsetHSL(floath,floats,floatl);
- voidupdateHSL();
- floatm_dH;
- floatm_dS;
- floatm_dL;
- GLuintm_dHlocation;
- GLuintm_dSlocation;
- GLuintm_dLlocation;
具体实现
- voidGameColorSprite::setHSLMode(){
- ccBlendFuncblendFunc={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=newCGLProgramWithUnifos();
- 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();
- }
- voidGameColorSprite::setHSL(floath,floatl){
- m_dH=h;
- m_dS=s;
- m_dL=l;
- updateHSL();
- }
- voidGameColorSprite::updateHSL(){
- glUniform1f(m_dHlocation,m_dH);
- glUniform1f(m_dSlocation,m_dS);
- glUniform1f(m_dLlocation,m_dL);
- }