在我的像素着色器(UWP、Win2D)中支持多种颜色输入

Support more than one color input in my Pixel Shader (UWP, Win2D)

我一直在开发一个可以提供颜色替换的应用程序,并且在解决方案上得到了@Jet Chopper 的很多帮助。他为我提供了以下代码,这些代码基本上使用 ControlSpectrum 控件来控制源和目标颜色。这个想法是您指定一种源颜色,然后将其替换为目标颜色。这是当前的工作代码:

这是我的原创post,其中包含带有 GIF 的原始解决方案。

XAML:

<Grid>
    <xaml:CanvasAnimatedControl x:Name="AnimatedControl"
                            CreateResources="AnimatedControl_OnCreateResources"
                            Draw="AnimatedControl_OnDraw"/>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Bottom">
        <TextBlock Text="Source Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="SourceColorSpectrum_OnColorChanged"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom">
        <TextBlock Text="Replace Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="TargetColorSpectrum_OnColorChanged"/>
    </StackPanel>

    <Slider Width="512" ValueChanged="RangeBase_OnValueChanged" VerticalAlignment="Center"/>
</Grid>

代码:

private PixelShaderEffect _textureShader;
private GaussianBlurEffect _blur;
private BlendEffect _blend;

private void AnimatedControl_OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
{
    args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}

private async Task CreateResourcesAsync(CanvasAnimatedControl sender)
{
    var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Shaders/TextureTest.bin"));
    var buffer = await FileIO.ReadBufferAsync(file);

    var sourceImage = await CanvasBitmap.LoadAsync(sender, new Uri("ms-appx:///Assets/image.jpg"));

    _textureShader = new PixelShaderEffect(buffer.ToArray())
    {
        Source1 = sourceImage
    };

    _blur = new GaussianBlurEffect
    {
        BlurAmount = 4,
        Source = _textureShader
    };

    _blend = new BlendEffect
    {
        Foreground = _blur,
        Background = sourceImage,
        Mode = BlendEffectMode.Color
    };
}

private void AnimatedControl_OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
    args.DrawingSession.DrawImage(_blend);
}

private void RangeBase_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _textureShader.Properties["threshold"] = (float)e.NewValue / 100;
}

private void SourceColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["sourceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void TargetColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["replaceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

像素着色器:

#define D2D_INPUT_COUNT 1
#define D2D_INPUT0_SIMPLE

#include "d2d1effecthelpers.hlsli"

float3 sourceColor;
float3 replaceColor;
float threshold;

D2D_PS_ENTRY(main)
{
    float3 color = D2DGetInput(0).rgb;

    if (abs(color.r - sourceColor.r) < threshold && abs(color.g - sourceColor.g) < threshold && abs(color.b - sourceColor.b) < threshold) 
    {
        float3 newColor = color - sourceColor + replaceColor;
        return float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else 
    {
        return float4(0, 0, 0, 0);
    }
}

所以我的下一步是将这个解决方案向前推进一步,同时引入不止一种颜色替换。所以我改变了一切以支持 2 种颜色:

我的更改

XAML:

<Grid>
    <xaml:CanvasAnimatedControl x:Name="AnimatedControl"
                            CreateResources="AnimatedControl_OnCreateResources"
                            Draw="AnimatedControl_OnDraw"/>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Bottom">
        <TextBlock Text="Source Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="SourceColorSpectrum_OnColorChanged"/>
        <ColorSpectrum Width="256" Height="256" ColorChanged="SourceColorSpectrum_OnColorChanged2"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom">
        <TextBlock Text="Replace Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="TargetColorSpectrum_OnColorChanged"/>
        <ColorSpectrum Width="256" Height="256" ColorChanged="TargetColorSpectrum_OnColorChanged2"/>
    </StackPanel>

    <Slider Width="512" ValueChanged="RangeBase_OnValueChanged" VerticalAlignment="Center"/>
</Grid>

代码:

private PixelShaderEffect _textureShader;
private GaussianBlurEffect _blur;
private BlendEffect _blend;

private void AnimatedControl_OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
{
    args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}

private async Task CreateResourcesAsync(CanvasAnimatedControl sender)
{
    var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Shaders/TextureTest.bin"));
    var buffer = await FileIO.ReadBufferAsync(file);

    var sourceImage = await CanvasBitmap.LoadAsync(sender, new Uri("ms-appx:///Assets/image.jpg"));

    _textureShader = new PixelShaderEffect(buffer.ToArray())
    {
        Source1 = sourceImage,
        Source2 = sourceImage
    };

    _blur = new GaussianBlurEffect
    {
        BlurAmount = 4,
        Source = _textureShader
    };

    _blend = new BlendEffect
    {
        Foreground = _blur,
        Background = sourceImage,
        Mode = BlendEffectMode.Color
    };
}

private void AnimatedControl_OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
    args.DrawingSession.DrawImage(_blend);
}

private void RangeBase_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _textureShader.Properties["threshold"] = (float)e.NewValue / 100;
}

private void SourceColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["sourceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void TargetColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["replaceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void SourceColorSpectrum_OnColorChanged2(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["sourceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void TargetColorSpectrum_OnColorChanged2(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["replaceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

像素着色器:

#define D2D_INPUT_COUNT 2
#define D2D_INPUT0_SIMPLE
#define D2D_INPUT1_SIMPLE

#include "d2d1effecthelpers.hlsli"

float3 sourceColor;
float3 replaceColor;
float3 sourceColor2;
float3 replaceColor2;
float threshold;

D2D_PS_ENTRY(main)
{
    float3 color1 = D2DGetInput(0).rgb;
    float3 color2 = D2DGetInput(1).rgb;

    float4 result1;
    float4 result2;

    if (abs(color1.r - sourceColor.r) < threshold &&
        abs(color1.g - sourceColor.g) < threshold &&
        abs(color1.b - sourceColor.b) < threshold) 
    {
        float3 newColor = color1 - sourceColor + replaceColor;
        result1 = float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else 
    {
        result1 = float4(0, 0, 0, 0);
    }

    if (abs(color2.r - sourceColor2.r) < threshold &&
        abs(color2.g - sourceColor2.g) < threshold &&
        abs(color2.b - sourceColor2.b) < threshold)
    {
        float3 newColor = color2 - sourceColor2 + replaceColor2;
        result2 = float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else
    {
        result2 = float4(0, 0, 0, 0);
    }

    return result1 * result2;
}

所以基本上我只是将 XAML、代码和像素着色器中的所有内容加倍。但是对于像素着色器,我不确定我的 return 值是否正确,将两个结果相乘。对于一次更换多种颜色的能力,我是否走在正确的轨道上?

在 WPF 中无法自动 "stack" 效果。但是,您可以使用组合来做到这一点。您不必重复使用像素着色器所做的任何事情。您基本上会加倍使用相关 child 的祖先,并应用多个像素着色器效果。例如,假设您想将 2 种颜色替换像素着色器应用于单个图像。以下 pseudo-code 将带您到达那里:

<Grid Name="Ancestor1">
    <Grid Name="Ancestor2">
        <Image Source="..." Name="Child" />
    </Grid>
</Grid>

在后面的代码中,您可以将像素着色器效果应用于每个祖先,如下所示:

Ancestor1.Effect = New ColorReplacementPixelShader() with { .PropertyA = someValue, ... }
Ancestor2.Effect = New ColorReplacementPixelShader() with { .PropertyA = somevalue, ... }

在执行此操作时,您已有效地将相同的像素着色器两次应用到 child Image 元素,但这样做是通过使用两个祖先来叠加效果.

希望对您有所帮助!

好的,这是您的示例,其中包含 2 种输入颜色、2 种替换颜色和 2 个阈值。

XAML:

<Grid>
    <xaml:CanvasAnimatedControl x:Name="AnimatedControl"
                            CreateResources="AnimatedControl_OnCreateResources"
                            Draw="AnimatedControl_OnDraw"/>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Bottom">
        <TextBlock Text="Source Color 2" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged2"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom">
        <TextBlock Text="Replace Color 2" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged3"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
        <TextBlock Text="Source Color 1" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Top">
        <TextBlock Text="Replace Color 1" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged1"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Slider Width="512" ValueChanged="RangeBase_OnValueChanged" VerticalAlignment="Center"/>
    <Slider Width="512" ValueChanged="RangeBase1_OnValueChanged" VerticalAlignment="Center"/>
    </StackPanel>
</Grid>

后面的代码:

    private PixelShaderEffect _textureShader;
    private GaussianBlurEffect _blur;
    private BlendEffect _blend;

    public Ripple()
    {
        InitializeComponent();
    }

    private void AnimatedControl_OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
    {
        args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
    }

    private async Task CreateResourcesAsync(CanvasAnimatedControl sender)
    {
        var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Shaders/TextureTest.bin"));
        var buffer = await FileIO.ReadBufferAsync(file);

        var sourceImage = await CanvasBitmap.LoadAsync(sender, new Uri("ms-appx:///Assets/image.jpg"));

        _textureShader = new PixelShaderEffect(buffer.ToArray())
        {
            Source1 = sourceImage
        };

        _blur = new GaussianBlurEffect
        {
            BlurAmount = 4,
            Source = _textureShader
        };

        _blend = new BlendEffect
        {
            Foreground = _blur,
            Background = sourceImage,
            Mode = BlendEffectMode.Color
        };
    }

    private void AnimatedControl_OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
    {
        args.DrawingSession.DrawImage(_blend);
    }

    private void RangeBase_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        _textureShader.Properties["threshold"] = (float)e.NewValue / 100;
    }

    private void ColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["sourceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

    private void ColorSpectrum_OnColorChanged1(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["replaceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

    private void RangeBase1_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        _textureShader.Properties["threshold2"] = (float)e.NewValue / 100;
    }

    private void ColorSpectrum_OnColorChanged2(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["sourceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

    private void ColorSpectrum_OnColorChanged3(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["replaceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

HLSL 着色器:

#define D2D_INPUT_COUNT 1
#define D2D_INPUT0_SIMPLE

#include "d2d1effecthelpers.hlsli"

float3 sourceColor;
float3 replaceColor;
float threshold;

float3 sourceColor2;
float3 replaceColor2;
float threshold2;

D2D_PS_ENTRY(main)
{
    float3 color = D2DGetInput(0).rgb;

    if (abs(color.r - sourceColor.r) < threshold && abs(color.g - sourceColor.g) < threshold && abs(color.b - sourceColor.b) < threshold) 
    {
        float3 newColor = color - sourceColor + replaceColor;
        return float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else if (abs(color.r - sourceColor2.r) < threshold2 && abs(color.g - sourceColor2.g) < threshold2 && abs(color.b - sourceColor2.b) < threshold2)
    {
        float3 newColor = color - sourceColor2 + replaceColor2;
        return float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else 
    {
        return float4(0, 0, 0, 0);
    }
}

尽情享受吧!