背景 美术给出一套资源后,可以通过改变图片色相,复用同一套资源产生出多套资源的效果: 上图中蓝色是原始图片,利用代码改变图片色相后,可以产生效果差异明显的资源出来。
背景
美术给出一套资源后,可以通过改变图片色相,复用同一套资源产生出多套资源的效果:
上图中蓝色是原始图片,利用代码改变图片色相后,可以产生效果差异明显的资源出来。像一些传统的游戏,如星际争霸等,都是通过这种技术实现了同一兵种,不同颜色种族的特效。
实现理论原理
看上去非常神奇的转换,实际上是利用了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
普通代码实现
利用上述转换公式,实现代码如下(透明像素不处理):
[cpp] view plain copy
- <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
[cpp] view plain copy
- #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中增加以下代码
[cpp] view plain copy
- 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;
具体实现
[cpp] view plain copy
- 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();
- }
[cpp] view plain copy
- void GameColorSprite::setHSL(float h , float s, float l){
- m_dH = h;
- m_dS = s;
- m_dL = l;
- updateHSL();
- }
[cpp] view plain copy
- void GameColorSprite::updateHSL(){
- glUniform1f(m_dHlocation,m_dH);
- glUniform1f(m_dSlocation,m_dS);
- glUniform1f(m_dLlocation,m_dL);
- }