利用着色器在WPF中实现阴影特效

疑问

着色器只能访问控件可视区域内的像素,但是阴影特效出现在控件可视区域外部,这是怎么实现的?
我想起来WPF中有个叫做装饰器的东西,然而阅读了一下文档,似乎不行

放置在装饰器层中的任何内容将呈现在设置的其他任何样式的顶部。 换言之,装饰器始终以可见的方式位于顶部,无法使用 z 顺序重写。

而且装饰器的写法也和Effect不一样,这不行。

一个有趣的属性

我顺着继承关系往上找,找到了ShaderEffect类的另外几个属性PaddingRight PaddingLeft PaddingBottom PaddingTop,这几个属性可以扩大控件传给shader的矩形框Rect范围,从而给了shader在在控件可视范围外部进行渲染的能力。
这给了我启发,我们给shader传入一个控件实际范围,然后在shader中判断,如果像素位于控件范围内,就渲染原来的颜色,否则就计算阴影。类似与这样
image

快速验证

  • 算法验证
    我们现在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; 
}

image

看起来还不错,但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>

效果看起来还不错!

image

问题

  • 实现这种阴影效果,需要我们了解控件尺寸,并计算控件左下角的纹理坐标

\[\frac{button.width}{button.width+PaddingRight} \]

然而shaderEffect获取并不天然支持获取控件尺寸,所以实现方式并不很好看。

posted @ 2024-07-04 18:39  ggtc  阅读(28)  评论(0编辑  收藏  举报
//右下角目录