Skip to content

Cesium 大屏告警特效实战:CSS遮罩+后期处理双实现,拷走就用!

接前文,之前说缺少一个告警特效。本来以为很简单的一个 class + 动画 还不就搞定了,结果给我调麻了。

这里采用两种方式实现一下告警提示的效果,也就是屏幕四周红色光晕+呼吸闪烁(游戏残血同款)。

纯CSS四边告警光晕(推荐)

在外层创建一个遮罩的 div 用于显示红色光晕的效果,增加一个动画闪烁,就完成了。

html
<div class="alarm-halo" v-if="isShowAlarm"></div>

<style>
.alarm-halo {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    z-index: 9999;
    background: radial-gradient(ellipse at center, transparent 60%, rgba(214, 18, 18, 0.2) 100%);
    box-shadow: inset 0 0 80px 40px rgba(214, 18, 18, 0.4);
    animation: alarmBlink 1s infinite alternate;
}

@keyframes alarmBlink {
    0% {
        opacity: 0.3;
    }

    100% {
        opacity: 0.8;
    }
}
</style>

其实代码很简单,这里我踩了俩坑。

坑1

不要把 .alarm-halo 直接绑定在外层 div 上,这里我调了很久才发现问题所在。CSS遮罩被background-image覆盖,z-index失效、inset`阴影被完全遮挡。

这会导致屏幕无法正常显示红色的边缘,只显示白色的闪烁。

不光是background-image会遮挡,Cesium Canvas渲染层也会遮挡。

所以这里一定要创建一个单独的 div,控制一下层级显示。

坑2

style 代码记得写在最外层,或者是内页的 style 标签不要添加 scoped

很基础的问题,但是硬是拌住我半小时,服了!

Cesium PostProcessStage 方案

基于Cesium内置的WebGL后期处理通道,直接在3D场景渲染完成后叠加屏幕边缘红色光晕,完全脱离DOM结构。

不存在任何层级遮挡问题,着色器控制边缘渐变,效果丝滑、高端、原生融合。

GLSL片段着色器实现屏幕四周渐变红边,动态uniform参数控制闪烁强度。

完整代码

javascript
// 初始化Cesium
const viewer = new Cesium.Viewer("cesiumContainer", {
  timeline: false,
  animation: false,
  baseLayerPicker: false,
});

// 全局存储实例,防止重复创建
let alarmPostProcess = null;
let alarmAnimationTimer = null;

/**
 * Cesium 后期处理告警光晕(永不被遮挡)
 * @param {boolean} isShowAlarm true-显示告警 false-关闭告警
 */
function setCesiumAlarmPostProcess(isShowAlarm) {
  if (!isShowAlarm) {
    // 移除后期处理
    if (alarmPostProcess) {
      viewer.scene.postProcessStages.remove(alarmPostProcess);
      alarmPostProcess = null;
    }
    // 清除动画定时器
    if (alarmAnimationTimer) {
      clearInterval(alarmAnimationTimer);
      alarmAnimationTimer = null;
    }
    return;
  }

  if (alarmPostProcess) return;

  // GLSL 告警着色器(核心)
  const alarmShader = `
    uniform float intensity;
    void main() {
      // 获取场景原始颜色
      vec4 sourceColor = texture2D(colorTexture, v_textureCoordinates);
      
      // 计算屏幕边缘渐变:中心0 → 边缘1
      vec2 uv = v_textureCoordinates;
      float vignette = 1.0 - length(uv - 0.5);
      // 调整光晕范围:数值越大,红边越窄、越贴边
      vignette = pow(vignette, 3.0);
      
      // 边缘叠加红色,绿色通道、蓝色通道弱化,突出告警红
      float red = sourceColor.r + vignette * intensity * 0.9;
      float green = sourceColor.g * (1.0 - vignette * intensity * 0.2);
      float blue = sourceColor.b * (1.0 - vignette * intensity * 0.2);
      
      // 输出最终颜色
      gl_FragColor = vec4(red, green, blue, sourceColor.a);
    }
  `;

  alarmPostProcess = viewer.scene.postProcessStages.add(
    new Cesium.PostProcessStage({
      fragmentShader: alarmShader,
      uniforms: {
        intensity: 0.0, // 告警强度:0~1
      },
    })
  );

  let intensityValue = 0.0;
  let isIncrease = true; // 递增/递减开关

  // 每30ms更新一次强度,实现平滑呼吸效果
  alarmAnimationTimer = setInterval(() => {
    // 数值变化
    intensityValue += isIncrease ? 0.05 : -0.05;
    // 边界控制
    if (intensityValue >= 0.9) isIncrease = false;
    if (intensityValue <= 0.1) isIncrease = true;
    // 赋值给着色器
    if (alarmPostProcess) {
      alarmPostProcess.uniforms.intensity = intensityValue;
    }
  }, 30);
}

// 开启Cesium原生告警效果
setCesiumAlarmPostProcess(true);

// 关闭告警效果
setCesiumAlarmPostProcess(false);

总结

这里推荐最简单实现的 纯CSS 实现方案,并不推荐Cesium后期处理方案。

Cesium方案实现的效果其实一般,而且实现起来还比较复杂。