目录
1. 前言
2. WPF
2.1. 透明过渡动画
2.2. 径向缩放模糊动画
2.3. 亮度动画
3. Winform
3.1. 透明过渡动画编辑
3.2. 径向缩放模糊动画编辑
3.3. 亮度动画编辑
4. Avalonia
4.1. 透明过渡动画
4.2. 径向缩放模糊动画
4.3. 亮度动画
5. WinUI3
5.1. 透明过渡动画
5.2. 径向缩放模糊动画
5.3. 亮度动画
6. Web
6.1. 透明过渡动画
6.2. 径向缩放模糊动画
编辑
6.3. 亮度动画
7. 五种比对
7.1. 屏幕录制
7.2. 手机录制
8. 总结
8.1. 难点
8.2. 原理与特性
WPF
HTML / WebGL
Avalonia-GPU
WinForms-GPU
WinUI3-GPU
8.3. 优势特色
9. 源码下载

1. 前言
这样的【闪白切换】特效看起来真的非常的酷,那么它是怎样实现的呢?我们最直观的感觉,它肯定有两个图片在进行透明的切换;也可以看到有缩放的跳跃呼吸感;然后中间有一个白色闪屏。那么这三者结合,能否达到相同的效果呢?其实,这是一个非常让人容易容易轻敌的特效。我们来观看它的慢放动画:

我们可以从慢放动画中发现,其实它的处理效果是非常复杂的,可以包含3种:
1,透明渐变动画:负责基础的过渡变换,使过渡平滑柔和;
2,径向缩放模糊动画:产生幻影错层感,焦点聚集,很有电影画面感;轻微的缩放有呼吸跳跃感,更立体;
3,亮度动画:像是相机拍照时的闪光灯,非常有场景感。
那么我们来分别实现它们试试。
2. WPF
2.1. 透明过渡动画
在WPF中,透明渐变动画实现的XAML:
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="img1"
Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
<LinearDoubleKeyFrame KeyTime="0:0:1.5" Value="0.3"/>
<LinearDoubleKeyFrame KeyTime="0:0:1.9" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="img2"
Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
<LinearDoubleKeyFrame KeyTime="0:0:1.5" Value="0.8"/>
<LinearDoubleKeyFrame KeyTime="0:0:2" Value="1"/>
</DoubleAnimationUsingKeyFrames>
效果:

2.2. 径向缩放模糊动画
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="zoomBlurEffect"
Storyboard.TargetProperty="BlurAmount">
<LinearDoubleKeyFrame KeyTime="0:0:1.1" Value="0"/>
<LinearDoubleKeyFrame KeyTime="0:0:1.5" Value="0.08"/>
<LinearDoubleKeyFrame KeyTime="0:0:1.65" Value="0"/>
</DoubleAnimationUsingKeyFrames>
模糊效果依赖于着色器:
public class ZoomBlurEffect : ShaderEffect
{
public static readonly DependencyProperty InputProperty =
RegisterPixelShaderSamplerProperty(nameof(Input), typeof(ZoomBlurEffect), 0);
public static readonly DependencyProperty CenterProperty = DependencyProperty.Register(
nameof(Center),
typeof(Point),
typeof(ZoomBlurEffect),
new UIPropertyMetadata(new Point(0.5, 0.5), PixelShaderConstantCallback(0)));
public static readonly DependencyProperty BlurAmountProperty = DependencyProperty.Register(
nameof(BlurAmount),
typeof(double),
typeof(ZoomBlurEffect),
new UIPropertyMetadata(0.1d, PixelShaderConstantCallback(1)));
public ZoomBlurEffect()
{
PixelShader = new PixelShader
{
UriSource = new Uri("pack://application:,,,/Shaders/ZoomBlurEffect.ps")
};
UpdateShaderValue(InputProperty);
UpdateShaderValue(CenterProperty);
UpdateShaderValue(BlurAmountProperty);
}
public Brush Input
{
get => (Brush)GetValue(InputProperty);
set => SetValue(InputProperty, value);
}
/// <summary>模糊中心(相对坐标,0.5,0.5 为元素中心)。</summary>
public Point Center
{
get => (Point)GetValue(CenterProperty);
set => SetValue(CenterProperty, value);
}
/// <summary>模糊强度。0 为无效果。</summary>
public double BlurAmount
{
get => (double)GetValue(BlurAmountProperty);
set => SetValue(BlurAmountProperty, value);
}
}
效果:

2.3. 亮度动画
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="contrastAdjustEffect"
Storyboard.TargetProperty="Brightness">
<LinearDoubleKeyFrame KeyTime="0:0:1.4" Value="0"/>
<LinearDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
<LinearDoubleKeyFrame KeyTime="0:0:1.6" Value="0"/>
</DoubleAnimationUsingKeyFrames>
亮度动画也依赖着色器:
public class ContrastAdjustEffect : ShaderEffect
{
public static readonly DependencyProperty InputProperty =
RegisterPixelShaderSamplerProperty(nameof(Input), typeof(ContrastAdjustEffect), 0);
public static readonly DependencyProperty BrightnessProperty = DependencyProperty.Register(
nameof(Brightness),
typeof(double),
typeof(ContrastAdjustEffect),
new UIPropertyMetadata(0d, PixelShaderConstantCallback(0)));
public static readonly DependencyProperty ContrastProperty = DependencyProperty.Register(
nameof(Contrast),
typeof(double),
typeof(ContrastAdjustEffect),
new UIPropertyMetadata(1d, PixelShaderConstantCallback(1)));
public ContrastAdjustEffect()
{
PixelShader = new PixelShader
{
UriSource = new Uri("pack://application:,,,/Shaders/ContrastAdjustEffect.ps")
};
UpdateShaderValue(InputProperty);
UpdateShaderValue(BrightnessProperty);
UpdateShaderValue(ContrastProperty);
}
public Brush Input
{
get => (Brush)GetValue(InputProperty);
set => SetValue(InputProperty, value);
}
/// <summary>亮度偏移。0 为原图,增大变亮。</summary>
public double Brightness
{
get => (double)GetValue(BrightnessProperty);
set => SetValue(BrightnessProperty, value);
}
/// <summary>对比度倍率。1 为原图。</summary>
public double Contrast
{
get => (double)GetValue(ContrastProperty);
set => SetValue(ContrastProperty, value);
}
}
效果:

以上三个动画综合在一起效果:

3. Winform
Winform的实现方式,我们既可以使用CPU算法,也可以使用GPU算法,这里我们使用更高性能的GPU方式处理:GPU · OpenTK GLControl + GLSL 330。
GPU 入口:OpenTK.GLControl(Core Profile 3.3)· 每帧 MakeCurrent → Uniform → DrawArrays → SwapBuffers
渲染管线片元着色器(所有动画共用)
原理:与 Avalonia/HTML 同构,语法为 #version 330 core + texture()。 WinForms.Timer 采样关键帧后 _gl.Invalidate() 触发 Paint。
private const string Fs = """
#version 330 core
in vec2 vUv;
out vec4 fragColor;
uniform sampler2D uTex1;
uniform sampler2D uTex2;
uniform float uOpacity1;
uniform float uOpacity2;
uniform float uBlur;
uniform float uBrightness;
void main() {
vec2 center = vec2(0.5, 0.5);
vec2 centered = vUv – center;
vec4 c = vec4(0.0);
for (int i = 0; i < 15; i++) {
float scale = 1.0 – uBlur * (float(i) / 14.0);
vec2 sampleUv = centered * scale + center;
vec4 s1 = texture(uTex1, sampleUv);
vec4 s2 = texture(uTex2, sampleUv);
c += s1 * uOpacity1 + s2 * uOpacity2;
}
c /= 15.0;
c.rgb += uBrightness;
fragColor = vec4(clamp(c.rgb, 0.0, 1.0), 1.0);
}
""";
透明过渡/缩放模糊/亮度/合并:
原理:按钮只切换 AnimDef;同一套 Timer + Paint 路径。 差异仅在关键帧表(见上方共用表)。
每帧绘制OnGlPaint
原理:把当前四个参数写入 uniform,绑定双纹理,画全屏 TriangleStrip。 像素运算全部在 GPU 着色器内完成。
private void OnGlPaint(object? sender, PaintEventArgs e)
{
_gl.MakeCurrent();
GL.Viewport(0, 0, _gl.Width, _gl.Height);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.UseProgram(_program);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, _tex1);
GL.ActiveTexture(TextureUnit.Texture1);
GL.BindTexture(TextureTarget.Texture2D, _tex2);
GL.Uniform1(_locO1, _o1);
GL.Uniform1(_locO2, _o2);
GL.Uniform1(_locBlur, _blur);
GL.Uniform1(_locBright, _bright);
GL.BindVertexArray(_vao);
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
_gl.SwapBuffers();
}
3.1. 透明过渡动画 
3.2. 径向缩放模糊动画 
3.3. 亮度动画 
合成效果:

4. Avalonia
Avalonia的实现方式,我们既可以使用CPU算法,也可以使用GPU算法,这里我们使用更高性能的GPU方式处理:GPU · OpenGlControlBase + GLSL ES
GPU 入口:Avalonia.OpenGL.Controls.OpenGlControlBase · 片元着色器在 GPU 执行 · 时间轴用 DispatcherTimer(约 16ms)驱动
渲染管线片元着色器(所有动画共用)
原理:全屏四边形 + ES2 风格 GLSL。四种动画只改 uniform,不换着色器。 RequestNextFrameRendering() 触发 OnOpenGlRender 真正画到 GPU 表面。
private const string FragmentShaderEs2 = """
precision mediump float;
varying vec2 vUv;
uniform sampler2D uTex1;
uniform sampler2D uTex2;
uniform float uOpacity1;
uniform float uOpacity2;
uniform float uBlur;
uniform float uBrightness;
void main() {
vec2 center = vec2(0.5, 0.5);
vec2 centered = vUv – center;
vec4 c = vec4(0.0);
for (int i = 0; i < 15; i++) {
float scale = 1.0 – uBlur * (float(i) / 14.0);
vec2 sampleUv = centered * scale + center;
vec4 s1 = texture2D(uTex1, sampleUv);
vec4 s2 = texture2D(uTex2, sampleUv);
c += s1 * uOpacity1 + s2 * uOpacity2;
}
c /= 15.0;
c.rgb += uBrightness;
gl_FragColor = vec4(clamp(c.rgb, 0.0, 1.0), 1.0);
}
""";
透明过渡Fade
原理:1 秒内 O1 从 1→0、O2 从 0→1;Blur/Bright 恒为 0。 着色器里退化为 mix(tex1, tex2) 式交叉溶解(仍走 15 次循环,但 scale 恒为 1)。
private void OnFade(…) => Preview.Play(AnimTimeline.Fade);
public void Play(AnimTimeline.AnimDef def)
{
Opacity1 = 1; Opacity2 = 0; BlurAmount = 0; Brightness = 0;
var sw = Stopwatch.StartNew();
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) };
_timer.Tick += (_, _) =>
{
var t = Math.Min((float)sw.Elapsed.TotalSeconds, def.Duration);
Opacity1 = AnimTimeline.Sample(def.O1, t);
Opacity2 = AnimTimeline.Sample(def.O2, t);
BlurAmount = AnimTimeline.Sample(def.Blur, t);
Brightness = AnimTimeline.Sample(def.Bright, t);
RequestNextFrameRendering(); // → OnOpenGlRender 写 uniform 并 DrawArrays
if (t >= def.Duration) _timer.Stop();
};
_timer.Start();
}
缩放模糊
原理:0.6s 内 BlurAmount 脉冲 0→0.06→0;O1 保持 1。 GPU 按径向 scale 多次采样,形成向放大方向发散的拖影。
private void OnZoom(…) => Preview.Play(AnimTimeline.Zoom);
// Blur 关键帧:0 → 0.06(at 0.3s) → 0(at 0.6s)
// 着色器:scale = 1.0 – uBlur * (i / 14.0)
亮度BrightAnim
原理:0.4s 内 Brightness 0→1→0;着色器末尾 c.rgb += uBrightness,形成闪白脉冲。
private void OnBright(…) => Preview.Play(AnimTimeline.BrightAnim);
// Bright 关键帧:0 → 1(at 0.2s) → 0(at 0.4s)
4.1. 透明过渡动画

4.2. 径向缩放模糊动画

4.3. 亮度动画

合成效果:

5. WinUI3
WinUI3的实现方式,我们既可以使用CPU算法,也可以使用GPU算法,这里我们使用更高性能的GPU方式处理:GPU · Win2D PixelShaderEffect + HLSL
GPU 入口:Microsoft.Graphics.Canvas · CanvasAnimatedControl 每帧 Draw · 自定义像素着色器编译为 Shaders/FlashTransition.bin
渲染管线HLSL 像素着色器(所有动画共用)
原理:Win2D PixelShaderEffect 把两张图作为 Source1/Source2, 常量缓冲写入 Opacity/Blur/Brightness。使用 D2D 助手宏做复杂采样(非 1:1)。
#define D2D_INPUT_COUNT 2
#define D2D_INPUT0_COMPLEX
#define D2D_INPUT1_COMPLEX
#include "d2d1effecthelpers.hlsli"
cbuffer constants : register(b0)
{
float Opacity1;
float Opacity2;
float BlurAmount;
float Brightness;
};
D2D_PS_ENTRY(main)
{
float2 uv = D2DGetInputCoordinate(0).xy;
float2 center = float2(0.5, 0.5);
float2 centered = uv – center;
float4 c = 0;
[unroll]
for (int i = 0; i < 15; i++)
{
float scale = 1.0 – BlurAmount * (i / 14.0);
float2 sampleUv = centered * scale + center;
c += D2DSampleInput(0, sampleUv) * Opacity1;
c += D2DSampleInput(1, sampleUv) * Opacity2;
}
c /= 15.0;
c.rgb += Brightness;
return float4(saturate(c.rgb), 1.0);
}
装载效果PixelShaderEffect
原理:图片先 Cover 到 400×600 的 CanvasRenderTarget(仅加载时一次), 再挂到 PixelShaderEffect;运行期只改 Properties,不再碰 CPU 逐像素。
var bytecode = await File.ReadAllBytesAsync(shaderPath);
_effect = new PixelShaderEffect(bytecode)
{
Source1 = _img1,
Source2 = _img2,
Source1BorderMode = EffectBorderMode.Hard,
Source2BorderMode = EffectBorderMode.Hard,
Source1Mapping = SamplerCoordinateMapping.Unknown,
Source2Mapping = SamplerCoordinateMapping.Unknown,
};
private void ApplyUniforms()
{
_effect.Properties["Opacity1"] = _o1;
_effect.Properties["Opacity2"] = _o2;
_effect.Properties["BlurAmount"] = _blur;
_effect.Properties["Brightness"] = _bright;
}
透明过渡/缩放模糊/亮度/合并
原理:CanvasAnimatedControl 自带帧循环; 在 OnDraw 里按经过时间采样关键帧、写 Properties、再 DrawImage(_effect)。 各按钮只是 Play(不同 AnimDef)。
private void OnFade(…) => Play(AnimTimeline.Fade);
private void OnZoom(…) => Play(AnimTimeline.Zoom);
private void OnBright(…) => Play(AnimTimeline.BrightAnim);
private void OnSum(…) => Play(AnimTimeline.Sum);
private void Play(AnimTimeline.AnimDef def)
{
_playing = def;
_sw = Stopwatch.StartNew();
_o1 = 1; _o2 = 0; _blur = 0; _bright = 0;
ApplyUniforms();
}
private void OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
if (_playing is not null && _sw is not null)
{
var t = Math.Min((float)_sw.Elapsed.TotalSeconds, _playing.Duration);
_o1 = AnimTimeline.Sample(_playing.O1, t);
_o2 = AnimTimeline.Sample(_playing.O2, t);
_blur = AnimTimeline.Sample(_playing.Blur, t);
_bright = AnimTimeline.Sample(_playing.Bright, t);
if (t >= _playing.Duration) _playing = null;
ApplyUniforms();
}
args.DrawingSession.DrawImage(_effect, dest, sourceRect);
}
5.1. 透明过渡动画

5.2. 径向缩放模糊动画

5.3. 亮度动画

合成效果:

6. Web
实现方式:单一片段着色器同时完成透明度混合 + 径向模糊 + 亮度;用 JS 关键帧驱动 uniform。
6.1. 透明过渡动画
时间轴定义 + 着色器里 s1*uOpacity1 + s2*uOpacity2。
fade: {
duration: 1,
o1: [{ t: 0, v: 1 }, { t: 1, v: 0 }],
o2: [{ t: 0, v: 0 }, { t: 1, v: 1 }],
blur: [{ t: 0, v: 0 }],
bright: [{ t: 0, v: 0 }],
}
着色器混合部分
vec4 s1 = texture2D(uTex1, sampleUv);
vec4 s2 = texture2D(uTex2, sampleUv);
c += s1 * uOpacity1 + s2 * uOpacity2;

6.2. 径向缩放模糊动画
片段着色器循环
zoom: {
duration: 0.6,
o1: [{ t: 0, v: 1 }],
o2: [{ t: 0, v: 0 }],
blur: [{ t: 0, v: 0 }, { t: 0.3, v: 0.06 }, { t: 0.6, v: 0 }],
bright: [{ t: 0, v: 0 }],
}
// WebGL fragment:
for (int i = 0; i < 15; i++) {
float scale = 1.0 – uBlur * (float(i) / 14.0);
vec2 sampleUv = centered * scale + uCenter;
// sample & accumulate…
}
c /= 15.0;
6.3. 亮度动画
着色器偏置
bright: {
duration: 0.4,
o1: [{ t: 0, v: 1 }],
o2: [{ t: 0, v: 0 }],
blur: [{ t: 0, v: 0 }],
bright: [{ t: 0, v: 0 }, { t: 0.2, v: 1 }, { t: 0.4, v: 0 }],
}
// WebGL fragment:
c.rgb += uBrightness;
驱动(三动画共用)
function play(def) {
const start = performance.now();
const tick = (now) => {
const t = Math.min((now – start) / 1000, def.duration);
state.o1 = lerpKeyframes(def.o1, t);
state.o2 = lerpKeyframes(def.o2, t);
state.blur = lerpKeyframes(def.blur, t);
state.bright = lerpKeyframes(def.bright, t);
draw(); // 写入 uniform 并绘制
if (t < def.duration) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}

合成效果:

7. 五种比对
7.1. 屏幕录制

7.2. 手机录制

8. 总结
8.1. 难点
实现这样的ui动画,最难的是哪一点呢?经过对动画拆分之后,单独的某一个动画实现起来是比较简单的,复杂的是把几种动画综合在一起,并且让不同的动画出现在正确的、符合人眼视觉的时机,这一点是最难的。在这个过程中,即使100毫秒的差异,所带给人的【感觉】都可能是不同的。我们上面虽然使用5种技术框架来模拟参考的效果,但仍然差强人意,有一些细节没有完全控制到位,这是最难调整的,也是最考验动画感的。
比如在调整动画的过程中, 以WPF的UI代码为例, 红色框选位置的这些时间点,就需要反复调整,花费非常多时间,才初步得到了还算ok的效果。(花费这样的时间,对于一个追求效率的快节奏公司来说通常是不可接受的)

所以调整ui动画,其实非常考验耐心,和考验开发者的【感觉】,但是这种细微差异带来的不同的感觉是 AI 无法达成的。每次在做一个新的UI动画之前,我会先描述给AI去处理,但基本上没有一次它能完成我预期的效果。
上面的制作过程,我一般是先把动画拆分之后,用WPF来完成所有的细节调整,然后再AI迁移到其他的 Ui框架里面去, AI对于现有逻辑的【迁移】和【模仿】是非常擅长的。因此就完成了以上的5种方式的处理。
但是我们也能看到,每一种框架的处理,尤其是在闪白的控制上面,何时淡入、何时峰值模糊、何时冲白,表现出来的细微的延迟是不太一样的,每种框架在 交叉溶解、径向 ZoomBlur、亮度偏置 多方面的性能差异导致的细微时间延迟到不同,都会让人产生节点错开的感觉, 正是这类工作真正耗时的地方。
8.2. 原理与特性
五种技术都在做同一件事——用 GPU 着色器(或等价像素效果)完成「双图叠化 + ZoomBlur + 亮度闪白」, 差别不在「能否计算出来」,而在动画时间模型、着色器方言、以及框架原生特效入口不同。
| WPF | UIElement.Effect ShaderEffect | HLSL(ps_2/3 风格) ZoomBlur.fx + ContrastAdjust.fx | XAML Storyboard 关键帧驱动依赖属性 | 控件树挂效果链;Storyboard 改 Opacity / BlurAmount / Brightness,GPU 实时着色 | 桌面 XAML 原生像素特效管线 |
| HTML | Canvas + WebGL | GLSL ES(片元着色器) 单 pass 合并三效果 | requestAnimationFrame JS 插值关键帧 | 双纹理绑定 + uniform;浏览器合成层上的可编程管线 | Web 标准图形管线 |
| Avalonia GPU 版 | OpenGlControlBase | GLSL ES2 风格 控件内嵌着色器字符串 | DispatcherTimer ≈16ms 采样后 RequestNextFrameRendering | 跨平台 UI 壳 + 显式 OpenGL 表面;参数进 uniform | Avalonia 上的托管 OpenGL 特效面 |
| WinForms GPU 版 | OpenTK GLControl | GLSL 330 Core 全屏 TriangleStrip | WinForms Timer Invalidate → Paint → SwapBuffers | 经典 Win32 窗体嵌原生 GL 上下文;无内置特效 API,自建管线 | WinForms 外壳 + 桌面 OpenGL |
| WinUI 3 GPU 版 | Win2D CanvasAnimatedControl | HLSL + D2D helpers PixelShaderEffect | Canvas 自带帧循环 OnDraw 内采样关键帧 | 现代 Windows UI + Direct2D 自定义像素效果;字节码 .bin 装载 | WinUI 生态内的 D2D/Win2D 特效 |
WPF
- 原理:视觉树节点挂 ShaderEffect;内层 ZoomBlur、外层 ContrastAdjust(亮度)。Storyboard 直接动画依赖属性(含 Effect 参数与 Image.Opacity)。
- 特性:声明式时间轴与特效深度绑定;设计师/开发者同一套 XAML 心智;无需自管 GL 上下文。
- 擅长:桌面 .NET 里「控件 + GPU 像素特效」一体化体验最成熟。
- 约束:着色器方言偏旧(经典 ps 档)、主要限 Windows + WPF 栈。
HTML / WebGL
- 原理:一张 Canvas 拿 WebGL 上下文;顶点铺全屏四边形;片元着色器内一次完成叠化、ZoomBlur、亮度。
- 特性:零安装、跨浏览器;可用纯静态页(含内嵌图)打开;与桌面栈完全解耦。
- 擅长:演示、分发、对照实验成本最低。
- 约束:依赖浏览器 GL 能力;与桌面控件树无直接互操作。
Avalonia-GPU
- 原理:自定义 OpenGlControlBase,在 OnOpenGlInit/Render 中编译链接 GLSL、上传双纹理、每帧写 uniform 并绘制。
- 特性:保留 Avalonia 的跨平台 UI 壳,特效面显式走 OpenGL(经 Avalonia/ANGLE 等上下文管理)。
- 擅长:需要 Avalonia 应用壳、又要可编程 GPU 特效时的「官方 OpenGL 控件」路径。
- 约束:不像 WPF 有一等公民 ShaderEffect;需自管着色器与资源生命周期。
WinForms-GPU
- 原理:窗体内嵌 OpenTK GLControl(Core 3.3);Load 时建 VAO/纹理/Program;Paint 时更新 uniform 并 SwapBuffers。
- 特性:WinForms 本身无像素着色器 API,GPU 能力完全来自嵌入的 OpenGL 运行时。
- 擅长:存量 WinForms 应用中「局部开一块真 GPU 特效面」而不重写整个 UI。
- 约束:GL 上下文与 WinForms 消息泵协作需自理;不是框架内建特效模型。
WinUI3-GPU
- 原理:Win2D CanvasAnimatedControl 驱动帧循环;预编译 HLSL → FlashTransition.bin → PixelShaderEffect;Properties 映射 cbuffer。
- 特性:站在现代 Windows 图形栈(D2D/Win2D)上;与 WinUI 控件并存自然。
- 擅长:WinUI 3 / Windows App SDK 产品中的自定义后处理特效。
- 约束:需按 D2D Effect 约定编译着色器;打包/运行时依赖 Windows App SDK(本工程已自包含)。
8.3. 优势特色
五种方式个有特色:WPF 声明式真源、Web 易分发、Avalonia 跨平台 GL 面、WinForms 存量嵌入、WinUI 现代 D2D/Win2D。对齐靠统一时间轴与公式;性能靠各自 GPU;

9. 源码下载

完整源码下载地址:
https://download.csdn.net/download/LateFrames/93137732
再来欣赏一下WPF版本的效果吧,我觉得时间节点的控制还算不错:









