普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月21日首页

WPF 使用 HLSL + Clip 实现高亮歌词光照效果

作者 大黄评测
2026年1月21日 14:09

在 WPF 中实现高亮歌词的光照效果(如舞台追光、聚光灯扫过文字),可以通过 HLSL 像素着色器(Pixel Shader) + Clip 几何裁剪 相结合的方式,实现高性能、流畅且视觉惊艳的动画效果。下面是一个完整的技术方案与实现示例。


✅ 效果目标

  • 歌词文本静态显示;
  • 一个“光斑”从左到右扫过当前行歌词;
  • 光斑区域高亮(白色/暖色),其余区域保持原色或变暗;
  • 支持平滑动画,60fps 流畅运行;
  • 利用 GPU 加速,避免频繁重绘文本。

🔧 技术组合

技术 作用
WPF TextBlock 显示歌词文本
ShaderEffect (HLSL) 实现动态光照遮罩
Clip 属性 限制光照仅作用于歌词区域(防溢出)
DoubleAnimation 驱动光斑位置变化

第一步:编写 HLSL 像素着色器(HighlightLight.ps)

创建 HighlightLight.ps 文件(编译为 .ps 后缀):

// HighlightLight.ps
sampler2D InputSampler : register(s0);
float2 LightCenter : register(c0);   // 光斑中心(归一化坐标 0~1)
float LightRadius : register(c1);    // 光斑半径(归一化)
float4 AmbientColor : register(c2);  // 背景/暗部颜色
float4 HighlightColor : register(c3); // 高亮颜色

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 original = tex2D(InputSampler, uv);
    
    // 计算当前像素到光斑中心的距离(归一化)
    float dist = distance(uv, LightCenter);
    
    // 光照强度:使用 smoothstep 实现柔和边缘
    float intensity = smoothstep(LightRadius, LightRadius * 0.7, dist);
    // 注意:smoothstep(edge0, edge1, x) 在 x<edge0 时为1,x>edge1 时为0
    
    // 混合:高亮区用 HighlightColor,其他用 AmbientColor
    float4 finalColor = lerp(HighlightColor, AmbientColor, intensity);
    
    // 保留原始 alpha(确保透明背景)
    return float4(finalColor.rgb, original.a);
}

💡 编译命令(使用 fxc):

fxc /T ps_3_0 /E main /Fo HighlightLight.ps HighlightLight.hlsl

第二步:在 C# 中封装 ShaderEffect

public class HighlightLightEffect : ShaderEffect
{
    public static readonly DependencyProperty InputProperty = 
        RegisterPixelShaderSamplerProperty("Input", typeof(HighlightLightEffect), 0);

    public static readonly DependencyProperty LightCenterProperty =
        DependencyProperty.Register("LightCenter", typeof(Point), typeof(HighlightLightEffect),
            new UIPropertyMetadata(new Point(0.5, 0.5), PixelShaderConstantCallback(0)));

    public static readonly DependencyProperty LightRadiusProperty =
        DependencyProperty.Register("LightRadius", typeof(double), typeof(HighlightLightEffect),
            new UIPropertyMetadata(0.3, PixelShaderConstantCallback(1)));

    public static readonly DependencyProperty AmbientColorProperty =
        DependencyProperty.Register("AmbientColor", typeof(Color), typeof(HighlightLightEffect),
            new UIPropertyMetadata(Colors.Gray, PixelShaderConstantCallback(2)));

    public static readonly DependencyProperty HighlightColorProperty =
        DependencyProperty.Register("HighlightColor", typeof(Color), typeof(HighlightLightEffect),
            new UIPropertyMetadata(Colors.White, PixelShaderConstantCallback(3)));

    public Brush Input
    {
        get => (Brush)GetValue(InputProperty);
        set => SetValue(InputProperty, value);
    }

    public Point LightCenter
    {
        get => (Point)GetValue(LightCenterProperty);
        set => SetValue(LightCenterProperty, value);
    }

    public double LightRadius
    {
        get => (double)GetValue(LightRadiusProperty);
        set => SetValue(LightRadiusProperty, value);
    }

    public Color AmbientColor
    {
        get => (Color)GetValue(AmbientColorProperty);
        set => SetValue(AmbientColorProperty, value);
    }

    public Color HighlightColor
    {
        get => (Color)GetValue(HighlightColorProperty);
        set => SetValue(HighlightColorProperty, value);
    }

    public HighlightLightEffect()
    {
        PixelShader = new PixelShader
        {
            UriSource = new Uri("pack://application:,,,/Shaders/HighlightLight.ps")
        };
        UpdateShaderValue(InputProperty);
        UpdateShaderValue(LightCenterProperty);
        UpdateShaderValue(LightRadiusProperty);
        UpdateShaderValue(AmbientColorProperty);
        UpdateShaderValue(HighlightColorProperty);
    }
}

第三步:XAML 布局 + Clip 裁剪

<Grid>
    <!-- 背景 -->
    <Rectangle Fill="Black" />

    <!-- 歌词容器(关键:设置 Clip 限制光照范围) -->
    <Border
        x:Name="LyricContainer"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Background="Transparent">
        
        <!-- 应用着色器的 TextBlock -->
        <TextBlock
            x:Name="LyricText"
            Text="这是一句高亮歌词示例"
            FontSize="48"
            Foreground="White"
            Effect="{StaticResource HighlightLightEffect}" />
            
    </Border>
</Grid>

⚠️ 为什么需要 Clip
若不裁剪,光照会作用于整个渲染区域(包括透明背景),造成性能浪费和视觉溢出。可通过代码动态设置 Clip 为歌词边界:

// 在窗口 Loaded 事件中
var bounds = LyricText.RenderSize;
LyricContainer.Clip = new RectangleGeometry(new Rect(bounds));

第四步:启动动画(C# 后台)

private void StartHighlightAnimation()
{
    var effect = (HighlightLightEffect)LyricText.Effect;
    
    var animation = new DoubleAnimation
    {
        From = -0.2,      // 从左侧外开始
        To = 1.2,         // 到右侧外结束
        Duration = TimeSpan.FromSeconds(3),
        RepeatBehavior = RepeatBehavior.Forever,
        AutoReverse = true
    };

    var centerPoint = new Point();
    var centerBinding = new PropertyGroupDescription();
    
    // 绑定 X 坐标动画
    Storyboard.SetTarget(animation, effect);
    Storyboard.SetTargetProperty(animation, new PropertyPath("LightCenter.X"));
    
    var sb = new Storyboard();
    sb.Children.Add(animation);
    sb.Begin();
}

🌟 优势总结

  • GPU 加速:HLSL 在显卡上运行,CPU 零负担;
  • 视觉柔和smoothstep 实现无锯齿光斑边缘;
  • 灵活可控:可调节光斑大小、颜色、速度;
  • 资源高效Clip 避免无效像素处理;
  • WPF 原生集成:无需第三方库,兼容 .NET Framework / .NET Core。

🔜 扩展方向

  • 多光斑同步(副歌部分双光效);
  • 结合音频节奏驱动光斑速度;
  • 使用 WriteableBitmap 实现更复杂的粒子+光照混合。

通过 HLSL + Clip + 动画 的组合,WPF 完全可以实现媲美游戏引擎的动态歌词高光效果,既保持了 XAML 的声明式优势,又释放了 GPU 的渲染潜力。

❌
❌