简单的矢量图形往往看起来不自然。 直线、平滑曲线和纯色与现实世界物体的缺陷并不相似。 在为 1982 年电影《Tron》制作计算机生成的图形时,计算机科学家 Ken Perlin 开始开发算法,使用随机过程为这些图像提供更真实的纹理。 1997 年,Ken Perlin 荣获奥斯卡技术成就奖。 他的成就称为 Perlin 噪点,并在 SkiaSharp 中得到支持。 下面是一个示例:
你可以看到,每个像素都不是随机的颜色值。 像素到像素的连续性导致随机形状。
Skia 中对 Perlin 噪点的支持基于 CSS 和 SVG 的 W3C 规范。 滤镜效果模块级别 1 第 8.20 节包含 C 代码中的基础 Perlin 噪点算法。
探索 Perlin 噪点
SKShader
类定义了两种不同的静态方法来生成 Perlin 噪点:CreatePerlinNoiseFractalNoise
和 CreatePerlinNoiseTurbulence
。 参数相同:
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
参数的重载版本中。 平铺 Perlin 噪点部分讨论了这些重载。
两个 baseFrequency
参数是 SkiaSharp 文档中定义的正值,范围为 0 到 1,但也可以将其设置为更高的值。 值越高,随机图像在水平和垂直方向上的变化越大。
numOctaves
值是 1 或更大的整数。 它与算法中的迭代因子有关。 每个额外的八度音阶会产生前一个八度音阶一半的效果,因此效果会随着八度音阶值的增加而减弱。
seed
参数是随机数生成器的起点。 尽管指定为浮点值,但小数在使用之前会被截断,并且 0 与 1 相同。
示例中的“柏林噪声”页面允许你试验 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>
它使用两个 Slider
视图来表示两个 baseFrequency
参数。 为了扩大较低值的范围,滑块是对数形式的。 代码隐藏文件根据 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()
调用中设置颜色,透明度就会变得明显。 该颜色在图案中变得突出。 你还会在组合多个着色器部分看到此效果。
这些 Perlin 噪点模式很少单独使用。 它们通常会受到后续文章中讨论的混合模式和滤色器的影响。
平铺 Perlin 噪点
用于创建 Perlin 噪点的两个静态 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
定义类型为 int
而不是 float
的 X
和 Y
属性。
这些方法创建指定大小的重复图案。 在每个图块中,右边缘与左边缘相同,顶部边缘与底部边缘相同。 “平铺 Perlin 噪点”页演示了此特征。 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
的两个参数:
paint.Shader = SKShader.CreateBitmap(bitmap,
SKShaderTileMode.Repeat,
SKShaderTileMode.Repeat);
此着色器用于覆盖画布。 最后,另一个 SKPaint
对象用于绘制显示原始位图大小的矩形。
只能从用户界面中选择 seed
参数。 如果每个平台上使用相同的 seed
图案,它们将显示相同的图案。 不同的 seed
值会导致不同的图案:
左上角的 200 像素正方形图案无缝流入其他图块中。
组合多个着色器
SKShader
类包含一个 CreateColor
方法,用于创建具有指定纯色的着色器。 此着色器本身并不是很有用,因为你只需将该颜色设置为 SKPaint
对象的 Color
属性,并将 Shader
属性设置为 null。
此 CreateColor
方法在 SKShader
定义的另一个方法中十分有用。 此方法为 CreateCompose
,它组合了两个着色器。 语法如下:
public static SKShader CreateCompose (SKShader dstShader, SKShader srcShader);
srcShader
(源着色器)实际上绘制在 dstShader
(目标着色器)之上。 如果源着色器是纯色或不透明的渐变,则目标着色器将被完全遮挡。
Perlin 噪点着色器包含透明度。 如果该着色器是源,则目标着色器将通过透明区域显示。
“组合的 Perlin 噪点”页包含一个 XAML 文件,该文件与第一个“Perlin 噪点”页几乎相同。 代码隐藏文件也类似。 但是,原始“Perlin 噪点”页将 SKPaint
的 Shader
属性设置为从静态 CreatePerlinNoiseFractalNoise
和 CreatePerlinNoiseTurbulence
方法返回的着色器。 此“组合的 Perlin 噪点”页调用 CreateCompose
以实现组合着色器。 目标是使用 CreateColor
创建的纯蓝色着色器。 源是 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);
}
}
}
分形噪点着色器位于顶部;湍流着色器位于底部:
请注意这些着色器比“Perlin 噪点”页显示的着色器要蓝多少。 这种差异说明了噪点着色器中的透明程度。
CreateCompose
方法也有一个重载:
public static SKShader CreateCompose (SKShader dstShader, SKShader srcShader, SKBlendMode blendMode);
最后一个参数是 SKBlendMode
枚举的成员,该枚举有 29 个成员,我们将在下一个有关 SkiaSharp 合成与混合模式的系列文章中讨论该枚举。