将灰度图像的精灵着色为彩色。

11
我有很多相同的图形,但颜色不同。我想通过从灰度图像上着色来优化它。同时,在游戏过程中,我希望实时更改精灵对象的颜色。还希望逐渐将颜色值从一种类型变为另一种类型。
我可以创建一个灰度图像,并寻找这样做的可能性: input -> 着色为:output input -> 着色为:enter image description here
1个回答

8
为了调整灰度雪碧图的色调,可以通过简单的片段着色器来实现,该着色器将纹理的像素颜色与染色颜色相乘。这会导致一个常数颜色在灰度纹理的亮度上变化。
以下所有着色器都考虑预乘阿尔法。 顶点着色器 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_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;

创建一个着色器程序,将其添加到精灵中,并在初始化期间设置uniform变量:
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 );
}

要使用此着色器,必须添加一个2D纹理成员:
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 );

生成渐变纹理

为了创建一组颜色的渐变纹理,我建议使用牛顿插值多项式算法。该算法处理任意数量的颜色,在渐变中必须分布这些颜色。每个颜色必须映射到一个灰度值,并且灰度值必须按升序设置。该算法至少需要设置两种颜色。

例如,如果存在颜色c0c1c2,它们对应于灰度值g0g1g2,那么该算法必须这样初始化:

enter image description here

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

请注意,在这种情况下,当然需要使用没有自动调整的着色器,因为调整会使非线性渐变线性化。
这是从灰度颜色到RGB颜色的简单映射。映射表的左侧(灰度值)是恒定的,而表的右侧(RGB值)必须根据要从灰度纹理重新创建的纹理进行调整。优点是可以映射所有灰度值,因为生成了渐变映射纹理。
虽然映射表中的颜色与源纹理完全匹配,但其中间的颜色是插值得到的。

请注意,渐变纹理的纹理过滤参数必须设置为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]);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接