To adjust the shade of gray, you can make a simple fragment shader that multiplies the color of the texture texture with a shade of color. This leads to the fact that the constant color changes in brightness according to shades of gray.
All of the following shaders consider multiply alpha .
Vertex shader shader / tone.vert
attribute vec4 a_position; attribute vec2 a_texCoord; varying vec2 cc_FragTexCoord1; void main() { gl_Position = CC_PMatrix * a_position; cc_FragTexCoord1 = a_texCoord; }
Fragment of a shader shader /tone.frag
#ifdef GL_ES precision mediump float;
Add a class member for the shader program object:
cocos2d::GLProgram* mProgram;
Create a shader program, add it to the sprite and configure the uniforms during initialization:
auto sprite = cocos2d::Sprite::create( ..... ); sprite->setPosition( ..... ); mProgram = new cocos2d::GLProgram(); mProgram->initWithFilenames("shader/tone.vert", "shader/tone.frag"); mProgram->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION); mProgram->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS); mProgram->link(); mProgram->updateUniforms(); mProgram->use(); GLProgramState* state = GLProgramState::getOrCreateWithGLProgram(mProgram); sprite->setGLProgram(mProgram); sprite->setGLProgramState(state); cocos2d::Color3B tintColor( 255, 255, 0 );
Create sprite gracileles and tint shades of gray
If you first need to create grayscale from an RGB sprite, and then you want to tint the sprite, you need to adapt the fragment shader a bit.
Grayscale color is usually created using the formula gray = 0.2126 * red + 0.7152 * green + 0.0722 * blue (There are different brightness formulas and explanations on the Internet: Luma (video) , Seven gradations of gray algorithms .) Depending on the distance, you interpolate between original color and black and white.
#ifdef GL_ES precision mediump float;
Gradient texture mapping
To make a comparison from shades of gray to color, you can use a gradient texture. See the following fragment shader:
#ifdef GL_ES precision mediump float;
To use this shader, you need to add a two-dimensional text field:
cocos2d::Texture2D* mGradinetTexture;
Texture and uniform should be configured as follows:
std::string gradPath = FileUtils::getInstance()->fullPathForFilename("grad.png"); cocos2d::Image *gradImg = new Image(); gradImg->initWithImageFile( gradPath ); mGradinetTexture = new Texture2D(); mGradinetTexture->setAliasTexParameters(); mGradinetTexture->initWithImage( gradImg ); state->setUniformTexture("u_texGrad", mGradinetTexture);
A further improvement would be automatic color gradient adjustment
#ifdef GL_ES precision mediump float;
If there should be a hard transition between the opaque part of the texture and the transparent part of the texture, then the part of the shaders that sets the color of the fragment should be adapted as follows:
float alpha = step( 0.5, texColor.a ) * lookUpCol.a; gl_FragColor = vec4( lookUpCol.rgb * alpha, alpha );
Create Gradient Texture
To create a gradient texture using a set of colors, I propose a Newton polynomial . The following algorithm deals with any number of colors that should be distributed along a gradient. Each color should be matched with gray values, and gray values should be set in ascending order. The algorithm must be configured with at least two colors.
This means, for example, if there are colors c0 , c1 and c2 , which corresponds to the gray scale values g0 , g1 and g2 , the algorithm should be initialized as follows:

g0 = 131 g1 = 176 g2 = 244 std::vector< cocos2d::Color3B > gradBase{ cg0, cg1, cg2 }; std::vector< float > x_val{ 131 / 255.0f, 176 / 255.0f, 244 / 255.0f }; std::vector< cocos2d::Color3B > gradBase{ cr0, cr1, cr2 }; std::vector< float > x_val{ 131 / 255.0f, 176 / 255.0f, 244 / 255.0f };
C ++ Code:
unsigned char ClampColor( float colF ) { int c = (int)(colF * 255.0f + 0.5f); return (unsigned char)(c < 0 ? 0 : ( c > 255 ? 255 : c )); }
std::vector< cocos2d::Color3B > gradBase{ c0, c1, ..., cN }; std::vector< float > x_val{ g0, g1, ..., gn }; for ( int g = 0; g < x_val.size(); ++ g ) { x_val[g] = x_val[g] / 255.0f; } x_val.push_back( 1.0f ); gradBase.push_back( Color3B( 255, 255, 255 ) ); std::vector< std::array< float, 3 > > alpha; for ( int c = 0; c < (int)gradBase.size(); ++c ) { std::array< float, 3 >alphaN{ gradBase[c].r / 255.0f, gradBase[c].g / 255.0f, gradBase[c].b / 255.0f }; for ( int i = 0; i < c; ++ i ) { alphaN[0] = ( alphaN[0] - alpha[i][0] ) / (x_val[c]-x_val[i]); alphaN[1] = ( alphaN[1] - alpha[i][1] ) / (x_val[c]-x_val[i]); alphaN[2] = ( alphaN[2] - alpha[i][2] ) / (x_val[c]-x_val[i]); } alpha.push_back( alphaN ); } std::array< unsigned char, 256 * 4 > gradPlane; for ( int g = 0; g < 256; ++ g ) { float x = g / 255.0; std::array< float, 3 >col = alpha[0]; if ( x < x_val[0] ) { col = { col[0]*x/x_val[0] , col[1]*x/x_val[0], col[2]*x/x_val[0] }; } else { for ( int c = 1; c < (int)gradBase.size(); ++c ) { float w = 1.0f; for ( int i = 0; i < c; ++ i ) w *= x - x_val[i]; col = { col[0] + alpha[c][0] * w, col[1] + alpha[c][1] * w, col[2] + alpha[c][2] * w }; } } size_t i = g * 4; gradPlane[i+0] = ClampColor(col[0]); gradPlane[i+1] = ClampColor(col[1]); gradPlane[i+2] = ClampColor(col[2]); gradPlane[i+3] = 255; }
mGradinetTexture = new Texture2D(); cocos2d::Size contentSize; mGradinetTexture->setAliasTexParameters(); mGradinetTexture->initWithData( gradPlane.data(), gradPlane.size() / 4, Texture2D::PixelFormat::RGBA8888, 256, 1, contentSize );
Note that in this case, of course, a shader should be used without automatic tuning , because tuning linearizes the non-linear gradient.
This is a simple comparison from grayscale to RGB. The left side of the mapping table (gray scale values) is constant, and the right side of the table (RGB values) must be adjusted to a texture that must be recreated from the gray texture. The advantage is that all grayscale values can be displayed as a gradient display texture is created.
Although the colors in the mapping table exactly match the original texture, the interpolated colors are interpolated.
Note that the texture filter options must be set to GL_NEAREST , for the gradient texture to get an accurate result. In cocos2d-x this can be done using Texture2D::setAliasTexParameters .
Simplified interpolation algorithm
Since the color channel is encoded in one byte ( unsigned byte ), the interpolation algorithm can be simplified without noticeable loss of quality, especially if there are several colors more than 3.
The following algorithm performs linear interpolation of colors between base points. From the beginning to the first point there is linear interpolation from the RGB color (0, 0, 0) to the first color. At the end (past the last base point), the last RGB color is saved to avoid bright white glitches.
unsigned char ClampColor( float colF ) { int c = (int)(colF * 255.0f + 0.5f); return (unsigned char)(c < 0 ? 0 : ( c > 255 ? 255 : c )); }
std::vector< cocos2d::Color4B >gradBase { Color4B( 129, 67, 73, 255 ), Color4B( 144, 82, 84, 255 ), Color4B( 161, 97, 95, 255 ), Color4B( 178, 112, 105, 255 ), Color4B( 195, 126, 116, 255 ), Color4B( 211, 139, 127, 255 ), Color4B( 219, 162, 133, 255 ), Color4B( 228, 185, 141, 255 ), Color4B( 235, 207, 149, 255 ), Color4B( 245, 230, 158, 255 ), Color4B( 251, 255, 166, 255 ) }; std::vector< float > x_val { 86, 101, 116, 131, 146, 159, 176, 193, 209, 227, 244 }; for ( int g = 0; g < x_val.size(); ++ g ) { x_val[g] = x_val[g] / 255.0f; }
std::array< unsigned char, 256 * 4 > gradPlane; size_t x_i = 0; for ( int g = 0; g < 256; ++ g ) { float x = g / 255.0; if ( x_i < x_val.size()-1 && x >= x_val[x_i] ) ++ x_i; std::array< float, 4 > col; if ( x_i == 0 ) { std::array< float, 4 > col0{ gradBase[0].r / 255.0f, gradBase[0].g / 255.0f, gradBase[0].b / 255.0f, gradBase[0].a / 255.0f }; col = { col0[0]*x/x_val[0] , col0[1]*x/x_val[0], col0[2]*x/x_val[0], col0[3]*x/x_val[0] }; } else if ( x_i == x_val.size() ) { col = { gradBase.back().r / 255.0f, gradBase.back().g / 255.0f, gradBase.back().b / 255.0f, gradBase.back().a / 255.0f }; } else { std::array< float, 4 > col0{ gradBase[x_i-1].r / 255.0f, gradBase[x_i-1].g / 255.0f, gradBase[x_i-1].b / 255.0f, gradBase[x_i-1].a / 255.0f }; std::array< float, 4 > col1{ gradBase[x_i].r / 255.0f, gradBase[x_i].g / 255.0f, gradBase[x_i].b / 255.0f, gradBase[x_i].a / 255.0f }; float a = (x - x_val[x_i-1]) / (x_val[x_i] - x_val[x_i-1]); col = { col0[0] + (col1[0]-col0[0])*a, col0[1] + (col1[1]-col0[1])*a, col0[2] + (col1[2]-col0[2])*a, col0[3] + (col1[3]-col0[3])*a }; } size_t i = g * 4; gradPlane[i+0] = ClampColor(col[0]); gradPlane[i+1] = ClampColor(col[1]); gradPlane[i+2] = ClampColor(col[2]); gradPlane[i+3] = ClampColor(col[3]); }