利用著色器在WPF中實現陰影特效

ggtc發表於2024-07-04

疑問

著色器只能訪問控制元件可視區域內的畫素,但是陰影特效出現在控制元件可視區域外部,這是怎麼實現的?
我想起來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獲取並不天然支援獲取控制元件尺寸,所以實現方式並不很好看。

相關文章