草庐IT

c++ - 从灰色到彩色对 Sprite 进行着色

coder 2023-11-15 原文

我有很多相同的图形,但颜色不同。我想通过从灰度图像着色来优化它。此外,我想在游戏中为实时 Sprite 对象动态更改其颜色。也逐渐将颜色值从一种颜色类型更改为另一种颜色类型。

Don't know it it useful - Image-Transformation-Grayscale-to-Color.

最佳答案

要色调灰度 Sprite ,可以通过一个简单的片段着色器完成,该着色器将纹理的纹理像素的颜色与色调颜色相乘。
这导致灰度纹理使亮度呈现恒定的颜色。
以下所有着色器均考虑Premultiplied Alpha

顶点着色器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;

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

从 Sprite 创建灰度并为灰度着色

如果首先必须从RGB Sprite 创建灰度,然后又想着色该 Sprite ,则必须稍微调整片段着色器。

通常使用gray = 0.2126 * red + 0.7152 * green + 0.0722 * blue公式创建灰度颜色(在网络上,有不同的亮度公式和说明:Luma (video)Seven grayscale conversion algorithms。)
根据距离,您可以在原始颜色和黑白之间进行插值。
#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纹理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_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 );

生成渐变纹理

要通过一组颜色创建渐变纹理,建议使用Newton polynomial。以下算法处理任意数量的颜色,这些颜色必须分布在渐变上。
每种颜色都必须映射为灰度值,并且灰度值必须按升序设置。该算法必须设置至少2种颜色。

这意味着,例如,如果存在颜色c0c1c2,它们对应于灰度值g0g1g2,则必须像这样初始化算法:


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,以获得准确的结果。在cocos2d-x中,可以通过Texture2D::setAliasTexParameters完成。

简化插值算法

由于将颜色 channel 编码为一个字节(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]);
}

关于c++ - 从灰色到彩色对 Sprite 进行着色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45962006/

有关c++ - 从灰色到彩色对 Sprite 进行着色的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  5. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  6. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  7. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  8. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  9. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  10. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

随机推荐