シンプルなベクター グラフィックスは不自然に見える傾向があります。 直線、滑らかな曲線、単色は、現実世界のオブジェクトの不完全性とは似ても似つきません。 1982 年の映画『トロン』のためのコンピューター生成グラフィックスに取り組んだコンピューター サイエンティストの Ken Perlin は、ランダムなプロセスを使用してこれらの画像をより現実的なテクスチャにするためのアルゴリズムの開発を始めました。 1997 年、Ken Perlin はアカデミー技術功労賞を受賞しました。 彼の功績はパーリン ノイズとして知られるようになり、SkiaSharp でもサポートされています。 次に例を示します。
ご覧のように、各ピクセルはランダムな色の値ではありません。 ピクセルからピクセルへの連続性により、ランダムな図形が生成されます。
Skia でのパーリン ノイズのサポートは、CSS と SVG の W3C 仕様に基づいています。 「フィルター効果モジュール レベル 1」のセクション 8.20 には、C コードの基になるパーリン ノイズ アルゴリズムが含まれています。
この SKShader
クラスは、パーリン ノイズ (CreatePerlinNoiseFractalNoise
と CreatePerlinNoiseTurbulence
) を生成するための 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 ページでは、baseFrequency
と numOctaves
引数のさまざまな値を試すことができます。 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) で実行されているプログラムを次に示します。 キャンバスの上半分に、フラクタル ノイズが表示されます。 下半分はタービュレント ノイズです。
同じ引数によって、左上隅から始まるパターンが常に同じになります。 この一貫性は、UWP ウィンドウの幅と高さを調整すると明らかです。 Windows 10 で画面を再描画すると、キャンバスの上半分のパターンが同じになります。
ノイズ パターンには、さまざまな透明度が組み込まれています。 canvas.Clear()
呼び出しで色を設定すると、透明度が明らかになります。 その色はパターンの中で目立ちます。 この効果は、「複数のシェーダーを組み合わせる」セクションでも示されています。
これらのパーリン ノイズ パターンは、それ自体ではほとんど使用されません。 多くの場合、後の記事で説明するブレンド モードとカラー フィルターが対象です。
パーリン ノイズを作成するための 2 つの静的メソッド SKShader
は、オーバーロード バージョンにも存在します。 CreatePerlinNoiseFractalNoise
と CreatePerlinNoiseTurbulence
オーバーロードには、追加の 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
型の X
と Y
プロパティを定義します。
これらのメソッドは、指定したサイズの繰り返しパターンを作成します。 各タイルの右端は左端と同じで、上端は下端と同じです。 この特性は、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
値は、異なるパターンとなります。
左上隅の 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 ページでは、CreatePerlinNoiseFractalNoise
と CreatePerlinNoiseTurbulence
の静的メソッドから返されたシェーダーに SKPaint
の Shader
プロパティが設定されます。 この 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);
}
}
}
フラクタル ノイズ シェーダーは上部にあり、タービュレント シェーダーは下部にあります。
Perlin Noise ページに表示されるシェーダーよりも、これらのシェーダーがどれだけ青いかに注目してください。 この違いは、ノイズ シェーダーの透明度を示しています。
CreateCompose
メソッドのオーバーロードもあります。
public static SKShader CreateCompose (SKShader dstShader, SKShader srcShader, SKBlendMode blendMode);
最後のパラメーターは、SKBlendMode
列挙体のメンバーであり、SkiaSharp の合成モードとブレンド モードに関する次の一連の記事で説明されている 29 個のメンバーを持つ列挙体です。