文本渐变 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);  // 对角线

方向参考

角度 效果 图示
从左到右 ▶▶▶
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 从左到右渐变
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));
});

注意事项

  1. 透明区域处理:透明部分(alpha < 0.01)不会参与渐变效果,保持透明
  2. Text 组件限制:LayaAir 的 Text 组件不支持直接使用 shader,需要将文本渲染为 texture 后使用 Sprite 显示
  3. 角度范围:角度为 0-360 度,超出范围会自动循环
  4. 颜色格式:颜色使用 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 组件的实现方式。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐