次の方法で共有


SkiaSharp のノイズと構成

シンプルなベクター グラフィックスは不自然に見える傾向があります。 直線、滑らかな曲線、単色は、現実世界のオブジェクトの不完全性とは似ても似つきません。 1982 年の映画『トロン』のためのコンピューター生成グラフィックスに取り組んだコンピューター サイエンティストの Ken Perlin は、ランダムなプロセスを使用してこれらの画像をより現実的なテクスチャにするためのアルゴリズムの開発を始めました。 1997 年、Ken Perlin はアカデミー技術功労賞を受賞しました。 彼の功績はパーリン ノイズとして知られるようになり、SkiaSharp でもサポートされています。 次に例を示します。

Perlin Noise サンプル

ご覧のように、各ピクセルはランダムな色の値ではありません。 ピクセルからピクセルへの連続性により、ランダムな図形が生成されます。

Skia でのパーリン ノイズのサポートは、CSS と SVG の W3C 仕様に基づいています。 「フィルター効果モジュール レベル 1」のセクション 8.20 には、C コードの基になるパーリン ノイズ アルゴリズムが含まれています。

パーリン ノイズを調べる

この SKShader クラスは、パーリン ノイズ (CreatePerlinNoiseFractalNoiseCreatePerlinNoiseTurbulence) を生成するための 2 つの異なる静的メソッドを定義します。 次のように、パラメーターは同じです。

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

どちらのメソッドも、追加の SKPointI パラメーターを持つオーバーロードされたバージョンに存在します。 「パーリン ノイズのタイリング」セクションでは、このオーバーロードについて説明します。

2 つの引数 baseFrequency は、SkiaSharp ドキュメントで 0 から 1 の範囲で定義されている正の値ですが、より大きい値に設定することもできます。 値が大きいほど、水平方向と垂直方向のランダム画像の変化が大きくなります。

numOctaves 値は 1 以上の整数になります。 これは、アルゴリズムの反復係数に関連しています。 オクターブが増えるごとに、効果は前のオクターブの半分になるので、オクターブの値が大きくなるほど効果は減少します。

seed パラメーターは、乱数ジェネレーターの開始点になります。 浮動小数点値として指定されていますが、分数は使用される前に切り捨てられ、0 は 1 と同じになります。

サンプルの Perlin Noise ページでは、baseFrequencynumOctaves 引数のさまざまな値を試すことができます。 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>

2 つの baseFrequency 引数に対して 2 つの Slider ビューを使用します。 小さい値の範囲を広げるために、スライダーは対数になります。 分離コード ファイルは、Slider 値の累乗から SKShader メソッドへの引数を計算します。 Label ビューには、次の計算値が表示されます。

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 値の 1 は 0.001 に対応し、Slider 値の 2 は 0.01 に対応し、Slider 値の 3 は 0.1 に対応し、Slider 値の 4 は 1 に対応します。

そのコードを含む分離コード ファイルを次に示します。

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

iOS、Android、ユニバーサル Windows プラットフォーム (UWP) で実行されているプログラムを次に示します。 キャンバスの上半分に、フラクタル ノイズが表示されます。 下半分はタービュレント ノイズです。

Perlin Noise

同じ引数によって、左上隅から始まるパターンが常に同じになります。 この一貫性は、UWP ウィンドウの幅と高さを調整すると明らかです。 Windows 10 で画面を再描画すると、キャンバスの上半分のパターンが同じになります。

ノイズ パターンには、さまざまな透明度が組み込まれています。 canvas.Clear() 呼び出しで色を設定すると、透明度が明らかになります。 その色はパターンの中で目立ちます。 この効果は、「複数のシェーダーを組み合わせる」セクションでも示されています。

これらのパーリン ノイズ パターンは、それ自体ではほとんど使用されません。 多くの場合、後の記事で説明するブレンド モードとカラー フィルターが対象です。

パーリン ノイズのタイリング

パーリン ノイズを作成するための 2 つの静的メソッド SKShader は、オーバーロード バージョンにも存在します。 CreatePerlinNoiseFractalNoiseCreatePerlinNoiseTurbulence オーバーロードには、追加の 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);

SKPointI 構造体は、使い慣れたSKPoint 構造体の整数バージョンです。 SKPointI では、float ではなく、int 型の XY プロパティを定義します。

これらのメソッドは、指定したサイズの繰り返しパターンを作成します。 各タイルの右端は左端と同じで、上端は下端と同じです。 この特性は、Tiled Perlin Noise ページで示されています。 XAML ファイルは前のサンプルと似ていますが、seed 引数を変更するための Stepper ビューのみが含まれています。

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

分離コード ファイルは、タイル サイズの定数を定義します。 PaintSurface ハンドラーは、そのサイズのビットマップと、そのビットマップに描画するための SKCanvas を作成します。 SKShader.CreatePerlinNoiseTurbulence メソッドは、そのタイル サイズのシェーダーを作成します。 このシェーダーは、次のようにビットマップに描画されます。

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

ビットマップが作成された後、別の SKPaint オブジェクトを使用して、SKShader.CreateBitmap を呼び出してタイル化されたビットマップ パターンを作成します。 次の SKShaderTileMode.Repeat の 2 つの引数に注目してください。

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

このシェーダーは、キャンバスを埋めるために使用されます。 最後に、別の SKPaint オブジェクトを使用して、元のビットマップのサイズを示す四角形を描きます。

ユーザー インターフェイスからは、seed パラメーターのみを選択できます。 各プラットフォームで同じ seed パターンが使用されている場合は、同じパターンが表示されます。 異なる seed 値は、異なるパターンとなります。

Tiled Perlin Noise

左上隅の 200 ピクセルの正方形パターンは、他のタイルにシームレスにつながっています。

複数のシェーダーを組み合わせる

SKShader クラスには、指定した単色を持つシェーダーを作成する CreateColor メソッドが含まれています。 このシェーダーは、単にその色を SKPaint オブジェクトの Color プロパティに設定し、Shader プロパティを null に設定できるため、単独ではあまり役に立ちません。

この CreateColor メソッドは、SKShader が定義する別のメソッドで役立ちます。 CreateCompose がこのメソッドであり、2 つのシェーダーを組み合わせています。 構文を次に示します。

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

srcShader (ソース シェーダー) は、実質的に dstShader (宛先シェーダー) の上に描画されます。 ソース シェーダーが単色または透明度のないグラデーションの場合、宛先シェーダーは完全に隠されます。

パーリン ノイズ シェーダーには透明度が含まれています。 そのシェーダーがソースである場合、宛先シェーダーは透明領域を通して表示されます。

Composed Perlin Noise ページには、最初の Perlin Noise ページとほぼ同じ XAML ファイルがあります。 分離コード ファイルも同様です。 しかし、元の Perlin Noise ページでは、CreatePerlinNoiseFractalNoiseCreatePerlinNoiseTurbulence の静的メソッドから返されたシェーダーに SKPaintShader プロパティが設定されます。 この Composed Perlin Noise ページは、組み合わせシェーダーの CreateCompose を呼び出します。 宛先は、CreateColor を使用して作成された青い単色シェーダーになります。 ソースはパーリン ノイズ シェーダーです。

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

フラクタル ノイズ シェーダーは上部にあり、タービュレント シェーダーは下部にあります。

Composed Perlin Noise

Perlin Noise ページに表示されるシェーダーよりも、これらのシェーダーがどれだけ青いかに注目してください。 この違いは、ノイズ シェーダーの透明度を示しています。

CreateCompose メソッドのオーバーロードもあります。

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

最後のパラメーターは、SKBlendMode 列挙体のメンバーであり、SkiaSharp の合成モードとブレンド モードに関する次の一連の記事で説明されている 29 個のメンバーを持つ列挙体です。