径向渐变 - 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 ¢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;
}
例如尺寸为 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) );
}
在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 ¢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;
}
例如尺寸为 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) );
}