【Laya】在LayaAir 3.3 中实现图片文字渐变效果(Shader)
TextGradientShader是一个2D精灵线性渐变着色器,支持调节渐变角度、双色混合和渐变强度。主要参数包括顶部/底部颜色(RGBA)、渐变角度(0-360°)和渐变强度(0-1)。可通过IDE拖拽材质或代码创建使用,支持颜色循环、角度旋转等动画效果。预设多种渐变效果如日落、海洋、森林等,角度0°为从左到右,90°从上到下,45°对角线渐变。适用于UI元素的视觉效果增强。
文本渐变 Shader 使用说明
简介
TextGradientShader 是一个用于 2D 精灵的线性渐变着色器。支持可调节的渐变角度、双颜色混合和渐变强度控制。适用于文本标签、按钮背景、图片渐变等场景。
效果演示

参数说明
渐变参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
u_GradientTopColor |
Vector4 | [1, 0.5, 0, 1] | 渐变顶部颜色 (RGBA) |
u_GradientBottomColor |
Vector4 | [0.5, 0, 1, 1] | 渐变底部颜色 (RGBA) |
u_GradientAngle |
Float | 90 | 渐变角度 (度),0=向右,90=向下,180=向左,270=向上 |
u_GradientIntensity |
Float | 1 | 渐变强度 (0=原始颜色,1=完全渐变) |
快速开始
方式一:在IDE中将材质拖到Image属性Material上
// 加载材质并应用到 Image 组件
Laya.loader.load("shader/gradient/TextGradient.lmat", Laya.Handler.create(this, (mat: Laya.Material) => {
this.image.material = mat;
// 修改渐变颜色
const topColorID = Laya.Shader3D.propertyNameToID("u_GradientTopColor");
const bottomColorID = Laya.Shader3D.propertyNameToID("u_GradientBottomColor");
mat.shaderData.setVector(topColorID, new Laya.Vector4(1, 0, 0, 1)); // 红色
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(0, 0, 1, 1)); // 蓝色
}));
方式二:运行时创建材质
const image = new Laya.Image();
image.skin = "atlas/comp/image.png";
Laya.stage.addChild(image);
// 创建材质
const mat = new Laya.Material();
mat.setShaderName("TextGradientShader");
image.material = mat;
// 设置渐变参数
const topColorID = Laya.Shader3D.propertyNameToID("u_GradientTopColor");
const bottomColorID = Laya.Shader3D.propertyNameToID("u_GradientBottomColor");
const angleID = Laya.Shader3D.propertyNameToID("u_GradientAngle");
const intensityID = Laya.Shader3D.propertyNameToID("u_GradientIntensity");
mat.shaderData.setVector(topColorID, new Laya.Vector4(1, 0.5, 0, 1)); // 橙色
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(0.5, 0, 1, 1)); // 紫色
mat.shaderData.setNumber(angleID, 90); // 从上到下
mat.shaderData.setNumber(intensityID, 1); // 完全渐变
动画示例
颜色循环动画
const topColorID = Laya.Shader3D.propertyNameToID("u_GradientTopColor");
const bottomColorID = Laya.Shader3D.propertyNameToID("u_GradientBottomColor");
let time = 0;
Laya.timer.frameLoop(1, this, () => {
time += 0.01;
// 顶部颜色:在红-绿-蓝之间循环
const r = (Math.sin(time) + 1) / 2;
const g = (Math.sin(time + 2) + 1) / 2;
const b = (Math.sin(time + 4) + 1) / 2;
mat.shaderData.setVector(topColorID, new Laya.Vector4(r, g, b, 1));
// 底部颜色:相位偏移
const r2 = (Math.sin(time + 3.14) + 1) / 2;
const g2 = (Math.sin(time + 5.14) + 1) / 2;
const b2 = (Math.sin(time + 7.14) + 1) / 2;
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(r2, g2, b2, 1));
});
角度旋转动画
const angleID = Laya.Shader3D.propertyNameToID("u_GradientAngle");
let angle = 0;
Laya.timer.frameLoop(1, this, () => {
angle = (angle + 1) % 360; // 每帧增加1度
mat.shaderData.setNumber(angleID, angle);
});
淡入渐变效果
const intensityID = Laya.Shader3D.propertyNameToID("u_GradientIntensity");
const target = { value: 0 };
Laya.Tween.create(target)
.to("value", 1) // 从0渐变到1
.duration(1000) // 1秒
.onUpdate(() => {
mat.shaderData.setNumber(intensityID, target.value);
});
效果预设
日落渐变
const topColorID = Laya.Shader3D.propertyNameToID("u_GradientTopColor");
const bottomColorID = Laya.Shader3D.propertyNameToID("u_GradientBottomColor");
const angleID = Laya.Shader3D.propertyNameToID("u_GradientAngle");
mat.shaderData.setVector(topColorID, new Laya.Vector4(1, 0.4, 0, 1)); // 橙色
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(0.5, 0, 0.3, 1)); // 深紫红
mat.shaderData.setNumber(angleID, 90); // 从上到下
海洋渐变
mat.shaderData.setVector(topColorID, new Laya.Vector4(0, 0.6, 0.9, 1)); // 浅蓝
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(0, 0.1, 0.3, 1)); // 深蓝
mat.shaderData.setNumber(angleID, 90); // 从上到下
森林渐变
mat.shaderData.setVector(topColorID, new Laya.Vector4(0.4, 0.8, 0.3, 1)); // 嫩绿
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(0.1, 0.3, 0.1, 1)); // 深绿
mat.shaderData.setNumber(angleID, 90); // 从上到下
火焰渐变
mat.shaderData.setVector(topColorID, new Laya.Vector4(1, 1, 0.2, 1)); // 黄色
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(1, 0.1, 0, 1)); // 红色
mat.shaderData.setNumber(angleID, 90); // 从上到下
金属渐变
mat.shaderData.setVector(topColorID, new Laya.Vector4(0.9, 0.9, 0.9, 1)); // 银白
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(0.4, 0.4, 0.5, 1)); // 灰蓝
mat.shaderData.setNumber(angleID, 90); // 从上到下
霓虹渐变
mat.shaderData.setVector(topColorID, new Laya.Vector4(1, 0, 1, 1)); // 洋红
mat.shaderData.setVector(bottomColorID, new Laya.Vector4(0, 1, 1, 1)); // 青色
mat.shaderData.setNumber(angleID, 45); // 对角线
方向参考
| 角度 | 效果 | 图示 |
|---|---|---|
| 0° | 从左到右 | ▶▶▶ |
| 45° | 左上到右下 | ↘↘↘ |
| 90° | 从上到下 | ▼▼▼ |
| 135° | 右上到左下 | ↙↙↙ |
| 180° | 从右到左 | ◀◀◀ |
| 225° | 右下到左上 | ↖↖↖ |
| 270° | 从下到上 | ▲▲▲ |
| 315° | 左下到右上 | ↗↗↗ |
Shader 源码详解
Shader 结构
Shader3D Start
{
type:Shader3D,
name:TextGradientShader,
enableInstancing:true,
supportReflectionProbe:true,
shaderType:D2_TextureSV, // 2D精灵纹理着色器类型
// Uniform 变量定义(从 CPU 传入 GPU 的数据)
uniformMap:{
u_GradientTopColor:{ type:Vector4, default:[1, 0.5, 0, 1] }, // 顶部颜色
u_GradientBottomColor:{ type:Vector4, default:[0.5, 0, 1, 1] }, // 底部颜色
u_GradientAngle:{ type:Float, default:90 }, // 渐变角度
u_GradientIntensity:{ type:Float, default:1 } // 渐变强度
},
// 顶点属性定义(顶点数据)
attributeMap: {
a_posuv: Vector4, // 位置和UV坐标
a_attribColor: Vector4, // 顶点颜色
a_attribFlags: Vector4, // 顶点标志
a_customs:Vector4, // 自定义数据
},
// Shader 宏定义
defines: {
GRADIENT: { type: bool, default: true } // 启用渐变
}
shaderPass:[
{
pipeline:Forward, // 前向渲染管线
VS:TextGradientVS, // 顶点着色器
FS:TextGradientFS // 片元着色器
}
]
}
顶点着色器 (TextGradientVS)
顶点着色器负责处理顶点数据,将 2D 精灵的顶点变换到屏幕空间。
#defineGLSL TextGradientVS
#define SHADER_NAME TextGradientShader
#include "Sprite2DVertex.glsl"; // 引入 LayaAir 内置的 2D 精灵顶点处理
void main() {
vertexInfo info;
getVertexInfo(info); // 获取顶点信息(位置、UV、颜色等)
// 将顶点信息传递给片元着色器
v_cliped = info.cliped; // 裁剪信息
v_texcoordAlpha = info.texcoordAlpha; // UV坐标和alpha
v_useTex = info.useTex; // 是否使用纹理
v_useClip = info.useClip; // 是否使用裁剪
v_customs = info.customs; // 自定义数据
v_color = info.color; // 顶点颜色
// 计算最终顶点位置(屏幕坐标)
gl_Position = getPosition(info.pos);
}
#endGLSL
关键点说明:
- 顶点着色器主要是一个"传递者",将原始精灵的顶点数据传递给片元着色器
v_texcoordAlpha包含纹理坐标 (0~1),用于片元着色器中计算渐变位置
片元着色器核心算法 (TextGradientFS)
片元着色器是渐变效果的核心,包含渐变方向计算和颜色混合逻辑。
主函数详解
void main()
{
clip(); // 处理裁剪(LayaAir 内置功能)
// 获取原始精灵颜色
vec4 originalColor = getSpriteTextureColor();
// 【透明检测】透明部分不参与渐变效果
if(originalColor.a < 0.01) {
setglColor(originalColor);
return;
}
// 【渐变因子计算】
vec2 uv = v_texcoordAlpha.xy; // 获取纹理坐标 (0~1)
// 将角度转换为弧度
float angleRad = radians(u_GradientAngle);
// 计算渐变方向向量
vec2 gradientDir = vec2(cos(angleRad), sin(angleRad));
// 投影 UV 坐标到渐变方向上
// UV 中心点为 (0.5, 0.5),计算当前点相对于中心的偏移
float gradientFactor = dot(uv - 0.5, gradientDir) + 0.5;
// 将渐变因子限制在 0-1 范围内
gradientFactor = clamp(gradientFactor, 0.0, 1.0);
// 【颜色混合】根据渐变因子混合顶部和底部颜色
vec4 gradientColor = mix(u_GradientTopColor, u_GradientBottomColor, gradientFactor);
// 【应用强度】在原始颜色和渐变颜色之间插值
vec4 finalColor = mix(originalColor, gradientColor * originalColor.a, u_GradientIntensity);
// 【保持透明度】使用原始透明度
finalColor.a = originalColor.a;
setglColor(finalColor);
}
关键算法图解
渐变因子计算原理:
UV 坐标系 (0~1):
(0,0) ─────────── (1,0)
│ │
│ ● │ ● = 中心点 (0.5, 0.5)
│ │
(0,1) ─────────── (1,1)
1. 计算相对位置:uv - 0.5
结果范围:(-0.5, -0.5) 到 (0.5, 0.5)
2. 投影到渐变方向:dot(uv - 0.5, gradientDir)
- angle = 90° (向下) → gradientDir = (0, 1)
- dot((x, y), (0, 1)) = y
- 顶部点 y = -0.5,底部点 y = 0.5
3. 归一化:+ 0.5
- 顶部:-0.5 + 0.5 = 0
- 底部:0.5 + 0.5 = 1
渐变方向示意图:
angle = 0° (→) angle = 45° (↘) angle = 90° (↓) angle = 135° (↙)
0 ────────→ 1 0 ↘↘↘↘↘↘ 1 0 ↓↓↓↓↓↓ 1 1 ↙↙↙↙↙↙ 0
TopColor BotColor TopColor BotColor TopColor BotColor
颜色混合公式:
// 1. 渐变颜色计算
gradientColor = mix(topColor, bottomColor, gradientFactor)
当 gradientFactor = 0: gradientColor = topColor
当 gradientFactor = 0.5: gradientColor = 两者各50%混合
当 gradientFactor = 1: gradientColor = bottomColor
// 2. 强度混合
finalColor = mix(originalColor, gradientColor * originalColor.a, intensity)
当 intensity = 0: finalColor = originalColor (无渐变)
当 intensity = 1: finalColor = gradientColor (完全渐变)
参数效果可视化
| 参数 | 值 | 视觉效果 |
|---|---|---|
u_GradientAngle |
0° | 从左到右渐变 |
| 90° | 从上到下渐变 | |
| 180° | 从右到左渐变 | |
| 270° | 从下到上渐变 | |
u_GradientIntensity |
0 | 无渐变,显示原图 |
| 0.5 | 50%渐变效果 | |
| 1 | 完全渐变 |
动态文本与 TextGradientShaderComponent 组件
问题:文本改变时颜色可能不一致
当使用 shader 方式实现文本渐变时,如果文本内容动态改变,会出现以下问题:
初始文本: "HELLO" → 渐变正常
改变后: "WORLD" → 渐变范围可能不正确
原因:shader 基于纹理的 UV 坐标计算渐变,文本长度改变后:
- 纹理尺寸变化 → UV 坐标范围变化 → 渐变效果不一致
解决方案:使用 TextGradientShaderComponent 组件
TextGradientShaderComponent 组件封装了文本渐变的完整逻辑,自动处理文本变化:
// 方式一:在 IDE 中添加组件
// 1. 创建一个 Text 组件
// 2. 添加 TextGradientShaderComponent 组件
// 3. 在属性面板配置渐变参数
// 方式二:代码中创建
const text = new Laya.Text();
text.text = "动态渐变文本";
text.fontSize = 48;
text.width = 400;
text.height = 60;
const gradientComp = text.addComponent(TextGradientShaderComponent);
gradientComp.topColor = { r: 1, g: 0.5, b: 0, a: 1 }; // 橙色
gradientComp.bottomColor = { r: 0.5, g: 0, b: 1, a: 1 }; // 紫色
gradientComp.angle = 90; // 从上到下
gradientComp.intensity = 1; // 完全渐变
// 文本改变时,渐变效果会自动更新
text.text = "新的文本内容"; // 渐变自动适应
组件特性
| 特性 | 说明 |
|---|---|
| 自动监听文本变化 | 检测 Text.text 属性变化 |
| 重新渲染纹理 | 文本改变后自动重新生成 texture |
| 保持渐变一致 | 渐变范围始终匹配当前文本尺寸 |
| 无需手动刷新 | 框架自动处理,调用 refresh() 可强制刷新 |
适用场景对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 直接使用 Shader | 性能更好,GPU 直接渲染 | 文本改变需手动处理 | 静态文本、Image 背景 |
| ImageGradientComponent | 文本改变自动处理 | 使用 canvas,性能稍低 | 动态文本、频繁变化 |
| TextGradientShaderComponent | 结合两者优点,自动刷新 | 需要额外组件 | 动态文本,需要稳定效果 |
使用场景
文本渐变
// 注意:Text 组件不支持直接使用 shader
// 需要将文本渲染为 texture,然后用 Sprite 显示
const text = new Laya.Text();
text.text = "渐变文本";
text.fontSize = 48;
text.color = "#FFFFFF";
// 将 text 的内容绘制到 canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
canvas.width = 200;
canvas.height = 60;
ctx.font = "48px Arial";
ctx.fillStyle = "#FFFFFF";
ctx.fillText("渐变文本", 0, 48);
// 创建 texture 和 sprite
const tex = new Laya.Texture(canvas);
const sprite = new Laya.Sprite();
sprite.graphics.drawTexture(tex, 0, 0);
// 应用渐变 shader
const mat = new Laya.Material();
mat.setShaderName("TextGradientShader");
mat.shaderData.setVector(Laya.Shader3D.propertyNameToID("u_GradientTopColor"),
new Laya.Vector4(1, 0.5, 0, 1));
mat.shaderData.setVector(Laya.Shader3D.propertyNameToID("u_GradientBottomColor"),
new Laya.Vector4(0.5, 0, 1, 1));
sprite.material = mat;
Laya.stage.addChild(sprite);
按钮背景渐变
const btn = new Laya.Image();
btn.skin = "button.png";
const mat = new Laya.Material();
mat.setShaderName("TextGradientShader");
// 按钮正常状态:蓝色渐变
mat.shaderData.setVector(Laya.Shader3D.propertyNameToID("u_GradientTopColor"),
new Laya.Vector4(0.4, 0.7, 1, 1));
mat.shaderData.setVector(Laya.Shader3D.propertyNameToID("u_GradientBottomColor"),
new Laya.Vector4(0.2, 0.4, 0.8, 1));
btn.material = mat;
// 鼠标悬停时改变颜色
btn.on(Laya.Event.MOUSE_OVER, this, () => {
mat.shaderData.setVector(Laya.Shader3D.propertyNameToID("u_GradientTopColor"),
new Laya.Vector4(0.6, 0.9, 1, 1));
mat.shaderData.setVector(Laya.Shader3D.propertyNameToID("u_GradientBottomColor"),
new Laya.Vector4(0.3, 0.6, 1, 1));
});
注意事项
- 透明区域处理:透明部分(alpha < 0.01)不会参与渐变效果,保持透明
- Text 组件限制:LayaAir 的 Text 组件不支持直接使用 shader,需要将文本渲染为 texture 后使用 Sprite 显示
- 角度范围:角度为 0-360 度,超出范围会自动循环
- 颜色格式:颜色使用 RGBA 格式,每个分量范围 0-1
常见问题
Q: 为什么图片没有渐变效果?
A: 确保材质已正确加载,并且 u_GradientIntensity 值大于 0。
Q: 如何实现水平渐变?
A: 将 u_GradientAngle 设置为 0(从左到右)或 180(从右到左)。
Q: 如何实现垂直渐变?
A: 将 u_GradientAngle 设置为 90(从上到下)或 270(从下到上)。
Q: 渐变颜色和原图怎么混合?
A: 使用 u_GradientIntensity 参数控制:0=完全原图,1=完全渐变,中间值=两者混合。
Q: 对角线渐变怎么设置?
A: 将 u_GradientAngle 设置为 45(左上到右下)或 135(右上到左下)。
Q: Text 组件能直接使用这个 shader 吗?
A: 不能。Text 组件有特殊的渲染机制,不支持自定义 shader。需要将文本渲染到 canvas,然后创建 Sprite 来显示并应用 shader。参考 TextGradientShaderComponent.ts 组件的实现方式。
更多推荐


所有评论(0)