Skip to content

Recolorer les sprites à la volée

Asia, membre de notre équipe, nous a fait la faveur d'écrire cette rubrique car elle connaît parfaitement le sujet.

Solution :

L'algorithme présenté dans l'article Comment utiliser un shader pour permuter dynamiquement les couleurs d'un sprite est très simple. Il est basé sur une table de consultation unidimensionnelle de 256 entrées. Cela permet à l'algorithme de ne mettre en correspondance que 256 couleurs différentes.

Dans le détail, les nouvelles couleurs (les couleurs utilisées pour remplacer) sont stockées dans une texture unidimensionnelle avec 256 entrées. Lorsqu'une couleur est lue à partir de la texture originale, une clé est utilisée pour trouver la nouvelle couleur dans la texture unidimensionnelle. swap texture. La clé qui est utilisée est le canal de couleur rouge de la couleur originale, cela signifie que toutes les différentes couleurs du texte original doivent également avoir des valeurs de couleur rouge différentes. Il s'agit d'une autre restriction.
Le document original (Comment utiliser un shader pour échanger dynamiquement les couleurs d'un sprite) dit :

Notez que cela peut ne pas fonctionner comme prévu si deux ou plusieurs couleurs sur la texture du sprite partagent la même valeur rouge ! Lorsque vous utilisez cette méthode, il est important de garder les valeurs rouges des couleurs de la texture du sprite différentes.

En outre, l'algorithme mélange la couleur d'origine et la couleur de l'image. swap par le canal alpha de la couleur swap couleur. Cela fait que la couleur swap est dessinée si la couleur swap est complètement opaque et la couleur originale est dessinée si la couleur swap est complètement transparente, entre les deux, il y aura une interpolation linéaire.

Une fonction GLSL avec cet algorithme est très courte et ressemble en quelque sorte à ceci :

uniform sampler2D u_spriteTexture; // sprite texture 
uniform sampler1D u_swapTexture;   // lookup texture with swap colors

vec4 SwapColor( vec2 textureCoord )
{
    vec4 originalColor = texture( u_spriteTexture, textureCoord.st );
    vec4 swapColor     = texture( u_swapTexture, originalColor.r );
    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
    return vec4( finalColor.rgb, originalColor.a );
}

Algorithme suggéré

En lisant le shader suggéré de la question, je suis arrivé à la solution suivante. Le shader utilise un algorithme pour convertir le RVB en teinte, saturation et valeur et inversement. J'ai pris cette idée et j'ai introduit mes propres pensées.

Des fonctions de conversion performantes entre RGB et HSV peuvent être trouvées à RGB to HSV/HSL/HCY/HCL in HLSL, qui peuvent facilement être traduites de HLSL à GLSL :

RVB vers HSV

const float Epsilon = 1e-10;

vec3 RGBtoHCV( in vec3 RGB )
{
   vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
   vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
   float C = Q.x - min(Q.w, Q.y);
   float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
   return vec3(H, C, Q.x);
}

vec3 RGBtoHSV(in vec3 RGB)
{
    vec3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + Epsilon);
    return vec3(HCV.x, S, HCV.z);
}

HSV vers RGB

vec3 HUEtoRGB(in float H)
{
    float R = abs(H * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(H * 6.0 - 2.0);
    float B = 2.0 - abs(H * 6.0 - 4.0);
    return clamp( vec3(R,G,B), 0.0, 1.0 );
}

vec3 HSVtoRGB(in vec3 HSV)
{
    vec3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}

Comme dans le premier algorithme de cette réponse, encore une fois une table de consultation unidimensionnelle est nécessaire. Mais la longueur de la table de consultation ne doit pas être exactement 256, elle dépend entièrement de l'utilisateur. La clé n'est pas le canal rouge, c'est le canal teinte qui est une expression claire de la couleur et qui peut être facilement calculée comme on le voit dans le tableau suivant. RGBtoHSV et RGBtoHSV. La table de consultation doit cependant, contenir une affectation de couleur distribuée linéairement sur la plage de * teinte * de 0 à 1 de la couleur d'origine.

L'algorithme peut être défini avec les étapes suivantes :

  • Convertir la couleur d'origine en la couleur d'origine. teinte, saturation et valeur
  • Utilisez l'original teinte comme clé pour trouver la swap couleur dans la table de recherche
  • Convertir le swap à la couleur swap teinte, saturation et valeur
  • Convertir la valeur teinte de la échange et de la couleur originale saturation et valeur à une nouvelle couleur RVB
  • Mélangez la couleur originale et la nouvelle couleur par le canal alpha de la valeur swap couleur

Avec cet algorithme, n'importe quelle couleur RVB peut être échangée, en conservant la valeur de l'élément saturation et valeur de la couleur d'origine. Voir la fonction GLSL courte et claire suivante :

uniform sampler2D u_spriteTexture; // sprite texture 
uniform sampler1D u_swapTexture;   // lookup texture with swap colors 
                                   // the texture coordinate is the hue of the original color

vec4 SwapColor( vec2 textureCoord )
{
    vec4 originalColor = texture( u_spriteTexture, textureCoord.st );
    vec3 originalHSV   = RGBtoHSV( originalColor.rgb );
    vec4 lookUpColor   = texture( u_swapTexture, originalHSV.x );
    vec3 swapHSV       = RGBtoHSV( lookUpColor.rgb );
    vec3 swapColor     = HSVtoRGB( vec3( swapHSV.x, originalHSV.y, originalHSV.z ) );
    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, lookUpColor.a );
    return vec4( finalColor.rgb, originalColor.a );
}

Appliquer à cocos2d-x v3.15

Pour appliquer le shader à cocos2d-x v3.15 j'ai adapté la fonction HelloWorldScene.h et HelloWorldScene.cpp dans le projet cpp-empty-test des projets de test de cocos2d-x v3.15.
Le shader peut être appliqué à n'importe quel sprite a peut échanger jusqu'à 10 teintes de couleur, mais cela peut facilement être étendu. Notez, le shader ne change pas seulement une seule couleur, il recherche toutes les couleurs qui sont similaires à une couleur, même les couleurs avec une saturation ou une luminosité complètement différente. La couleur est échangée avec une couleur, qui a une saturation et une luminosité égales, mais une nouvelle couleur de base.
L'information qui permute les couleurs, est stockée dans un tableau de vec3. Le site x contient l'élément teinte de la couleur originale, la composante y contient le composant teinte de la échange de couleur, et le z contient une valeur epsilon, qui définit la gamme de couleurs.

Les fichiers sources du shader doivent être placés dans le sous-répertoire "resource/shader" du répertoire du projet.

Shader de sommets shader/colorswap.vert

attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

void main()
{
    gl_Position = CC_PMatrix * a_position;
    cc_FragColor = a_color;
    cc_FragTexCoord1 = a_texCoord;
}

Shader de fragments shader/colorswap.frag

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

const float Epsilon = 1e-10;

vec3 RGBtoHCV( in vec3 RGB )
{
   vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
   vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
   float C = Q.x - min(Q.w, Q.y);
   float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
   return vec3(H, C, Q.x);
}

vec3 RGBtoHSV(in vec3 RGB)
{
    vec3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + Epsilon);
    return vec3(HCV.x, S, HCV.z);
}

vec3 HUEtoRGB(in float H)
{
    float R = abs(H * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(H * 6.0 - 2.0);
    float B = 2.0 - abs(H * 6.0 - 4.0);
    return clamp( vec3(R,G,B), 0.0, 1.0 );
}

vec3 HSVtoRGB(in vec3 HSV)
{
    vec3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}

#define MAX_SWAP 10
uniform vec3 u_swap[MAX_SWAP];
uniform int  u_noSwap;

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec3 originalHSV   = RGBtoHSV( originalColor.rgb );
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < 10 ; ++ i )
    {
        if ( i >= u_noSwap )
            break;
        if ( abs( originalHSV.x - u_swap[i].x ) < u_swap[i].z )
        {
            swapColor.rgb = HSVtoRGB( vec3( u_swap[i].y, originalHSV.y, originalHSV.z ) );
            break;
        }
    }

    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
    gl_FragColor       = vec4( finalColor.rgb, originalColor.a );
} 

Fichier d'en-tête HelloWorldScene.h:

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"

#define MAX_COLOR 10

class HelloWorld : public cocos2d::Scene
{
public:
    virtual bool init() override;
    static cocos2d::Scene* scene();
    void menuCloseCallback(Ref* sender);
    CREATE_FUNC(HelloWorld);
    void InitSwapInfo( int i, const cocos2d::Color3B &sourceCol, const cocos2d::Color3B &swapCol, float deviation );
private:
    cocos2d::GLProgram* mProgramExample;
    cocos2d::Vec3 mSource[MAX_COLOR];
    cocos2d::Vec3 mSwap[MAX_COLOR];
    float mDeviation[MAX_COLOR];
    cocos2d::Vec3 mSwapInfo[MAX_COLOR];
};

#endif // __HELLOWORLD_SCENE_H__

Fichier source HelloWorldScene.cpp:

Note, la fonction C++ RGBtoHue et la fonction GLSL RGBtoHue, devrait implémenter exactement le même algorithme.
L'entrée de la fonction SwapInfo sont des couleurs RVB codées en cocos2d::Vec3. Si les canaux sources des couleurs RVB sont des octets (unsigned char), alors cela peut facilement être converti en cocos2d::Vec3 par cocos2d::Vec3( R / 255.0f, G / 255.0f, B / 255.0f ).

#include "HelloWorldScene.h"
#include "AppMacros.h"

USING_NS_CC;

float RGBtoHue( const cocos2d::Vec3 &RGB )
{
   const float Epsilon = 1e-10f;
   cocos2d::Vec4 P = (RGB.y < RGB.z) ? 
       cocos2d::Vec4(RGB.y, RGB.z, -1.0f, 2.0f/3.0f) :
       cocos2d::Vec4(RGB.y, RGB.z, 0.0f, -1.0f/3.0f);
   cocos2d::Vec4 Q = (RGB.x < P.x) ? 
       cocos2d::Vec4(P.x, P.y, P.w, RGB.x) :
       cocos2d::Vec4(RGB.x, P.y, P.z, P.x);
   float C = Q.x - (Q.w < Q.y ? Q.w : Q.y);
   float H = fabs((Q.w - Q.y) / (6.0f * C + Epsilon) + Q.z);
   return H;
}

cocos2d::Vec3 SwapInfo( const cocos2d::Vec3 &sourceCol, const cocos2d::Vec3 &swapCol, float epsi )
{
  return cocos2d::Vec3( RGBtoHue( sourceCol ), RGBtoHue( swapCol ), epsi );
}

void  HelloWorld::InitSwapInfo( int i, const cocos2d::Color3B &sourceCol, const cocos2d::Color3B &swapCol, float deviation )
{
    mSource[i]    = cocos2d::Vec3( sourceCol.r/255.0, sourceCol.g/255.0, sourceCol.b/255.0 );
    mSwap[i]      = cocos2d::Vec3( swapCol.r/255.0, swapCol.g/255.0, swapCol.b/255.0 );
    mDeviation[i] = deviation;
    mSwapInfo[i]  = SwapInfo( mSource[i], mSwap[i], mDeviation[i] );
}

Scene* HelloWorld::scene()
{
     return HelloWorld::create();
}

bool HelloWorld::init()
{
    if ( !Scene::init() )  return false;     
    auto visibleSize = Director::getInstance()->getVisibleSize();
    auto origin = Director::getInstance()->getVisibleOrigin();

    auto closeItem = MenuItemImage::create(
                                        "CloseNormal.png",
                                        "CloseSelected.png",
                                        CC_CALLBACK_1(HelloWorld::menuCloseCallback,this));

    closeItem->setPosition(origin + Vec2(visibleSize) - Vec2(closeItem->getContentSize() / 2));

    auto menu = Menu::create(closeItem, nullptr);
    menu->setPosition(Vec2::ZERO);
    this->addChild(menu, 1);

    auto sprite = Sprite::create("HelloWorld.png");
    sprite->setPosition(Vec2(visibleSize / 2) + origin);

    mProgramExample = new GLProgram();
    mProgramExample->initWithFilenames("shader/colorswap.vert", "shader/colorswap.frag");
    mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
    mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
    mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS);
    mProgramExample->link();
    mProgramExample->updateUniforms(); 
    mProgramExample->use();

    GLProgramState* state = GLProgramState::getOrCreateWithGLProgram(mProgramExample);
    sprite->setGLProgram(mProgramExample);
    sprite->setGLProgramState(state);

    InitSwapInfo( 0, cocos2d::Color3B( 41, 201, 226 ), cocos2d::Color3B( 255, 0, 0 ),   0.1f );
    InitSwapInfo( 1, cocos2d::Color3B( 249, 6, 6 ),    cocos2d::Color3B( 255, 255, 0 ), 0.1f );
    int noOfColors = 2;
    state->setUniformVec3v("u_swap", noOfColors, mSwapInfo);
    state->setUniformInt("u_noSwap", noOfColors);

    this->addChild(sprite);

    return true;
}

void HelloWorld::menuCloseCallback(Ref* sender)
{
    Director::getInstance()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}

Comparez les valeurs RVB au lieu de la teinte

Un fragment shader qui compare directement les couleurs RVB ressemblerait à ceci :

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

const float Epsilon = 1e-10;

vec3 RGBtoHCV( in vec3 RGB )
{
   vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
   vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
   float C = Q.x - min(Q.w, Q.y);
   float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
   return vec3(H, C, Q.x);
}

vec3 RGBtoHSV(in vec3 RGB)
{
    vec3 HCV = RGBtoHCV(RGB);
    float S = HCV.y / (HCV.z + Epsilon);
    return vec3(HCV.x, S, HCV.z);
}

vec3 HUEtoRGB(in float H)
{
    float R = abs(H * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(H * 6.0 - 2.0);
    float B = 2.0 - abs(H * 6.0 - 4.0);
    return clamp( vec3(R,G,B), 0.0, 1.0 );
}

vec3 HSVtoRGB(in vec3 HSV)
{
    vec3 RGB = HUEtoRGB(HSV.x);
    return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}

#define MAX_SWAP 10
uniform vec3  u_orig[MAX_SWAP];
uniform vec3  u_swap[MAX_SWAP];
uniform float u_deviation[MAX_SWAP];
uniform int   u_noSwap;

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec3 originalHSV   = RGBtoHSV( originalColor.rgb );
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < 10 ; ++ i )
    {
        if ( i >= u_noSwap )
            break;
        if ( all( lessThanEqual( abs(originalColor.rgb - u_orig[i]), vec3(u_deviation[i]) ) ) )
        {
            vec3 swapHSV  = RGBtoHSV( u_swap[i].rgb );
            swapColor.rgb = HSVtoRGB( vec3( swapHSV.x, originalHSV.y, originalHSV.z ) );
            break;
        }
    }

    vec3 finalColor    = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
    gl_FragColor       = vec4( finalColor.rgb, originalColor.a );
}

Attention, l'initialisation des uniformes doit être adaptée :

int noOfColors = 2;
state->setUniformVec3v("u_orig", noOfColors, mSource);
state->setUniformVec3v("u_swap", noOfColors, mSwap);
state->setUniformFloatv("u_deviation", noOfColors, mDeviation);
state->setUniformInt("u_noSwap", noOfColors);

Extension de la réponse

Si des couleurs exactement spécifiées doivent être échangées, le shader peut être beaucoup plus simplifié. Pour cela, les déviations u_deviation doivent être restreints (par exemple deviation = 0.02;).

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

#define MAX_SWAP 11
uniform vec3  u_orig[MAX_SWAP];
uniform vec3  u_swap[MAX_SWAP];
uniform float u_deviation[MAX_SWAP];
uniform int   u_noSwap;

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < MAX_SWAP ; ++ i )
    {
        vec3  deltaCol = abs( originalColor.rgb - u_orig[i] );
        float hit      = step( deltaCol.x + deltaCol.y + deltaCol.z, u_deviation[i] * 3.0 );
        swapColor.rgb  = mix( swapColor.rgb, u_swap[i].rgb, hit );
    }

    gl_FragColor    = vec4( swapColor.rgb, originalColor.a );
}

Si chaque couleur de la texture source a un canal de couleur individuel (cela signifie que la valeur de la couleur n'est utilisée que pour cette couleur spéciale, par exemple le canal de couleur rouge), alors le code du shader peut être encore simplifié, car seul le canal unique doit être comparé :

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = vec4( originalColor.rgb, 1.0 );

    for ( int i = 0; i < MAX_SWAP ; ++ i )
    {
        float hit      = step( abs( originalColor.r - u_orig[i].r ), u_deviation[i] );
        swapColor.rgb  = mix( swapColor.rgb, u_swap[i].rgb, hit );
    }

    gl_FragColor    = vec4( swapColor.rgb, originalColor.a );
}

Une optimisation supplémentaire nous ramènerait au premier algorithme, qui a été décrit dans cette réponse. Le grand avantage de cet algorithme serait, que chaque couleur est échangée (sauf le canal alpha de la texture d'échange est 0), mais aucune recherche coûteuse dans la table de consultation doit être faite dans le shader.
Chaque couleur sera permutée par la couleur correspondante en fonction de son canal de couleur rouge. Comme mentionné, si une couleur ne doit pas être permutée, le canal alpha de la texture de swap doit être mis à 0.

Un nouveau membre mSwapTexture doit être ajouté à la classe :

cocos2d::Texture2D* mSwapTexture;

La texture peut être facilement créée, et l'échantillonneur de texture uniforme peut être défini comme suit :

#include 

.....

std::array< unsigned char, 256 * 4 > swapPlane{ 0 };
for ( int c = 0; c < noOfColors; ++ c )
{
    size_t i = (size_t)( mSource[c].x * 255.0 ) * 4;
    swapPlane[i+0] = (unsigned char)(mSwap[c].x*255.0);
    swapPlane[i+1] = (unsigned char)(mSwap[c].y*255.0);
    swapPlane[i+2] = (unsigned char)(mSwap[c].z*255.0);
    swapPlane[i+3] = 255;
}
mSwapTexture = new Texture2D();
mSwapTexture->setAliasTexParameters();
cocos2d::Size contentSize;
mSwapTexture->initWithData( swapPlane.data(), swapPlane.size(), Texture2D::PixelFormat::RGBA8888, 256, 1, contentSize );
state->setUniformTexture( "u_swapTexture", mSwapTexture );

Le fragment shader ressemblerait à ceci :

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

uniform sampler2D u_swapTexture;   // lookup texture with 256 swap colors

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = texture2D(u_swapTexture, vec2(originalColor.r, 0.0));
    vec3 finalColor    = mix(originalColor.rgb, swapColor.rgb, swapColor.a); 
    gl_FragColor       = vec4(finalColor.rgb, originalColor.a);
}

Bien sûr, la clé de recherche ne doit pas toujours être le canal rouge, tout autre canal est également possible.
Même une combinaison de 2 canaux de couleur serait possible en utilisant une texture de lookup bidimensionnelle augmentée. Voir l'exemple suivant qui démontre l'utilisation d'une texture de look up avec 1024 entrées. La table de consultation utilise le canal rouge complet (256 indices) dans la dimension X et le canal vert divisé par 64 (4 indices) dans la dimension Y.

Créez une table de consultation à deux dimensions :

std::array< unsigned char, 1024 * 4 > swapPlane{ 0 };
for ( int c = 0; c < noOfColors; ++ c )
{
    size_t ix = (size_t)( mSource[c].x * 255.0 );
    size_t iy = (size_t)( mSource[c].y * 255.0 / 64.0 );
    size_t i = ( iy * 256 + ix ) * 4;
    swapPlane[i+0] = (unsigned char)(mSwap[c].x*255.0);
    swapPlane[i+1] = (unsigned char)(mSwap[c].y*255.0);
    swapPlane[i+2] = (unsigned char)(mSwap[c].z*255.0);
    swapPlane[i+3] = 255;
}
mSwapTexture = new Texture2D();
mSwapTexture->setAliasTexParameters();
cocos2d::Size contentSize;
mSwapTexture->initWithData( swapPlane.data(), swapPlane.size(), Texture2D::PixelFormat::RGBA8888, 256, 4, contentSize ); 

Et adaptez le fragment shader :

void main()
{
    vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    vec4 swapColor     = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor    = mix(originalColor.rgb, swapColor.rgb, swapColor.a); 
    gl_FragColor       = vec4(finalColor.rgb, originalColor.a);
}

Interpoler la texture

Puisqu'il n'est pas possible d'utiliser GL_LINEAR avec l'approche ci-dessus, cela doit être émulé, si cela s'avère nécessaire :

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

uniform sampler2D u_swapTexture;   // lookup texture with 256 swap colors
uniform vec2 u_spriteSize;

void main()
{
    vec2 texS = 1.0 / u_spriteSize;
    vec2 texF = fract( cc_FragTexCoord1 * u_spriteSize + 0.5 );
    vec2 texC = (cc_FragTexCoord1 * u_spriteSize + 0.5 - texF) / u_spriteSize; 

    vec4 originalColor = texture2D(CC_Texture0, texC);
    vec4 swapColor     = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor00  = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    originalColor     = texture2D(CC_Texture0, texC+vec2(texS.x, 0.0));
    swapColor         = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor10 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    originalColor     = texture2D(CC_Texture0, texC+vec2(0.0,texS.y));
    swapColor         = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor01 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    originalColor     = texture2D(CC_Texture0, texC+texS.xy);
    swapColor         = texture2D(u_swapTexture, originalColor.rg);
    vec3 finalColor11 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);

    vec3 finalColor0 = mix( finalColor00, finalColor10, texF.x );
    vec3 finalColor1 = mix( finalColor01, finalColor11, texF.x );
    vec3 finalColor  = mix( finalColor0, finalColor1, texF.y );

    gl_FragColor = vec4(finalColor.rgb, originalColor.a);
}

La nouvelle variable uniforme u_spriteSize doit être définie comme suit :

auto size = sprite->getTexture()->getContentSizeInPixels();
state->setUniformVec2( "u_spriteSize", Vec2( (float)size.width, (float)size.height ) );

Modifier la texture sur l'unité centrale

Bien sûr, la texture peut également être modifiée sur le CPU, mais alors pour chaque ensemble de swap couleurs une texture séparée doit être générée. l'avantage serait que plus aucun shader n'est nécessaire.
Le code suivant permute les couleurs lorsque la texture est chargée. Le shader doit être complètement sauté.

Sprite * sprite = nullptr;

std::string     imageFile = ....;
std::string     fullpath  = FileUtils::getInstance()->fullPathForFilename(imageFile);
cocos2d::Image *img       = !fullpath.empty() ? new Image() : nullptr;
if (img != nullptr && img->initWithImageFile(fullpath))
{
    if ( img->getRenderFormat() == Texture2D::PixelFormat::RGBA8888 )
    {
        unsigned char *plane = img->getData();
        for ( int y = 0; y < img->getHeight(); ++ y )
        {
            for ( int x = 0; x < img->getWidth(); ++ x )
            { 
                size_t i = ( y * img->getWidth() + x ) * 4;
                unsigned char t = plane[i];
                for ( int c = 0; c < noOfColors; ++ c )
                {
                    if ( fabs(mSource[c].x - plane[i+0]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].y - plane[i+1]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].z - plane[i+2]/255.0f) < mDeviation[c] )
                    {
                        plane[i+0] = (unsigned char)(mSwap[c].x*255.0);
                        plane[i+1] = (unsigned char)(mSwap[c].y*255.0);
                        plane[i+2] = (unsigned char)(mSwap[c].z*255.0);
                    }
                }
            }
        }
    }

    std::string key = "my_swap_" + imageFile;
    if ( Texture2D *texture = _director->getTextureCache()->addImage( img, key ) )
        sprite = Sprite::createWithTexture( texture );
}

Approche combinée sur le CPU et le GPU

Cette approche peut être utilisée si toujours les mêmes régions (couleurs) de la texture sont échangées. L'avantage de cette approche est, que la texture originale n'est modifiée qu'une seule fois, mais chaque application de la texture peut tenir sa propre.... swap table.
Pour cette approche, le canal alpha est utilisé pour contenir l'indice de la couleur swap. Dans l'exemple de code ci-dessous, la plage de valeurs de 1 à 11 inclus est utilisée pour stocker les indices de la couleur de swap. La valeur 0 est réservée à la transparence absolue.

Sprite * sprite = nullptr;

std::string     imageFile = ....;
std::string     key       = "my_swap_" + imageFile;
Texture2D      *texture   = _director->getTextureCache()->getTextureForKey( key );
if (texture == nullptr)
{
    std::string     fullpath  = FileUtils::getInstance()->fullPathForFilename(imageFile);
    cocos2d::Image *img       = !fullpath.empty() ? new Image() : nullptr;
    if ( img->initWithImageFile(fullpath) &&
         img->getRenderFormat() == Texture2D::PixelFormat::RGBA8888 )
    {
        unsigned char *plane = img->getData();
        for ( int y = 0; y < img->getHeight(); ++ y )
        {
            for ( int x = 0; x < img->getWidth(); ++ x )
            { 
                size_t i = ( y * img->getWidth() + x ) * 4;
                unsigned char t = plane[i];
                for ( int c = 0; c < noOfColors; ++ c )
                {
                    if ( fabs(mSource[c].x - plane[i+0]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].y - plane[i+1]/255.0f) < mDeviation[c] &&
                         fabs(mSource[c].z - plane[i+2]/255.0f) < mDeviation[c] )
                    {
                        plane[i+3] = (unsigned char)(c+1);
                    }
                }
            }
        }
        texture = _director->getTextureCache()->addImage( img, key );
    }
}
if ( texture != nullptr )
    sprite = Sprite::createWithTexture( texture );

Le fragment shader n'a besoin que des uniformes u_swap et u_noSwap et n'a pas besoin d'effectuer une recherche coûteuse.

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;

#define MAX_SWAP 11
uniform vec3  u_swap[MAX_SWAP];
uniform int   u_noSwap;

void main()
{
    vec4  originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
    float fIndex        = originalColor.a * 255.0 - 0.5;
    float maxIndex      = float(u_noSwap) + 0.5; 
    int   iIndex        = int( clamp( fIndex, 0.0, maxIndex ) );
    float isSwap        = step( 0.0, fIndex ) * step( fIndex, maxIndex );
    vec3  swapColor     = mix( originalColor.rgb, u_swap[iIndex], isSwap );
    gl_FragColor        = vec4( swapColor.rgb, max(originalColor.a, isSwap) );
}

Notes et commentaires

N'oubliez pas que vous avez la possibilité de dire si cela vous a aidé.



Utilisez notre moteur de recherche

Ricerca
Generic filters

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.