顶点着色器 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;}片段着色器 shader/tone.frag #ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform vec3 u_tintColor;void main(){ float normTint = 0.30 * u_tintColor.r + 0.59 * u_tintColor.g + 0.11 * u_tintColor.b; vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); vec3 mixColor = u_tintColor * texColor / normTint; gl_FragColor = vec4( mixColor.rgb, texColor.a );}为着色器程序对象添加一个类成员:cocos2d::GLProgram* mProgram;创建一个着色器程序,将其添加到精灵中,并在初始化期间设置制服: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 ); // e.g yellowcocos2d::Vec3 tintVal( tintColor.r/255.0f, tintColor.g/255.0f, tintColor.b/255.0f );state->setUniformVec3("u_tintColor", tintVal);从精灵创建灰度并为灰度着色如果首先必须从RGB精灵创建灰度,然后又想着色该精灵,则必须稍微调整片段着色器.通常使用公式gray = 0.2126 * red + 0.7152 * green + 0.0722 * blue创建灰度颜色(在网上有不同的亮度公式和解释: Luma(视频),七个灰度转换算法.)根据距离,您可以在原始颜色和黑白之间进行插值.#ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform vec3 u_tintColor;void main(){ float normTint = 0.30 * u_tintColor.r + 0.59 * u_tintColor.g + 0.11 * u_tintColor.b; vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); float gray = 0.30 * texColor.r + 0.59 * texColor.g + 0.11 * texColor.b; vec3 mixColor = u_tintColor * gray / normTint; gl_FragColor = vec4( mixColor.rgb, texColor.a );}渐变纹理映射要进行从灰度到颜色的映射,也可以使用渐变纹理.请参见以下片段着色器:#ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform sampler2D u_texGrad;void main(){ vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); vec4 lookUpCol = texture2D( u_texGrad, vec2( texColor.r / max(texColor.a, 0.01), 0.0 ) ); float alpha = texColor.a * lookUpCol.a; gl_FragColor = vec4( lookUpCol.rgb * alpha, alpha );}要使用此着色器,必须添加2D纹理Mebmer:cocos2d::Texture2D* mGradinetTexture;纹理和制服必须这样设置: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);进一步的改进是自动调整颜色的渐变#ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform sampler2D u_texGrad;void main(){ vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); vec4 lookUpCol = texture2D( u_texGrad, vec2( texColor.r / max(texColor.a, 0.01), 0.5 ) ); float lookUpGray = 0.30 * lookUpCol.r + 0.59 * lookUpCol.g + 0.11 * lookUpCol.b; lookUpCol *= texColor.r / lookUpGray; float alpha = texColor.a * lookUpCol.a; gl_FragColor = vec4( lookUpCol.rgb * alpha, alpha );}如果纹理的不透明部分和纹理的透明部分之间应该存在硬过渡,则设置片段颜色的着色器部分必须像这样进行修改:float alpha = step( 0.5, texColor.a ) * lookUpCol.a;gl_FragColor = vec4( lookUpCol.rgb * alpha, alpha );生成渐变纹理要通过一组颜色创建渐变纹理,我建议牛顿多项式.以下算法处理任意数量的颜色,这些颜色必须分布在渐变上.每种颜色都必须映射为灰度值,并且灰度值必须按升序设置.该算法必须设置至少2种颜色.这意味着,例如,如果存在与灰度值g0,g1和g2相对应的颜色c0,c1和c2,则算法必须为初始化如下: g0 = 131g1 = 176g2 = 244std::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 ++代码: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 );请注意,在这种情况下,当然必须使用没有自动调整的着色器,因为调整会线性化非线性梯度.这是从灰度颜色到RGB颜色的简单映射.映射表的左侧(灰度值)是恒定的,而映射表的右侧(RGB值)必须调整为纹理,必须从灰度纹理重新创建.优点是可以生成所有灰度值,因为会生成渐变映射纹理.虽然映射表的颜色与源纹理完全匹配,但它们之间的颜色是插值的.请注意,对于渐变纹理,必须将纹理过滤器参数设置为GL_NEAREST,以获得准确的结果.在cocos2d-x中,可以通过Texture2D::setAliasTexParameters完成.简化的插值算法由于将颜色通道编码为一个字节(unsigned byte),因此可以简化插值算法,而不会明显降低质量,尤其是当某些颜色的颜色不止3种时.以下算法对基点之间的颜色进行线性插值.从起点到第一个点,从RGB颜色(0,0,0)到第一个颜色存在线性插值.最后,保留最后一个RGB颜色(超出最后一个基点),以避免出现明亮的白色毛刺.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]);}I have a lot of same graphics but different colors. I want to optimize it by colorizing from grayscale image. Also, I would like to change it color on a fly, during gameplay for a live sprite object. Also gradually change color values from one color type to another.Don't know it it useful - Image-Transformation-Grayscale-to-Color. 解决方案 To tone a grayscale sprite, can be done by a simple fragment shader, which multiplies the color of the texel of the texture, with a tint color.This causes that a constant color is varayed in the brightness by the grayscale texture.All the following shaders consider Premultiplied Alpha.Vertex shader shader/tone.vertattribute vec4 a_position;attribute vec2 a_texCoord;varying vec2 cc_FragTexCoord1;void main(){ gl_Position = CC_PMatrix * a_position; cc_FragTexCoord1 = a_texCoord;}Fragment shader shader/tone.frag#ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform vec3 u_tintColor;void main(){ float normTint = 0.30 * u_tintColor.r + 0.59 * u_tintColor.g + 0.11 * u_tintColor.b; vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); vec3 mixColor = u_tintColor * texColor / normTint; gl_FragColor = vec4( mixColor.rgb, texColor.a );}Add a class member for the shader program object:cocos2d::GLProgram* mProgram;Create a shader program, add it to the sprite and set up 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 ); // e.g yellowcocos2d::Vec3 tintVal( tintColor.r/255.0f, tintColor.g/255.0f, tintColor.b/255.0f );state->setUniformVec3("u_tintColor", tintVal);Create grayscale from sprite and tint the grayscaleIf you first have to create a grayscale from an RGB sprite and second you want to tint the sprite, then you have to adapt the fragment shader slightly.A grayscale color is usually created with the formula gray = 0.2126 * red + 0.7152 * green + 0.0722 * blue (On the web there are different luminance formulas and explanations: Luma (video), Seven grayscale conversion algorithms.)Depending on the distance, you interpolate between the original color and the black and white color.#ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform vec3 u_tintColor;void main(){ float normTint = 0.30 * u_tintColor.r + 0.59 * u_tintColor.g + 0.11 * u_tintColor.b; vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); float gray = 0.30 * texColor.r + 0.59 * texColor.g + 0.11 * texColor.b; vec3 mixColor = u_tintColor * gray / normTint; gl_FragColor = vec4( mixColor.rgb, texColor.a );}Gradient texture mappingTo do the mapping from the grayscale to the color, a gradient texture can be used too. See the following fragment shader:#ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform sampler2D u_texGrad;void main(){ vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); vec4 lookUpCol = texture2D( u_texGrad, vec2( texColor.r / max(texColor.a, 0.01), 0.0 ) ); float alpha = texColor.a * lookUpCol.a; gl_FragColor = vec4( lookUpCol.rgb * alpha, alpha );}To use this shader, a 2D texture mebmer has to be add:cocos2d::Texture2D* mGradinetTexture;The texture and the uniform has to be set up like this: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 to automatically adjust the gradient of the color#ifdef GL_ESprecision mediump float;#endifvarying vec2 cc_FragTexCoord1;uniform sampler2D u_texGrad;void main(){ vec4 texColor = texture2D( CC_Texture0, cc_FragTexCoord1 ); vec4 lookUpCol = texture2D( u_texGrad, vec2( texColor.r / max(texColor.a, 0.01), 0.5 ) ); float lookUpGray = 0.30 * lookUpCol.r + 0.59 * lookUpCol.g + 0.11 * lookUpCol.b; lookUpCol *= texColor.r / lookUpGray; float alpha = texColor.a * lookUpCol.a; gl_FragColor = vec4( lookUpCol.rgb * alpha, alpha );}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, which sets the fragment color has to be adapted like this:float alpha = step( 0.5, texColor.a ) * lookUpCol.a;gl_FragColor = vec4( lookUpCol.rgb * alpha, alpha );Generating a gradient textureTo create a gradient texture by a set of colors, I suggest Newton polynomial. The following algorithm deals with any number of colors, which have to be distributed over the gradient.Each color has to be mapped to an gray value, and the gray values have to be setup in ascending order. The algorithm has to be setup with at least 2 colors.This means for example, if there are the colors c0, c1 and c2, which corresponds to the gray scale values g0, g1 and g2, the the algorithm has to be initialized like this:g0 = 131g1 = 176g2 = 244std::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, in this case of course the shader without the automatically adjustment has to be used, because the adjustment would linearize the nonlinear gradient.This is a simple mapping from a grayscale color to a RGB color. The left side of the mapping table (the gray scale values) is constant, while the right side of the table (the RGB values) have to be adjusted to the texture, which has to be recreate from the grayscale texture. The advantage is that all grayscale values can be mapped, because a gradient mapping texture is generated.While the colors of the mapping table exactly match to the source texture, the colors in between are interpolated.Note, that the texture filter parameters have to be set to GL_NEAREST, for the gradient texture, to get a accurate result. In cocos2d-x this can be done by Texture2D::setAliasTexParameters.Simplified interpolation algorithmSince a color channel is encoded into one byte (unsigned byte) the interpolation algorithm can be simplified, without a noticeable loss of quality, especially if there are some colors more than only 3.The following algorithm does a linear interpolation of the colors between the base points. From the beginning to the first point there is a linear interpolation from the RGB color (0, 0, 0) to the first color. At the end the (beyond the last base point) the last RGB color is kept, 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]);} 这篇关于从灰色到彩色对精灵进行着色的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
07-23 20:54