「闪白切换」UI 特效 - 在 Winform/WPF/WinUI3/Avalonia/Web 的实现

a3333bdb9d0247b492a93d9953bb5e62


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

我们可以从慢放动画中发现,其实它的处理效果是非常复杂的,可以包含3种:

1,透明渐变动画:负责基础的过渡变换,使过渡平滑柔和;

2,径向缩放模糊动画:产生幻影错层感,焦点聚集,很有电影画面感;轻微的缩放有呼吸跳跃感,更立体;

3,亮度动画:像是相机拍照时的闪光灯,非常有场景感。

那么我们来分别实现它们试试。


  1. 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>

效果:
bca19d01ebb14c95b885f0e4a2b44d53

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);
    }
}

效果:
79e0d9cf2448410b8e4af273c5d2a938

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);
    }
}

效果:
f696176fa4ec474a8bf55fe8c6494ba4

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

3878a13dc0224a9f9024a1cc381e06f0


  1. 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. 透明过渡动画

8f9b18394ecd4db3874736cec321b198

3.2. 径向缩放模糊动画
ff394a6ccd294ecb9c0cedcb5ddd320a

3.3. 亮度动画
4b03115dad604d0ca529c3c508049d60

合成效果:
cd1bc3f502b74da9b40bb43dea7f5f04


  1. 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. 透明过渡动画
bdfd0ffd0fb14733951c04962d9b5418

4.2. 径向缩放模糊动画

c94a409f00ee4b8f8f4d24d80dd0489a

4.3. 亮度动画
7badf5938ea64580aaca579e2c98e53c

合成效果:

bc12c17754504063b6873de857d75ff4


  1. 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. 透明过渡动画
f6c06590005048e8b78ba9d002e678c1

5.2. 径向缩放模糊动画
f6c06590005048e8b78ba9d002e678c1

5.3. 亮度动画
06c50a29ef3741d4a04939ccd665d483

合成效果:

919583d35ae848ae8935468f02815e62


  1. Web
    实现方式:单一片段着色器同时完成透明度混合 + 径向模糊 + 亮度;用 JS 关键帧驱动 uniform。

6.1. 透明过渡动画
时间轴定义 + 着色器里 s1uOpacity1 + s2uOpacity2。

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;

668baf17e0b54d769b221ab21009c146

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;

f840ec3077974f53be9a1b90e87a0a1e

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);
}

411be91040784903a4186123b3ac8a1f

合成效果:

9880043401454962bbee718075cb1ae8


  1. 五种比对
    7.1. 屏幕录制
    9880043401454962bbee718075cb1ae8

7.2. 手机录制
5d0ebd2b991040478dfced9c0967f982


  1. 总结
    8.1. 难点
    实现这样的ui动画,最难的是哪一点呢?经过对动画拆分之后,单独的某一个动画实现起来是比较简单的,复杂的是把几种动画综合在一起,并且让不同的动画出现在正确的、符合人眼视觉的时机,这一点是最难的。在这个过程中,即使100毫秒的差异,所带给人的【感觉】都可能是不同的。我们上面虽然使用5种技术框架来模拟参考的效果,但仍然差强人意,有一些细节没有完全控制到位,这是最难调整的,也是最考验动画感的。

比如在调整动画的过程中, 以WPF的UI代码为例, 红色框选位置的这些时间点,就需要反复调整,花费非常多时间,才初步得到了还算ok的效果。(花费这样的时间,对于一个追求效率的快节奏公司来说通常是不可接受的)
cdcdc33f32844a80a3e2b2d8b0bd6c15

所以调整ui动画,其实非常考验耐心,和考验开发者的【感觉】,但是这种细微差异带来的不同的感觉是 AI 无法达成的。每次在做一个新的UI动画之前,我会先描述给AI去处理,但基本上没有一次它能完成我预期的效果。

上面的制作过程,我一般是先把动画拆分之后,用WPF来完成所有的细节调整,然后再AI迁移到其他的 Ui框架里面去,  AI对于现有逻辑的【迁移】和【模仿】是非常擅长的。因此就完成了以上的5种方式的处理。

但是我们也能看到,每一种框架的处理,尤其是在闪白的控制上面,何时淡入、何时峰值模糊、何时冲白,表现出来的细微的延迟是不太一样的,每种框架在 交叉溶解、径向 ZoomBlur、亮度偏置 多方面的性能差异导致的细微时间延迟到不同,都会让人产生节点错开的感觉, 正是这类工作真正耗时的地方。


8.2. 原理与特性
五种技术都在做同一件事——用 GPU 着色器(或等价像素效果)完成「双图叠化 + ZoomBlur + 亮度闪白」, 差别不在「能否计算出来」,而在动画时间模型、着色器方言、以及框架原生特效入口不同。

image

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;
image

posted @ 2026-08-03 08:20  行人--  阅读(15)  评论(0)    收藏  举报