径向渐变 - cocos2d-x v3.15+ 的程序纹理

Radial gradient - procedural texture for cocos2d-x v3.15+

在cocos2d-xthere is already一个LayerRadialGradient。 效果很好,但我的游戏场景中还有其他着色器 运行,所以现在性能有问题。 It's using a shader.

我想用生成径向渐变纹理的 Sprite 替换它。

这里是LayerRadialGradient绘图结果的例子:

LayerRadialGradient::create( 
  Color4B(179, 232, 184, 89),
  Color4B(0, 90, 128, 0),
  620,
  center,
  0.0f);

我有一些代码建议,例如:

static Color4B Lerp(const Color4B& value1, const Color4B& value2, float amount)
{
    amount = clampf(amount, 0.0f, 1.0f);
    return Color4B(
        (int)MathUtil::lerp(value1.r, value2.r, amount),
        (int)MathUtil::lerp(value1.g, value2.g, amount),
        (int)MathUtil::lerp(value1.b, value2.b, amount),
        (int)MathUtil::lerp(value1.a, value2.a, amount)
    );
}

static float Falloff(float distance, float maxDistance, float scalingFactor)
{
    if (distance <= maxDistance / 3)
    {
        return scalingFactor * (1 - 3 * distance * distance / (maxDistance * maxDistance));
    }
    else if (distance <= maxDistance)
    {
        float x = 1 - distance / maxDistance;
        return (3.f / 2.f) * scalingFactor * x * x;
    }
    else
        return 0;
}

static float Falloff(float distance, float maxDistance)
{
    return Falloff(distance, maxDistance, 1.f);
}

static Texture2D* generateRadialGradientTexture(int size) {
    auto radius = size / 2;
    auto colors = new (std::nothrow) GLubyte[size * size * 4];

    Color4B centerColor(Color4B(179, 232, 184, 89));
    Color4B borderColor(Color4B(0, 90, 128, 0));        

    for (int y = 0; y < size; y++)
    {
        for (int x = 0; x < size; x++)
        {
            float distance = Vec2::ONE.distance(Vec2(x, y) / radius);
            float alpha = Falloff(distance, 1, 1);

            int idx = (y * size + x) * 4;
            float innerGradient = Falloff(distance, 0.6f, 0.8f);
            auto color = Lerp(borderColor, centerColor, innerGradient);

            colors[idx + 0] = color.r;
            colors[idx + 1] = color.g;
            colors[idx + 2] = color.b;

            //alpha
            colors[idx + 3] = (GLbyte)clampf(alpha * 256.f + 0.5f, 0.f, 255.f);
        }
    }

    auto txt = new Texture2D();
    txt->initWithData(colors, size * size * 4, Texture2D::PixelFormat::RGBA8888, size, size, Size(size, size));

    delete[] colors;
    return txt;
}

并且只使用:

Sprite::createWithTexture(generateRadialGradientTexture(radius));

但是结果真的不一样:

我调查了问题的 shader code 并用 C++ 开发了一个类似的算法。该算法在两种颜色之间进行线性插值,从中心点开始,使用内部颜色,径向向外到外部颜色。
C++ 算法生成大小由输入参数指定的纹理:

#include <vector>  // std::vector
#include <math.h>  // sqrt

Texture2D * TextureRadialGradientCreate(
    int            widht,
    int            height,
    const Color4B &startColor,
    const Color4B &endColor,
    float          radius,
    const Vec2    &center,
    float          expand )
{
    Vec4 sCol( startColor.r / 255.0, startColor.g / 255.0, startColor.b / 255.0, startColor.a / 255.0 );
    Vec4 eCol( endColor.r / 255.0, endColor.g / 255.0, endColor.b / 255.0, endColor.a / 255.0 );

    std::vector<unsigned char> plane( widht * height * 4, 0 );
    for ( int y = 0; y < height; ++ y )
    {
        for ( int x = 0; x < widht; ++ x )
        {
             float dx = x - center.x;
             float dy = y - center.y;
             float d = sqrt( dx*dx + dy*dy ) / radius;

             Vec4 mixCol( 0.0f, 0.0f, 0.0f, 0.0f );
             if ( expand < 1.0f && d < 1.0f )
             {
                 float a = ( d - expand ) / ( 1.0 - expand );
                 mixCol = (d <= expand) ? sCol : ( 1.0 - a ) * sCol + a*eCol;
             }

             size_t i = ( y * widht + x ) * 4;
             plane[i+0] = (unsigned char)(mixCol.x * 255.0f);
             plane[i+1] = (unsigned char)(mixCol.y * 255.0f);
             plane[i+2] = (unsigned char)(mixCol.z * 255.0f);
             plane[i+3] = (unsigned char)(mixCol.w * 255.0f);
        }
    }

    Texture2D *texture = new Texture2D();
    if ( texture != nullptr )
        texture->initWithData( plane.data(), plane.size() / 4, Texture2D::PixelFormat::RGBA8888, widht, height, cocos2d::Size(widht, height) );
    return texture;
}

例如尺寸为 600*600 的二次纹理,纹理中心有渐变高光:

int w = 600;
int h = 600;
float rad = ( w < h ? w : h ) / 2.0f;
Vec2 cpt( w / 2.0f, h / 2.0f );
texture = TextureRadialGradientCreate( w, h, layer_startColor, layer_endColor, rad, cpt, layer_expand );


答案的扩展

I don't know, maybe radial shader can be optimized?

条件分支可以通过使用 GLSL clamp function, by limiting the interpolation parameter of the mix 函数到范围 (0, 1) 来避免。
我建议像这样优化 fragment shader

#ifdef GL_ES
varying lowp vec4 v_position;
#else
varying vec4 v_position; 
#endif

uniform vec4 u_startColor;
uniform vec4 u_endColor;
uniform vec2 u_center;
uniform float u_radius;
uniform float u_expand;

void main()
{
    float d = distance(v_position.xy, u_center) / u_radius;
    float a = (d - u_expand) / (1.0 - u_expand);
    gl_FragColor = mix( u_startColor, u_endColor, clamp(0.0, 1.0, a) ); 
}