利用着色器在WPF中实现阴影特效
疑问
着色器只能访问控件可视区域内的像素,但是阴影特效出现在控件可视区域外部,这是怎么实现的?
我想起来WPF中有个叫做装饰器的东西,然而阅读了一下文档,似乎不行
放置在装饰器层中的任何内容将呈现在设置的其他任何样式的顶部。 换言之,装饰器始终以可见的方式位于顶部,无法使用 z 顺序重写。
而且装饰器的写法也和Effect
不一样,这不行。
一个有趣的属性
我顺着继承关系往上找,找到了ShaderEffect类的另外几个属性PaddingRight
PaddingLeft
PaddingBottom
PaddingTop
,这几个属性可以扩大控件传给shader的矩形框Rect
范围,从而给了shader在在控件可视范围外部进行渲染的能力。
这给了我启发,我们给shader传入一个控件实际范围,然后在shader中判断,如果像素位于控件范围内,就渲染原来的颜色,否则就计算阴影。类似与这样
快速验证
- 算法验证
我们现在Shazzam
中快速验证下这个想法是否可行
float2 shadowPoint : register(C0);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 color;
//origin color
color= tex2D( input , uv.xy);
float2 checks =1-step(1-shadowPoint,uv );
float2 checke =step(shadowPoint,uv );
float border = max( checke.x , checke.y);
float rightTop=border*checks.x;
float leftBottom=border*checks.y;
float4 tempColor=lerp(color,float4(0,0,0,1),border );
float4 finalColor = lerp(tempColor,float4(0,0,0,0),max(rightTop,leftBottom));
return finalColor;
}
看起来还不错,但wpf传入的矩形区域是否如我们所设想的那样?
- 参数验证
internal class MyShadowEffect:ShaderEffect
{
public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(MyShadowEffect), 0);
public static readonly DependencyProperty ShadowPointProperty = DependencyProperty.Register("ShadowPoint", typeof(Point), typeof(MyShadowEffect), new UIPropertyMetadata(new Point(0D, 0D), PixelShaderConstantCallback(0)));
public MyShadowEffect()
{
PixelShader pixelShader = new PixelShader();
pixelShader.UriSource = new Uri("pack://application:,,,/WpfApp1;component/x3D/ShadowEffect.ps", UriKind.Absolute);
this.PixelShader = pixelShader;
this.UpdateShaderValue(InputProperty);
this.UpdateShaderValue(ShadowPointProperty);
ShadowPoint = new Point(0.8, 0.8);
PaddingRight=50;
PaddingBottom=50;
}
public Brush Input
{
get
{
return ((Brush)(this.GetValue(InputProperty)));
}
set
{
this.SetValue(InputProperty, value);
}
}
public Point ShadowPoint
{
get
{
return ((Point)(this.GetValue(ShadowPointProperty)));
}
set
{
this.SetValue(ShadowPointProperty, value);
}
}
}
<Button Content="Btn" FontSize="28" Margin="0" Width="200" Height="200">
<Button.Effect>
<local:MyShadowEffect/>
</Button.Effect>
</Button>
效果看起来还不错!
问题
- 实现这种阴影效果,需要我们了解控件尺寸,并计算控件左下角的纹理坐标
\[\frac{button.width}{button.width+PaddingRight}
\]
然而shaderEffect
获取并不天然支持获取控件尺寸,所以实现方式并不很好看。