2017-01-23 2 views
0

J'essaie d'obtenir un simple dégradé d'angle à 3 couleurs dans WPF. Dans le meilleur des cas, vous obtenez une réponse XAML-ONLY, mais je suis à peu près sûr que c'est impossible.3 couleurs AngleGradient dans WPF

Cette question & Réponse (AngleGradient in WPF) est la moitié de ce que je veux. J'ai essayé de jouer avec le code mais je ne comprends pas les maths.

enter image description here

Ma question est: comment puis-je faire exactement ce qui est demandé dans la question ci-dessus, mais avec 3 couleurs et un dégradé entre la couleur 3 et la couleur 1 (Dans la question ci-dessus, il va de pair avec un gradient de bleu à blanc, mais tout de suite à bleu après, je voudrais aussi un dégradé inverse du blanc au bleu aussi). Pensez-y comme un triangle équilatéral avec une base horizontale parfaite. Je veux par exemple le rouge dans le coin supérieur, le vert dans le coin inférieur gauche et le bleu dans le coin inférieur droit, et un rayonnement angulaire parfait entre les deux.

enter image description here

Je ne me dérange pas d'avoir à compiler dans un .ps comme dans la réponse suggérée dans l'autre thread :)

Merci beaucoup!

Répondre

0

Je pense que d'autres threads expliquent bien ce que vous devez faire. Vous aurez besoin de passer les trois couleurs au shader, puis faites le calcul par pixel pour obtenir la couleur que vous voulez. Obtenez l'angle 0 - 1 du pixel courant de la même manière que l'autre réponse. Puis lerp entre les couleurs appropriées à cet angle.

shader

sampler2D inputSampler : register(S0); 
float2 center : register(C0); 
float4 firstColor : register(C1); 
float4 secondColor : register(C2); 
float4 thirdColor : register(C3); 

float4 main(float2 uv : TEXCOORD) : COLOR 
{ 
    // Put the three colors into an array. 
    float4 colors[3] = { firstColor, secondColor, thirdColor }; 

    // Figure out where this pixel is in relation to the center point 
    float2 pos = center - uv; 

    // Compute the angle of this pixel relative to the center (in radians), 
    // then divide by 2 pi to normalize the angle into a 0 to 1 range. 
    // We are flipping the Y here so that 0 is at the top (instead of the bottom) and we 
    // rotate clockwise. Could also flip X if we want to rotate counter-clockwise. 
    float value = (atan2(pos.x, -pos.y) + 3.141596)/(2.0 * 3.141596); 

    // Scale the angle based on the size of our array and determine which indices 
    // we are currently between, wrapping around to 0 at the end. 
    float scaledValue = value * 3; 
    float4 prevColor = colors[(int)scaledValue]; 
    float4 nextColor = colors[((int)scaledValue + 1) % 3]; 

    // Figure out how far between the two colors we are 
    float lerpValue = scaledValue - (float)((int)scaledValue); 

    // Get the alpha of the incoming pixel from the sampler. 
    float alpha = tex2D(inputSampler, uv).a; 

    // Lerp between the colors. Multiply each color by its own alpha and the result by the 
    // incoming alpha becuse WPF expects shaders to return premultiplied alpha pixel values. 
    return float4(
     lerp(prevColor.rgb * prevColor.a, nextColor.rgb * nextColor.a, lerpValue) * alpha, 
     lerp(prevColor.a, nextColor.a, lerpValue) * alpha); 
} 

Effet

class AngleGradientEffect : ShaderEffect 
{ 
    public Brush Input 
    { 
     get { return (Brush)GetValue(InputProperty); } 
     set { SetValue(InputProperty, value); } 
    } 
    public static readonly DependencyProperty InputProperty = RegisterPixelShaderSamplerProperty("Input", typeof(AngleGradientEffect), 0); 

    public Point Center 
    { 
     get { return (Point)GetValue(CenterProperty); } 
     set { SetValue(CenterProperty, value); } 
    } 
    public static readonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(AngleGradientEffect), 
     new PropertyMetadata(new Point(0.5, 0.5), PixelShaderConstantCallback(0))); 

    public Color FirstColor 
    { 
     get { return (Color)GetValue(FirstColorProperty); } 
     set { SetValue(FirstColorProperty, value); } 
    } 
    public static readonly DependencyProperty FirstColorProperty = DependencyProperty.Register("FirstColor", typeof(Color), typeof(AngleGradientEffect), 
     new PropertyMetadata(Color.FromRgb(255, 0, 0), PixelShaderConstantCallback(1))); 

    public Color SecondColor 
    { 
     get { return (Color)GetValue(SecondColorProperty); } 
     set { SetValue(SecondColorProperty, value); } 
    } 
    public static readonly DependencyProperty SecondColorProperty = DependencyProperty.Register("SecondColor", typeof(Color), typeof(AngleGradientEffect), 
     new PropertyMetadata(Color.FromRgb(0, 255, 0), PixelShaderConstantCallback(2))); 

    public Color ThirdColor 
    { 
     get { return (Color)GetValue(ThirdColorProperty); } 
     set { SetValue(ThirdColorProperty, value); } 
    } 
    public static readonly DependencyProperty ThirdColorProperty = DependencyProperty.Register("ThirdColor", typeof(Color), typeof(AngleGradientEffect), 
     new PropertyMetadata(Color.FromRgb(0, 0, 255), PixelShaderConstantCallback(3))); 

    public AngleGradientEffect() 
    { 
     // ResourceHelper is my own utility that formats URIs for me. The returned URI 
     // string will be something like /AssemblyName;component/Effects/AngleGradient.ps 
     PixelShader = new PixelShader() { UriSource = ResourceHelper.GetResourceUri("Effects/AngleGradient.ps", relative: true)}; 

     UpdateShaderValue(InputProperty); 
     UpdateShaderValue(CenterProperty); 
     UpdateShaderValue(FirstColorProperty); 
     UpdateShaderValue(SecondColorProperty); 
     UpdateShaderValue(ThirdColorProperty); 
    } 
} 

Utilisation

<Ellipse 
    Width="200" 
    Height="200" 
    Fill="White"> 
    <Ellipse.Effect> 
     <effects:AngleGradientEffect 
      FirstColor="Red" 
      SecondColor="Lime" 
      ThirdColor="Blue" /> 
    </Ellipse.Effect> 
</Ellipse> 

Gardez à l'esprit que faire une interpolation entre différentes teintes dans l'espace RVB donnera des résultats laids dans certains cas. Vous voudrez peut-être vous pencher sur la conversion au HSV et l'interpolation de la teinte si c'est quelque chose que vous vous attendez à faire.

+0

Je n'ai aucune connaissance de la construction du shader, et je ne comprends pas clairement la partie Shader. Mais ça marche! :) La seule chose qui me manque est de comprendre comment faire tourner votre solution (dans le shader) pour obtenir le premier centre de couleur. – ericmas001

+0

C'est un simple changement. Il suffit d'inverser la composante y de la position dans le shader lors du calcul de l'angle à retourner verticalement. 'float value = (atan2 (pos.x, -pos.y) + 3.141596)/(2.0 * 3.141596);' Vous pouvez également inverser le x si vous voulez le retourner horizontalement. – Xavier

+0

J'ai édité le shader dans ma réponse pour inverser l'axe Y, et aussi changé la manipulation alpha afin que les valeurs alpha dans la source soient respectées ainsi que dans les couleurs passées. J'ai également ajouté un tas de commentaires de code pour essayer d'expliquer ce que fait le shader. – Xavier