cocos2dx版本为3.10

1.具体原理和代码可以参考博文《利用shader改变图片色相Hue》,下面的代码根据该博文进行整理优化。

基本原理就是将RGB值转换为HSL值后加上输入的HSL值,再转换为RGB值。

2.spine变色的思路有三种:

①spine::SkeletonAnimation调用shader

②读取spine对应的atlas文件,分析该文件得到所需的png图片,将该图片读入内存,修改内存中像素颜色,然后生成texture赋值给spine中的spAtlas->pages->rendererObject

③读取spine对应的atlas文件,分析该文件得到所需的png图片,将该图片读入内存,修改内存中像素颜色,保存为新的png图片;读取atlas文件,修改里面png名字为新png图片名字,保存为新的atlas文件;spine创建时使用新的png图片和新的atlas文件;

以上三种方法都可行,但在实现过程中我选择使用了第③种方法;

第①种方法简单易用,但是打包到一些低端的android手机时发现掉帧很厉害,所以才会有第②和第③种方法。

第②种方法经过测试也是可行的,但是破坏了spine的正常创建流程,导致对源码的修改过于复杂容易导致各种问题。

第③种方法独立在正常创建spine流程之前,更稳当可控。

3.根据资料色相H值的范围在[0,360],饱和度S值的范围在[0,1],明度L值的范围在[0,1];在PhotoShop中(快捷键ctrl+u)可调值范围为色相H[-180,180],饱和度A[-100,100],明度I[-100,100];

所以在代码中,H值不进行转换,S值和L值都除以100;在实际测试过程中发现,PS调试出来的效果和程序得到的效果在S/L不为0的情况下还是有区别的,具体原因不懂,估计可能是算法不一致吧。

4.第①种方法shader

①shader代码

 #ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
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[];
t[] = hk + 1.0 / 3.0;
t[] = hk;
t[] = hk - 1.0 / 3.0; float c[];
for (int i = ; i < ; i++){
if (t[i] < 0.0)t[i] += 1.0;
if (t[i] > 1.0)t[i] -= 1.0; 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[], c[], c[], a);
}
finalColor += vec4(u_dL, u_dL, u_dL, 0.0);
gl_FragColor = finalColor;
}

②调用代码

 auto glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_vert, shader_content);//如果是Sprite使用这个Shader,则需要换为ccPositionTextureColor_noMVP_vert
GLProgramState* glState = GLProgramState::getOrCreateWithGLProgram(glprogram); glState->setUniformFloat("u_dH", h);
glState->setUniformFloat("u_dS", s / );
glState->setUniformFloat("u_dL", l / ); skeleton_animation->setGLProgramState(glState);

5.第③种方法创建新的png文件和atlas文件

①创建新png文件函数(这里的函数实现和参考博文《利用shader改变图片色相Hue》的实现不一样,参考博文只传了一个H值,这里是直接根据shader进行的移植修改)

 Image* create_new_image_hsl(Image* image, float u_dH, float u_dS, float u_dL){
u_dH = u_dH;
u_dS = u_dS / 100.0f;
u_dL = u_dL / 100.0f; bool hasAlpha = image->hasAlpha();
Texture2D::PixelFormat pixelFormat = image->getRenderFormat();
int bit_per_pixel = image->getBitPerPixel(); //每像素多少位
unsigned int length = image->getWidth()*image->getHeight(); if (hasAlpha){
//只处理了RGBA8888的格式
if (pixelFormat == Texture2D::PixelFormat::RGBA8888){
unsigned int* inPixel32 = (unsigned int*)image->getData();
for (unsigned int i = ; i < length; ++i, ++inPixel32)
{
unsigned char cr = (*inPixel32 >> ) & 0xFF; // R
unsigned char cg = (*inPixel32 >> ) & 0xFF; // G
unsigned char cb = (*inPixel32 >> ) & 0xFF; // B
unsigned char ca = (*inPixel32 >> ) & 0xFF; // A
//透明图层不做处理
if (ca){
float r = cr*1.0f / 0xFF;
float g = cg*1.0f / 0xFF;
float b = cb*1.0f / 0xFF; float h = ;
float s = ;
float l = ;
{
float max = r > g ? r : g;
max = max > b ? max : b; float min = r < g ? r : g;
min = min < b ? min : b; //----h
if (max == min){
h = ;
}
else if (max == r && g >= b){
h = 60.0f*(g - b) / (max - min);
}
else if (max == r && g < b){
h = 60.0f*(g - b) / (max - min) + 360.0f;
}
else if (max == g){
h = 60.0f*(b - r) / (max - min) + 120.0f;
}
else if (max == b){
h = 60.0f*(r - g) / (max - min) + 240.0f;
}
//----l
l = 0.5f*(max + min);
//----s
if (l == || max == min){
s = ;
}
else if ( <= l&&l <= 0.5f){
s = (max - min) / (2.0f*l);
}
else if (l > 0.5f){
s = (max - min) / (2.0f - 2.0f*l);
}
}
//(h,s,l)+(dH,dS,dL) -> (h,s,l)
h = h + u_dH;
s = > s + u_dS ? : s + u_dS;
s = s < 1.0f ? s : 1.0f; l = l + u_dL;
//convert (h,s,l) to rgb and got final color //vec4 finalColor;
{
float q;
if (l < 0.5f){
q = l*(1.0f + s);
}
else if (l >= 0.5f){
q = l + s - l*s;
}
float p = 2.0f*l - q;
float hk = h / 360.0f; float t[] = {};
t[] = hk + 1.0f / 3.0f;
t[] = hk;
t[] = hk - 1.0f / 3.0f; float c[] = {};
for (int i = ; i < ; i++){
if (t[i] < ){
t[i] += 1.0f;
}
if (t[i] > 1.0f){
t[i] -= 1.0f;
} if (t[i] < 1.0f / 6.0f){
c[i] = p + ((q - p)*6.0f*t[i]);
}
else if (1.0f / 6.0f <= t[i] && t[i] < 0.5f){
c[i] = q;
}
else if (0.5f <= t[i] && t[i] < 2.0f / 3.0f){
c[i] = p + ((q - p)*6.0f*(2.0f / 3.0f - t[i]));
}
else{
c[i] = p;
}
}
//finalColor = vec4(c[0], c[1], c[2], a); r = c[];
g = c[];
b = c[];
} //finalColor += vec4(u_dL, u_dL, u_dL, 0.0);
r += u_dL;
g += u_dL;
b += u_dL;
//限制rgb值的有效范围
if (r > 1.0f) r = 1.0f;
if (g > 1.0f) g = 1.0f;
if (b > 1.0f) b = 1.0f;
if (r < ) r = ;
if (g < ) g = ;
if (b < ) b = ; unsigned char final_r = r * 0xFF;
unsigned char final_g = g * 0xFF;
unsigned char final_b = b * 0xFF;
unsigned char final_a = ca; unsigned int val = final_a;
val = val << ;
val |= final_b;
val = val << ;
val |= final_g;
val = val << ;
val |= final_r; *inPixel32 = val;
}
}
}
} return image;
}

②分析atlas文件,得到对应的png文件,并生成新png文件和atlas文件

 bool create_new_png_and_atlas(const char* atlas_name_no_extension, float u_dH, float u_dS, float u_dL)
{
bool ret = false; std::string atlas_name = atlas_name_no_extension;
atlas_name += ".atlas";
std::string atlas_file_full_name = FileUtils::getInstance()->fullPathForFilename(atlas_name);
Data data = FileUtils::getInstance()->getDataFromFile(atlas_file_full_name); if (!data.isNull()){
std::string writeable_path = FileUtils::getInstance()->getWritablePath(); char* orginal_atlas_content = new char[data.getSize()]();
memcpy(orginal_atlas_content, data.getBytes(), data.getSize()); char sz_new_atlas_path[] = {};
sprintf(sz_new_atlas_path, "%s%s_%d_%d_%d.atlas", writeable_path.c_str(), atlas_name_no_extension, (int)u_dH, (int)u_dS, (int)u_dL); //为了保证大小,创建一个两倍大小的内存大小(这里可以适当减少大小)
unsigned char* new_atlas_content = new unsigned char[ * data.getSize()]();
memset(new_atlas_content, , * data.getSize()); //分析得出png文件名字,可能有分为多个png的情况
std::vector<std::string> png_no_extension_list;
int line_start_idx = , line_end_idx = ;
int orginal_content_start_idx = , new_content_idx = , new_content_total_len = ; for (int i = ; i < data.getSize(); ++i){
line_end_idx = i; //每一行判断是否为png
if (orginal_atlas_content[i] == '\n'){
int line_len = line_end_idx - line_start_idx; //处理是图片的情况
if (line_len > && orginal_atlas_content[line_end_idx - ] == '.' &&
(orginal_atlas_content[line_end_idx - ] == 'p' || orginal_atlas_content[line_end_idx - ] == 'P') &&
(orginal_atlas_content[line_end_idx - ] == 'n' || orginal_atlas_content[line_end_idx - ] == 'N') &&
(orginal_atlas_content[line_end_idx - ] == 'g' || orginal_atlas_content[line_end_idx - ] == 'G')){
//将png行前面的数据拷贝进来
int cpy_len = line_start_idx - orginal_content_start_idx;
if (cpy_len > ){
memcpy(new_atlas_content + new_content_idx, orginal_atlas_content + orginal_content_start_idx, cpy_len);
new_content_idx += cpy_len;
new_content_total_len += cpy_len;
} //没有后缀的png的名字
char png_name_no_extension[] = {};
memcpy(png_name_no_extension, orginal_atlas_content + line_start_idx, line_len - );
png_no_extension_list.push_back(png_name_no_extension); //记录列表
char sz_new_png_name[] = {};
sprintf(sz_new_png_name, "%s_%d_%d_%d.png\n", png_name_no_extension, (int)u_dH, (int)u_dS, (int)u_dL); //将图片名字写入新内容
cpy_len = strlen(sz_new_png_name);
memcpy(new_atlas_content + new_content_idx, sz_new_png_name, cpy_len);
new_content_idx += cpy_len;
new_content_total_len += cpy_len; orginal_content_start_idx = line_end_idx + ;
} line_start_idx = line_end_idx + ;
}
} //将最后一个png行到最后那部分数据也拷贝进来
if (data.getSize() > orginal_content_start_idx){
int cpy_len = data.getSize() - orginal_content_start_idx;
memcpy(new_atlas_content + new_content_idx, orginal_atlas_content + orginal_content_start_idx, cpy_len);
new_content_total_len += cpy_len;
} Data data;
data.copy(new_atlas_content, new_content_total_len); if (!FileUtils::getInstance()->writeDataToFile(data, sz_new_atlas_path)){
cocos2d::log("========!!! save file %s error !!!========.", sz_new_atlas_path);
}
else{
ret = true;
} data.clear(); delete[]orginal_atlas_content;
delete[]new_atlas_content; if (png_no_extension_list.size() <= ){
cocos2d::log("========!!! png_name_list is empty error !!!========.");
return false;
} if (!ret){
return false;
} //写入新图片
for (std::vector<std::string>::iterator it = png_no_extension_list.begin(); it != png_no_extension_list.end(); ++it){
if (!ret){
break;
} char sz_new_png_path[] = {};
sprintf(sz_new_png_path, "%s%s_%d_%d_%d.png", writeable_path.c_str(), it->c_str(), (int)u_dH, (int)u_dS, (int)u_dL); char sz_png_name[] = {};
sprintf(sz_png_name, "%s.png", it->c_str());
std::string png_file_full_name = FileUtils::getInstance()->fullPathForFilename(sz_png_name); Image* image = new Image();
if (image->initWithImageFile(png_file_full_name)){
if (Image* new_image = create_new_image_hsl(image, u_dH, u_dS, u_dL)){
if (!new_image->saveToFile(sz_new_png_path, false)){
cocos2d::log("========!!! save file %s error !!!========.", sz_new_png_path);
ret = false;
}
}
else{
cocos2d::log("========!!! create_new_image_hsl %s error !!!========.", sz_new_png_path);
ret = false;
}
}
else{
cocos2d::log("========!!! initWithImageFile %s error !!!========.", png_file_full_name.c_str());
ret = false;
} delete image;
}
} return ret;
}

6.实现效果

①原图

②PhotoShop中调到115,0,0的效果

③传入参数115,0,0后创建新Image的效果

可以看到,使用ps修改的效果和程序输出的效果基本一样。

以上,完。

cocos2dx spine之二 :spine变色的更多相关文章

  1. cocos2dx spine之一 :spine缓存 (c++ & lua)

    cocos2dx版本为3.10 1.在使用spine的过程中,发现了一个比较严重的问题:每次创建SkeletonAnimation的时候都会很卡,即使是使用同一个骨骼数据skeletonData. 跟 ...

  2. Spine学习二 -播放Spine动画

    要想播放一个Spine动画,必须要在一个物体上绑定一个Spine播放的组件,这里暂时使用SkeletonAnimation组件. 然后就是编写动画的控制脚本. 这里提一个特性: [SpineAnima ...

  3. Spine应用--使用Spine动画制作动作游戏

    在前面的文章中,已经陆陆续续的讲解了一些使用Spine动画的细节,有了这些细节,我们已经满足了在unity中使用Spine动画制作动作游戏的技术基础. 那么,要使用Spine动画在unity中制作一款 ...

  4. Spine学习七 - spine动画资源+ Unity Mecanim动画系统

    前面已经讲过 Spine自己动画状态机的动画融合,但是万一有哥们就是想要使用Unity的动画系统,那有没有办法呢?答案是肯定的,接下来,就说说如何实现: 1. 在project面板找打你导入的Spin ...

  5. Spine学习五- spine动画融合

    在许多地方,都需要用到动画融合,unity的新版动画系统已经能够很方便的进行动画融合,那么使用spine的动画状态机的情况下,如何来进行动画融合呢? 官方有两种方案,一种是使用混合动作实现,另一种是使 ...

  6. Cocos2d-x PluginX (二)增加新的Plugin

    创建Plugin目录 第一步,在plugin/plugins下,目录需要严格按照如下规范实现: plugin/plugins/alipay/proj.android /proj.ios 因为publi ...

  7. HelloWorld——Cocos2d-x学习历程(二)

    HelloWorld分析: 1."resource"文件夹 该文件夹主要用于存放游戏中需要的图片.音频和配置等资源文件. 2."include"和"s ...

  8. Cocos2dx项目启程二 之 封装属于我的按钮类

    不知道为什么,很讨厌cocos2dx的 各菜单类,比如按钮:如果一张图片上就已经有按钮的几个状态了,我还是要创建多张资源图片, 最起码要指定这张图片上哪块区域是这个普通状态,哪块区域是那个选中状态.. ...

  9. 从零开始のcocos2dx生活(二)Node

    节点 Node 文章目录 节点 Node 前言 变量初始化 创建一个节点对象 获取节点依赖的计数器 获取节点的描述(获取节点的Tag) 节点的局部层顺序值(LocalZOrder) 设置节点的Loca ...

随机推荐

  1. linux centos6.8搭建 jdk 环境

    1. 上官网下载jdk1.8的包 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html ...

  2. vue.JS 介绍

    vueJS 介绍 首先,vueJS 是我很早之前就想要接触学习的东西,但是呢,一直没时间,主要是在学校,事太多,没心思定下心来学习,我学生生涯的最后一个假期的第一天晚上,万事开头难,那就先写点儿什么东 ...

  3. 20145311 王亦徐 《网络对抗技术》 Web基础

    20145311 王亦徐 <网络对抗技术> Web基础 实验内容 简单的web前端页面(HTML.CSS等) 简单的web后台数据处理(PHP) Mysql数据库 一个简单的web登陆页面 ...

  4. 动态规划之140 Word Break2

    这是一题不太明显的动态规划,主要考察的应该是深度优先搜索. static LinkedList<String> list = new LinkedList<String>(); ...

  5. 我的QML

    1.键盘加Text import QtQuick 2.7 import QtGraphicalEffects 1.0 Rectangle{ width:; height:; color:"# ...

  6. How to use Junit Listener

    JUnit Listeners If you want to do some operations when your tests are started, passed, finished, fai ...

  7. Python3 tkinter基础 Label compound 图片上显示文字 fg字体颜色 font字体大小

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  8. Flask学习【第7篇】:Flask中的wtforms使用

    简介flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装 pip3 install wtforms 简单使用wtforms组件 用 ...

  9. HDU - 1849 Rabbit and Grass 【Nim博弈】

    Problem Description 大学时光是浪漫的,女生是浪漫的,圣诞更是浪漫的,但是Rabbit和Grass这两个大学女生在今年的圣诞节却表现得一点都不浪漫:不去逛商场,不去逛公园,不去和AC ...

  10. 更新32位Spyder从3.0.0-> 3.2.3

    https://stackoverflow.com/questions/51222550/how-to-update-spyder-3-3-0 It works!! 1. went to the An ...