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 ...
随机推荐
- Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源
Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源 在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等.今天就给大家介绍一个 Spri ...
- Python3 freetds.conf odbcinst.ini odbc.ini 之间的关系
Python3 freetds.conf odbcinst.ini odbc.ini 之间的关系 三者分别是FreeTDS和UnixODBC的配置文件: 1,FreeTDS中的freetds.conf ...
- opencv学习之路(5)、鼠标和滑动条操作
一.鼠标事件 #include<opencv2/opencv.hpp> #include<iostream> using namespace cv; using namespa ...
- topcoder srm 445 div1
problem1 link 这个的结论是只需要考虑坐标是整数或者是整数.5,比如(2.5,3),(4,3.5),(1.5,4.5)这样的时候.这个详细证明起来应该挺麻烦的.这里有一些讨论. probl ...
- Restful framework【第十二篇】版本控制
简单使用 -drf版本控制 -在setting中配置 'DEFAULT_VERSION': 'v1', # 默认版本(从request对象里取不到,显示的默认值) 'ALLOWED_VERSIONS' ...
- linux如何使make输出makefile中所有的规则和变量
答: make -p (会执行makefile,加入-q可以阻止makefile的执行)
- Java内存模型原理总结(转自51CTO)
转载地址:http://developer.51cto.com/art/201811/587220.htm [51CTO.com原创稿件]这篇文章主要介绍模型产生的问题背景,解决的问题,处理思路,相关 ...
- hihoCoder week3 KMP算法
题目链接 https://hihocoder.com/contest/hiho3/problems kmp算法 #include <bits/stdc++.h> using namespa ...
- How to Install Apache Tomcat 8.5 on CentOS 7.3
How to Install Apache Tomcat 8.5 on CentOS 7.3 From: https://www.howtoforge.com/tutorial/how-to-inst ...
- Sql Ado.net 学习笔记之连接字符串
https://www.cnblogs.com/heng95/p/5902019.html 连接字符串 SQL Client .net数据提供程序在连接到数据库时极其灵活,它提供了多种用以生成连接字符 ...