news 2026/5/1 11:40:22

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WPF 使用 HLSL + Clip 实现高亮歌词光照效果

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

本文经原作者授权以原创方式二次分享,欢迎转载、分享。

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

作 者: WPFDevelopersOrg - TwilightLemon

原文链接[1]:https://www.cnblogs.com/TwilightLemon/p/19497125

最近在搓一个 Lyricify Lite 类似物,原本使用渐变画刷实现歌词高亮,但是发现视觉效果与Apple Music相去甚远:单纯使用白色渐变画刷缺乏“高亮”的光照感觉,而Apple Music的歌词高亮则更像是有光线投射在歌词上,形成一种柔和的发光效果。

受到吕毅大佬的文章使用 WPF 做一个可以逼真地照亮你桌面的高性能阳光 - walterlv[2]启发,遂尝试使用HLSL编写一个简单的文本高亮着色器。先来看实装在LemonLite[3]中的效果:

整体上高亮文本与背景混色自然,渐变过渡由WPF动画驱动,高亮部分就有了光感。

以下是本文的最小可运行示例:https://github.com/TwilightLemon/TextHighlighterTest[4]

一、实现思路

简单说一下踩过的几个坑:

  1. 直接给TextBlock焊上Effect会导致文本像素化,变得模糊而且难以处理文本边界

  2. 使用VisualBrush,内部套一个Rectangle with Effect, 然后用这个VisualBrush作为TextBlock的Foreground,性能极差

  3. 给Rectangle with Effect做一个Clip裁剪出文本形状,性能可以,但是需要复刻TextBlock的排版逻辑

最终使用了第三种方案,并将其封装成一个用户控件。

如此一来,HLSL着色器要干的事情就很简单了:取一个高亮过渡位置pos,根据采样像素的X坐标计算光照强度,然后将这个强度与文本颜色做加法混合即可。

二、HLSL着色器代码

为了适配歌词高亮需求,我又追加了一些特性:

  1. 支持高亮颜色自定义

  2. 支持高亮宽度调整

  3. 支持切换高亮模式(加法混合/线性渐变)

以下是完整的HLSL代码:

sampler2D input : register(s0); float HighlightPos : register(c0); float HighlightWidth : register(c1); float4 HighlightColor : register(c2); float UseAdditive : register(c3); // 0 = lerp, 1 = additive float HighlightIntensity : register(c4); float4 main(float2 uv : TEXCOORD) : COLOR { float4 color = tex2D(input, uv); float d = max(0, uv.x - HighlightPos); float glow = saturate(1 - d / HighlightWidth); glow = glow * glow; float intensity = glow * HighlightColor.a * HighlightIntensity; float3 lerpResult = lerp(color.rgb, HighlightColor.rgb, intensity); float lerpAlpha = lerp(color.a, 1.0, intensity); float3 additiveResult = color.rgb + HighlightColor.rgb * intensity; color.rgb = lerp(lerpResult, additiveResult, UseAdditive); color.a = lerp(lerpAlpha, color.a, UseAdditive); return color; }

着色器输入参数

参数

作用

input

输入纹理(文本像素)

HighlightPos

高亮过渡位置(0-1,从左到右, 当然也可以取负数)

HighlightWidth

高亮衰减宽度(0-1)

HighlightColor

高亮颜色(含透明度)

UseAdditive

混合模式开关(0=线性渐变,1=加法混合)

HighlightIntensity

高亮强度倍数

计算过程

  1. 采样 → 读取当前像素颜色
    color = tex2D(input, uv)

  2. 计算距离 → 像素X坐标与高亮位置的距离
    d = max(0, uv.x - HighlightPos)

  3. 计算光晕强度 → 根据距离生成平滑衰减
    glow = saturate(1 - d / HighlightWidth)
    glow = glow * glow // 平方处理,使衰减更陡峭

  4. 最终强度 = 光晕 × 颜色透明度 × 强度倍数

  5. 混合处理 → 根据UseAdditive选择混合方式

  • 线性渐变:在原色和高亮色间插值

  • 加法混合:直接叠加高亮色

函数说明
  • saturate(x)- 将值钳制在 [0, 1] 范围内

  • step(edge, x)- 如果 x < edge 返回 0,否则返回 1

  • lerp(a, b, t)- 线性插值,计算 a + (b-a)×t

  • tex2D(sampler, uv)- 从纹理采样指定坐标的像素

编译并加载着色器

编译HLSL代码生成.ps文件,然后在WPF中使用PixelShader类加载:

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

封装成Effect类此处不做赘述,只需要严格按照参数顺序传递即可。

三、WPF用户控件封装

核心出装

用一个Rectangle承载着色器效果,通过FormattedText生成文本的几何形状作为裁剪路径,这样WPF只会渲染文本区域并且保留清晰的文本边缘。

属性继承与元数据覆写

为了让高亮控件支持标准的文本属性(字体、大小、粗细等),使用了WPF的元数据覆写机制。在静态构造函数中对FontFamilyFontSizeFontWeight等属性进行OverrideMetadata,绑定到统一的OnTextPropertyChanged回调。当这些属性变化时,触发文本裁剪的重新计算。类似地,Foreground属性也被覆写,当文本颜色改变时直接更新Rectangle的填充颜色。

static HighlightTextBlock() { FontFamilyProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(SystemFonts.MessageFontFamily, OnTextPropertyChanged)); FontSizeProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(14.0, OnTextPropertyChanged)); FontWeightProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(FontWeights.Normal, OnTextPropertyChanged)); ForegroundProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(Brushes.Black, OnForegroundChanged)); } private static void OnForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is HighlightTextBlock control) control.PART_Rectangle.Fill = e.NewValue as Brush; }

这样用户就可以像使用普通TextBlock一样使用这个控件,享受XAML的属性绑定和样式系统。

文本排版与裁剪

UpdateTextClip()方法负责:

  1. 创建排版对象- 通过FormattedText将文本按照控件的字体、大小、样式参数进行排版,获得与实际渲染完全一致的文本度量

var formattedText = new FormattedText( Text, CultureInfo.CurrentCulture, FlowDirection, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), FontSize, Brushes.Black, VisualTreeHelper.GetDpi(this).PixelsPerDip);
  1. 处理换行与宽度约束- 当启用TextWrapping时,从WidthMaxWidthActualWidth中取出约束宽度。只有在需要换行且有约束宽度的情况下,才设置MaxTextWidth让文本自动折行

var constraintWidth = !double.IsNaN(Width) && Width > 0 ? Width : (!double.IsInfinity(MaxWidth) && MaxWidth > 0 ? MaxWidth : ActualWidth); if (TextWrapping != TextWrapping.NoWrap && constraintWidth > 0) formattedText.MaxTextWidth = constraintWidth;
  1. 计算容器尺寸- NoWrap下容器宽度就是文本宽度,换行模式下则为约束宽度,以确保Rectangle的大小与实际文本范围匹配

var containerWidth = TextWrapping == TextWrapping.NoWrap ? textWidth : (constraintWidth > 0 ? constraintWidth : textWidth);
  1. 处理文本对齐- 根据TextAlignment计算文本相对于容器的起始偏移(用于居中和右对齐),然后生成Rectangle时传入这个偏移量,同时更新Rectangle的水平对齐属性使其与文本对齐方式一致

double offsetX = 0; if (containerWidth > textWidth) { offsetX = TextAlignment switch { TextAlignment.Center => (containerWidth - textWidth) / 2, TextAlignment.Right => containerWidth - textWidth, _ => 0 }; }
  1. 生成并应用裁剪- 调用formattedText.BuildGeometry()得到精确的文本轮廓Rectangle,设置为Rectangle.Clip。这样着色器的输出就被限制在文本像素范围内

var geometry = formattedText.BuildGeometry(new Point(offsetX, 0)); PART_Rectangle.Clip = geometry; PART_Rectangle.Width = containerWidth; PART_Rectangle.Height = textHeight;

高亮参数的动画驱动

高亮效果的三个关键参数(HighlightPosHighlightWidthHighlightColor)都被暴露为依赖属性,在属性变化回调中直接同步到着色器 Effect 对象:

public static readonly DependencyProperty HighlightPosProperty = DependencyProperty.Register( nameof(HighlightPos), typeof(double), typeof(HighlightTextBlock), new PropertyMetadata(0.0, OnHighlightPosChanged)); private static void OnHighlightPosChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is HighlightTextBlock c) c._effect.HighlightPos = (double)e.NewValue; }

这样就可以在XAML中轻松使用StoryboardDoubleAnimation

<Storyboard> <DoubleAnimation Storyboard.TargetName="HighlightBlock" Storyboard.TargetProperty="HighlightPos" From="-0.2" To="1.2" Duration="0:0:2" RepeatBehavior="Forever" /> </Storyboard>

驱动HighlightPos的平滑变化,从而实现光线扫过文本的连贯动画效果。

四、看看效果

对比Additive叠加和Lerp线性渐变两种模式,可以看到Additive模式下高亮部分更亮更有光感,而Lerp模式尾段偏灰。

尝试使用彩色高亮,变成了混色效果。

写在最后

LemonLite正在龟速开发中,过程中遇到的各种问题和解决方案都会陆续写成博客分享出来,欢迎各位大佬持续关注。
什么!你问我开头的背景是怎么做的?见上一篇文章:WPF 使用GDI+提取图片主色调并生成Mica材质特效背景[5]

参考资料:

  • WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码 - walterlv[6]

  • 使用 WPF 做一个可以逼真地照亮你桌面的高性能阳光 - walterlv[7]

参考资料

[1]

原文链接:https://www.cnblogs.com/TwilightLemon/p/19497125

[2]

使用 WPF 做一个可以逼真地照亮你桌面的高性能阳光 - walterlv:https://blog.walterlv.com/post/create-a-realistic-sunshine-on-your-desktop-using-wpf.html

[3]

LemonLite:https://github.com/TwilightLemon/LemonLite

[4]

https://github.com/TwilightLemon/TextHighlighterTest:https://github.com/TwilightLemon/TextHighlighterTest

[5]

WPF 使用GDI+提取图片主色调并生成Mica材质特效背景:/posts/wpf-mica-image-with-major-color-extract/

[6]

WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码 - walterlv:https://blog.walterlv.com/post/create-wpf-pixel-shader-effects-using-shazzam-shader-editor.html

[7]

使用 WPF 做一个可以逼真地照亮你桌面的高性能阳光 - walterlv:https://blog.walterlv.com/post/create-a-realistic-sunshine-on-your-desktop-using-wpf.html

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 7:09:27

线下活动反馈收集:掌声笑声数据可视化分析

线下活动反馈收集&#xff1a;掌声笑声数据可视化分析 1. 背景与问题提出 在线下会议、讲座、演出等现场活动中&#xff0c;观众的即时情绪反应是衡量内容质量的重要指标。传统方式依赖问卷调查或人工观察&#xff0c;存在滞后性强、样本覆盖率低、主观偏差大等问题。如何实时…

作者头像 李华
网站建设 2026/4/12 13:08:31

高职计算机专业证书规划指南(2026版)

职业定位与方向分析 高职计算机专业学生核心竞争力在于实践能力与问题解决效率&#xff0c;就业方向集中在技术应用层。主流岗位包括软件开发、网络运维、数据分析、云计算等&#xff0c;职业晋升路径通常从技术员逐步发展为技术经理或架构师。证书选择需紧密贴合岗位需求与行业…

作者头像 李华
网站建设 2026/4/22 13:59:22

Sambert模型显存溢出?SciPy接口兼容性修复部署案例

Sambert模型显存溢出&#xff1f;SciPy接口兼容性修复部署案例 1. 引言&#xff1a;Sambert多情感中文语音合成的工程挑战 在当前AI语音合成领域&#xff0c;基于深度学习的TTS&#xff08;Text-to-Speech&#xff09;系统正逐步走向工业级应用。阿里达摩院推出的Sambert-HiF…

作者头像 李华
网站建设 2026/5/1 6:14:08

抓紧搞钱!2026年程序员做副业赚钱的_25_种方法,零基础小白也能做

【强烈收藏】2026网络安全赚钱宝典&#xff1a;25种副业渠道完整学习路线 本文详细介绍了程序员/网络安全专家可利用的25种赚钱途径&#xff0c;包括副业接单、API服务、漏洞赏金、内容创作等多元化收入方式。特别针对网络安全领域&#xff0c;提供了挖SRC漏洞、安全测试委托、…

作者头像 李华
网站建设 2026/5/1 6:25:29

图像透明度处理难题解决:科哥UNet Alpha蒙版实测

图像透明度处理难题解决&#xff1a;科哥UNet Alpha蒙版实测 1. 引言&#xff1a;图像抠图中的透明度挑战与技术演进 在数字内容创作、电商展示、UI设计等场景中&#xff0c;高质量的图像抠图是提升视觉表现力的关键环节。传统基于边缘检测或色度键控的方法难以应对复杂背景、…

作者头像 李华
网站建设 2026/5/1 6:25:13

远程玩转PLC:一个码农的调试实战笔记

远程PLC通讯编程调试监控方案&#xff0c;提供PLC安全验证型中转服务器全套方案&#xff0c;含服务器及客户端源代码&#xff0c;定制对接用户自己的WIFI串口或网口服务器设备&#xff0c;实现远程编程调试任务。 支持上千路PLC设备的并发对接调试。搞工业自动化的兄弟都懂&…

作者头像 李华