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_ES
precision mediump float;
#endif
varying 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 yellow
cocos2d::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 (video),七种灰度转换算法)。根据距离的远近,您可以在原始颜色与黑白颜色之间进行插值处理。
#ifdef GL_ES
precision mediump float;
#endif
varying 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_ES
precision mediump float;
#endif
varying 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 );
}
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_ES
precision mediump float;
#endif
varying 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 );
为了创建一组颜色的渐变纹理,我建议使用牛顿插值多项式算法。该算法处理任意数量的颜色,在渐变中必须分布这些颜色。每个颜色必须映射到一个灰度值,并且灰度值必须按升序设置。该算法至少需要设置两种颜色。
例如,如果存在颜色c0
、c1
和c2
,它们对应于灰度值g0
、g1
和g2
,那么该算法必须这样初始化:
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++代码:
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 );
请注意,渐变纹理的纹理过滤参数必须设置为GL_NEAREST
才能获得准确的结果。在中,可以通过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]);
}