在飞行中重新着色精灵

KAM*_*AZE 5 c++ glsl cocos2d-x

我需要替换精灵的颜色.
一些建立在谷歌的例子
图片

这里我发现了一个看起来像Unity的工作解决方案 - [如何使用着色器动态交换精灵的颜色] [2]

如何将它移植到cocos2d-x?有人可以帮助代码示例吗?

我正在寻找cocos2d-x v3代码片段.真的很期待一些帮助.

Rab*_*d76 5

文章如何使用着色器动态交换精灵颜色的算法非常简单.它基于具有256个条目的一维查找表.这允许算法仅映射256种不同的颜色.

详细地说,新颜色(用于替换的颜色)存储在具有256个条目的一维纹理中.当从原始纹理读取颜色时,使用键在一维交换纹理中找到新颜色.使用的关键是原始颜色的红色通道,这意味着原始文本中的所有不同颜色也必须具有不同的红色值.这是另一个限制.
原始文档(如何使用着色器动态交换Sprite的颜色)说:

请注意,如果精灵纹理上的两种或多种颜色共享相同的红色值,这可能无法正常工作!使用此方法时,保持精灵纹理中颜色的红色值不同非常重要.

此外,算法通过交换颜色的alpha通道混合原始颜色和交换颜色.这会导致该交换如果颜色绘制交换颜色是完全不透明的,并且如果原始颜色绘制交换颜色是完全透明的,在两者之间将被线性插值.

使用此算法的GLSL函数非常短,看起来像这样:

uniform sampler2D u_spriteTexture; // sprite texture 
uniform sampler1D u_swapTexture;   // lookup texture with swap colors

vec4 SwapColor( vec2 textureCoord )
{
    vec4 originalColor = texture( u_spriteTexture, textureCoord.st );
    vec4 swapColor     = texture( u_swapTexture, originalColor.r );
    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
    return vec4( finalColor.rgb, originalColor.a );
}
Run Code Online (Sandbox Code Playgroud)

建议的算法

从问题中读出建议的着色器,我找到了以下解决方案.着色器使用算法将RGB转换为色调,饱和度,值和返回.我接受了这个想法并介绍了我自己的想法.

RGB和HSV之间的高性能转换功能可以在HLSL中的RGB到HSV/HSL/HCY/HCL中找到,可以很容易地从HLSL转换为GLSL:

RGB到HSV

const float Epsilon = 1e-10;

vec3 RGBtoHCV( in vec3 RGB )
{
   vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
   vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
   float C = Q.x - min(Q.w, Q.y);
   float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
   return vec3(H, C, Q.x);
}

vec3 RGBtoHSV(in vec3 RGB)
{
    vec3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + Epsilon);
    return vec3(HCV.x, S, HCV.z);
}
Run Code Online (Sandbox Code Playgroud)

HSV到RGB

vec3 HUEtoRGB(in float H)
{
    float R = abs(H * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(H * 6.0 - 2.0);
    float B = 2.0 - abs(H * 6.0 - 4.0);
    return clamp( vec3(R,G,B), 0.0, 1.0 );
}

vec3 HSVtoRGB(in vec3 HSV)
{
    vec3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}
Run Code Online (Sandbox Code Playgroud)

如在该答案的第一算法中那样,再次需要一维查找表.但是查找表的长度不一定是256,它完全取决于用户.关键不是红色通道,它是色调值,它是颜色的清晰表达,可以很容易地计算,如RGBtoHSV和中所示RGBtoHSV.然而,查找表必须包含在原始颜色的0到1的*hue*范围内线性分布的颜色分配.

可以使用以下步骤定义算法:

  • 将原始颜色转换为原始色调,饱和度
  • 使用原始色调作为键在查找表中查找交换颜色
  • 交换颜色转换为交换色调,饱和度
  • 转换的色调中的交换颜色和原来的饱和度,以及到一个新的RGB色彩
  • 通过交换颜色的Alpha通道混合原始颜色和新颜色

使用此算法,可以通过保持原始颜色的饱和度来交换任何RGB 颜色.请参阅以下简短明了的GLSL功能:

uniform sampler2D u_spriteTexture; // sprite texture 
uniform sampler1D u_swapTexture;   // lookup texture with swap colors 
                                   // the texture coordinate is the hue of the original color

vec4 SwapColor( vec2 textureCoord )
{
    vec4 originalColor = texture( u_spriteTexture, textureCoord.st );
    vec3 originalHSV   = RGBtoHSV( originalColor.rgb );
    vec4 lookUpColor   = texture( u_swapTexture, originalHSV.x );
    vec3 swapHSV       = RGBtoHSV( lookUpColor.rgb );
    vec3 swapColor     = HSVtoRGB( vec3( swapHSV.x, originalHSV.y, originalHSV.z ) );
    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, lookUpColor.a );
    return vec4( finalColor.rgb, originalColor.a );
}
Run Code Online (Sandbox Code Playgroud)


适用于cocos2d-x v3.15

要将着色器应用于cocos2d-x v3.15,我在cocos2d-x v3.15测试项目的项目cpp-empty-test中调整了HelloWorldScene.hHelloWorldScene.cpp. 着色器可以应用于任何精灵,可以交换多达10种颜色的色调,但这很容易扩展.请注意,着色器不仅会更改单个颜色,还会搜索与颜色相似的所有颜色,甚至是饱和度或亮度完全不同的颜色.每种颜色都用一种颜色交换,颜色具有相同的饱和度和亮度,但是具有新的基色. 交换颜色的信息存储在一个数组中.该组件包含原始颜色的色调,

vec3xycomponent包含交换颜色的色调,组件包含epsilon值,用于定义颜色范围.z

着色器源文件应放在项目目录的"resource/shader"子目录中.

顶点着色着色器/ colorswap.vert

attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

void main()
{
    gl_Position = CC_PMatrix * a_position;
    cc_FragColor = a_color;
    cc_FragTexCoord1 = a_texCoord;
}
Run Code Online (Sandbox Code Playgroud)

片段着色着色器/ colorswap.frag

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

const float Epsilon = 1e-10;

vec3 RGBtoHCV( in vec3 RGB )
{
   vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
   vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
   float C = Q.x - min(Q.w, Q.y);
   float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
   return vec3(H, C, Q.x);
}

vec3 RGBtoHSV(in vec3 RGB)
{
    vec3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + Epsilon);
    return vec3(HCV.x, S, HCV.z);
}

vec3 HUEtoRGB(in float H)
{
    float R = abs(H * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(H * 6.0 - 2.0);
    float B = 2.0 - abs(H * 6.0 - 4.0);
    return clamp( vec3(R,G,B), 0.0, 1.0 );
}

vec3 HSVtoRGB(in vec3 HSV)
{
    vec3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}

#define MAX_SWAP 10
uniform vec3 u_swap[MAX_SWAP];
uniform int  u_noSwap;

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec3 originalHSV   = RGBtoHSV( originalColor.rgb );
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < 10 ; ++ i )
    {
        if ( i >= u_noSwap )
            break;
        if ( abs( originalHSV.x - u_swap[i].x ) < u_swap[i].z )
        {
            swapColor.rgb = HSVtoRGB( vec3( u_swap[i].y, originalHSV.y, originalHSV.z ) );
            break;
        }
    }

    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
    gl_FragColor       = vec4( finalColor.rgb, originalColor.a );
} 
Run Code Online (Sandbox Code Playgroud)

头文件HelloWorldScene.h:

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"

#define MAX_COLOR 10

class HelloWorld : public cocos2d::Scene
{
public:
    virtual bool init() override;
    static cocos2d::Scene* scene();
    void menuCloseCallback(Ref* sender);
    CREATE_FUNC(HelloWorld);
    void InitSwapInfo( int i, const cocos2d::Color3B &sourceCol, const cocos2d::Color3B &swapCol, float deviation );
private:
    cocos2d::GLProgram* mProgramExample;
    cocos2d::Vec3 mSource[MAX_COLOR];
    cocos2d::Vec3 mSwap[MAX_COLOR];
    float mDeviation[MAX_COLOR];
    cocos2d::Vec3 mSwapInfo[MAX_COLOR];
};

#endif // __HELLOWORLD_SCENE_H__
Run Code Online (Sandbox Code Playgroud)

源文件HelloWorldScene.cpp:

Note, the C++ function RGBtoHue and the GLSL function RGBtoHue, should implement the exactly same algorithm.
The input to the function SwapInfo are RGB colors encoded to cocos2d::Vec3. If the source channels of the RGB colors are bytes (unsigned char), then this can easily converted to cocos2d::Vec3 by cocos2d::Vec3( R / 255.0f, G / 255.0f, B / 255.0f ).

#include "HelloWorldScene.h"
#include "AppMacros.h"

USING_NS_CC;

float RGBtoHue( const cocos2d::Vec3 &RGB )
{
   const float Epsilon = 1e-10f;
   cocos2d::Vec4 P = (RGB.y < RGB.z) ? 
       cocos2d::Vec4(RGB.y, RGB.z, -1.0f, 2.0f/3.0f) :
       cocos2d::Vec4(RGB.y, RGB.z, 0.0f, -1.0f/3.0f);
   cocos2d::Vec4 Q = (RGB.x < P.x) ? 
       cocos2d::Vec4(P.x, P.y, P.w, RGB.x) :
       cocos2d::Vec4(RGB.x, P.y, P.z, P.x);
   float C = Q.x - (Q.w < Q.y ? Q.w : Q.y);
   float H = fabs((Q.w - Q.y) / (6.0f * C + Epsilon) + Q.z);
   return H;
}

cocos2d::Vec3 SwapInfo( const cocos2d::Vec3 &sourceCol, const cocos2d::Vec3 &swapCol, float epsi )
{
  return cocos2d::Vec3( RGBtoHue( sourceCol ), RGBtoHue( swapCol ), epsi );
}

void  HelloWorld::InitSwapInfo( int i, const cocos2d::Color3B &sourceCol, const cocos2d::Color3B &swapCol, float deviation )
{
    mSource[i]    = cocos2d::Vec3( sourceCol.r/255.0, sourceCol.g/255.0, sourceCol.b/255.0 );
    mSwap[i]      = cocos2d::Vec3( swapCol.r/255.0, swapCol.g/255.0, swapCol.b/255.0 );
    mDeviation[i] = deviation;
    mSwapInfo[i]  = SwapInfo( mSource[i], mSwap[i], mDeviation[i] );
}

Scene* HelloWorld::scene()
{
     return HelloWorld::create();
}

bool HelloWorld::init()
{
    if ( !Scene::init() )  return false;     
    auto visibleSize = Director::getInstance()->getVisibleSize();
    auto origin = Director::getInstance()->getVisibleOrigin();

    auto closeItem = MenuItemImage::create(
                                        "CloseNormal.png",
                                        "CloseSelected.png",
                                        CC_CALLBACK_1(HelloWorld::menuCloseCallback,this));

    closeItem->setPosition(origin + Vec2(visibleSize) - Vec2(closeItem->getContentSize() / 2));

    auto menu = Menu::create(closeItem, nullptr);
    menu->setPosition(Vec2::ZERO);
    this->addChild(menu, 1);

    auto sprite = Sprite::create("HelloWorld.png");
    sprite->setPosition(Vec2(visibleSize / 2) + origin);

    mProgramExample = new GLProgram();
    mProgramExample->initWithFilenames("shader/colorswap.vert", "shader/colorswap.frag");
    mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
    mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
    mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS);
    mProgramExample->link();
    mProgramExample->updateUniforms(); 
    mProgramExample->use();

    GLProgramState* state = GLProgramState::getOrCreateWithGLProgram(mProgramExample);
    sprite->setGLProgram(mProgramExample);
    sprite->setGLProgramState(state);

    InitSwapInfo( 0, cocos2d::Color3B( 41, 201, 226 ), cocos2d::Color3B( 255, 0, 0 ),   0.1f );
    InitSwapInfo( 1, cocos2d::Color3B( 249, 6, 6 ),    cocos2d::Color3B( 255, 255, 0 ), 0.1f );
    int noOfColors = 2;
    state->setUniformVec3v("u_swap", noOfColors, mSwapInfo);
    state->setUniformInt("u_noSwap", noOfColors);

    this->addChild(sprite);

    return true;
}

void HelloWorld::menuCloseCallback(Ref* sender)
{
    Director::getInstance()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}
Run Code Online (Sandbox Code Playgroud)


Compare RGB values instead of Hue

A fragment shader which directly compares RGB colors would look like this:

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

const float Epsilon = 1e-10;

vec3 RGBtoHCV( in vec3 RGB )
{
   vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
   vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
   float C = Q.x - min(Q.w, Q.y);
   float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
   return vec3(H, C, Q.x);
}

vec3 RGBtoHSV(in vec3 RGB)
{
    vec3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + Epsilon);
    return vec3(HCV.x, S, HCV.z);
}

vec3 HUEtoRGB(in float H)
{
    float R = abs(H * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(H * 6.0 - 2.0);
    float B = 2.0 - abs(H * 6.0 - 4.0);
    return clamp( vec3(R,G,B), 0.0, 1.0 );
}

vec3 HSVtoRGB(in vec3 HSV)
{
    vec3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}

#define MAX_SWAP 10
uniform vec3  u_orig[MAX_SWAP];
uniform vec3  u_swap[MAX_SWAP];
uniform float u_deviation[MAX_SWAP];
uniform int   u_noSwap;

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec3 originalHSV   = RGBtoHSV( originalColor.rgb );
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < 10 ; ++ i )
    {
        if ( i >= u_noSwap )
            break;
        if ( all( lessThanEqual( abs(originalColor.rgb - u_orig[i]), vec3(u_deviation[i]) ) ) )
        {
            vec3 swapHSV  = RGBtoHSV( u_swap[i].rgb );
            swapColor.rgb = HSVtoRGB( vec3( swapHSV.x, originalHSV.y, originalHSV.z ) );
            break;
        }
    }

    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
    gl_FragColor       = vec4( finalColor.rgb, originalColor.a );
}
Run Code Online (Sandbox Code Playgroud)

Note, the initialization of the uniforms has to be adapt:

int noOfColors = 2;
state->setUniformVec3v("u_orig", noOfColors, mSource);
state->setUniformVec3v("u_swap", noOfColors, mSwap);
state->setUniformFloatv("u_deviation", noOfColors, mDeviation);
state->setUniformInt("u_noSwap", noOfColors);
Run Code Online (Sandbox Code Playgroud)

Extension to the answer

If exactly specified colors should be exchanged, the shader can be much more simplified. For this, the deviations u_deviation have to be restricted (e.g deviation = 0.02;).

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

#define MAX_SWAP 11
uniform vec3  u_orig[MAX_SWAP];
uniform vec3  u_swap[MAX_SWAP];
uniform float u_deviation[MAX_SWAP];
uniform int   u_noSwap;

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < MAX_SWAP ; ++ i )
    {
        vec3  deltaCol = abs( originalColor.rgb - u_orig[i] );
        float hit      = step( deltaCol.x + deltaCol.y + deltaCol.z, u_deviation[i] * 3.0 );
        swapColor.rgb  = mix( swapColor.rgb, u_swap[i].rgb, hit );
    }

    gl_FragColor    = vec4( swapColor.rgb, originalColor.a );
}
Run Code Online (Sandbox Code Playgroud)


If each color in the source texture has an individual color channel (this means the color value is only use for this special color, e.g. red color channel), then the shader code can be further simplified, because only the one channel has to be compared:

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < MAX_SWAP ; ++ i )
    {
        float hit      = step( abs( originalColor.r - u_orig[i].r ), u_deviation[i] );
        swapColor.rgb  = mix( swapColor.rgb, u_swap[i].rgb, hit );
    }

    gl_FragColor    = vec4( swapColor.rgb, originalColor.a );
}
Run Code Online (Sandbox Code Playgroud)


A further optimization would bring us back to the first algorithm, which was described in this answer. The big advantage of this algorithm would be, that each color is swapped (except the alpha channel of the swap texture is 0), but no expensive searching in the look up table has to be done in the shader.
Each color will be swapped by the corresponding color according to its red color channel. As mentioned, if a color should not be swapped, the alpha channel of the swap texture has to be set to 0.

A new member mSwapTexture has to be add to the class:

cocos2d::Texture2D* mSwapTexture;
Run Code Online (Sandbox Code Playgroud)

The texture can be easily created, and the uniform texture sampler can be set like this:

#include <array>

.....

std::array< unsigned char, 256 * 4 > swapPlane{ 0 };
for ( int c = 0; c < noOfColors; ++ c )
{
    size_t i = (size_t)( mSource[c].x * 255.0 ) * 4;
    swapPlane[i+0] = (unsigned char)(mSwap[c].x*255.0);
    swapPlane[i+1] = (unsigned char)(mSwap[c].y*255.0);
    swapPlane[i+2] = (unsigned char)(mSwap[c].z*255.0);
    swapPlane[i+3] = 255;
}
mSwapTexture = new Texture2D();
mSwapTexture->setAliasTexParameters();
cocos2d::Size contentSize;
mSwapTexture->initWithData( swapPlane.data(), swapPlane.size(), Texture2D::PixelFormat::RGBA8888, 256, 1, contentSize );
state->setUniformTexture( "u_swapTexture", mSwapTexture );
Run Code Online (Sandbox Code Playgroud)

The fragment shader would look like this:

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

uniform sampler2D u_swapTexture;   // lookup texture with 256 swap colors

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = texture2D(u_swapTexture, vec2(originalColor.r, 0.0));
    vec3 finalColor    = mix(originalColor.rgb, swapColor.rgb, swapColor.a); 
    gl_FragColor       = vec4(finalColor.rgb, originalColor.a);
}
Run Code Online (Sandbox Code Playgroud)

Of course, the lookup key has not always to be the red channel, any other channel is also possible.
Even a combination of 2 color channels would be possible by using a increased two dimensional lookup texture. See the following example which demonstrates the use of look up texture with 1024 entries. The look up table uses the full red channel (256 indices) in the X dimension and the green channel divided by 64 (4 indices) in the Y dimension.

Create a two dimensional look up table:

std::array< unsigned char, 1024 * 4 > swapPlane{ 0 };
for ( int c = 0; c < noOfColors; ++ c )
{
    size_t ix = (size_t)( mSource[c].x * 255.0 );
    size_t iy = (size_t)( mSource[c].y * 255.0 / 64.0 );
    size_t i = ( iy * 256 + ix ) * 4;
    swapPlane[i+0] = (unsigned char)(mSwap[c].x*255.0);
    swapPlane[i+1] = (unsigned char)(mSwap[c].y*255.0);
    swapPlane[i+2] = (unsigned char)(mSwap[c].z*255.0);
    swapPlane[i+3] = 255;
}
mSwapTexture = new Texture2D();
mSwapTexture->setAliasTexParameters();
cocos2d::Size contentSize;
mSwapTexture->initWithData( swapPlane.data(), swapPlane.size(), Texture2D::PixelFormat::RGBA8888, 256, 4, contentSize ); 
Run Code Online (Sandbox Code Playgroud)

And adapt the fragment shader:

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor    = mix(originalColor.rgb, swapColor.rgb, swapColor.a); 
    gl_FragColor       = vec4(finalColor.rgb, originalColor.a);
}
Run Code Online (Sandbox Code Playgroud)


Interpolate the texture

Since it is not possible to use GL_LINEAR with the above approach, this has to be emulated, if it would be of need:

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

uniform sampler2D u_swapTexture;   // lookup texture with 256 swap colors
uniform vec2 u_spriteSize;

void main()
{
    vec2 texS = 1.0 / u_spriteSize;
    vec2 texF = fract( cc_FragTexCoord1 * u_spriteSize + 0.5 );
    vec2 texC = (cc_FragTexCoord1 * u_spriteSize + 0.5 - texF) / u_spriteSize; 

    vec4 originalColor = texture2D(CC_Texture0, texC);
    vec4 swapColor     = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor00  = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    originalColor     = texture2D(CC_Texture0, texC+vec2(texS.x, 0.0));
    swapColor         = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor10 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    originalColor     = texture2D(CC_Texture0, texC+vec2(0.0,texS.y));
    swapColor         = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor01 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    originalColor     = texture2D(CC_Texture0, texC+texS.xy);
    swapColor         = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor11 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    vec3 finalColor0 = mix( finalColor00, finalColor10, texF.x );
    vec3 finalColor1 = mix( finalColor01, finalColor11, texF.x );
    vec3 finalColor  = mix( finalColor0, finalColor1, texF.y );

    gl_FragColor = vec4(finalColor.rgb, originalColor.a);
}
Run Code Online (Sandbox Code Playgroud)

The new uniform variable u_spriteSize has to be set like this:

auto size = sprite->getTexture()->getContentSizeInPixels();
state->setUniformVec2( "u_spriteSize", Vec2( (float)size.width, (float)size.height ) );
Run Code Online (Sandbox Code Playgroud)


Modify the texture on the CPU

Of course the texture can also be modified on the CPU, but then for each set of swap colors a separated texture has to be generated. the advantage would be that no more shader is of need.
The following code swaps the colors when the texture is loaded. The shader has to be skipped completely.

Sprite * sprite = nullptr;

std::string     imageFile = ....;
std::string     fullpath  = FileUtils::getInstance()->fullPathForFilename(imageFile);
cocos2d::Image *img       = !fullpath.empty() ? new Image() : nullptr;
if (img != nullptr && img->initWithImageFile(fullpath))
{
    if ( img->getRenderFormat() == Texture2D::PixelFormat::RGBA8888 )
    {
        unsigned char *plane = img->getData();
        for ( int y = 0; y < img->getHeight(); ++ y )
        {
            for ( int x = 0; x < img->getWidth(); ++ x )
            { 
                size_t i = ( y * img->getWidth() + x ) * 4;
                unsigned char t = plane[i];
                for ( int c = 0; c < noOfColors; ++ c )
                {
                    if ( fabs(mSource[c].x - plane[i+0]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].y - plane[i+1]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].z - plane[i+2]/255.0f) < mDeviation[c] )
                    {
                        plane[i+0] = (unsigned char)(mSwap[c].x*255.0);
                        plane[i+1] = (unsigned char)(mSwap[c].y*255.0);
                        plane[i+2] = (unsigned char)(mSwap[c].z*255.0);
                    }
                }
            }
        }
    }

    std::string key = "my_swap_" + imageFile;
    if ( Texture2D *texture = _director->getTextureCache()->addImage( img, key ) )
        sprite = Sprite::createWithTexture( texture );
}
Run Code Online (Sandbox Code Playgroud)


Combined approach on the CPU and GPU

This approach can be used if always the same regions (colors) of the texture are swapped. The advantage of this approach is, that the original texture is modified only once, but every application of the texture can hold its own swap table.
For this approach the alpha channel is used to hold the index of the swap color. In the example code below, the value range from 1 to including 11 is used to store the indices of the swap color. 0 is reserved for absolute transparency.

Sprite * sprite = nullptr;

std::string     imageFile = ....;
std::string     key       = "my_swap_" + imageFile;
Texture2D      *texture   = _director->getTextureCache()->getTextureForKey( key );
if (texture == nullptr)
{
    std::string     fullpath  = FileUtils::getInstance()->fullPathForFilename(imageFile);
    cocos2d::Image *img       = !fullpath.empty() ? new Image() : nullptr;
    if ( img->initWithImageFile(fullpath) &&
         img->getRenderFormat() == Texture2D::PixelFormat::RGBA8888 )
    {
        unsigned char *plane = img->getData();
        for ( int y = 0; y < img->getHeight(); ++ y )
        {
            for ( int x = 0; x < img->getWidth(); ++ x )
            { 
                size_t i = ( y * img->getWidth() + x ) * 4;
                unsigned char t = plane[i];
                for ( int c = 0; c < noOfColors; ++ c )
                {
                    if ( fabs(mSource[c].x - plane[i+0]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].y - plane[i+1]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].z - plane[i+2]/255.0f) < mDeviation[c] )
                    {
                        plane[i+3] = (unsigned char)(c+1);
                    }
                }
            }
        }
        texture = _director->getTextureCache()->addImage( img, key );
    }
}
if ( texture != nullptr )
    sprite = Sprite::createWithTexture( texture );
Run Code Online (Sandbox Code Playgroud)

The fragment shader needs only the uniforms u_swap and u_noSwap and does not have to do an expensive searching.

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

#define MAX_SWAP 11
uniform vec3  u_swap[MAX_SWAP];
uniform int   u_noSwap;

void main()
{
    vec4  originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    float fIndex        = originalColor.a * 255.0 - 0.5;
    float maxIndex      = float(u_noSwap) + 0.5; 
    int   iIndex        = int( clamp( fIndex, 0.0, maxIndex ) );
    float isSwap        = step( 0.0, fIndex ) * step( fIndex, maxIndex );
    vec3  swapColor     = mix( originalColor.rgb, u_swap[iIndex], isSwap );
    gl_FragColor        = vec4( swapColor.rgb, max(originalColor.a, isSwap) );
}
Run Code Online (Sandbox Code Playgroud)