シェーダーの実践
ImageShaderによる画像の塗り、4種類のグラデーション、パーリンノイズ、Blend/ColorShaderによる合成など、SkSLと組み込みシェーダーの実践的な使い方をまとめます。
前章でSkSLの基本構文を見たので、この章では実際によく使うシェーダーを一通り見ていきます。グラデーションやノイズは自分でSkSLを書かなくても組み込みコンポーネントとして用意されているので、まずはそちらから押さえていきましょう。
ImageShader: 画像をシェーダーとして塗る
ImageShaderは画像を塗り(shader)として扱うためのコンポーネントです。Imageコンポーネントが「矩形に画像を描く」のに対して、ImageShaderは任意の図形の塗りとして画像を使えます。円や複雑なパスの中に写真を収めたいときに便利です。
| Name | Type | Description |
|---|---|---|
| image | SkImage |
画像インスタンス |
| tx? / ty? | TileMode |
X・Y方向のタイリング方法(clamp・repeat・mirror・decal) |
| fit? | Fit |
rectにフィットさせる方法 |
| rect? | SkRect |
フィット計算の基準となる出力先の矩形 |
| transform? | Transforms2d |
追加の変換 |
| sampling? | Sampling |
拡大縮小時のサンプリング方法 |
import {
Canvas,
Circle,
ImageShader,
useImage,
} from "@shopify/react-native-skia";
const ImageShaderDemo = () => {
const image = useImage(require("./assets/photo.jpg"));
if (image === null) {
return null;
}
return (
<Canvas style={{ flex: 1 }}>
<Circle cx={128} cy={128} r={128}>
<ImageShader
image={image}
fit="cover"
rect={{ x: 0, y: 0, width: 256, height: 256 }}
/>
</Circle>
</Canvas>
);
};
グラデーション
グラデーションもシェーダーの一種です。すべてのグラデーションコンポーネントに共通するpropは次の通りです。
| Name | Type | Description |
|---|---|---|
| colors | string[] |
分布させる色の配列 |
| positions? | number[] |
各色の相対位置(colorsと同じ長さで指定) |
| mode? | TileMode |
clamp・repeat・mirror・decal |
| transform? | Transforms2d |
追加の変換 |
4種類のグラデーションを並べてみます。
LinearGradient:startとendの2点を結ぶ直線状に色を分布させる、もっとも基本的なグラデーションRadialGradient: 中心cと半径rで、中心から外側へ円状に色を分布させるSweepGradient: 中心cを軸に、角度(start〜end、デフォルトは0〜360度)に沿って色を分布させる。時計の針が回るようなグラデーションTwoPointConicalGradient: 開始円(start・startR)と終了円(end・endR)という2つの円の間を補間する、もっとも自由度の高いグラデーション。中心をずらしたりRadiusを変えたりすると光源のような表現になる
import { Canvas, Rect, LinearGradient, vec } from "@shopify/react-native-skia";
const LinearGradientDemo = () => {
return (
<Canvas style={{ flex: 1 }}>
<Rect x={0} y={0} width={256} height={256}>
<LinearGradient
start={vec(0, 0)}
end={vec(256, 256)}
colors={["blue", "yellow"]}
/>
</Rect>
</Canvas>
);
};
パーリンノイズ
パーリンノイズ(Perlin noise)は自然な起伏を持つ乱数パターンを生成するアルゴリズムで、雲・煙・大理石模様や、前章のDisplacementMapの変位マップなど幅広い用途に使われます。React Native SkiaにはFractalNoise(フラクタルノイズ)とTurbulence(乱流)の2種類が用意されています。
| Name | Type | Description |
|---|---|---|
| freqX / freqY | number |
X・Y方向の基本周波数(0.0〜1.0) |
| octaves | number |
重ね合わせる周波数成分の数。多いほど細かいディテールが増える |
| seed | number |
乱数のシード値 |
| tileWidth? / tileHeight? | number |
両方0以外の場合、指定サイズでタイル状に繰り返し可能なノイズにする |
FractalNoiseは滑らかな雲のような模様に、Turbulenceはより鋭くコントラストの強い模様になります。octavesを増やすほど計算コストが上がるので、リアルタイムに動かす場合は必要最小限に留めるのが無難です。
import { Canvas, Rect, FractalNoise, Fill } from "@shopify/react-native-skia";
const FractalNoiseDemo = () => {
return (
<Canvas style={{ flex: 1 }}>
<Fill color="white" />
<Rect x={0} y={0} width={256} height={256}>
<FractalNoise freqX={0.05} freqY={0.05} octaves={4} />
</Rect>
</Canvas>
);
};
Blend: シェーダー同士を合成する
Blendは2つのシェーダーを、指定したブレンドモード(前章で見たblendModeと同じ一覧)で合成するシェーダーです。グラデーションとノイズを重ねてマーブル模様のような表現を作れます。
| Name | Type | Description |
|---|---|---|
| mode | BlendMode |
ブレンドモード |
| children | ReactNode |
合成する2つのシェーダー |
import { Canvas, Rect, Turbulence, RadialGradient, Blend, vec } from "@shopify/react-native-skia";
const BlendDemo = () => {
return (
<Canvas style={{ flex: 1 }}>
<Rect x={0} y={0} width={256} height={256}>
<Blend mode="difference">
<RadialGradient r={128} c={vec(128, 128)} colors={["blue", "yellow"]} />
<Turbulence freqX={0.05} freqY={0.05} octaves={4} />
</Blend>
</Rect>
</Canvas>
);
};
ColorShader: 単色をシェーダーとして扱う
ColorShaderは単色を返すだけのシェーダーです。単独で使うことはほとんどありませんが、BlendやShaderのuniform shaderとして「一定の色」を渡したい場合に使います。
import { Canvas, Fill, ColorShader } from "@shopify/react-native-skia";
const ColorShaderDemo = () => {
return (
<Canvas style={{ flex: 1 }}>
<Fill>
<ColorShader color="lightBlue" />
</Fill>
</Canvas>
);
};
以下のデモではBlendのブレンドモードと、2番目のシェーダーをTurbulence(ノイズ)かColorShader(単色)かで切り替えて試せます。
自作シェーダーを動かしてみる
最後に、前章のuniformの知識を使って、自作のSkSLシェーダーをアニメーションパラメーター付きで動かしてみます。ピクセルごとのループを持たない単純な三角関数の合成なので、以下のデモ自体は軽量ですが、RuntimeShaderを使ったより複雑な自作シェーダー(多重ループを含むもの、複数のシェーダーを何段にも重ねるものなど)は画素数分の計算が毎フレーム走るため処理コストが高くなりがちです。ブラウザのプレビューで滑らかに動いていても実機では重く感じることがあるので、複雑なシェーダーを実装した際は実機での確認を推奨します。
const source = Skia.RuntimeEffect.Make(`
uniform float2 resolution;
uniform float frequency;
uniform float amplitude;
vec4 main(vec2 pos) {
vec2 uv = pos / resolution;
float wave = sin((uv.x + uv.y) * frequency) * amplitude;
vec3 color = vec3(0.4 + wave, 0.5 - wave, 0.8);
return vec4(color, 1.0);
}
`)!;
frequencyは波の細かさ、amplitudeは色の変化の強さに対応するuniformです。どちらもJavaScript側のスライダー操作でそのまま再計算されるので、シェーダーがReactのstateと地続きで動くことを体感できるはずです。
注意点
この章で扱ったグラデーションやノイズは、いずれもShaderとしてPaintの子要素に渡せます。カラーフィルターやイメージフィルターと組み合わせるとさらに表現の幅が広がりますが、重ねるほど処理コストも積み上がる点は常に意識してください。特にRuntimeShaderを含むチェーンをリスト内の多数のアイテムに適用するような使い方は、パフォーマンスへの影響が大きくなりやすいので避けるのが無難です。
公式ドキュメントのImage Shaders・Gradients・Perlin Noise Shaders・Blending and Colorsも参照してください。