[WPF] 使用 Shazzam Shader Editor 编写一个 Lighten Effect

之前在一篇文章(实现两个任天堂 Switch 的加载动画)里为了实现不同亮度的 Grid,使用了一个 LightenConverter 类,但是它只能处理 SolidColorBrush。为了可以应用在更多场合,这篇文章自己写一个 Effect 来实现相同 Lighten 的效果。

1. WPF 中的 Effect#

Wpf 自带两种 Effect:BlurEffect 和 DropShadowEffect,用法如下:

Copy
<Grid.Effect> <BlurEffect/> </Grid.Effect>

除了 WPF 自带的这两个,还可以在 Microsoft Blend for Visual Studio 2015 里找到由 Microsoft.Expression.Effects 这个 dll 提供的一些 Effect。

现在这个 dll 也可以在 Nuget 上找到。

2. 编写 Shader#

WPF 中的 Effect 使用 HLSL(高级着色器语言)编写,如果需要自定义 Effect 可以使用 Shazzam Shader Editor, 关于这款编辑器 walterlv 有一篇如何使用的教程:

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

其实我之前也没写过,语法什么的完全不懂,可是从网上抄一抄,很快就搞明白了一些基础,最后从 Lightness.fx 这个改一改完成了我需要的 LightenEffect:

Copy
// Copyright (c) 2014 Marcus Schweda // This file is licensed under the MIT license (see LICENSE) sampler2D input : register(s0); float delta : register(c0); // RGB -> HSL float4 hsl(float4 c) { float4 c2 = c.a; float M = max(c.r, max(c.g, c.b)), m = min(c.r, min(c.g, c.b)); float chroma = M - m; // Lightness c2[2] = (M + m) / 2; // Hue if (chroma != 0) { if (M == c.r) c2[0] = ((c.g - c.b) / chroma) % 6; else if (M == c.g) c2[0] = (c.b - c.r) / chroma + 2; else c2[0] = (c.r - c.g) / chroma + 4; if (c2[0] < 0) c2[0] += 6; // Saturation c2[1] = chroma / (1 - abs(2 * c2[2] - 1)); } else { c2[0] = c2[1] = 0; } return c2; } float4 rgb(float4 c) { float4 c2 = c[3]; float chroma = c[1] * (1 - abs(2 * c[2] - 1)); float X = chroma * (1 - abs(c[0] % 2 - 1)); if (0 <= c[0] && c[0] < 1) c2.rgb = float3(chroma, X, 0); else if (1 <= c[0] && c[0] < 2) c2.rgb = float3(X, chroma, 0); else if (2 <= c[0] && c[0] < 3) c2.rgb = float3(0, chroma, X); else if (3 <= c[0] && c[0] < 4) c2.rgb = float3(0, X, chroma); else if (4 <= c[0] && c[0] < 5) c2.rgb = float3(X, 0, chroma); else if (5 <= c[0] && c[0] < 6) c2.rgb = float3(chroma, 0, X); c2.rgb += c[2] - chroma / 2; return c2; } float4 main(float2 uv : TEXCOORD) : COLOR { float4 hcyin = hsl(tex2D(input, uv)); if( delta>0) { hcyin[2] = saturate(hcyin[2] + (1-hcyin[2])* delta); }else { hcyin[2] = saturate(hcyin[2] + hcyin[2] * delta); } return rgb(hcyin); }

这份代码分三部分,首先是定义的两个变量 input 和 delta,input 即输入的图像,是每个 Shader 的固定部分,不要修改它;delta 是定义来控制 LightenEffect 亮度变化率的变量。然后是两个自定义的函数,用于 rgb 和 hsl 互相转换。最后是 main 函数,这也是每个 Effect 必须包含的部分,这个函数的输入 uv 看起来是坐标,用 tex2D(input, uv) 获取 input 在 uv 的颜色,函数的返回值是处理后的 uv 所在的颜色。

在这段代码里的 main 函数还算简单,就是把当前位置的颜色转换为 hsl,然后根据 delta 调整亮度,再转换为 rgb 返回。

函数完成并运行 Apply Shader 后可以使用 Shazzam Shader Editor 的 Tryout 功能验证效果。可以看到 Delta 为 -1 即全黑,为 1 就全白。

2. 应用 Effect#

验证完这个 Shader,把生成的 C# 代码和 .ps 文件放进项目,改好命名空间,编译后就能使用(关于这部分的详细操作,请参考 walterlv 的 这篇文章)。现在来看看生成的 C# 代码:

Copy
public class LightenEffect : ShaderEffect { public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(LightenEffect), 0); public static readonly DependencyProperty DeltaProperty = DependencyProperty.Register("Delta", typeof(double), typeof(LightenEffect), new UIPropertyMetadata(((double)(0D)), PixelShaderConstantCallback(0))); public LightenEffect() { PixelShader pixelShader = new PixelShader(); pixelShader.UriSource = new Uri("/WpfDesignAndAnimationLab.Effects;component/Shaders/LightenEffect.ps", UriKind.Relative); this.PixelShader = pixelShader; this.UpdateShaderValue(InputProperty); this.UpdateShaderValue(DeltaProperty); } private Brush Input { get { return ((Brush)(this.GetValue(InputProperty))); } set { this.SetValue(InputProperty, value); } } public double Delta { get { return ((double)(this.GetValue(DeltaProperty))); } set { this.SetValue(DeltaProperty, value); } } }

首先是自定义的两个变量 Input 和 Delta,它们被封装成依赖属性。然后看看这句话,这句话定位产生的 .ps 文件,一定要保证位置正确:

Copy
pixelShader.UriSource = new Uri("/WpfDesignAndAnimationLab.Effects;component/Shaders/LightenEffect.ps", UriKind.Relative);

最后使用时只需要在前面加上 Effect 的命名空间。

Copy
<Rectangle.Effect> <effects:LightenEffect Delta=".5"/> </Rectangle.Effect>

3. 最后#

感谢 walterlv 写的文章,让我终于学会了 Shazzam Shader Editor 的用法。

4. 源码#

https://github.com/DinoChan/wpf_design_and_animation_lab

posted @   dino.c  阅读(998)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示
CONTENTS