Skip to content
React Native Skia ガイドブック
Esc
navigateopen⌘Jpreview
On this page

シェーダー入門とSkSL

SkSL(Skia Shading Language)の基本構文と、Skia.RuntimeEffect.Makeによるシェーダーのコンパイル、Shaderコンポーネントとuniformの使い方をコード例中心に解説します。

時間、色、座標、画像をシェーダーへ入力し、ピクセルごとの色を計算して模様を描く流れ

シェーダーは時間・色・座標・画像などを受け取り、各ピクセルの色を並列に決めます。

ここまでの章で登場したBlur・Shadow・グラデーションといったエフェクトは、すべて内部的にはシェーダー(shader)という仕組みで実装されています。この章では、自分で書ける汎用シェーダー言語であるSkSL(Skia Shading Language)の基本構文を、コード例を中心に見ていきます。

SkSLの構文はGLSLとよく似ています。ブラウザで触りながら試せるshaders.skia.orgや、GLSLとの差分をまとめた公式のsksl READMEも参考になります。

シェーダーをコンパイルする

SkSLのコードは文字列として書き、Skia.RuntimeEffect.Makeでコンパイルします。コンパイルに失敗するとnullが返るので、エラーハンドリングが必要です。

import { Skia } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
  // このキャンバスは256x256
  vec2 canvas = vec2(256);
  // 0〜1に正規化したx,y座標
  vec2 normalized = pos / canvas;
  return vec4(normalized.x, normalized.y, 0.5, 1);
}`);

if (!source) {
  throw new Error("シェーダーのコンパイルに失敗しました");
}

すべてのSkSLシェーダーはmain関数を持ち、引数として現在描画中のピクセル座標(vec2 pos)を受け取り、そのピクセルの色(vec4、RGBA各0〜1)を返します。この最小限の例では、x座標を赤(R)、y座標を緑(G)にマッピングしているので、左上が黒に近く、右下が黄色に近いグラデーションになります。

Shader: コンパイル結果を描画に使う

コンパイルしたsourceShaderコンポーネントに渡して使います。Shaderはシェーダーの一種なので、FillCircleなどの塗りとして機能します。

Name Type Description
source RuntimeEffect コンパイル済みシェーダー
uniforms { [name: string]: number | Vector | Vector[] | number[] | number[][] } uniform変数の値
children Shader uniform shaderとして渡す別のシェーダー
import { Skia, Canvas, Shader, Fill } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
  vec2 normalized = pos / vec2(256);
  return vec4(normalized.x, normalized.y, 0.5, 1);
}`)!;

const SimpleShader = () => {
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill>
        <Shader source={source} />
      </Fill>
    </Canvas>
  );
};

posをキャンバスサイズで正規化してそのままR・Gチャンネルに使うと、左上が黒に近く右下が黄色に近いグラデーションになります。

Skiaを読み込み中…

uniform: 外から値を渡す

uniformは、JavaScript側からシェーダーにパラメーターを渡すための変数です。Reactのpropsのように、毎フレーム異なる値を渡してシェーダーの見た目を動的に変えられます。対応する型はfloatfloat2float3float4float2x2float3x3float4x4intint2int3int4とそれらの配列(uniform float3 colors[12]のように書く)です。

import { Canvas, Skia, Shader, Fill, vec } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
uniform vec2 c;
uniform float r;
uniform float blue;

vec4 main(vec2 pos) {
  vec2 normalized = pos / vec2(2 * r);
  return distance(pos, c) > r ? vec4(1) : vec4(normalized, blue, 1);
}`)!;

const UniformShader = () => {
  const r = 128;
  const c = vec(2 * r, r);
  const blue = 1.0;
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill>
        <Shader source={source} uniforms={{ c, r, blue }} />
      </Fill>
    </Canvas>
  );
};

distance(pos, c) > rは、現在のピクセルが円の外側にあるかどうかの判定です。外側なら白(vec4(1))、内側ならx・y座標由来の色とblueuniformを組み合わせた色を返します。以下のデモではblueをスライダーで操作でき、JavaScript側の状態変化がそのままシェーダーの計算結果に反映される様子を確認できます。

Skiaを読み込み中…

uniformspropに渡すオブジェクトのキー名は、SkSLコード内のuniform変数名と一致させる必要があります。型が合っていない場合(たとえばvec2のuniformに単一の数値を渡すなど)はランタイムエラーになるので注意してください。

ネストしたシェーダー: 画像を入力にする

uniform shaderという特別な型を使うと、別のシェーダー(画像やグラデーションなど)を入力として受け取れます。Shaderコンポーネントの子要素に別のシェーダー系コンポーネントを渡すことで、このuniform shaderにバインドされます。

import { Canvas, Skia, ImageShader, Shader, Fill, useImage } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
uniform shader image;

half4 main(float2 xy) {
  xy.x += sin(xy.y / 3) * 4;
  return image.eval(xy).rbga;
}`)!;

const NestedShader = () => {
  const image = useImage(require("./assets/photo.jpg"));
  if (!image) {
    return null;
  }
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill>
        <Shader source={source}>
          <ImageShader
            image={image}
            fit="cover"
            rect={{ x: 0, y: 0, width: 256, height: 256 }}
          />
        </Shader>
      </Fill>
    </Canvas>
  );
};

image.eval(xy)で任意の座標の色を取得できるので、xy自体を書き換えてからevalすれば、波打つような歪みも簡単に作れます。.rbgaのようにチャンネルの並びを入れ替えるスウィズル(swizzle)記法もGLSL・SkSL共通の書き方です。以下のデモでは、amplitude(歪みの強さ)とfrequency(波の細かさ)をuniformとしてスライダーから操作できます。

Skiaを読み込み中…

主な組み込み関数

SkSLではGLSL同様、次のような組み込み関数がよく使われます。

  • mix(a, b, t): abt(0〜1)で線形補間する
  • clamp(x, min, max): xminmaxの範囲に収める
  • smoothstep(edge0, edge1, x): edge0edge1の間を滑らかに0→1へ補間する
  • distance(a, b): 2点間の距離
  • sin / cos / fract / mod: 波や繰り返しパターンの生成によく使う

これらを組み合わせるだけでも、グラデーションや簡単な模様は十分に表現できます。より実践的な例(グラデーション・パーリンノイズ・画像処理)は次章でまとめて扱います。

注意点

SkSLはGLSLとほぼ同じですが完全に同一ではありません。texture()ではなく.eval()を使う、gl_FragCoordではなくmainの引数として座標を受け取るなど、細かな違いがあります。GLSLのシェーダーを移植する場合は公式の差分一覧を確認してください。

公式ドキュメントのShading Languageも参照してください。