Cocos Creator Shader 实现波浪进度条

波浪进度条由两部分组成:

  • WaveProgressFill.effect:顶点与片元 shader,负责绘制动态液面;
  • WaveProgressComponent.ts:加载材质、更新进度,并持续把时间传给 shader。

它不依赖节点裁切或序列帧,而是直接在 Sprite 的 UV 坐标中计算液面,因此可以适配不同形状和尺寸的进度图。

核心思路

普通的从底部填充进度条,可以理解为:

glsl 复制代码
fillY <= progress

其中 progress 范围为 0 到 1。

为了让水平液面变成波浪,只要给进度值叠加一个随横坐标和时间变化的正弦函数:

glsl 复制代码
float wave = sin(uv0.x * waveFreq + time * waveSpeed) * waveAmp;
float fillLine = clamp(progress + wave, 0.0, 1.0);

参数含义:

  • uv0.x:当前像素的横向 UV 坐标;
  • waveFreq:波浪频率,越大波峰越密;
  • time:累计时间;
  • waveSpeed:波浪移动速度;
  • waveAmp:波浪上下摆动幅度;
  • progress:当前进度;
  • fillLine:当前横向位置上的液面高度。

项目中使用:

glsl 复制代码
fillY = 1.0 - uv0.y;

这样 Sprite 底部为 0、顶部为 1,更符合"从下向上填充"的进度语义。

Effect 参数设计

Shader 中定义了这些可调参数:

参数 默认值 作用
progress 0.0 填充进度,范围 0~1
time 0.0 波浪动画时间
waveAmp 0.02 波浪振幅
waveFreq 21.0 波浪频率
waveSpeed 5.0 波浪滚动速度
edgeSoftness 0.005 液面边缘柔和程度
crestWidth 0.028 液面高光带宽度
crestStrength 0.18 高光强度
vertexWaveAmp 3.0 顶点起伏幅度

这些属性写在 Cocos Creator Effect 的 properties 中,因此可以直接在材质面板调节。

使用 smoothstep 制作柔和液面

如果直接判断:

glsl 复制代码
fillY < fillLine

液面边缘会很生硬,容易产生锯齿。

因此使用 smoothstep 生成平滑遮罩:

glsl 复制代码
float fillMask =
    (1.0 - smoothstep(
        fillLine - edgeSoftness,
        fillLine + edgeSoftness,
        fillY
    )) * fillStart;

结果是:

  • 液面下方:fillMask 接近 1;
  • 液面上方:fillMask 接近 0;
  • 液面附近:透明度平滑过渡。

最终把遮罩乘到纹理 alpha 上:

glsl 复制代码
vec4 outputColor = vec4(
    texColor.rgb,
    texColor.a * fillMask
);

这样即使 Sprite 本身带有透明区域或异形轮廓,也能保持原始形状。

给液面增加高光

仅有波浪边缘时,视觉上会比较平。可以在液面附近增加一条高光带:

glsl 复制代码
float crestMask =
    smoothstep(fillLine - crestWidth, fillLine, fillY) * fillMask;

vec3 crestColor =
    texColor.rgb + vec3(crestStrength * crestMask);

vec4 outputColor = vec4(
    mix(texColor.rgb, crestColor, crestMask),
    texColor.a * fillMask
);

这种做法不是替换纹理颜色,而是在原颜色基础上轻微增亮,因此不会破坏原有美术资源。

顶点阶段的轻微起伏

片元 shader 已经能完成波浪裁切。不过项目中还在顶点 shader 中,对液面附近的顶点做了轻微偏移:

glsl 复制代码
float surfaceBand =
    smoothstep(
        fillLine - crestWidth * 2.0,
        fillLine - edgeSoftness,
        fillY
    ) * fillMask;

float vertexOffset =
    sin(uv0.x * waveFreq + time * waveSpeed)
    * vertexWaveAmp
    * surfaceBand;

pos.y += vertexOffset;

这里的关键是 surfaceBand:它把位移限制在液面附近,避免整个填充区域一起上下抖动。

需要注意,顶点位移依赖 Sprite 网格的顶点数量。普通四边形 Sprite 的顶点较少,因此主要效果仍然由片元阶段的透明度遮罩提供;顶点位移只是额外增强液面的动态感。

TypeScript 组件如何驱动 Shader

组件挂在带有 Sprite 的节点上,负责加载材质并设置参数。

加载材质后,不应直接共用材质资源,而是创建独立副本:

ts 复制代码
const clonedMaterial = new Material();
clonedMaterial.copy(material);

this.sprite.customMaterial = clonedMaterial;

这样每个进度条都能拥有独立的 progresstime,不会出现多个进度条同步变化的问题。

更新进度时:

ts 复制代码
public setProgress(progress: number): void {
    this.currentProgress = Math.max(0, Math.min(1, progress));
    this.waveMaterial.setProperty('progress', this.currentProgress);
}

每帧更新波浪时间:

ts 复制代码
protected update(): void {
    if (!this.waveMaterial) {
        return;
    }

    const time = director.getTotalTime() * 0.001 - this.waveStartTime;
    this.waveMaterial.setProperty('time', Math.max(0, time));
}

使用时只需要:

ts 复制代码
const wave = node.getComponent(WaveProgressComponent)
    || node.addComponent(WaveProgressComponent);

wave.setup({
    bundleName: GameDef.base_common,
    materialPath: 'material/WaveProgressFill',
    progress: 0.35,
});

wave.setProgress(0.72);

业务层只负责传入 0~1 的进度值,波浪的持续运动完全交给 shader。

常见问题

进度方向反了

重点检查:

glsl 复制代码
fillY = 1.0 - uv0.y;

如果你的 UV 坐标已经是"底部为 0",则可能应直接使用:

glsl 复制代码
fillY = uv0.y;

另外,RenderTexture 采样时也要注意 UV 翻转。

多个进度条一起动

通常是多个 Sprite 共用了同一个材质实例。解决办法是加载后执行:

ts 复制代码
const clonedMaterial = new Material();
clonedMaterial.copy(material);

每个组件使用独立材质。

进度为 0 时还残留波纹

不要只写硬判断。应通过 fillStartsmoothstep 控制零进度边界,避免波浪在空状态下漏出。

波浪不动

检查:

  • 材质是否已异步加载完成;
  • 节点是否激活,组件的 update() 是否执行;
  • time 是否持续传入;
  • waveSpeed 是否太小。

可扩展方向

当前效果只有一层正弦波。还可以继续扩展:

  • 叠加两组不同频率和速度的波浪,获得更自然的液体效果;
  • 使用独立颜色制作毒液、魔法能量或海水效果;
  • 用 tween 平滑驱动 setProgress(),让数值增长也更自然;
  • 根据进度动态修改波浪振幅,例如低进度时平稳、满进度时更活跃。

总结

波浪进度条的关键,是把普通的进度线转换为 UV 空间中的动态正弦液面:

glsl 复制代码
fillLine = progress + sin(x + time) * amplitude;

再通过 smoothstep 制作柔和边缘,用原纹理 alpha 与填充遮罩相乘实现异形裁切,并由组件持续更新 progresstime

这种方案渲染逻辑集中在 shader 内,业务层只维护进度值,适合在 Cocos Creator 中复用到经验条、等级图标、体力条或各类能量填充 UI。

相关推荐
郝学胜-神的一滴8 小时前
中级OpenGL教程 026:Assimp库从编译到实战全攻略
c++·unity·游戏引擎·图形渲染·unreal engine·opengl
凯尔3151 天前
Unity简单涂料实现
unity·游戏引擎·图形渲染
郝学胜-神的一滴5 天前
[简化版 GAMES 104] 现代游戏引擎 02:拆解现代游戏引擎5+1层级架构,吃透引擎底层核心逻辑
c++·unity·架构·游戏引擎·图形渲染·unreal engine·系统设计
郝学胜-神的一滴11 天前
中级OpenGL教程 020:巧用数组与循环实现多点点光源渲染,告别冗余代码重构方案
c++·unity·游戏引擎·godot·图形渲染·unreal
图扑软件11 天前
其域创新 × 图扑软件|3DGS 高斯泼溅智慧钢厂实景孪生
前端·低代码·3d·编辑器·图形渲染·可视化
ysu_031414 天前
07 | 全局缩放重构实录
c语言·重构·游戏程序·图形渲染
郝学胜-神的一滴15 天前
[简化版 GAMES 101] 计算机图形学 17:纹理技术从基础原理到多场景实战应用
c++·unity·游戏引擎·图形渲染·three.js·opengl·unreal
csdndenglu18 天前
Easy3D:一个轻量级、易用、高效的C++库,用于处理和渲染3D数据,无复杂要求时可替代VTK
开发语言·c++·图形渲染·3d渲染
郝学胜-神的一滴19 天前
中级OpenGL教程 013:渲染器类架构设计与逐帧渲染流程详解
开发语言·c++·unity·游戏引擎·图形渲染·opengl·unreal