代码之家  ›  专栏  ›  技术社区  ›  Maximus

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

  •  2
  • Maximus  · 技术社区  · 7 年前

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

    这是我的原始帖子,包含带有GIF的原始解决方案。 Original Post

    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、代码和像素着色器中的所有内容加倍。但对于像素着色器,我不确定将两个结果相乘返回值是否正确。在一次更换多个颜色的能力方面,我是否走上了正确的道路?

    2 回复  |  直到 7 年前
        1
  •  1
  •   Jet Chopper    7 年前

    好的,这是您的示例,有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);
        }
    }
    

    enter image description here

    享受

        2
  •  0
  •   Michael A. Allen    7 年前

    在WPF中,无法自动“堆叠”效果。不过,您可以使用合成来完成。您不必重复使用像素着色器所做的任何事情。基本上,您将对子对象的祖先进行加倍,并应用多个像素着色器效果。例如,假设您希望将2个颜色替换像素着色器应用于单个图像。下面的伪代码可以帮助您实现这一点:

    <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, ... }
    

    在执行此操作时,您已将同一像素着色器有效地应用于子对象两次 形象 元素,但通过使用两个祖先叠加效果来实现。

    希望有帮助!