Udostępnij za pomocą


Szum SkiaSharp i komponowanie

Prosta grafika wektorowa zwykle wygląda nienaturalne. Proste linie, gładkie krzywe i stałe kolory nie przypominają niedoskonałości rzeczywistych obiektów. Podczas pracy nad wygenerowaną komputerowo grafiką dla filmu Tron z 1982 roku, informatyk Ken Perlin zaczął opracowywać algorytmy, które używały losowych procesów, aby dać tym obrazom bardziej realistyczne tekstury. W 1997 roku Ken Perlin zdobył Nagrodę Akademii Za Osiągnięcie Techniczne. Jego praca stała się znana jako hałas Perlin i jest obsługiwana w SkiaSharp. Oto przykład:

Próbka szumu perlin

Jak widać, każdy piksel nie jest losową wartością koloru. Ciągłość od piksela do piksela powoduje losowe kształty.

Obsługa szumu Perlin w Skia jest oparta na specyfikacji W3C dla CSS i SVG. Sekcja 8.20 modułu Efekty filtru poziom 1 zawiera podstawowe algorytmy szumu perlin w kodzie języka C.

Eksplorowanie szumu Perlin

Klasa SKShader definiuje dwie różne metody statyczne generowania szumu Perlin: CreatePerlinNoiseFractalNoise i CreatePerlinNoiseTurbulence. Parametry są identyczne:

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);

Obie metody istnieją również w przeciążonych wersjach z dodatkowym SKPointI parametrem. W sekcji Tiling Perlin szum omawia te przeciążenia.

Dwa baseFrequency argumenty są wartościami dodatnimi zdefiniowanymi w dokumentacji SkiaSharp w zakresie od 0 do 1, ale można je również ustawić na wyższe wartości. Im większa wartość, tym większa zmiana losowego obrazu w kierunkach poziomych i pionowych.

Wartość numOctaves jest liczbą całkowitą o wartości 1 lub wyższej. Odnosi się on do czynnika iteracji w algorytmach. Każda dodatkowa oktawa przyczynia się do efektu, który jest połowę poprzedniej oktawy, więc efekt zmniejsza się o wyższe wartości oktawy.

Parametr seed jest punktem wyjścia generatora liczb losowych. Mimo że jest określona jako wartość zmiennoprzecinkowa, ułamek jest obcięty przed jego zastosowaniem, a wartość 0 jest taka sama jak 1.

Strona Szum perlin w przykładzie umożliwia eksperymentowanie z różnymi wartościami argumentów baseFrequency i numOctaves . Oto plik 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>

Używa dwóch Slider widoków dla dwóch baseFrequency argumentów. Aby rozwinąć zakres niższych wartości, suwaki są logarytmyczne. Plik tworzący kod oblicza argumenty metodom SKShaderz uprawnień Slider wartości. Widoki Label wyświetlają wartości obliczeniowe:

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);

Slider Wartość 1 odpowiada 0,001, Slider wartość os 2 odpowiada wartości 0,01, Slider wartości 3 odpowiadają 0,1, a Slider wartość 4 odpowiada 1.

Oto plik z kodem, który zawiera ten kod:

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);
        }
    }
}

Oto program uruchomiony na urządzeniach z systemami iOS, Android i platforma uniwersalna systemu Windows (UWP). Szum fraktalny jest wyświetlany w górnej połowie kanwy. Hałas turbulencji znajduje się w dolnej połowie:

Szum perlin

Te same argumenty zawsze tworzą ten sam wzorzec, który zaczyna się w lewym górnym rogu. Ta spójność jest oczywista podczas dostosowywania szerokości i wysokości okna platformy UWP. Gdy system Windows 10 ponownie rysuje ekran, wzorzec w górnej połowie kanwy pozostaje taki sam.

Wzorzec szumu obejmuje różne stopnie przezroczystości. Przezroczystość staje się oczywista, jeśli ustawisz kolor w wywołaniu canvas.Clear() . Ten kolor staje się widoczny w wzorcu. Ten efekt zostanie również wyświetlony w sekcji Łączenie wielu cieniowania.

Te wzorce szumu perlin są rzadko używane przez siebie. Często podlegają trybom mieszania i filtrom kolorów omówionym w kolejnych artykułach.

Szum tiling Perlin

Dwie statyczne SKShader metody tworzenia szumu perlin również istnieją w wersjach przeciążenia. Przeciążenia CreatePerlinNoiseFractalNoise i CreatePerlinNoiseTurbulence mają dodatkowy SKPointI parametr:

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);

Struktura SKPointI jest wersją całkowitą znanej SKPoint struktury. SKPointIdefiniuje X właściwości typuint, Y a nie float.

Te metody tworzą powtarzający się wzorzec określonego rozmiaru. W każdym kafelku prawa krawędź jest taka sama jak lewa krawędź, a górna krawędź jest taka sama jak krawędź dolna. Ta cecha jest pokazana na stronie Szum perlin kafelka. Plik XAML jest podobny do poprzedniego przykładu, ale ma Stepper tylko widok zmiany argumentu 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>

Plik kodu definiuje stałą dla rozmiaru kafelka. Procedura PaintSurface obsługi tworzy mapę bitową o tym rozmiarze i obiekt SKCanvas do rysowania w tej mapie bitowej. Metoda SKShader.CreatePerlinNoiseTurbulence tworzy cieniowanie o tym rozmiarze kafelka. Ten cieniator jest rysowany na mapie bitowej:

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);
            }
        }
    }
}

Po utworzeniu mapy bitowej inny SKPaint obiekt jest używany do tworzenia kafelków wzorca mapy bitowej przez wywołanie metody SKShader.CreateBitmap. Zwróć uwagę na dwa argumenty polecenia SKShaderTileMode.Repeat:

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

Ten cieniator służy do pokrycia kanwy. Na koniec inny SKPaint obiekt służy do pociągnięcia prostokąta pokazującego rozmiar oryginalnej mapy bitowej.

seed Tylko parametr można wybrać z interfejsu użytkownika. Jeśli ten sam seed wzorzec jest używany na każdej platformie, będą one pokazywać ten sam wzorzec. Różne seed wartości powodują różne wzorce:

Szum na kafelkach perlin

Wzorzec kwadratowy 200 pikseli w lewym górnym rogu bezproblemowo przepływa do innych kafelków.

Łączenie wielu cieniowania

Klasa SKShader zawiera metodę CreateColor , która tworzy cieniowanie z określonym kolorem stałym. Ten cieniowanie nie jest bardzo przydatne samodzielnie, ponieważ można po prostu ustawić ten kolor na Color właściwość SKPaint obiektu i ustawić Shader właściwość na wartość null.

Ta CreateColor metoda staje się przydatna w innej metodzie, która SKShader definiuje. Ta metoda to CreateCompose, która łączy dwa cieniowania. Oto składnia:

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

( srcShader cieniator źródła) jest skutecznie rysowany na górze dstShader (cieniowania docelowego). Jeśli cieniowanie źródła jest kolorem stałym lub gradientem bez przezroczystości, cieniowanie docelowe zostanie całkowicie zasłonięte.

Cieniator szumu perlin zawiera przezroczystość. Jeśli ten cieniowanie jest źródłem, cieniowanie docelowe będzie wyświetlane za pośrednictwem przezroczystych obszarów.

Na stronie Skomponowany szum perlin znajduje się plik XAML, który jest praktycznie identyczny z pierwszą stroną Szum perlin. Plik związany z kodem jest również podobny. Jednak oryginalna strona Szum perlin ustawia Shader właściwość SKPaint na cieniowanie zwrócone ze statycznych CreatePerlinNoiseFractalNoise metod i CreatePerlinNoiseTurbulence . Ta strona skomponowanego szumu perlin wywołuje CreateCompose moduł cieniowania kombinacji. Miejsce docelowe jest niebieskim cieniatorem utworzonym przy użyciu polecenia CreateColor. Źródłem jest cieniator szumu 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);
        }
    }
}

Cieniator szumu fraktalnego znajduje się na górze; cieniowanie turbulencji znajduje się na dole:

Skomponowany szum perlin

Zwróć uwagę, ile bluer tych cieniowania są niż te wyświetlane na stronie Szum perlin. Różnica ilustruje ilość przezroczystości w cieniowaniach szumów.

Istnieje również przeciążenie CreateCompose metody:

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

Ostatni parametr jest elementem SKBlendMode członkowskim wyliczenia, wyliczenia z 29 elementami członkowskimi omówionymi w następnej serii artykułów na temat komponowania i trybu mieszania SkiaSharp.