在飞行中重新着色精灵

问题描述 投票:5回答:2

我需要替换精灵的颜色。 一些建立在谷歌的例子

这里我发现了一个看起来像Unity的工作解决方案 - [如何使用着色器动态交换精灵的颜色] [2]

如何将它移植到cocos2d-x?有人可以帮助代码示例吗?

我正在寻找cocos2d-x v3代码片段。真的很期待一些帮助。

c++ glsl cocos2d-x
2个回答
5
投票

文章How to Use a Shader to Dynamically Swap a Sprite's Colors中的算法非常简单。它基于具有256个条目的一维查找表。这允许算法仅映射256种不同的颜色。

详细地说,新颜色(用于替换的颜色)存储在具有256个条目的一维纹理中。当从原始纹理读取颜色时,使用键在一维交换纹理中找到新颜色。使用的关键是原始颜色的红色通道,这意味着原始文本中的所有不同颜色也必须具有不同的红色值。这是另一个限制。 原始文件(How to Use a Shader to Dynamically Swap a Sprite's Colors)说:

请注意,如果精灵纹理上的两种或多种颜色共享相同的红色值,这可能无法正常工作!使用此方法时,保持精灵纹理中颜色的红色值不同非常重要。

此外,算法通过交换颜色的alpha通道混合原始颜色和交换颜色。这导致如果交换颜色完全不透明则绘制交换颜色,并且如果交换颜色完全透明则绘制原始颜色,其间将进行线性插值。

使用此算法的GLSL函数非常短,看起来像这样:

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

Suggested Algorithm

从问题中读出建议的shader,我找到了以下解决方案。着色器使用算法将RGB转换为hue, saturation, and value并返回。我接受了这个想法并介绍了我自己的想法。

RGB和HSV之间的高性能转换函数可以在RGB to HSV/HSL/HCY/HCL in HLSL找到,它可以很容易地从HLSL转换为GLSL:

RGB到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到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;
}

如在该答案的第一算法中那样,再次需要一维查找表。但是查找表的长度不一定是256,它完全取决于用户。关键不是红色通道,它是色调值,它是颜色的清晰表达,可以很容易地计算,如RGBtoHSVRGBtoHSV所示。然而,查找表必须包含在原始颜色的0到1的* hue *范围内线性分布的颜色分配。

可以使用以下步骤定义算法:

  • 将原始颜色转换为原始色调,饱和度和值
  • 使用原始色调作为键在查找表中查找交换颜色
  • 将交换颜色转换为交换色调,饱和度和值
  • 将交换颜色的色调和原始饱和度以及值转换为新的RGB颜色
  • 通过交换颜色的Alpha通道混合原始颜色和新颜色

使用此算法,可以通过保持原始颜色的饱和度和值来交换任何RGB颜色。请参阅以下简短明了的GLSL功能:

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

适用于cocos2d-x v3.15

要将着色器应用于cocos2d-x v3.15,我在cocos2d-x v3.15测试项目的项目cpp-empty-test中调整了HelloWorldScene.h和HelloWorldScene.cpp。 着色器可以应用于任何精灵,可以交换多达10种颜色的色调,但这很容易扩展。请注意,着色器不仅会更改单个颜色,还会搜索与颜色相似的所有颜色,甚至是饱和度或亮度完全不同的颜色。每种颜色都用一种颜色交换,颜色具有相同的饱和度和亮度,但是具有新的基色。 交换颜色的信息存储在vec3数组中。 x组件包含原始颜色的色调,y组件包含交换颜色的色调,z组件包含epsilon值,用于定义颜色范围。

着色器源文件应放在项目目录的“resource / 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;
}

片段着色器着色器/ 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 );
} 

头文件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__

源文件HelloWorldScene.cpp:

注意,C ++函数RGBtoHue和GLSL函数RGBtoHue应该实现完全相同的算法。 功能SwapInfo的输入是编码为cocos2d::Vec3的RGB颜色。如果RGB颜色的源通道是字节(unsigned char),那么这可以通过cocos2d::Vec3轻松转换为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
}

Compare RGB values instead of Hue

直接比较RGB颜色的片段着色器将如下所示:

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

注意,制服的初始化必须适应:

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 to the answer

如果应该交换精确指定的颜色,则可以更加简化着色器。为此,必须限制u_deviation的偏差(例如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 );
}

如果源纹理中的每种颜色都有一个单独的颜色通道(这意味着颜色值仅用于此特殊颜色,例如红色通道),则可以进一步简化着色器代码,因为只需要比较一个通道:

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

进一步的优化将使我们回到第一个算法,这在本答案中有所描述。这种算法的最大优点是,交换每种颜色(交换纹理的alpha通道除外),但是在着色器中不必在查找表中进行昂贵的搜索。 每种颜色将根据其红色通道由相应的颜色交换。如上所述,如果不应交换颜色,则必须将交换纹理的Alpha通道设置为0。

一个新成员mSwapTexture必须添加到该类:

cocos2d::Texture2D* mSwapTexture;

可以轻松创建纹理,并且可以像下面这样设置均匀纹理采样器:

#include <array>

.....

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

片段着色器看起来像这样:

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

当然,查找键并不总是红色通道,任何其他通道也是可能的。 通过使用增加的二维查找纹理,甚至可以组合2个颜色通道。请参阅以下示例,该示例演示了使用1024个条目的查找纹理。查找表使用X维度中的全红色通道(256个索引)和绿色通道在Y维度中除以64(4个索引)。

创建二维查找表:

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

并调整片段着色器:

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

Interpolate the texture

由于无法使用上述方法使用GL_LINEAR,因此必须进行仿真,如果需要:

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

新的统一变量u_spriteSize必须设置如下:

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

Modify the texture on the CPU

当然,纹理也可以在CPU上修改,但是对于每组交换颜色,必须生成分离的纹理。优点是不再需要着色器。 下载代码在加载纹理时交换颜色。必须完全跳过着色器。

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

Combined approach on the CPU and GPU

如果始终交换纹理的相同区域(颜色),则可以使用此方法。这种方法的优点是原始纹理只被修改一次,但纹理的每个应用程序都可以保存自己的交换表。 对于这种方法,alpha通道用于保持交换颜色的索引。在下面的示例代码中,值范围从1到包括11用于存储交换颜色的索引。 0保留用于绝对透明度。

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

片段着色器只需要制服u_swapu_noSwap,而不需要进行昂贵的搜索。

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

1
投票

使用着色器更改精灵的色调,饱和度,值。着色器代码示例:

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 v_texCoord;

////uniform sampler2D CC_Texture0;
uniform float u_dH;
uniform float u_dS;
uniform float u_dL;
//algorithm ref to: https://en.wikipedia.org/wiki/HSL_and_HSV
void main() {
    vec4 texColor=texture2D(CC_Texture0, v_texCoord);
    float r=texColor.r;
    float g=texColor.g;
    float b=texColor.b;
    float a=texColor.a;
    //convert rgb to hsl
    float h;
    float s;
    float l;
    {
        float max=max(max(r,g),b);
        float min=min(min(r,g),b);
        //----h
        if(max==min){
            h=0.0;
        }else if(max==r&&g>=b){
            h=60.0*(g-b)/(max-min)+0.0;
        }else if(max==r&&g<b){
            h=60.0*(g-b)/(max-min)+360.0;
        }else if(max==g){
            h=60.0*(b-r)/(max-min)+120.0;
        }else if(max==b){
            h=60.0*(r-g)/(max-min)+240.0;
        }
        //----l
        l=0.5*(max+min);
        //----s
        if(l==0.0||max==min){
            s=0.0;
        }else if(0.0<=l&&l<=0.5){
            s=(max-min)/(2.0*l);
        }else if(l>0.5){
            s=(max-min)/(2.0-2.0*l);
        }
    }
    //(h,s,l)+(dH,dS,dL) -> (h,s,l)
    h=h+u_dH;
    s=min(1.0,max(0.0,s+u_dS));
    l=l;//do not use HSL model to adjust lightness, because the effect is not good
    //convert (h,s,l) to rgb and got final color
    vec4 finalColor;
    {
        float q;
        if(l<0.5){
            q=l*(1.0+s);
        }else if(l>=0.5){
            q=l+s-l*s;
        }
        float p=2.0*l-q;
        float hk=h/360.0;
        float t[3];
        t[0]=hk+1.0/3.0;t[1]=hk;t[2]=hk-1.0/3.0;
        for(int i=0;i<3;i++){
            if(t[i]<0.0)t[i]+=1.0;
            if(t[i]>1.0)t[i]-=1.0;
        }//got t[i]
        float c[3];
        for(int i=0;i<3;i++){
            if(t[i]<1.0/6.0){
                c[i]=p+((q-p)*6.0*t[i]);
            }else if(1.0/6.0<=t[i]&&t[i]<0.5){
                c[i]=q;
            }else if(0.5<=t[i]&&t[i]<2.0/3.0){
                c[i]=p+((q-p)*6.0*(2.0/3.0-t[i]));
            }else{
                c[i]=p;
            }
        }
        finalColor=vec4(c[0],c[1],c[2],a);
    }
    //actually, it is not final color. the lightness has not been adjusted
    //adjust lightness use the simplest method
    finalColor+=vec4(u_dL,u_dL,u_dL,0.0);

    gl_FragColor=finalColor;
}
© www.soinside.com 2019 - 2024. All rights reserved.