Condividi tramite


Rumore skiaSharp e composizione

La grafica vettoriale semplice tende a sembrare innaturale. Le linee rette, le curve uniformi e i colori a tinta unita non assomigliano alle imperfezioni degli oggetti reali. Mentre si lavora sulla grafica generata dal computer per il film Tron del 1982, ken perlin ha iniziato a sviluppare algoritmi che usavano processi casuali per dare a queste immagini trame più realistiche. Nel 1997, Ken Perlin ha vinto un Premio Oscar per il successo tecnico. Il suo lavoro è venuto a essere conosciuto come rumore Perlin, ed è supportato in SkiaSharp. Ecco un esempio:

Esempio di rumore perlin

Come si può notare, ogni pixel non è un valore di colore casuale. La continuità da pixel a pixel comporta forme casuali.

Il supporto del rumore Perlin in Skia si basa su una specifica W3C per CSS e SVG. La sezione 8.20 del modulo Filter Effects Module 1 include gli algoritmi di disturbo Perlin sottostanti nel codice C.

Esplorazione del rumore di Perlin

La SKShader classe definisce due metodi statici diversi per generare rumore Perlin: CreatePerlinNoiseFractalNoise e CreatePerlinNoiseTurbulence. I parametri sono identici:

public static SkiaSharp CreatePerlinNoiseFractalNoise (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed);

public static SkiaSharp.SKShader CreatePerlinNoiseTurbulence (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed);

Entrambi i metodi esistono anche nelle versioni di overload con un parametro aggiuntivo SKPointI . La sezione Tiling Perlin noise illustra questi overload.

I due baseFrequency argomenti sono valori positivi definiti nella documentazione skiaSharp, compresi tra 0 e 1, ma possono essere impostati anche su valori più elevati. Maggiore è il valore, maggiore è la modifica nell'immagine casuale nelle direzioni orizzontali e verticali.

Il numOctaves valore è un numero intero di 1 o superiore. Si riferisce a un fattore di iterazione negli algoritmi. Ogni ottava aggiuntiva contribuisce a un effetto che è metà dell'ottavo precedente, quindi l'effetto diminuisce con valori di ottave più elevati.

Il seed parametro è il punto iniziale per il generatore di numeri casuali. Anche se specificato come valore a virgola mobile, la frazione viene troncata prima dell'uso e 0 è uguale a 1.

La pagina Perlin Noise nell'esempio consente di sperimentare diversi valori degli baseFrequency argomenti e numOctaves . Ecco il file XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.PerlinNoisePage"
             Title="Perlin Noise">

    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Slider x:Name="baseFrequencyXSlider"
                Maximum="4"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label x:Name="baseFrequencyXText"
               HorizontalTextAlignment="Center" />

        <Slider x:Name="baseFrequencyYSlider"
                Maximum="4"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label x:Name="baseFrequencyYText"
               HorizontalTextAlignment="Center" />

        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="Center"
                     Margin="10">

            <Label Text="{Binding Source={x:Reference octavesStepper},
                                  Path=Value,
                                  StringFormat='Number of Octaves: {0:F0}'}"
                   VerticalOptions="Center" />

            <Stepper x:Name="octavesStepper"
                     Minimum="1"
                     ValueChanged="OnStepperValueChanged" />
        </StackLayout>
    </StackLayout>
</ContentPage>

Usa due Slider visualizzazioni per i due baseFrequency argomenti. Per espandere l'intervallo dei valori inferiori, i dispositivi di scorrimento sono logaritmici. Il file code-behind calcola gli argomenti dei SKShadermetodi dai poteri dei Slider valori. Le Label visualizzazioni visualizzano i valori calcolati:

float baseFreqX = (float)Math.Pow(10, baseFrequencyXSlider.Value - 4);
baseFrequencyXText.Text = String.Format("Base Frequency X = {0:F4}", baseFreqX);

float baseFreqY = (float)Math.Pow(10, baseFrequencyYSlider.Value - 4);
baseFrequencyYText.Text = String.Format("Base Frequency Y = {0:F4}", baseFreqY);

Il Slider valore 1 corrisponde a 0,001, un Slider valore os 2 corrisponde a 0,01, un Slider valore pari a 3 corrisponde a 0,1 e il Slider valore 4 corrisponde a 1.

Ecco il file code-behind che include tale codice:

public partial class PerlinNoisePage : ContentPage
{
    public PerlinNoisePage()
    {
        InitializeComponent();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnStepperValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Get values from sliders and stepper
        float baseFreqX = (float)Math.Pow(10, baseFrequencyXSlider.Value - 4);
        baseFrequencyXText.Text = String.Format("Base Frequency X = {0:F4}", baseFreqX);

        float baseFreqY = (float)Math.Pow(10, baseFrequencyYSlider.Value - 4);
        baseFrequencyYText.Text = String.Format("Base Frequency Y = {0:F4}", baseFreqY);

        int numOctaves = (int)octavesStepper.Value;

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader =
                SKShader.CreatePerlinNoiseFractalNoise(baseFreqX,
                                                       baseFreqY,
                                                       numOctaves,
                                                       0);

            SKRect rect = new SKRect(0, 0, info.Width, info.Height / 2);
            canvas.DrawRect(rect, paint);

            paint.Shader =
                SKShader.CreatePerlinNoiseTurbulence(baseFreqX,
                                                     baseFreqY,
                                                     numOctaves,
                                                     0);

            rect = new SKRect(0, info.Height / 2, info.Width, info.Height);
            canvas.DrawRect(rect, paint);
        }
    }
}

Ecco il programma in esecuzione su dispositivi iOS, Android e piattaforma UWP (Universal Windows Platform) (UWP). Il rumore frattale viene visualizzato nella metà superiore dell'area di disegno. Il rumore della turbolenza è nella metà inferiore:

Rumore perlin

Gli stessi argomenti producono sempre lo stesso modello che inizia nell'angolo superiore sinistro. Questa coerenza è ovvia quando si regola la larghezza e l'altezza della finestra UWP. Quando Windows 10 ridisegna lo schermo, il motivo nella metà superiore dell'area di disegno rimane invariato.

Il modello di rumore incorpora vari gradi di trasparenza. La trasparenza diventa evidente se si imposta un colore nella canvas.Clear() chiamata. Tale colore diventa prominente nel motivo. Questo effetto verrà visualizzato anche nella sezione Combinazione di più shader.

Questi modelli di rumore perlin vengono usati raramente da soli. Spesso sono soggetti a modalità di fusione e filtri di colore descritti negli articoli successivi.

Disturbo perlin di legatura

I due metodi statici SKShader per la creazione del disturbo Perlin esistono anche nelle versioni di overload. Gli CreatePerlinNoiseFractalNoise overload e CreatePerlinNoiseTurbulence hanno un parametro aggiuntivo SKPointI :

public static SKShader CreatePerlinNoiseFractalNoise (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed, SKPointI tileSize);

public static SKShader CreatePerlinNoiseTurbulence (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed, SKPointI tileSize);

La SKPointI struttura è la versione integer della struttura familiare SKPoint . SKPointI definisce X le proprietà e Y di tipo int anziché float.

Questi metodi creano un modello ripetuto delle dimensioni specificate. In ogni riquadro, il bordo destro è uguale al bordo sinistro e il bordo superiore è uguale al bordo inferiore. Questa caratteristica è illustrata nella pagina Rumore perlin affiancato. Il file XAML è simile all'esempio precedente, ma ha solo una Stepper visualizzazione per modificare l'argomento seed :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.TiledPerlinNoisePage"
             Title="Tiled Perlin Noise">

    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="Center"
                     Margin="10">

            <Label Text="{Binding Source={x:Reference seedStepper},
                                  Path=Value,
                                  StringFormat='Seed: {0:F0}'}"
                   VerticalOptions="Center" />

            <Stepper x:Name="seedStepper"
                     Minimum="1"
                     ValueChanged="OnStepperValueChanged" />

        </StackLayout>
    </StackLayout>
</ContentPage>

Il file code-behind definisce una costante per le dimensioni del riquadro. Il PaintSurface gestore crea una bitmap di tale dimensione e un oggetto SKCanvas per il disegno in tale bitmap. Il SKShader.CreatePerlinNoiseTurbulence metodo crea uno shader con le dimensioni del riquadro. Questo shader viene disegnato sulla bitmap:

public partial class TiledPerlinNoisePage : ContentPage
{
    const int TILE_SIZE = 200;

    public TiledPerlinNoisePage()
    {
        InitializeComponent();
    }

    void OnStepperValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Get seed value from stepper
        float seed = (float)seedStepper.Value;

        SKRect tileRect = new SKRect(0, 0, TILE_SIZE, TILE_SIZE);

        using (SKBitmap bitmap = new SKBitmap(TILE_SIZE, TILE_SIZE))
        {
            using (SKCanvas bitmapCanvas = new SKCanvas(bitmap))
            {
                bitmapCanvas.Clear();

                // Draw tiled turbulence noise on bitmap
                using (SKPaint paint = new SKPaint())
                {
                    paint.Shader = SKShader.CreatePerlinNoiseTurbulence(
                                        0.02f, 0.02f, 1, seed,
                                        new SKPointI(TILE_SIZE, TILE_SIZE));

                    bitmapCanvas.DrawRect(tileRect, paint);
                }
            }

            // Draw tiled bitmap shader on canvas
            using (SKPaint paint = new SKPaint())
            {
                paint.Shader = SKShader.CreateBitmap(bitmap,
                                                     SKShaderTileMode.Repeat,
                                                     SKShaderTileMode.Repeat);
                canvas.DrawRect(info.Rect, paint);
            }

            // Draw rectangle showing tile
            using (SKPaint paint = new SKPaint())
            {
                paint.Style = SKPaintStyle.Stroke;
                paint.Color = SKColors.Black;
                paint.StrokeWidth = 2;

                canvas.DrawRect(tileRect, paint);
            }
        }
    }
}

Dopo aver creato la bitmap, viene usato un altro SKPaint oggetto per creare un modello bitmap affiancato chiamando SKShader.CreateBitmap. Si notino i due argomenti di SKShaderTileMode.Repeat:

paint.Shader = SKShader.CreateBitmap(bitmap,
                                     SKShaderTileMode.Repeat,
                                     SKShaderTileMode.Repeat);

Questo shader viene usato per coprire l'area di disegno. Infine, viene usato un altro SKPaint oggetto per tracciare un rettangolo che mostra le dimensioni della bitmap originale.

Solo il seed parametro è selezionabile dall'interfaccia utente. Se in ogni piattaforma viene usato lo stesso seed modello, viene visualizzato lo stesso modello. I valori diversi seed generano modelli diversi:

Rumore perlin affiancato

Il modello quadrato da 200 pixel nell'angolo superiore sinistro scorre senza problemi negli altri riquadri.

Combinazione di più shader

La SKShader classe include un CreateColor metodo che crea uno shader con un colore a tinta unita specificato. Questo shader non è molto utile da solo perché è sufficiente impostare tale colore sulla Color proprietà dell'oggetto SKPaint e impostare la Shader proprietà su null.

Questo CreateColor metodo diventa utile in un altro metodo che SKShader definisce. Questo metodo è CreateCompose, che combina due shader. Ecco la sintassi:

public static SKShader CreateCompose (SKShader dstShader, SKShader srcShader);

( srcShader shader di origine) viene disegnato in modo efficace sopra lo dstShader shader di destinazione. Se lo shader di origine è un colore a tinta unita o una sfumatura senza trasparenza, lo shader di destinazione verrà completamente oscurato.

Un perlin noise shader contiene trasparenza. Se tale shader è l'origine, lo shader di destinazione verrà visualizzato attraverso le aree trasparenti.

La pagina Composta perlin Noise ha un file XAML praticamente identico alla prima pagina Perlin Noise . Anche il file code-behind è simile. Tuttavia, la pagina Originale Perlin Noise imposta la Shader proprietà di SKPaint sullo shader restituito dai metodi statici CreatePerlinNoiseFractalNoise e CreatePerlinNoiseTurbulence . Questa pagina Composta perlin Noise chiama CreateCompose un shader combinato. La destinazione è uno shader blu solido creato usando CreateColor. L'origine è uno shader del rumore Perlin:

public partial class ComposedPerlinNoisePage : ContentPage
{
    public ComposedPerlinNoisePage()
    {
        InitializeComponent();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnStepperValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Get values from sliders and stepper
        float baseFreqX = (float)Math.Pow(10, baseFrequencyXSlider.Value - 4);
        baseFrequencyXText.Text = String.Format("Base Frequency X = {0:F4}", baseFreqX);

        float baseFreqY = (float)Math.Pow(10, baseFrequencyYSlider.Value - 4);
        baseFrequencyYText.Text = String.Format("Base Frequency Y = {0:F4}", baseFreqY);

        int numOctaves = (int)octavesStepper.Value;

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateCompose(
                SKShader.CreateColor(SKColors.Blue),
                SKShader.CreatePerlinNoiseFractalNoise(baseFreqX,
                                                       baseFreqY,
                                                       numOctaves,
                                                       0));

            SKRect rect = new SKRect(0, 0, info.Width, info.Height / 2);
            canvas.DrawRect(rect, paint);

            paint.Shader = SKShader.CreateCompose(
                SKShader.CreateColor(SKColors.Blue),
                SKShader.CreatePerlinNoiseTurbulence(baseFreqX,
                                                     baseFreqY,
                                                     numOctaves,
                                                     0));

            rect = new SKRect(0, info.Height / 2, info.Width, info.Height);
            canvas.DrawRect(rect, paint);
        }
    }
}

Il disturbo frattale è in cima; lo shader turbolenza si trova nella parte inferiore:

Composto rumore perlin

Si noti quanto blu sono questi shader rispetto a quelli visualizzati dalla pagina Perlin Noise . La differenza illustra la quantità di trasparenza negli shader del rumore.

Esiste anche un overload del CreateCompose metodo :

public static SKShader CreateCompose (SKShader dstShader, SKShader srcShader, SKBlendMode blendMode);

Il parametro finale è un membro dell'enumerazione, un'enumerazione SKBlendMode con 29 membri discussa nella serie successiva di articoli sulle modalità di composizione e fusione SkiaSharp.