In cocos2d-x there is already a LayerRadialGradient
.
Works well, but I've other shaders running on the my game scene, so performance in a problem now. It's using a shader.
I want to replace it with just a Sprite with generated radial gradient texture.
Here is example of LayerRadialGradient
drawing result:
LayerRadialGradient::create(
Color4B(179, 232, 184, 89),
Color4B(0, 90, 128, 0),
620,
center,
0.0f);
I've some code suggestions like:
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;
}
And use just:
Sprite::createWithTexture(generateRadialGradientTexture(radius));
But result is really different:
I investigated the shader code of the questions and developed a similar algorithm in C++. The algorithm linearly interpolates between two colors, starting at a center point, with a inner color, radially outwards to a outer color.
The C++ algorithm generates a texture with a size, specified by input parameters:
#include <vector> // std::vector
#include <math.h> // sqrt
Texture2D * TextureRadialGradientCreate(
int widht,
int height,
const Color4B &startColor,
const Color4B &endColor,
float radius,
const Vec2 ¢er,
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;
}
e.g. a quadratic texture wiht a size of 600*600 and a gradient highlight in the center of the texture:
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?
The conditional branches can be avoided, by using the GLSL clamp
function, by limiting the interpolation parameter of the mix
function to the range (0, 1).
I suggest to optimize the fragment shader like this:
#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) );
}