Sdílet prostřednictvím


Šum skiaSharp a psaní

Jednoduchá vektorová grafika má tendenci vypadat nepřirozeně. Rovné čáry, hladké křivky a plné barvy se podobají nedostatkům reálných objektů. Při práci na počítači generované grafiky pro film Tron z roku 1982, počítačový vědec Ken Perlin začal vyvíjet algoritmy, které používaly náhodné procesy k tomu, aby tyto obrázky realističtější textury. V roce 1997 získal Ken Perlin cenu Akademie za technické úspěchy. Jeho práce se označuje jako Perlin šum a je podporována ve SkiaSharpu. Tady je příklad:

Ukázka šumu Perlin

Jak vidíte, každý pixel není náhodná hodnota barvy. Kontinuita od pixelu po pixel vede k náhodným obrazcům.

Podpora šumu Perlin v Skia je založená na specifikaci W3C pro CSS a SVG. Oddíl 8.20 modulu Efekty filtru 1 obsahuje základní algoritmy šumu Perlin v kódu C.

Zkoumání šumu Perlin

Třída SKShader definuje dvě různé statické metody pro generování šumu Perlin: CreatePerlinNoiseFractalNoise a CreatePerlinNoiseTurbulence. Parametry jsou identické:

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

Obě metody existují také v přetížených verzích s dalším SKPointI parametrem. Tato přetížení popisuje část Tiling Perlin šum.

baseFrequency Dva argumenty jsou kladné hodnoty definované v dokumentaci SkiaSharp v rozsahu od 0 do 1, ale dají se nastavit i na vyšší hodnoty. Čím vyšší je hodnota, tím větší je změna náhodného obrázku ve vodorovném a svislém směru.

Hodnota numOctaves je celé číslo 1 nebo vyšší. Souvisí s faktorem iterace v algoritmech. Každý další oktáv přispívá k efektu, který je polovinou předchozího oktávu, takže účinek se sníží s vyššími oktávovými hodnotami.

Parametr seed je výchozím bodem generátoru náhodných čísel. I když je zadaná jako hodnota s plovoucí desetinnou čárkou, zlomek se před jeho použití zkrátí a hodnota 0 je stejná jako 1.

Stránka Šum perlinu v ukázce umožňuje experimentovat s různými hodnotami baseFrequency a numOctaves argumenty. Tady je soubor 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>

Pro dva baseFrequency argumenty používá dvě Slider zobrazení. Pokud chcete rozšířit rozsah nižších hodnot, posuvníky jsou logaritmické. Soubor kódu vypočítá argumenty metod SKShaderz mocnin Slider hodnot. Zobrazení Label zobrazují počítané hodnoty:

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 Hodnota 1 odpovídá hodnotě 0,001, Slider hodnota os 2 odpovídá 0,01, Slider hodnoty 3 odpovídají 0,1 a Slider hodnota 4 odpovídá 1.

Tady je soubor kódu, který tento kód obsahuje:

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

Tady je program spuštěný na zařízeních s iOSem, Androidem a Univerzální platforma Windows (UPW). Fraktální šum se zobrazuje v horní polovině plátna. Šum turbulence je v dolní polovině:

Šum perlin

Stejné argumenty vždy vytvářejí stejný vzor, který začíná v levém horním rohu. Tato konzistence je zřejmé, když upravíte šířku a výšku okna UPW. Když Windows 10 překreslí obrazovku, vzor v horní polovině plátna zůstane stejný.

Vzor šumu zahrnuje různé stupně transparentnosti. Průhlednost se zjeví, pokud v hovoru nastavíte barvu canvas.Clear() . Tato barva se změní na vzor. Tento efekt uvidíte také v části Kombinování více shaderů.

Tyto vzory šumu Perlin se zřídka používají sami. Často se na ně vztahují režimy blendu a barevné filtry, které jsou popsány v pozdějších článcích.

Šum v perlinové vazbě

Ve verzích přetížení existují také dvě statické SKShader metody pro vytváření šumu Perlin. CreatePerlinNoiseTurbulence Přetížení CreatePerlinNoiseFractalNoise mají další 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 je celočíselná verze známé SKPoint struktury. SKPointIX definuje a Y vlastnosti typuint, nikoli float.

Tyto metody vytvoří opakující se vzor zadané velikosti. V každé dlaždici je pravý okraj stejný jako levý okraj a horní okraj je stejný jako dolní okraj. Tato charakteristika je ukázaná na stránce Šum v dlaždicích Perlin. Soubor XAML se podobá předchozí ukázce, ale má Stepper jenom zobrazení pro změnu 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>

Soubor kódu za kódem definuje konstantu pro velikost dlaždice. Obslužná rutina PaintSurface vytvoří rastrový obrázek této velikosti a SKCanvas pro kreslení do tohoto rastrového obrázku. Metoda SKShader.CreatePerlinNoiseTurbulence vytvoří shader s danou velikostí dlaždice. Tento shader je nakreslený na rastrovém obrázku:

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 vytvoření rastrového obrázku se k vytvoření dlaždicového rastrového vzoru používá další SKPaint objekt voláním SKShader.CreateBitmap. Všimněte si dvou argumentů SKShaderTileMode.Repeat:

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

Tento shader slouží k pokrytí plátna. Nakonec se k tahu obdélníku s velikostí původního rastrového obrázku použije jiný SKPaint objekt.

seed Z uživatelského rozhraní je možné vybrat pouze parametr. Pokud se na každé platformě použije stejný seed vzor, zobrazí se stejný vzor. Různé seed hodnoty mají za následek různé vzory:

Šum perlinu s dlaždicemi

Čtvercový vzor 200 pixelů v levém horním rohu hladce prochází do ostatních dlaždic.

Kombinování více shaderů

Třída SKShader obsahuje metodu CreateColor , která vytvoří shader se zadanou plnou barvou. Tento shader není velmi užitečný sám o sobě, protože můžete jednoduše nastavit tuto barvu na Color vlastnost SKPaint objektu a nastavit Shader vlastnost na hodnotu null.

Tato CreateColor metoda se stane užitečnou v jiné metodě, která SKShader definuje. Tato metoda je CreateCompose, která kombinuje dva shadery. Tady je syntaxe:

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

Stínovač srcShader (zdrojový shader) se efektivně nakreslený nad dstShader (cílový shader). Pokud je zdrojový shader plnou barvou nebo přechodem bez průhlednosti, cílový shader bude zcela nejasný.

Shader šumu Perlin obsahuje průhlednost. Pokud je tento shader zdrojem, cílový shader se zobrazí prostřednictvím průhledných oblastí.

Složená stránka šumu Perlin obsahuje soubor XAML, který je prakticky identický s první stránkou šumu Perlin. Soubor s kódem je také podobný. Původní stránka šumu Perlin ale nastaví Shader vlastnost SKPaint shaderu vráceného ze statických CreatePerlinNoiseFractalNoise a CreatePerlinNoiseTurbulence metod. Tato složená stránka šumu Perlin volá CreateCompose kombinaci shaderu. Cílem je pevný modrý shader vytvořený pomocí CreateColor. Zdrojem je shader šumu 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);
        }
    }
}

Fraktal šum shader je nahoře; stínovač turbulence je dole:

Složený šum Perlin

Všimněte si, kolik jsou tyto shadery modré než ty, které jsou zobrazeny na stránce Šum Perlin. Rozdíl znázorňuje množství průhlednosti v stínovačích šumu.

Existuje také přetížení CreateCompose metody:

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

Poslední parametr je členem výčtu SKBlendMode , výčet s 29 členy, které jsou popsány v další řadě článků o režimech kompozitingu a mixu SkiaSharp.