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

  • A+
所属分类:.NET技术
摘要

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

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

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

1. WPF 中的 Effect

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

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

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

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

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

2. 编写 Shader

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

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

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

// 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 就全白。

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

2. 应用 Effect

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

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 文件,一定要保证位置正确:

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

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

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

3. 最后

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

4. 源码

https://github.com/DinoChan/wpf_design_and_animation_lab