Skip to content

Ricolorare gli sprite al volo

Se trovi qualche parte che ti fa dubitare, puoi commentarla e cercheremo di aiutarti il ​​più velocemente possibile.

Soluzione:

L'algoritmo descritto nell'articolo Come usare uno shader per scambiare dinamicamente i colori di uno sprite è molto semplice. Si basa su una tabella di ricerca monodimensionale con 256 voci. Questo permette all'algoritmo di mappare solo 256 colori diversi.

In dettaglio, i nuovi colori (quelli utilizzati per la sostituzione) sono memorizzati in una texture monodimensionale con 256 voci. Quando un colore viene letto dalla texture originale, viene utilizzata una chiave per trovare il nuovo colore nella texture monodimensionale. scambiare nella texture monodimensionale swap . La chiave utilizzata è il canale del colore rosso del colore originale, il che significa che tutti i diversi colori del testo originale devono avere valori diversi di colore rosso. Questa è un'altra restrizione.
Il documento originale (How to Use a Shader to Dynamically Swap a Sprite's Colors) dice:

Si noti che questo metodo potrebbe non funzionare come previsto se due o più colori della texture dello sprite condividono lo stesso valore di rosso! Quando si usa questo metodo, è importante mantenere diversi i valori di rosso dei colori nella texture dello sprite.

L'algoritmo mescola ulteriormente il colore originale e il colore scambiare per il canale alfa del colore scambia colore. Questo fa sì che il colore scambia viene disegnato se il colore scambia è completamente opaco e il colore originale viene disegnato se il colore scambia è completamente trasparente, le zone intermedie saranno interpolate linearmente.

Una funzione GLSL con questo algoritmo è molto breve e si presenta in qualche modo come questa:

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

Algoritmo suggerito

Leggendo lo shader suggerito nella domanda, ho trovato la seguente soluzione. Lo shader utilizza un algoritmo per convertire da RGB a tinta, saturazione e valore e viceversa. Ho preso questa idea e ho introdotto le mie idee.

Le funzioni di conversione tra RGB e HSV possono essere trovate in RGB to HSV/HSL/HCY/HCL in HLSL, che può essere facilmente tradotto da HLSL a GLSL:

Da RGB a 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);
}

Da HSV a 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;
}

Come nel primo algoritmo di questa risposta, anche in questo caso è necessaria una tabella di ricerca monodimensionale. Ma la lunghezza della tabella di ricerca non deve essere esattamente 256, dipende completamente dall'utente. La chiave non è il canale rosso, ma il canale tinta che è una chiara espressione del colore e può essere facilmente calcolato come si vede in RGBtoHSV e RGBtoHSV. La tabella di ricerca, tuttavia, deve contenere un'assegnazione di colore distribuita linearmente sulla * tinta * da 0 a 1 del colore originale.

L'algoritmo può essere definito con i seguenti passaggi:

  • Convertire il colore originale nel colore originale tinta, saturazione e valore
  • Utilizzare il valore originale tonalità come chiave per trovare il valore scambio nella tabella di ricerca
  • Convertire il valore scambiare al colore di scambio tonalità, saturazione e valore
  • Convertire il valore tinta dell'elemento scambiare e il colore originale saturazione e valore ad un nuovo colore RGB
  • Mescolare il colore originale e il nuovo colore tramite il canale alfa del valore scambiare colore

Con questo algoritmo è possibile scambiare qualsiasi colore RGB, mantenendo il parametro saturazione e valore del colore originale. Vedere la seguente funzione GLSL breve e chiara:

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

Applicare a cocos2d-x v3.15

Per applicare lo shader a cocos2d-x v3.15 ho adattato la funzione HelloWorldScene.h e HelloWorldScene.cpp nel progetto cpp-vuoto-test dei progetti di test di cocos2d-x v3.15.
Lo shader può essere applicato a qualsiasi sprite e può cambiare fino a 10 tinte di colore, ma questo può essere facilmente ampliato. Si noti che lo shader non cambia solo un singolo colore, ma cerca tutti i colori che sono simili a un colore, anche quelli con una saturazione o una luminosità completamente diversa. Ogni colore viene scambiato con un colore che ha la stessa saturazione e luminosità, ma un nuovo colore di base.
Le informazioni che scambiano i colori sono memorizzate in una matrice di vec3. Il x contiene il componente tinta del colore originale, il componente y contiene il componente tinta del componente scambiare e il colore z contiene un valore epsilon, che definisce la gamma di colori.

I file sorgenti degli shader devono essere collocati nella sottodirectory "resource/shader" della directory del progetto.

Shader del vertice 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 frammenti 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 );
} 

File di intestazione 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__

File sorgente HelloWorldScene.cpp:

Nota, la funzione C++ RGBtoHue e la funzione GLSL RGBtoHuedovrebbero implementare esattamente lo stesso algoritmo.
L'ingresso alla funzione SwapInfo sono i colori RGB codificati in cocos2d::Vec3. Se i canali di origine dei colori RGB sono byte (unsigned char), si può facilmente convertire in cocos2d::Vec3 da 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
}

Confrontare i valori RGB invece della tinta

Uno shader di frammenti che confronta direttamente i colori RGB avrebbe questo aspetto:

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

Si noti che l'inizializzazione delle uniformi deve essere adattata:

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

Estensione della risposta

Se i colori esattamente specificati devono essere scambiati, lo shader può essere molto più semplificato. Per questo, le deviazioni u_deviation devono essere limitate (ad esempio 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 );
}

Se ogni colore della texture di origine ha un canale di colore individuale (ciò significa che il valore del colore viene utilizzato solo per questo colore speciale, ad esempio il canale del colore rosso), il codice dello shader può essere ulteriormente semplificato, poiché deve essere confrontato solo un canale:

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

Un'ulteriore ottimizzazione ci riporterebbe al primo algoritmo, descritto in questa risposta. Il grande vantaggio di questo algoritmo sarebbe che ogni colore viene scambiato (tranne il canale alfa della texture di scambio che è 0), ma nello shader non deve essere effettuata alcuna ricerca costosa nella tabella di ricerca.
Ogni colore verrà scambiato con il colore corrispondente in base al suo canale del colore rosso. Come accennato, se un colore non deve essere scambiato, il canale alfa della texture scambia deve essere impostato su 0.

Un nuovo membro mSwapTexture deve essere aggiunto alla classe:

cocos2d::Texture2D* mSwapTexture;

La texture può essere facilmente creata e il campionatore uniforme della texture può essere impostato in questo modo:

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

Lo shader dei frammenti sarà simile a questo:

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

Naturalmente, la chiave di ricerca non deve essere sempre il canale rosso, ma è possibile utilizzare qualsiasi altro canale.
Anche una combinazione di due canali di colore sarebbe possibile utilizzando una texture di ricerca bidimensionale aumentata. Si veda l'esempio seguente, che dimostra l'uso di una texture di ricerca con 1024 voci. La tabella di ricerca usa l'intero canale rosso (256 indici) nella dimensione X e il canale verde diviso per 64 (4 indici) nella dimensione Y.

Crea una tabella di ricerca bidimensionale:

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

E adattare lo shader dei frammenti:

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

Interpolare la texture

Poiché non è possibile utilizzare GL_LINEAR con l'approccio precedente, questo deve essere emulato, se necessario:

#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 nuova variabile uniforme u_spriteSize deve essere impostata in questo modo:

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

Modificare la texture sulla CPU

Naturalmente la texture può essere modificata anche sulla CPU, ma allora per ogni set di swap il vantaggio sarebbe che non sarebbe necessario un altro shader.
Il codice seguente scambia i colori quando viene caricata la texture. Lo shader deve essere completamente saltato.

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

Approccio combinato su CPU e GPU

Questo approccio può essere utilizzato se vengono scambiate sempre le stesse regioni (colori) della texture. Il vantaggio di questo approccio è che la texture originale viene modificata una sola volta, ma ogni applicazione della texture può mantenere la propria scambio tabella.
In questo approccio il canale alfa viene utilizzato per contenere l'indice del colore di scambio. Nell'esempio di codice riportato di seguito, l'intervallo di valori da 1 a 11 incluso viene utilizzato per memorizzare gli indici del colore di scambio. 0 è riservato alla trasparenza assoluta.

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

Lo shader dei frammenti ha bisogno solo delle uniformi u_swap e u_noSwap e non deve effettuare una ricerca costosa.

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

Sezione Recensioni e Valutazioni



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.