cocos2dx spine之二 :spine变色
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变色的更多相关文章
- cocos2dx spine之一 :spine缓存 (c++ & lua)
cocos2dx版本为3.10 1.在使用spine的过程中,发现了一个比较严重的问题:每次创建SkeletonAnimation的时候都会很卡,即使是使用同一个骨骼数据skeletonData. 跟 ...
- Spine学习二 -播放Spine动画
要想播放一个Spine动画,必须要在一个物体上绑定一个Spine播放的组件,这里暂时使用SkeletonAnimation组件. 然后就是编写动画的控制脚本. 这里提一个特性: [SpineAnima ...
- Spine应用--使用Spine动画制作动作游戏
在前面的文章中,已经陆陆续续的讲解了一些使用Spine动画的细节,有了这些细节,我们已经满足了在unity中使用Spine动画制作动作游戏的技术基础. 那么,要使用Spine动画在unity中制作一款 ...
- Spine学习七 - spine动画资源+ Unity Mecanim动画系统
前面已经讲过 Spine自己动画状态机的动画融合,但是万一有哥们就是想要使用Unity的动画系统,那有没有办法呢?答案是肯定的,接下来,就说说如何实现: 1. 在project面板找打你导入的Spin ...
- Spine学习五- spine动画融合
在许多地方,都需要用到动画融合,unity的新版动画系统已经能够很方便的进行动画融合,那么使用spine的动画状态机的情况下,如何来进行动画融合呢? 官方有两种方案,一种是使用混合动作实现,另一种是使 ...
- Cocos2d-x PluginX (二)增加新的Plugin
创建Plugin目录 第一步,在plugin/plugins下,目录需要严格按照如下规范实现: plugin/plugins/alipay/proj.android /proj.ios 因为publi ...
- HelloWorld——Cocos2d-x学习历程(二)
HelloWorld分析: 1."resource"文件夹 该文件夹主要用于存放游戏中需要的图片.音频和配置等资源文件. 2."include"和"s ...
- Cocos2dx项目启程二 之 封装属于我的按钮类
不知道为什么,很讨厌cocos2dx的 各菜单类,比如按钮:如果一张图片上就已经有按钮的几个状态了,我还是要创建多张资源图片, 最起码要指定这张图片上哪块区域是这个普通状态,哪块区域是那个选中状态.. ...
- 从零开始のcocos2dx生活(二)Node
节点 Node 文章目录 节点 Node 前言 变量初始化 创建一个节点对象 获取节点依赖的计数器 获取节点的描述(获取节点的Tag) 节点的局部层顺序值(LocalZOrder) 设置节点的Loca ...
随机推荐
- hibernate validator自定义校验注解以及基于服务(服务组)的校验
hibernate validator是Bean Validation 1.1 (JSR 349) Reference Implementation,其广泛的应用在mvc的参数校验中,尤其是使用服务端 ...
- 维护keepalived与mysql漂移脚本
环境拓扑 chengAlived #!/bin/bash function checkModelone(){ echo "重新进行获取" wget 192.168.158.147: ...
- 如何获取sdcard的总容量
activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android& ...
- 数据库 --- 4 多表查询 ,Navicat工具 , pymysql模块
一.多表查询 1.笛卡儿积 查询 2.连接 语法: ①inner 显示可构成连接的数据 mysql> select employee.id,employee.name,department ...
- Eclipse之maven插件link方式安装
maven是开发人员要具备的必不可少的技能之一.在使用eclipse进行开发时,我们需要安装maven插件,网上有很多教程,但是有些教程写的太过模糊.在此,我将自己的安装方法总结一下,尽量细致. 前提 ...
- strerror函数的总结【转】
本文转载自:https://www.cnblogs.com/xrcun/p/3210889.html 定义函数:char * strerror(int errnum); 函数说明:strerror() ...
- 【做题】Codeforces Round #453 (Div. 1) D. Weighting a Tree——拆环
前言:结论题似乎是我的硬伤…… 题意是给你一个无向图,已知连接到每一个点的边的权值和(为整数,且属于区间[-n,n]),需要求出每条边权值的一个合法解(都要是在区间[-2*n^2,2*n^2]内的整数 ...
- GDOI2018D2T1 谈笑风生
T1 谈笑风生 [题目描述] [输入] [输出] 一行两个数,所需能量P与在能量最小的前提下最短的到达时间t. [样例输入] 5 7 66 4 3 2 1 5 1 2 1 5 2 3 2 4 2 5 ...
- Visual Question Answering with Memory-Augmented Networks
Visual Question Answering with Memory-Augmented Networks 2018-05-15 20:15:03 Motivation: 虽然 VQA 已经取得 ...
- Python 安装与环境变量配置
一.软件下载 Python安装包下载地址:https://www.python.org/ 二.安装过程(略) 三.环境变量配置: 方法一:使用cmd命令添加path环境变量 在cmd下输入: path ...