Repainting sprites on the fly

I need to replace the sprite colors.
Google based example
image

So I found a similar working solution for Unity - [How to use a shader to dynamically replace Sprite colors] [2]

How to transfer it to cocos2d-x? Can anyone help with some code examples?

I am looking for a cocos2d-x v3 code snippet. I really hope for help.

+5
source share
2 answers

The algorithm in the article How to use a shader to dynamically replace Sprite colors is very simple. It is based on a one-dimensional lookup table with 256 elements. This allows the algorithm to display only 256 different colors.

In detail, new colors (colors used for replacement) are stored in a one-dimensional texture with 256 elements. When a color is read from the original texture, the key is used to find the new color in the one-dimensional swap texture. The key used is the red color of the source color, which means that all different colors of the source text must also have different red values. This is another limitation.
Source document ( How to use a shader to dynamically replace sprite colors ):

Please note that this may not work as expected if two or more colors on the sprite texture have the same red value! When using this method, it is important to keep the red color values โ€‹โ€‹in the sprite texture different.

Next, the algorithm mixes the source and swap colors with the swap color alpha channel. This leads to the fact that the swap color is drawn if the swap color is completely opaque, and the original color is drawn if the swap color is completely transparent, it will be linearly interpolated between them.

The GLSL function with this algorithm is very short and looks something like this:

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 ); } 

Proposed algorithm

Considering the proposed shader from the question, I went to the next solution. The shader uses an algorithm for converting from RGB to hue, saturation and value, and vice versa. I accepted this idea and presented my own thoughts.

Artist conversion functions between RGB and HSV can be found in RGB for HSV / HSL / HCY / HCL to HLSL , which can be easily translated from HLSL to GLSL:

RGB for 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 < Px) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx); float C = Qx - min(Qw, Qy); float H = abs((Qw - Qy) / (6.0 * C + Epsilon) + Qz); return vec3(H, C, Qx); } vec3 RGBtoHSV(in vec3 RGB) { vec3 HCV = RGBtoHCV(RGB); float S = HCV.y / (HCV.z + Epsilon); return vec3(HCV.x, S, HCV.z); } 

WWW to 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; } 

As in the first algorithm of this answer, again we need a one-dimensional search table. But the length of the search tables does not have to be exactly 256, it depends entirely on the user. The key is not a red channel, it is a hue value that is a clear expression of color and can be easily calculated as shown in RGBtoHSV and RGBtoHSV . However, the lookup table should contain a color distribution linearly distributed over the * hue * range from 0 to 1 of the original color.

The algorithm can be defined using the following steps:

  • Convert orignal color to hue, saturation, and orignal value.
  • Use the hue start style as a key to find the swap color in the lookup table.
  • Convert swap color to hue, saturation, and swap value
  • Convert swap color to original saturation, and the value of the new RGB color
  • Mix the original color and the new color on the swap color alpha channel.

Using this algorithm, any RGB color can be replaced while maintaining the saturation and value of the original color. See the following short and clear GLSL function:

 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 ); } 


Apply to cocos2d-x v3.15

To apply the shader to cocos2d-x v3.15, I adapted HelloWorldScene.h and HelloWorldScene.cpp in the cpp-empty-test project of cocos2d-x v3.15 test projects.
A shader can be applied to any sprite that can replace up to 10 color shades, but this can be easily expanded. Please note: the shader not only changes one color, it searches for all colors that are similar to the color, even colors with a completely different saturation or brightness. Each color exchanges a color that has equal saturation and brightness, but a new base color.
Information that changes colors is stored in the vec3 array. Component x contains the hue of the original color, component y contains the hue of the swap color, and component z contains the epsilon value, which defines the range of colors.

Shader source files should be placed in the "resource / shader" subdirectory of the project directory.

Vertex shader 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; } 

Fragment of a shader shader /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 < Px) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx); float C = Qx - min(Qw, Qy); float H = abs((Qw - Qy) / (6.0 * C + Epsilon) + Qz); return vec3(H, C, Qx); } 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 ); } 

Header file 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__ 

Source file HelloWorldScene.cpp:

Please note that the C ++ RGBtoHue function and the GLSL RGBtoHue function must implement exactly the same algorithm.
The input to the SwapInfo function is RGB colors encoded before cocos2d::Vec3 . If the original RGB color channels are bytes ( unsigned char ), then this can easily be converted to cocos2d::Vec3 to 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 < Px) ? cocos2d::Vec4(Px, Py, Pw, RGB.x) : cocos2d::Vec4(RGB.x, Py, Pz, Px); float C = Qx - (Qw < Qy ? Qw : Qy); float H = fabs((Qw - Qy) / (6.0f * C + Epsilon) + Qz); 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 } 


Compare RGB values โ€‹โ€‹instead of Hue

A fragment shader that directly compares RGB colors will 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 < Px) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx); float C = Qx - min(Qw, Qy); float H = abs((Qw - Qy) / (6.0 * C + Epsilon) + Qz); return vec3(H, C, Qx); } 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 ); } 

Please note that uniform initialization must be adapted:

 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); 

Answer extension

If you need to exchange exactly the specified colors, the shader can be greatly simplified. For this deviation, u_deviation should be limited (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 ); } 


If each color in the source texture has a separate color channel (this means that the color value is used only for this particular color, for example, red), then the shader code can be further simplified, since only one channel needs 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 ); } 


Further optimization will return us to the first algorithm that was described in this answer. A big advantage of this algorithm would be that each color is replaced (except that the alpha channel of the swap texture is 0), but the shader does not require an expensive search in the lookup table.
Each color will be replaced with the corresponding color in accordance with its red color. As already mentioned, if the color should not be replaced, the alpha channel of the tr swap texture should be set to 0.

The new mSwapTexture member must be added to the class:

 cocos2d::Texture2D* mSwapTexture; 

A texture can be easily created, and a single texture pattern can be set as follows:

 #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 ); 

The fragment shader will 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); } 

Of course, the search key does not always have to be a red channel, any other channel is possible.
Even a combination of two color channels would be possible using an enlarged two-dimensional search texture. See the following example for a use of a search texture with 1,024 elements. The lookup table uses the full red channel (256 indexes) in the X dimension, and the green channel is divided by 64 (4 indexes) in the Y dimension.

Create a lookup table with two dimensions:

 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 ); 

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); } 


Interpolate Texture

Since it is not possible to use GL_LINEAR with the above aproach, this needs to be emulated if necessary:

 #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); } 

The new uniform variable u_spriteSize should be set as follows:

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


Change texture on CPU

Of course, the texture can also be changed on the CPU, but then a separate texture must be created for each set of swap colors. the advantage would be that you no longer need a shader.
The following code changes colors when loading a texture. The shader should 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 ); } 


Combined approach to the processor and GPU

This approach can be used if the same areas (colors) of the texture always change places. The advantage of this approach is that the original texture is changed only once, but each texture application can contain its own pivot table.
For this approach, the alpha channel is used to store the swap color index. The following code example uses a range of values โ€‹โ€‹from 1 to 11 to store swap color indices. 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 ); 

The flash shader only needs the u_swap and u_noSwap and does not need an expensive search.

 #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) ); } 
+5
source

Change the hue, saturation, value of your sprite using a shader. Shader code example:

 #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; //algorithm ref to: https://en.wikipedia.org/wiki/HSL_and_HSV 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*(gb)/(max-min)+0.0; }else if(max==r&&g<b){ h=60.0*(gb)/(max-min)+360.0; }else if(max==g){ h=60.0*(br)/(max-min)+120.0; }else if(max==b){ h=60.0*(rg)/(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;//do not use HSL model to adjust lightness, because the effect is not good //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+sl*s; } float p=2.0*lq; 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+((qp)*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+((qp)*6.0*(2.0/3.0-t[i])); }else{ c[i]=p; } } finalColor=vec4(c[0],c[1],c[2],a); } //actually, it is not final color. the lightness has not been adjusted //adjust lightness use the simplest method finalColor+=vec4(u_dL,u_dL,u_dL,0.0); gl_FragColor=finalColor; } 
+1
source

Source: https://habr.com/ru/post/1271113/


All Articles